巧用 Class Extension 分離介面依賴

發表於2016-05-11

Class ExtensionCategory 是我們經常使用的 Objective-C 語法:

還記得最開始學習 Objective-C 時,並沒有支援 Class Extension,當時只能湊活的用個 Private 的 Category 充當,需要新增私有成員變數時那叫個痛苦,直到大概四年前的 WWDC 終於宣佈新增上了 Class Extension 的語法,當時底下的開發者們含淚報以了熱烈掌聲,它讓類的封裝變的更加得心用手。

在類組織結構上,Category 可以用來幫助拆分功能,讓一個大型的類分治管理:(類似 NSString.h

不過有兩個設計原則必須要遵守:

  1. Category 的實現可以依賴主類,但主類一定不依賴 Category,也就是說移除任何一個 Category 的程式碼不會對主類產生任何影響。
  2. Category 可以直接使用主類已有的私有成員變數,但不應該為實現 Category 而往主類中新增成員變數,考慮在 Category 的實現中使用 objc association 來達到相同效果。

所以 Category 一定是簡單插拔的,就像買個外接鍵盤來擴充套件在 MacBook 上的寫碼能力,但當拔了鍵盤,MacBook 的執行不會受到任何影響。

而 Class Extension 和 Category 在語言機制上有著很大差別:Class Extension 在編譯期就會將定義的 Ivar、屬性、方法等直接合入主類,而 Category 在程式啟動 Runtime Loading 時才會將屬性(沒 Ivar)和方法合入主類。但有意思的是,兩者在在語法解析層面卻只有細微的差別,可以嘗試用 clang 命令檢視一個檔案的 AST(抽象語法樹)

生成 AST 是 Clang 其中一個比較重要的職責,像 Xcode 的程式碼補全、語法檢查、程式碼風格規範都是在這一層做的;如果像我一樣無聊,也可以玩玩 libclang,一個 C 語言 Clang API,輸入程式碼,就能將其解析成語法樹,通過遍歷 AST,可以取得每個 Decl 和 Token 的資訊和所處的原始碼行數和位置,大到類定義,小到一個逗號一個分號都能完全掌控,非常有助於理解編譯器如何處理原始碼;有了 libclang,定義些規則就能實現個簡單的 Linter 啦。

上面的命令會在控制檯中列印出一堆花花綠綠的語法樹結構,挑出我們關注的資訊:

可以看出,Class Extension 和 Category 在 AST 中的表示都是 ObjCCategoryDecl,只是有無名字的區別,也可以說 Class Extension 是匿名的 Category

既然 Category 可以有 N 個,Class Extension 也可以有,且它不限於寫在 .m 中,只要在 @implementation 前定義就可以,我們可以利用這個性質,將 Header 中的宣告按功能歸類:

與 Category 不同,Class Extension 的分組形式並沒有破壞 “一個主類” 的 基本外交原則 基本結構,還可以把屬性( Ivar )也放心丟進來。

— 正題分割線 —

除此之外,Class Extension 還能巧妙的解決一個介面暴露問題,若有下面的宣告:

假設 Sark.h 是 Sark.framework 唯一暴露的 Header,而 framework 中的一個私有類需要獲取這個公共類的某個屬性(或方法)該怎麼辦?上面的 creditCardPassword 屬性需要一個對外不可見而對內可見的地方宣告,這時候可以利用 Class Extension:

將對公業務和對私業務用 Class Extension 的形式拆到兩個 Header 中,這樣私有類對私有屬性的依賴就被成功隔離開了:

Done.

 

 

相關文章