Using Objective-C Lightweight GenericsJun 6, 2016 · 3 minute read · Comments
I wrote about using nullable to annotate your Objective-C code some time ago. It is some extra work and not pretty but has some advantages if you want to use Objective-C code from Swift. The nullable hints give you a safer Swift interface that does not assume everything to be an ugly and potentially unsafe implicitly unwrapped optional.
What I did not mention is an extra step you can take to improve the Swift interface that also has benefits for the Objective-C code. This post summarises what you need to know about Objective-C Lightweight Generics.
Foundation Collection Classes
The Xcode 7.0 release notes introduced lightweight generics to the Foundation collection classes:
Lightweight generics now allow you to specify type information for collection classes such as NSArray, NSSet, and NSDictionary. The type information improves Swift access when you bridge from Objective-C, and simplifies the code you have to write.
To understand how this is useful consider an Objective-C class that has three properties as follows:
NS_ASSUME_NONNULL_BEGIN @property (nonatomic,strong) NSArray *dueDates; @property (nonatomic,strong) NSDictionary *dataDictionary; @property (nonatomic,strong) NSSet *filter; NS_ASSUME_NONNULL_END
The generated Swift interface for these properties is not ideal:
public var dueDates: [AnyObject] public var dataDictionary: [NSObject : AnyObject] public var filter: Set<NSObject>
We may intend for the array of dates to contain
NSDate objects but that is not clear to the compiler. This leaves our Swift code looking less than type safe.
Starting with Xcode 7.0 we can make our
NSSet collections safer by declaring the types of the parameters:
@property (nonatomic,strong) NSArray<NSDate *> *dueDates; @property (nonatomic,strong) NSDictionary<NSNumber *, NSString *> *dataDictionary; @property (nonatomic,strong) NSSet<NSString *> *filter;
Compare the difference in the generated Swift interface we get now. We have replaced the
NSObject types with the actual types we expect in each collection:
public var dueDates: [NSDate] public var dataDictionary: [NSNumber : String] public var filter: Set<String>
Note: This only works for
NSSet. Swift ignores lightweight generics for other classes when imported. For example:
// Objective-C @property (nonatomic,strong) NSMutableArray<NSDate *> *dates; // Swift public var dates: NSMutableArray
Good for Swift, Good for Objective-C
Even if you don’t care about Swift interoperability using lightweight generics can help your Objective-C code. The compiler can now warn you when you mistake the contents of a collection:
// NSArray<NSDate *> *dueDates; NSString *lastDue = self.dueDates.lastObject; Incompatible pointer types initializing 'NSString *' with an expression of type 'NSDate * _Nullable'
An example adding an
NSString to a mutable array of
NSMutableArray<NSDate *> *dates = [NSMutableArray new]; [dates addObject:@"today"]; Incompatible pointer types sending 'NSString *' to parameter of type 'NSDate * _Nonnull'