![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
Overview
自 WWDC 2015 推出和開源 Swift 2.0 後,大家對 Swift 的熱情又一次高漲起來,在羨慕創業公司的朋友們大談
Swift 新特性的同時,也有很多像我一樣工作上依然需要堅守着 Objective-C 語言的開發者們。今年的 WWDC 中介紹了幾個
Objective-C 語言的新特性,還是在“與 Swift 協同工作”這種 Topic
裡講的,越發凸顯這門語言的邊緣化了,不過有新特性還是極好的,接下來,本文将介紹下面三個主要的新特性:
Nullability
Lightweight Generics *
__kindof
然而 Nullability 并不算新特性了,從上一個版本的 llvm 6.1 (Xcode 6.3) 就已經支援。這個簡版的
Optional ,沒有 Swift 中 ? 和 ! 文法糖的支援,在 Objective-C 中就顯得非常啰嗦了:
block 裡面就更加詭異,比如一個 Request 的 start 方法可以寫成:
setter nullable,但是 getter nonnull,繞死了,最直覺例子就是 UIViewController 中的
它可以被設成 nil,但是調用 getter 時會觸發 -loadView 進而建立并傳回一個非 nil 的 view。
從 iOS9 SDK 中可以發現,頭檔案中所有 API 都已經增加了 Nullability 相關修飾符,想了解這個特性的用法,翻幾個系統頭檔案就差不離了。接口中
nullable 的是少數,是以為了防止寫一大堆 nonnull,Foundation 還提供了一對兒宏,包在裡面的對象預設加
nonnull 修飾符,隻需要把
nullable 的指出來就行,黑話叫 Audited Regions:
Nullability 在編譯器層面提供了空值的類型檢查,在類型不符時給出
warning,友善開發者第一時間發現潛在問題。不過我想更大的意義在于能夠更加清楚的描述接口,是主調者和被調者間的一個協定,比多少句文檔描述都來得清晰,打個比方:
NSURL 的這個 API 前面加了 nullable 後,更加顯式的指出了這個接口可能因為 URLString
的格式錯誤而建立失敗,使用時自然而然的就考慮到了判空處理。
不僅是屬性和方法中的對象,對于局部的對象、甚至
c 指針都可以用帶雙下劃線的修飾符,可以了解成能用
const 關鍵字的地方都能用 Nullability。
是以 Nullability 總的來說就是,寫着醜B,用着舒服 - -
Lightweight
Generics
Lightweight Generics 輕量級泛型,輕量是因為這是個純編譯器的文法支援(llvm 7.0),和
Nullability 一樣,沒有借助任何 objc runtime 的更新,也就是說,這個新文法在 Xcode 7
上可以使用且完全向下相容(更低的 iOS 版本)
帶泛型的容器
這無疑是本次最重大的改進,有了泛型後終于可以指定容器類中對象的類型了:
傳回值的 id 被替換成具體的類型後,令人感動的代碼提示也出來了:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
假如向泛型容器中加入錯誤的對象,編譯器會不開心的:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
系統中常用的一系列容器類型都增加了泛型支援,甚至連 NSEnumerator 都支援了,這是非常 Nice 的改進。和
Nullability 一樣,我認為最大的意義還是豐富了接口描述資訊,對比下面兩種寫法:
不用多想就清楚下面的數組中存的是什麼,避免了 NSString 和 NSURL 的混亂。
自定義泛型類
比起使用系統的泛型容器,更好玩的是自定義一個泛型類,目前這裡還沒什麼文檔,但攔不住我們寫測試代碼,假設我們要自定義一個 Stack
容器類:
這個 ObjectType 是傳入類型的 placeholder,它隻能在 @interface
上定義(類聲明、類擴充、Category),如果你喜歡用 T 表示也 ok,這個類型在 @interface 和 @end
區間的作用域有效,可以把它作為入參、出參、甚至内部 NSArray 屬性的泛型類型,應該說一切都是符合預期的。我們還可以給
ObjectType 增加類型限制,比如:
若什麼都不加,表示接受任意類型 ( id );當類型不滿足時編譯器将産生 error。
執行個體化一個 Stack,一切工作正常:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
對于多參數的泛型,用逗号隔開,其他都一樣,可以參考 NSDictionary 的頭檔案。
協變性和逆變性
當類支援泛型後,它們的 Type 發生了變化,比如下面三個對象看上去都是 Stack,但實際上屬于三個 Type:
當其中兩種類型做類型轉化時,編譯器需要知道哪些轉化是允許的,哪些是禁止的,比如,預設情況下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
我們可以看到,不指定泛型類型的 Stack
可以和任意泛型類型轉化,但指定了泛型類型後,兩個不同類型間是不可以強轉的,假如你希望主動控制轉化關系,就需要使用泛型的協變性和逆變性修飾符了:
__covariant - 協變性,子類型可以強轉到父類型(裡氏替換原則)
__contravariant - 逆變性,父類型可以強轉到子類型(WTF?)
協變:
效果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
逆變:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
協變是非常好了解的,像 NSArray 的泛型就用了協變的修飾符,而逆變我還沒有想到有什麼實際的使用場景。
__kindof 這修飾符還是很實用的,解決了一個長期以來的小痛點,拿原來的
UITableView 的這個方法來說:
使用時前面基本會使用 UITableViewCell 子類型的指針來接收傳回值,是以這個 API
為了讓開發者不必每次都蛋疼的寫顯式強轉,把傳回值定義成了 id 類型,而這個 API 實際上的意思是傳回一個
UITableViewCell 或 UITableViewCell 子類的執行個體,于是新的 __kindof
關鍵字解決了這個問題:
既明确表明了傳回值,又讓使用者不必寫強轉。再舉個帶泛型的例子,UIView 的
subviews 屬性被修改成了:
這樣,寫下面的代碼時就沒有任何警告了:
Where to
go
有了上面介紹的這些新特性以及如 instancetype 這樣的曆史更新,Objective-C
這門古老語言的類型檢測和類型推斷終于有所長進,現在不論是接口還是代碼中的 id
類型都越來越少,更多潛在的類型錯誤可以被編譯器的靜态檢查發現。
同時,個人感覺新版的 Xcode
對繼承鍊構造器的檢測也加強了,NS_DESIGNATED_INITIALIZER 這個宏并不是新面孔,可以使用它标志出像
Swift 一樣的指定構造器和便捷構造器。
最後,附上一段用上了所有新特性的代碼,Swift 是發展趨勢,如果你暫時依然要寫 Objective-C
代碼,把所有新特性都用上,或許能讓你到新語言的遷移更無痛一點。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiYWan5ycuFmc091Zz9CXu9Wbt92Yvw1cldWYtl2LcVGb5R3c3c2bsJ2Lc52YuMnah5Waz5yZtl2cvw1LcpDc0RHaiojIsJye.gif)
References
<a href="https://msdn.microsoft.com/zh-cn/library/dd799517.aspx">https://msdn.microsoft.com/zh-cn/library/dd799517.aspx</a>
<a href="https://gist.github.com/jtbandes/881f07a955ff2eadd1a0">https://gist.github.com/jtbandes/881f07a955ff2eadd1a0</a>