Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to annotate Objective-C APIs for use in Swift (e.g. return types)

According to the Xcode release notes, Apple has been "auditing" their existing APIs to remove implicitly unwrapped optionals. That means that instead of T!, their APIs will return T or T? where appropriate.

Where do they do this? How can I annotate/wrap my existing Objective-C code (especially libraries) to make it cleaner to use from Swift?

like image 215
nschum Avatar asked Sep 09 '14 14:09

nschum


2 Answers

Xcode 6.3/Swift 1.2

Xcode 6.3 added official support for annotating nullability in Objective-C.

Nullability

The nullability of a value can be declared by annotating the type with the keywords __nullable, __nonnull and __null_unspecified (the default). In properties and methods the keywords are nullable, nonnull and null_unspecified.

Examples from the Xcode release notes

   - (void)registerNib:(nonnull UINib *)nib
forCellReuseIdentifier:(nonnull NSString *)identifier;

- (nullable UITableViewCell *)cellForRowAtIndexPath:(nonnull NSIndexPath)indexPath;

@property (nonatomic, readwrite, retain, nullable) UIView *backgroundView;

Changing the default

null_unspecified (which translates to T!) is the default for all existing code. A useful feature is the ability to change the default for sections of your API.

NS_ASSUME_NONNULL_BEGIN
// nonnull is the default here
NS_ASSUME_NONNULL_END

This removes a lot of noise, since methods that accept and handle nil usually are the exception, not the rule. Personally, I would use this for all audited APIs.

null_resettable

null_resettable is an additional annotation that is used for the uncommon case where you can set a property to nil, but it will never be nil (because it resets to a default value).

 @property (nonatomic, retain, null_resettable) UIColor *tintColor;

Personally, I would avoid this behavior for new code. The hybrid nature of such properties isn't a good fit for Swift.

Xcode 7/Swift 2 (Beta)

Xcode 7 adds support for annotating generic types in Objective-C.

Generic type annotations for collections

NSArray, NSSet and NSDictionary (which are automatically bridged to Swift's Array, Set and Dictionary can be annotated with the type of their contents.

@property NSArray<NSString *> *stringArray;
@property NSSet<NSString *> *stringSet;
@property NSDictionary<NSString *, NSString *> *stringDict;

There's also the __kindof keyword that tells the Objective-C compiler to be less strict and allow downcasting. But it doesn't affect the Swift side.

Generic type annotations for custom classes

@interface MyArray1<__covariant T> : NSObject
- (void)addObject:(T)object;
@end

@interface MyArray2<__covariant T : NSObject *> : NSObject
- (void)addObject:(T)object;
@end

@interface MyArray3<__covariant T : id<NSCopying>> : NSObject
- (void)addObject:(T)object;
@end

The @implementation doesn't know about T and uses id/NSObject */id<NSCopying> as it always did.

like image 68
nschum Avatar answered Oct 21 '22 12:10

nschum


I believe there's a way to do this at the moment. It looks like the information is being compiled to

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator

in files like

UIKit.apinotesc 
UIKit.swiftdoc 
UIKit.swiftmodule

The *.swiftmodule classes are generated by Xcode as part of the swift build but the additional information may be in the .apinotesc file, which there doesn't appear to be a documented form of generating it at the moment.

However, the swift command has an -apinotes option, which is used to generate this. You can see from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/UIKit.apinotesc that you can parse this with:

xcrun swift -apinotes -binary-to-yaml  -o=- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/UIKit.apinotesc

This generates a YAML file looking like:

Classes:         
  - Name:            NSFileProviderExtension
    Availability:    available
    AvailabilityMsg: ''
    Methods:         
      - Selector:        'URLForItemWithPersistentIdentifier:'
        MethodKind:      Instance
        Nullability:     [ N ]
        NullabilityOfRet: U
        Availability:    available
        AvailabilityMsg: ''

If I were a betting man (and I'm not), I'd say that the MethodKind determines whether it's an Instance or a Class and that the Nullability is being used by Swift to determine whether it's an optional or not. I would suspect:

  • U - (implicitly) unwrapped optional
  • O - optional
  • N - non-optional?

These are wild guesses though.

like image 44
AlBlue Avatar answered Oct 21 '22 11:10

AlBlue