Xcode 6.3新特性,nullability annotations。
最近在用Xcode 6.3寫(xiě)代碼,一些涉及到對(duì)象的代碼會(huì)報(bào)如下編譯器警告:
- Pointer is missing a nullability type specifier (__nonnull or __nullable)
于是google了一下,發(fā)現(xiàn)這是Xcode 6.3的一個(gè)新特性,即nullability annotations。
Nullability Annotations
我們都知道在swift中,可以使用!和?來(lái)表示一個(gè)對(duì)象是optional的還是non-optional,如view?和view!。而在Objective-C中則沒(méi)有這一區(qū)分,view即可表示這個(gè)對(duì)象是optional,也可表示是non-optioanl。這樣就會(huì)造成一個(gè)問(wèn)題:在Swift與Objective-C混編時(shí),Swift編譯器并不知道一個(gè)Objective-C對(duì)象到底是optional還是non-optional,因此這種情況下編譯器會(huì)隱式地將Objective-C的對(duì)象當(dāng)成是non-optional。
為了解決這個(gè)問(wèn)題,蘋(píng)果在Xcode 6.3引入了一個(gè)Objective-C的新特性:nullability annotations。這一新特性的核心是兩個(gè)新的類(lèi)型注釋:__nullable和__nonnull。從字面上我們可以猜到,__nullable表示對(duì)象可以是NULL或nil,而__nonnull表示對(duì)象不應(yīng)該為空。當(dāng)我們不遵循這一規(guī)則時(shí),編譯器就會(huì)給出警告。
我們來(lái)看看以下的實(shí)例,
- @interface TestNullabilityClass ()
- @property (nonatomic, copy) NSArray * items;
- - (id)itemWithName:(NSString * __nonnull)name;
- @end
- @implementation TestNullabilityClass
- ...
- - (void)testNullability {
- [self itemWithName:nil]; // 編譯器警告:Null passed to a callee that requires a non-null argument
- }
- - (id)itemWithName:(NSString * __nonnull)name {
- return nil;
- }
- @end
不過(guò)這只是一個(gè)警告,程序還是能編譯通過(guò)并運(yùn)行。
事實(shí)上,在任何可以使用const關(guān)鍵字的地方都可以使用__nullable和__nonnull,不過(guò)這兩個(gè)關(guān)鍵字僅限于使用在指針類(lèi)型上。而在方法的聲明中,我們還可以使用不帶下劃線的nullable和nonnull,如下所示:
- - (nullable id)itemWithName:(NSString * nonnull)name
在屬性聲明中,也增加了兩個(gè)相應(yīng)的特性,因此上例中的items屬性可以如下聲明:
- @property (nonatomic, copy, nonnull) NSArray * items;
當(dāng)然也可以用以下這種方式:
- @property (nonatomic, copy) NSArray * __nonnull items;
推薦使用nonnull這種方式,這樣可以讓屬性聲明看起來(lái)更清晰。
Nonnull區(qū)域設(shè)置(Audited Regions)
如果需要每個(gè)屬性或每個(gè)方法都去指定nonnull和nullable,是一件非常繁瑣的事。蘋(píng)果為了減輕我們的工作量,專門(mén)提供了兩個(gè)宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在這兩個(gè)宏之間的代碼,所有簡(jiǎn)單指針對(duì)象都被假定為nonnull,因此我們只需要去指定那些nullable的指針。如下代碼所示:
- NS_ASSUME_NONNULL_BEGIN
- @interface TestNullabilityClass ()
- @property (nonatomic, copy) NSArray * items;
- - (id)itemWithName:(nullable NSString *)name;
- @end
- NS_ASSUME_NONNULL_END
在上面的代碼中,items屬性默認(rèn)是nonnull的,itemWithName:方法的返回值也是nonnull,而參數(shù)是指定為nullable的。
不過(guò),為了安全起見(jiàn),蘋(píng)果還制定了幾條規(guī)則:
- typedef定義的類(lèi)型的nullability特性通常依賴于上下文,即使是在Audited Regions中,也不能假定它為nonnull。
- 復(fù)雜的指針類(lèi)型(如id *)必須顯示去指定是nonnull還是nullable。例如,指定一個(gè)指向nullable對(duì)象的nonnull指針,可以使用”__nullable id * __nonnull”。
- 我們經(jīng)常使用的NSError **通常是被假定為一個(gè)指向nullable NSError對(duì)象的nullable指針。
兼容性
因?yàn)镹ullability Annotations是Xcode 6.3新加入的,所以我們需要考慮之前的老代碼。實(shí)際上,蘋(píng)果已以幫我們處理好了這種兼容問(wèn)題,我們可以安全地使用它們:
老代碼仍然能正常工作,即使對(duì)nonnull對(duì)象使用了nil也沒(méi)有問(wèn)題。
老代碼在需要和swift混編時(shí),在新的swift編譯器下會(huì)給出一個(gè)警告。
nonnull不會(huì)影響性能。事實(shí)上,我們?nèi)匀豢梢栽谶\(yùn)行時(shí)去判斷我們的對(duì)象是否為nil。
事實(shí)上,我們可以將nonnull/nullable與我們的斷言和異常一起看待,其需要處理的問(wèn)題都是同一個(gè):違反約定是一個(gè)程序員的錯(cuò)誤。特別是,返回值是我們可控的東西,如果返回值是nonnull的,則我們不應(yīng)該返回nil,除非是為了向后兼容。
參考
Nullability and Objective-C