Optional dynamic properties in Swift - ios

I'm attempting to compile the following Swift class:
class Waypoint: NSObject {
dynamic var coordinate: CLLocationCoordinate2D?
}
But I get the following compiler error:
Property cannot be marked dynamic because its type cannot be represented in Objective-C
If I change coordinate to be non-optional everything works just fine. I suppose this makes sense, since Objective-C has no concept of optionals. Is there any known solution or workaround?

In Objective-C, you can use nil to signal the absence of value, but only on object types. Swift generalizes this (and makes it type-safe) with the Optional generic type: you can have an Optional<NSObject>, a.k.a. NSObject?, but you can also have an Int? or a CLLocationCoordinate2D?.
But CLLocationCoordinate2D is a struct — if you use it in Objective-C, you can't assign nil to a variable of type CLLocationCoordinate2D. This is why you get this error.
As for an (ugly) workaround, you could wrap CLLocationCoordinate2D in a object:
class CLLocationCoordinate2DObj: NSObject {
let val: CLLocationCoordinate2D
init(_ val: CLLocationCoordinate2D) {
self.val = val
}
}
class Waypoint: NSObject {
dynamic var coordinate: CLLocationCoordinate2DObj?
}
Unfortunately, you can't find a more general solution with a generic object wrapper class for structs, as Objective-C doesn't have generics… An alternative would be to use NSValue as object type as described here, but I doubt that it would be more elegant.

Related

Why #Objc dynamic variable not accepting optional Int [duplicate]

Within a Swift class derived from an Obj-C based framework (but could just as easily be a Swift class with an #objc attribute) I declare two stored properties:
var optInt: Int?
var optString: String?
Only optString is being exposed to Obj-C via the generated -Swift.h header.
String? is presumably fine because it is exposed using an NSString object which can be nil, so the bridging has a way to represent no value.
If I remove the ? from optInt it's exposed with an NSInteger type, so I can see that for non-optional integers it avoids objects and bridges value type to value type, but does this literally mean that an Int? can't be exposed?
I can't seem to find any documentation that explicitly says this is the case. There is a whole list of incompatible Swift features here that this doesn't appear on: Using Swift from Objective-C
The use case here is the classic situation requiring the passing of a numeric ID which could legitimately be zero. In the pre-Swift world NSNumber and nil is exactly how I went about implementing this, but it just feels wrong to be trying to migrate a class to Swift but then hanging on to Obj-C types within the Swift class specifically for this reason.
I suppose I had envisaged that an Int? unlike Int would bridge as an NSNumber in the background, with its potentially nil value feeding the "has no value" element of the optional in Swift.
Is there anything I'm missing here? To reiterate, can a Swift Optional Int (Int?) be exposed to Objective-C via bridging?
The problem with exposing an Int? property as NSNumber* is that you could store a non-Int-compatible NSNumber to it from Objective-C.
You can make a computed NSNumber? property to wrap your Int? property. The getter would simply return the Int? variable. The setter would set the Int variable from -[NSNumber integerValue].
Here's a concrete answer for the solution described above:
private var _number: Int?
public var number: NSNumber? {
get {
return _number as NSNumber?
}
set(newNumber) {
_number = newNumber?.intValue
}
}
// In case you want to set the number in the init
public init(number: NSNumber?) {
_number = number?.intValue
}

Swift init from unknown class which conforms to protocol

I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.
The most important thing is that all the objects must be #objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.
You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:
#objc(Person) // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}
extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}
This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).
With that, in Swift, this code would be written this way:
if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}
The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.
Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.
I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Unknown swift property in obj-c class

I have declare a property var coordinate:CLLocationCoordinate2D? in my swift class
but i can't find this property in obj-c class. I have tried to add #obj before the class, but it doesn't work.
You can not access the instance variable because coordinate:CLLocationCoordinate2D is not bridged with Objective C. Instead of declaring CLLocationCoordinate2D, you can declare latitude and longitude as NSNumber type and later using them make 2D coordinate.
var lat: NSNumber?
var lon: NSNumber?
And then assign value in your Objc Class as bellow:
switObject.lat = #3;
switObject.lon = #5;
For more information check this link Cannot access property of Swift type from Objective-C
The problem is that you have declared an optional property (?), which is a pure Swift construct. They cannot be accessed by Objective C classes unless specifically bridged, like String -> NSString.
Obj-C doesn't support optional structs. But if you make it non-optional, it compiles:
import CoreLocation
class Class : NSObject
{
dynamic var coordinate:CLLocationCoordinate2D // no `?`: non-optional
override init()
{
coordinate = CLLocationCoordinate2D() // it's not optional, so you must initialize `coordinate`
}
}

Determining from Objective-c if a Swift property is declared as dynamic

I have been trying for some time to inspect a Swift class, and determine if any of the properties are declared as dynamic. My example class is as below:
class SwiftTestClass : DBObject {
dynamic var SwiftTestString : String!
dynamic var SwiftTestNumber : NSNumber!
dynamic var lowercaseField : String!
var nonDynamicVariable : String!
func testThyself() {
SwiftTestClass.query().fetchLightweight().removeAll()
let newObject = SwiftTestClass();
newObject.SwiftTestString = "hello, world"
newObject.SwiftTestNumber = 123
newObject.lowercaseField = "lowercase"
newObject.nonDynamicVariable = "should not be persisted"
newObject.commit()
let result = SwiftTestClass.query().fetch().firstObject;
print(result)
}
}
I am basically trying to pick out the fact that the property nonDynamicVariable is not declared as dynamic as the rest of them are.
DBObject is a subclass of NSObject.
I have tried:
Looking at the type encoding of the property, they are identical (type for type)
Seeing if they have a difference in the method implementations, they do not. (e.g. class_getMethod), the dynamic properties still have getter/setter methods.
Grabbing the Ivars to see if there is any difference there
Looking at all of the property attributes, also identical.
What I do know:
If I try to class_replaceMethod for the <propertyName>/set<propertyName>, it works for a dynamic property (as you would expect, because it adds objc compatibility) but fails to work (but does replace?, well, the memory address of the method changes!) or be actioned on the non dynamic property.
Does anyone know how to differentiate the two property declarations in swift from objc?
Thanks

Swift dynamic variable can't be of type Printable

I have a Swift project that contains two UITableViewControllers. The second UITableViewController is linked to a MVC model called Model. According to the UITableViewCell I select in the first UITableViewController, I want to initialize some properties of Model with Ints or Strings. Therefore, I've decided to define those properties with Printable protocol type. In the same time, I want to perform Key Value Observing on one of these properties.
Right now, Model looks like this:
class Model: NSObject {
let title: String
let array: [Printable]
dynamic var selectedValue: Printable //error message
init(title: String, array: [Printable], selectedValue: Printable) {
self.title = title
self.array = array
self.selectedValue = selectedValue
}
}
The problem here is that the following error message appears on the selectedValue declaration line:
Property cannot be marked dynamic because its type cannot be
represented in Objective-C
If I go to the Xcode Issue Navigator, I can also read the following line:
Protocol 'Printable' is not '#objc'
Is there any workaround?
There is no way to do what you want. Non-#objc protocols cannot be represented in Objective-C. One reason is that Non-#objc protocols can represent non-class types (and indeed, you said that you wanted to use it for Int and String, both non-class types), and protocols in Objective-C are only for objects.
KVO is a feature designed for Objective-C, so you must think about what you expect it to see from the perspective of Objective-C. If you were doing this in Objective-C, you would not want to have a property that could either be an object like id or a non-object like int -- you can't even declare that. Instead, as you said in your comment, you probably want it to be just objects. And you want to be able to use Foundation's bridging to turn Int into NSNumber * and String into NSString *. These are regular Cocoa classes that inherit from NSObject, which implements Printable.
So it seems to me you should just use NSObject or NSObjectProtocol.
Unfortunately ObjC does not treat protocols as types, they are just a convenient way of grouping members. Under the covers they are of type Any, so regretfully you will have to make the property Any and cast to Printable.
The best I can thing of is:
dynamic var selectedValue: Any
var printableValue : Printable {
get {
return (Printable)selectedValue
}
set {
selectedValue = newValue
}
}

Resources