Nullability and Objective-C

UPDATE: This post was updated to use the new _Nullable syntax in Xcode 7.

One of the great things about Swift is that it transparently interoperates with Objective-C code, both existing frameworks written in Objective-C and code in your app. However, in Swift there’s a strong distinction between optional and non-optional references, e.g. NSView vs. NSView?, while Objective-C represents boths of these two types as NSView *. Because the Swift compiler can’t be sure whether a particular NSView * is optional or not, the type is brought into Swift as an implicitly unwrapped optional, NSView!.

In previous Xcode releases, some Apple frameworks had been specially audited so that their API would show up with proper Swift optionals. Xcode 6.3 supports this for your own code with a new Objective-C language feature: nullability annotations.

The Core: _Nullable and _Nonnull

At the core of this feature we have two new type annotations: _Nullable and _Nonnull. As you might expect, a _Nullable pointer may have a NULL or nil value, while a _Nonnull one should not. The compiler will tell you if you try to break the rules.

@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (AAPLListItem * _Nullable)itemWithName:(NSString * _Nonnull)name;
@property (copy, readonly) NSArray * _Nonnull allItems;
// ...
@end

// --------------

[self.list itemWithName:nil]; // warning!

You can use _Nullable and _Nonnull almost anywhere you can use the normal C const keyword, though of course they have to apply to a pointer type. However, in the common case there’s a much nicer way to write these annotations: within method declarations you can use the non-underscored forms nullable and nonnull immediately after an open parenthesis, as long as the type is a simple object or block pointer.

- (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name;
- (NSInteger)indexOfItem:(nonnull AAPLListItem *)item;

And for properties, you can use the same non-underscored spelling by moving the annotation into the property attributes list.

@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray *allItems;

The non-underscored forms are nicer than the underscored ones, but you’d still need to apply them to every type in your header. To make that job easier and to make your headers clearer, you’ll want to use audited regions.

Audited Regions

To ease adoption of the new annotations, you can mark certain regions of your Objective-C header files as audited for nullability. Within these regions, any simple pointer type will be assumed to be nonnull. This collapses our earlier example down into something much simpler.

NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// ...
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;

@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END

// --------------

self.list.name = nil;   // okay

AAPLListItem *matchingItem = [self.list itemWithName:nil];  // warning!

For safety, there are a few exceptions to this rule:

  • typedef types don’t usually have an inherent nullability—they can easily be either nullable or non-nullable depending on the context. Therefore, typedef types are not assumed to be nonnull, even within audited regions.
  • More complex pointer types like id * must be explicitly annotated. For example, to specify a non-nullable pointer to a nullable object reference, use _Nullable id * _Nonnull.
  • The particular type NSError ** is so often used to return errors via method parameters that it is always assumed to be a nullable pointer to a nullable NSError reference.

You can read more about this in the Error Handling Programming Guide.

Compatibility

What if your Objective-C framework has existing code written against it? Is it safe to just change your types like this? Yes, it is.

  • Existing compiled code that uses your framework will continue to work, i.e. the ABI does not change. This also means that existing code will not catch incorrect passing of nil at runtime.
  • Existing source code that uses your framework may get additional warnings for current uses of unsafe behavior at compile time when you move to the new Swift compiler.
  • nonnull does not affect optimization. In particular, you can still check parameters marked nonnull to see if they are actually nil at runtime. This may be necessary for backwards-compatibility.

In general, you should look at nullable and nonnull roughly the way you currently use assertions or exceptions: violating the contract is a programmer error. In particular, return values are something you control, so you should never return nil for a non-nullable return type unless it is for backwards-compatibility.

This feature was first released in Xcode 6.3 with the keywords __nullable and __nonnull. Due to potential conflicts with third-party libraries, we’ve changed them in Xcode 7 to the _Nullable and _Nonnull you see here. However, for compatibility with Xcode 6.3 we’ve predefined macros __nullable and __nonnull to expand to the new names.

Back to Swift

Now that we’ve added nullability annotations to our Objective-C header, let’s use it from Swift:

Before annotating our Objective-C:

class AAPLList : NSObject, NSCoding, NSCopying { 
	// ...
	func itemWithName(name: String!) -> AAPLListItem!
	func indexOfItem(item: AAPLListItem!) -> Int

	@NSCopying var name: String! { get set }
	@NSCopying var allItems: [AnyObject]! { get }
	// ...
}

After annotations:

class AAPLList : NSObject, NSCoding, NSCopying { 
	// ...
	func itemWithName(name: String) -> AAPLListItem?
	func indexOfItem(item: AAPLListItem) -> Int

	@NSCopying var name: String? { get set }
	@NSCopying var allItems: [AnyObject] { get }
	// ...
}

The Swift code is now cleaner. It’s a subtle change, but it will make using your framework more pleasant.

Nullability annotations for C and Objective-C are available starting in Xcode 6.3. For more information, see the Xcode 6.3 Release Notes.

All Blog Posts