Cannot access property on Swift type from Objective-C
Objective CSwiftTypesObjective C-Swift-BridgeObjective C Problem Overview
I am trying to access a Swift class's Double?
property from Objective-C.
class BusinessDetailViewController: UIViewController {
var lat : Double?
var lon : Double?
// Other elements...
}
In another view controller, I am trying to access lat
like following:
#import "i5km-Swift.h"
@interface ViewController ()
@property (strong, nonatomic) BusinessDetailViewController *businessDetailViewController;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.businessDetailViewController = [[BusinessDetailViewController alloc] initWithNibName:@"BusinessDetailViewController" bundle:nil];
self.businessDetailViewController.lat = businessArray[1]; /* THIS GIVES ME AN ERROR */
}
and I am getting
> Property 'lat' not found on object of type 'BusinessDetailViewController *'
Why can't I access this property? What am I missing?
Objective C Solutions
Solution 1 - Objective C
Optional values of non-Objective-C types aren't bridged into Objective-C. That is, the first three properties of TestClass
below would be available in Objective-C, but the fourth wouldn't:
class TestClass: NSObject {
var nsNumberVar: NSNumber = 0 // obj-c type, ok
var nsNumberOpt: NSNumber? // optional obj-c type, ok
var doubleVar: Double = 0 // bridged Swift-native type, ok
var doubleOpt: Double? // not bridged, inaccessible
}
In your Objective-C code, you'd access those first three properties like this:
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = @1.0;
optTest.nsNumberVar = @2.0;
optTest.doubleVar = 3.0;
In your case, you can either convert lat
and long
to be non-Optional or switch them to be instances of NSNumber
.
Note that you need to be careful about your Objective-C code if you take the second approach (switching lat
and lon
to non-optional properties of type NSNumber
) -- while the Swift compiler will prevent you from assigning nil
to non-optional properties, the Objective-C compiler has no qualms about allowing it, letting nil
values sneak into your Swift code with no chance of catching them at runtime. Consider this method on TestClass
:
extension TestClass {
func badIdea() {
// print the string value if it exists, or 'nil' otherwise
println(nsNumberOpt?.stringValue ?? "nil")
// non-optional: must have a value, right?
println(nsNumberVar.stringValue)
}
}
This works fine if invoked with values in both of the properties, but if nsNumberVar
is set to nil
from the Objective-C code, this will crash at runtime. Note that there is no way to check whether or not nsNumberVar
is nil
before using it!
TestClass *optTest = [[TestClass alloc] init];
optTest.nsNumberOpt = @1.0;
optTest.nsNumberVar = @2.0;
[optTest badIdea];
// prints 1, 2
optTest.nsNumberOpt = nil;
optTest.nsNumberVar = nil;
[optTest badIdea];
// prints nil, then crashes with an EXC_BAD_ACCESS exception
Solution 2 - Objective C
If your property is a Swift protocol type, just add @objc
in front of it.
Example:
class Foo: UIViewController {
var delegate: FooDelegate?
...
}
@objc protocol FooDelegate {
func bar()
}
Solution 3 - Objective C
Optionals is a swift specific feature, not available in obj-c. Optional class instances work because a nil optional can be mapped to a nil value, but value types (int, floats, etc.) are not reference types, hence variable of those types don't store a reference, but the value itself.
I don't know if there's a solution - a possible workaround is creating non optional properties mapping the nil
value to an unused data type value (such as -1 when representing an index, or 999999 for a coordinate):
class Test {
var lat : Double? {
didSet {
self._lat = self.lat != nil ? self.lat! : 999999
}
}
var lon : Double? {
didSet {
self._lon = self.lon != nil ? self.lon! : 999999
}
}
var _lat: Double = 99999999
var _lon: Double = 99999999
}
That should expose the _lat
and _lon
properties to obj-c.
Note that I have never tried that, so please let us know if it works.
Solution 4 - Objective C
> [UInt?
Int?
or Double?
properties] cannot be marked @objc because its type cannot be represented in Objective-C.
It is, however, possible to "wrap" them in a NSNumber like so :
class Foo {
var bar: Double?
}
// MARK: Objective-C Support
extension Foo {
/// bar is `Double?` in Swift and `(NSNumber * _Nullable)` in Objective-C
@objc(bar)
var z_objc_bar: NSNumber? {
get {
return bar as NSNumber?
}
set(value) {
bar = value?.doubleValue ?? nil
}
}
}