你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 巧用 Class Extension 分離接口依賴

巧用 Class Extension 分離接口依賴

編輯:IOS開發基礎

aquila-drone-facebook1-550.jpg

Class Extension 和 Category 是我們經常使用的 Objective-C 語法:

// Class Extension
@interface Sark ()
@end
// Category
@interface Sark (Gay)
@end

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

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

// Sark.h
@interface Sark : NSObject
@property (nonatomic, copy) NSSting *name;
@end
@interface Sark (Gay)
- (void)behaviorLikeGay;
@end
// Sark+Work.h <----- 也可拆分成多個文件
@interface Sark (Work)
- (void)writeObjectiveC;
@end

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

  • Category 的實現可以依賴主類,但主類一定不依賴 Category,也就是說移除任何一個 Category 的代碼不會對主類產生任何影響。

  • Category 可以直接使用主類已有的私有成員變量,但不應該為實現 Category 而往主類中添加成員變量,考慮在 Category 的實現中使用 objc association 來達到相同效果。

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

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

$ clang -Xclang -ast-dump -fsyntax-only main.m

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

上面的命令會在控制台中打印出一堆花花綠綠的語法樹結構,挑出我們關注的信息:

// ...
|-ObjCCategoryDecl  line:7:12
| |-ObjCInterface 'Sark'
|-ObjCCategoryDecl  line:15:12 Gay
| |-ObjCInterface 'Sark'
// ...

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

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

// Sark.h
@interface Sark : NSObject
// 這裡定義了很多基本屬性和方法
@end
@interface Sark () // Gay
@property (nonatomic, copy) NSString *gayFriend; // 屬性 √
- (void)behaviorLikeGay;
@end
@interface Sark () // Work
@property (nonatomic, copy) NSString *company; // 屬性 √
- (void)writeObjectiveC;
@end

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

— 正題分割線 —

除此之外,Class Extension 還能巧妙的解決一個接口暴露問題,若有下面的聲明:

// Sark.framework/Sark.h
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *creditCardPassword; // secret!
@end
// Sark.framework/PrivateSarkWife.h
@interface PrivateSarkWife : NSObject
- (void)robAllMoneyFromCreditCardOfSark:(Sark *)sark; // needs password!
@end

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

// Sark.h
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
// Sark+Internal.h <--- new
@interface Sark ()
@property (nonatomic, copy) NSString *creditCardPassword;
@end
// Sark.m
#import "Sark.h"
#import "Sark+Internal.h" // <--- new

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

// PrivateSarkWife.m
#import "PrivateSarkWife.h"
#import "Sark+Internal.h" // <--- 私有依賴
@implementation PrivateSarkWife
- (void)robAllMoneyFromCreditCardOfSark:(Sark *)sark {
    NSString *password = sark.creditCardPassword; // oh yeah!
}
@end
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved