Swift extensions can be applied to Objective-C types

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.

Leave a Reply

Your email address will not be published. Required fields are marked *