The more I think about this, the more obvious it sounds. And the more I think about it, the less I believe it deserves a post. But it was a pleasant surprise anyway, so here it goes.
Let’s assume we have an Objective-C project, that contains a class like this:
//Person.h
@interface Person : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger age;
- (instancetype) initWithName: (NSString *) name age: (NSInteger) age;
@end
//Person.m
#import "Person.h"
@interface Person()
@property (nonatomic, copy, readwrite) NSString *name;
@property (nonatomic, assign, readwrite) NSInteger age;
@end
@implementation Person
- (instancetype) initWithName: (NSString *) name age: (NSInteger) age
{
if (self = [super init])
{
_name = name;
_age = age;
}
return self;
}
@end
And it also contains another class like this:
//Movie.h
@interface Movie : NSObject
@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, copy, readonly) NSString *director;
- (instancetype) initWithTitle: (NSString *) title director: (NSString *) director;
@end
//Movie.m
#import "Movie.h"
@interface Movie()
@property (nonatomic, copy, readwrite) NSString *title;
@property (nonatomic, copy, readwrite) NSString *director;
@end
@implementation Movie
- (instancetype) initWithTitle: (NSString *) title director: (NSString *) director
{
if (self = [super init])
{
_title = title;
_director = director;
}
return self;
}
@end
Now, let’s say we declare a Swift protocol like this:
@objc protocol Displayable {
var firstLineContent: String { get }
var secondLineContent: String { get }
}
And extensions to the Person and Movie object to enforce compliance with the Displayable protocol:
extension Person: Displayable {
var firstLineContent: String {
return "Name \(name)"
}
var secondLineContent: String {
return "Age \(age)"
}
}
extension Movie: Displayable {
var firstLineContent: String {
return "Title \(title)"
}
var secondLineContent: String {
return "Director \(director)"
}
}
Now, we can consume the Displayable protocol from Swift:
final class TableBuilder: NSObject {
override init() {
super.init()
printAllTheThings()
}
private func printAllTheThings() {
let items = getItems()
for displayable in items {
print("Swift: First line: ", displayable.firstLineContent)
print("Swift: Second line: ", displayable.secondLineContent)
}
}
private func getItems( ) -> [Displayable] {
let meFakingMyAge = Person(name: "Cesar", age: 21)
let movie = Movie(title: "The Quiet Man", director: "John Ford")
return [meFakingMyAge, movie]
}
}
Or from Objective-C (please, excuse the weakness of my ObjC-generics kung fu):
#import "TableBuilderObjC.h"
#import "ProtocolExtensionsInterop-Swift.h"
@implementation TableBuilderObjC
- (instancetype) init {
if (self = [super init]) {
[self printAllTheThings];
}
return self;
}
- (void) printAllTheThings {
NSLog(@"printing from Obj-C ========");
NSArray<NSObject<Displayable> *> *items = [self buildItems];
for (NSObject<Displayable> *item in items)
{
NSLog(@"ObjC: first Line %@", [item firstLineContent]);
NSLog(@"ObjC: second line %@", [item secondLineContent]);
}
}
- (NSArray<NSObject<Displayable> *> *) buildItems {
Person *meFakingMyAge = [[Person alloc] initWithName: @"Cesar" age: 21];
Movie *theQuiteMan = [[Movie alloc] initWithTitle: @"The Quiet Man" director: @"John Ford"];
NSMutableArray<NSObject<Displayable> *> *returnValue = [[NSMutableArray alloc] initWithCapacity:2];
[returnValue addObject:meFakingMyAge];
[returnValue addObject:theQuiteMan];
return returnValue;
}
@end
Which, believe me, is a huge relief when dealing with large legacy projects.
In case you want to play with a complete example, I uploaded a sample project to GitHub.