I've seen lots of questions regarding implementing Obj-C protocols in Swift, but not so much the other way around, and I haven't seen this specifically.
I am using a mixed Obj-C / Swift codebase. I have a Swift protocol defined as follows:
NamedItem.swift
@objc protocol NamedItem {
var name: String { get }
}
I have an existing Objective-C class that currently has its own name
property:
MyObjcClass.h
@interface MyObjcClass : NSObject
@property (nonatomic, strong, readonly) NSString* name;
@end
I have a couple other classes that have a name
property, so obviously I'd like to associate them all with a protocol instead of typecasting to a bunch of different types. Now, if I try to switch my Obj-C class from having its own property to implementing the Swift protocol:
MyObjcClass.h
@protocol MyObjcProtocol
@property (nonatomic, strong, readonly) NSString* place;
@end
@interface MyObjcClass : NSObject
@end
MyObjcClass.m
@interface MyObjcClass () <NamedItem>
@end
@implementation MyObjcClass
@synthesize name = _name;
@synthesize place = _place;
@end
This works great, in my other Objective-C classes, but if I try to access the name property from a Swift class:
SomeSwiftClass.swift
let myObj = MyObjcClass()
myObj.name // error
myObj.place // no problem
I get the following error:
Value of type 'MyObjcClass' has no member 'name'
If I don't remove the existing @property
declaration from MyObjcClass.h
and omit the @synthesize
statement everything builds correctly. Which seems weird and wrong - If you adopt a Objc-C protocol from an Obj-C class you don't have to re-define the property, just the @synthesize
statement is sufficient.
I've tried defining the Swift protocol about every way I could think of and have been able to find suggestions about, but I haven't been able to get around this.
So, what is the correct way to implement a Swift protocol property (maybe specifically a read-only property?) in an Objective-C class such that another Swift class can access it? Do I really have to re-declare the property in the Obj-C header? I know I could always give it up and just define the protocol in Objective-C, but... Swift is the future! (or something like that...)
Ok, managed to get a working solution with some suggestions from JoshCaswell and this blog post.
Classes now look as follows:
NamedItem.swift
@objc protocol NamedObject {
var name: String { get }
}
MyObjcClass.h
@protocol NamedItem;
@interface MyObjcClass : NSObject
- (id<NamedItem>)asNamedItem;
@end
MyObjcClass.m
@interface MyObjcClass () <NamedItem>
@synthesize name = _name;
- (id<NamedItem>)asNamedItem
{
return self;
}
@end
Usage now looks like:
SomeSwiftClass.swift
let myObj = MyObjcClass()
myObj.asNamedItem.name
Not as squeaky clean as I'd like, but it's better than a bunch of warnings or admitting defeat and re-writing the protocol in Objective-C. (I dunno, maybe it's not... but it's what I went with).
Hope that helps someone else.