Using Objective-C Lightweight Generics

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 NSArray, NSDictionary and 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 AnyObject and 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 NSArray, NSDictionary and 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 NSDate objects:

NSMutableArray<NSDate *> *dates = [NSMutableArray new];
[dates addObject:@"today"];

Incompatible pointer types sending 'NSString *' to parameter of type 'NSDate * _Nonnull'

Adding lightweight generics to your own classes is also possible (see here or here) - just remember Swift will ignore them when imported.

Further Reading

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Success! Now check your email to confirm your subscription and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

OK! Check your inbox (or spam folder) for an email to confirm your details and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
Archives Categories