# Swift Nullability and Objective-C
通過Bridging-Header文件,Swift可以與Objective-C無縫調用,但是Swift與Objective-C有一個很大的不同點:Swift支持`Optional`類型。比如`NSView`和`NSView?`,在Objective-C里對此只有一種表示,即`NSView *`,既可以用來表示該View為nil、也能表示為非nil,此時Swift編譯器是無法確定這個NSView是不是`Optional`類型的,這種情況下Swift編譯器會把它當作`NSView!`處理,隱式拆包。
在早期發布的Xcode版本中,蘋果的一些框架針對Swift的Optional類型進行了一些專門審查,使他們的API能夠適配Optional,而Xcode 6.3的發布,給我們帶來了Objetive-C的一個新特性:`nullability`注解,利用該特性我們也能對自己的代碼進行類似的處理。
## 核心:__nullable 和 __nonnull
這個功能給我們帶來了兩個新的類型注解:`__nullable`和`__nonnull`,就像你看到的,`__nullable`可以表示一個`NULL`或者`nil`值,而`__nonnull`則剛好相反。如果你違反了這個規則,你將會收到編譯器的警告:
~~~
@interface AAPLList : NSObject <NSCoding, NSCopying>
//---
- (AAPLListItem * __nullable)itemWithName:(NSString * __nonnull)name;
@property (copy, readonly) NSArray * __nonnull allItems;
//---
@end
//--------------
[self.list itemWithName:nil]; // warning!
~~~
你能在任何地方使用`__nullable`和`__nonnull`關鍵字,比如和標準C的`const`一起使用,也能直接應用到指針上。但是在大多數情況下,你會以優雅的方式寫下這些注解:在方法定義或聲明里,只要類型是一個簡單的對象或者Block指針,你就能以***不帶下劃線***的方式(`nullable`或`nonnull`)直接寫在左括號后面:
~~~
- (nullable AAPLListItem *)itemWithName:(nonnull NSString *)name;
- (NSInteger)indexOfItem:(nonnull AAPLListItem *)item;
~~~
對于`@property`,你也能以同樣的方式寫在它的屬性列表里:
~~~
@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray *allItems;
~~~
不帶下劃線的形式比帶下劃線的形式看起來更簡潔,但你仍然需要將它們應用到頭文件的每一個類型里。如果你覺得麻煩同時想讓頭文件變得更加簡潔,你就會使用到審查區域。
## 審查區域(Audited Regions)
如果想更加輕松的添加這些注解,那么你可以把Objective-C頭文件的某個區域標記為需要審查(for nullability),在這個區域內,所有簡單的指針類型都會被當作`nonnull`,我們之前的例子會變成這樣:
~~~
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!
~~~
> Xcode 6.3(iOS 8.3 SDK)引入了`NS_ASSUME_NONNULL_BEGIN / END`宏?
> 其中itemWithName方法的name參數沒有使用Nullability特征,但是會被當作`nonnull`處理
為了安全起見,這個規則也有一些例外情況:
* `typedef`定義的類型不會繼承`nullability`特性—它們會輕松地根據上下文選擇nullable或non-nullable,所以,就算是在審查區域內,`typedef`定義的類型也不會被當作`nonnull`。
* 像`id *`這樣更復雜的指針類型必須被顯式地注解,比如,你要指定一個nonnull的指針為一個nullable的對象引用,那么需要使用`__nullable id * __nonnull`。
* 像`NSError **`這些特殊的、通過方法參數返回錯誤對象的類型,將總是被當作是一個nullable的指針指向一個nullable的指針:`__nullable NSError ** __nullable`。
你可以通過[Error Handling Programming Guide](http://developer.apple.com/go/?id=error-handling-cocoa)了解更多詳細內容。
## 兼容性
你的Objective-C框架現有的代碼寫對了嗎?是否能安全的改變它們的類型??*Yes, it is*.
* 現有的、被*編譯過*的代碼還能繼續使用你的框架,也就是說[ABI](https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Introduction/Introduction.html)沒有變化(編譯器不會報錯),這也意味著現有的代碼不會在運行時捕獲到`nil`的不正確傳值。
* 用新的Swift編譯器編譯現有的*源碼*,并在使用你的框架的時候,可能會因為一些不安全的行為在編譯時得到額外的警告。
* `nonnull`不影響優化,尤其是你還可以在運行時檢查標記為`nonnull`的參數是否為`nil`,這可能需要必要的向后兼容。
大多數情況下,應該接受`nullable`和`nonnull`,你當前所使用的斷言或者異常太粗暴了:違反約定是程序員經常犯的錯誤(而`nullable`和`nonnull`能在編譯時就解決問題)。特別的,返回值是你能控制的東西,永遠不應該對一個non-nullable的返回類型返回一個`nil`,除非這是為了向后兼容。
## 回到Swift
現在我們給我們的Objective-C頭文件添加了nullability注解,我們在Swift中使用它:?
在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 }
//---
}
~~~
添加注解之后:
~~~
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 }
//---
}
~~~
這些Swift代碼非常清晰。只有一些細節的變化,但是它讓你的框架使用起來更爽。
- 前言
- iOS 自定義頁面的切換動畫與交互動畫 By Swift
- Swift 元組(Tuples)介紹
- Swift 可選值(Optional Values)介紹
- Swift Switch介紹
- Swift 值類型和引用類型
- Swift 柯里化(Currying)
- iOS GCD使用指南
- iOS8 Core Image In Swift:自動改善圖像以及內置濾鏡的使用
- 讓Xcode自動更新Build版本
- Swift 全功能的繪圖板開發
- Swift Nullability and Objective-C
- Swift Core Data 圖片存儲與讀取Demo
- Swift 繪圖板功能完善以及終極優化
- 如何設計一個 iOS 控件?(iOS 控件完全解析)