objc@interface的設計哲學與設計技巧

blog.sunnyxx.com

我是前言

學習objc時,尤爲是先學過其餘編程語言再來看objc時,總會對objc的聲明的關鍵字interface感到有點奇怪,在其它面向對象的語言中一般由class關鍵字來表示,而interface在java中表示的卻大約至關於objc的protocol,這個關鍵字的區別究竟表明了objc語言的設計者怎樣的思想呢,在objc類設計中須要注意哪些問題呢?接下來對這個問題進行一些思考和探究.java


interface?

先來段Wiki:c++

In object-oriented programming, a protocol or interface is a common means for unrelated objects to communicate with each other. These are definitions of methods and values which the objects agree upon in order to cooperate.git

接口約定了對象間交互的屬性和方法,使得對象間無需瞭解對方就能夠協做。
說的洋氣點就是解耦嘛,細心點也能發現Wiki中interfaceprotocol表示了相近的語義。
引用我和項目組架構師討論有關interface的問題時他的說法:github

interface就是一個object定義的能夠被外界影響的方式編程

說着他指了下旁邊桌子上放着的一把傘,說,這把傘我能夠打開它,打開這個動做就是它的一個interface,桌子旁邊還放着一個盒子,雖然它和傘都放在這張桌子上,可是它們之間永遠不會互相影響,因此:微信

interface只存在於能互相影響的二者間架構


@interface生成了class?

學習objc時最先接觸的就是怎麼寫一個類了,從.h中寫@interface聲明類,再從.m中寫@implementation實現方法,因此,objc中寫一個@interface就至關於c++中寫一個class。但這是真的麼?app

寫個小test驗證一下:
有兩個類,SarkDarkSark類只有.m文件,其中只寫@implementationDark類只有.h頭文件,其中只寫@interface,而後以下測試代碼:編程語言

1
2
Class sarkClass = NSClassFromString(@"Sark");
Class darkClass = NSClassFromString(@"Dark");

NSClassFromString方法調用了runtime方法,根據類名將加載進runtime的這個類找出來,沒有這個類就回返回空(Nil)。
結果是sarkClass存在,而darkClass爲空,說明什麼?是否說明其實@implementation纔是真正的Class?
進一步,不止能取到這個沒有@interface的類,還能夠正常調用方法(由於萬能的runtime)ide

以下面的測試代碼:

1
2
Sark *sark = [Sark new];
[sark speak];

 

要是沒有@interface的聲明,類名,方法名都會報錯說找不到,可是能夠像下面同樣繞一下:

1
2
3
Class cls = NSClassFromString(@"Sark");
id obj = [cls performSelector:NSSelectorFromString(@"new")];
[obj performSelector:NSSelectorFromString(@"speak")];

其實,從rewrite後的objc代碼能夠發現,對於消息的發送,偏偏就是會被處理成相似上面的代碼,使用字符串mapping出Classselctor等再使用objc_msgSend()進行函數調用,以下面所示:

1
2
3
// 通過clang -rewrite-objc 命令重寫後的代碼
Sark *sark = ((id (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Sark"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)sark, sel_registerName("speak"));

對比@interface和@implementation

@interface 咱們幹過的事:

  1. 繼承
  2. 聲明協議
  3. 定義實例變量(@interface後面加大括號那種)
  4. 定義@property
  5. 聲明方法

@implementation 咱們幹過的和能夠乾的事:

  1. 繼承
  2. 定義實例變量
  3. 合成屬性(@synthesize和@dynamic)
  4. 實現方法(包括協議方法)

@implementation幹一些事情用的相對較少,可是是徹底合法的,如這樣用:

1
2
3
@implementation Sark : NSObject {
NSString *_name;
}

經過對比能夠發現,@interface對objc類結構的合成並沒有決定性做用,加上無決定性是由於若是沒有@interface會丟失一些類自省的原始數據,如屬性列表和協議列表,但對於純粹的對象消息發送並沒有影響。
因此說,能夠得出這麼一個結論,objc中@interface就是爲了給調用者看的,是和調用者的一個protocol,沒錯,就是protocol

對比@interface和@protocol

與其把@implementation扯進來不如對比下@protocol

我理解objc的@interface@protocal間惟一的區別就是是否和一個類型綁定,這讓我想起來鴨子類型(Duck typing), wiki連接

「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就能夠被稱爲鴨子。」

Duck type在objc的體現無疑就是@protocol了,咱們經常使用id<XXXDelegate> delegate的方式聲明一個delegate,咱們無需care這貨究竟是什麼類型,咱們只知道他能幹什麼就能夠work了。一樣的功能我也可使用XXXDelegate *delegate的方式來定義,只不過這樣的話這個類又須要耦合一個XXXDelegate類型,而這個delegate類是它本來並不須要關心的。

因此說,@interface@protocol的強類型升級版。

舉個NSObject的栗子最合適:

1
2
3
@interface NSObject <NSObject> {
Class isa;
}

 

NSObject之因此成爲NSObject,絕大多數都是<NSObject>協議定義的方法,實體類@interface定義的惟一一個變量isa指針,爲了繼承鏈和消息傳遞。
除了<NSObject>協議外,NSObject還有不少Category來補充它的功能,其實仔細想一想,Category更像protocol,一個補充協議,一樣不能添加實例變量,可是和@interface同樣須要與Class綁定。

進一步來說,自從屬性能自動合成變量以後,在頭文件@interface中寫大括號聲明實例變量的狀況愈來愈少(能夠參見近幾個版本iOS SDK中類頭文件裏這種寫法幾乎消失),所以,@interface@protocol的差異進一步縮小。


類與接口的設計原則 - 電視和遙控器

我喜歡將Classinterface的關係比喻成電視+遙控器,那麼objc中的消息機制就能夠理解成:
用戶(caller)經過遙控器(interface)上的按鈕(methods)發送紅外線(message)來操縱電視(object)
因此,有沒有遙控器,電視都在那兒,也就是說,有沒有interface,class都是存在的,只是這種存在並無意義,就好像這個電視沒人會打開,沒人會用,沒人能看,一堆廢鐵擺在那兒。

對比簡潔的遙控器,一個擁有不少按鈕的老式電視遙控器,咱們常常會用到的按鈕能有幾個呢?

因此,在設計一個類的interface的時候,如同在設計遙控器應該有怎樣功能的按鈕,要從調用者的角度出發,區分邊界,應該時刻有如下幾點考慮:

  1. 這個方法或屬性真的屬於這個類的職責麼?(電視遙控器能遙控空調?)
  2. 這個方法或屬性真的必須放在.h中(而不是放在.m的類擴展中)麼?
  3. 調用者必須看文檔才能知道這個類該如何使用麼?(同一個業務須要調用者按順序調用屢次(而不是將這些細節隱藏,同時提供一個簡潔的接口)才行)
  4. 調用者是否能夠很容易發現類內部的變量和實現方式?(腦補下電視裏面一塊電路板漏在外面半截- -)

objc的@interface設計技巧Tips

看過很多代碼,從@interface設計上多少就能看出做者的水平,分享下我對於這個問題的一些拙見。

只暴露外部須要看到的

好比,有以下一個類(這個類無心義,主要關注寫法):

1
2
3
4
5
6
7
8
// Sark.h
@interface SarkViewController : NSObject <NSXMLParserDelegate /*1*/, NSCopying> {
NSString *_name; // 2
IBOutlet UITextField *_nameTextField; // 2
}
@property (nonatomic, strong) NSXMLParser *parser; // 3
- (IBAction)nameChangedAction:(id)sender; // 4
@end

 

這個interface出現的問題:

  1. 類內部本身使用的協議,如<NSXMLParserDelegate>不該該在頭文件@interface中聲明,而應該在類擴展中聲明;公開由外部調用的協議,如<NSCopying>則寫在這兒是正確的。
  2. 實例變量IBOutlet不該出如今這兒定義,這將類的內部實現暴露了出去,自從屬性能夠自動合成後,這裏就更應該清淨了。
  3. 內部使用的屬性對象不要暴露在外,應該移動到類擴展中。
  4. 調用者對IBAction一樣不須要關心,那麼就不該該放在這兒。

合理分組子功能

  • 將相同功能的一組屬性或方法寫在一塊兒

使用這個類或者對其進行修改時,通常都是從功能上找,因此把同一功能模塊的一組屬性或方法寫在一塊

  • 純操做方法的子功能(無需向類添加變量)使用Category分塊
  • 在頭文件中也可使用類擴展將interface按功能分區

Category裏不能添加實例變量,可是類擴展能夠,通常都在.m中做爲私有interface使用,一樣在頭文件裏做爲分區使用,如,ReactiveCocoa中的RACStream.h

避免頭文件污染

首先,類實現內部.m文件中使用的其餘interface應該在.m文件import,若是也寫在header中就會形成對調用者的污染;當interface中出現其餘Classprotocol時,可使用前置聲明@class XXX@protocol XXX;當模塊(一組類)內部間須要有一些定義(如常量、類型)而又不須要模塊使用者知道時,使用一個內部頭文件在模塊中使用。

避免接口過分設計

考慮調用者的使用方即是很必要的,過火了反而增長了複雜度:

1
2
3
4
5
6
7
8
@interface Sark : NSObject
- (instancetype)init;
- (instancetype)initWithName:(NSString *)name;
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex;
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex age:(NSInteger)age;
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex age:(NSInteger)age friends:(NSArray *)friends;
// 無數多個 //
@end

提供了一組這樣的方法,調用者可能只能用到其中的一個,那這樣倒不如只留一個接口。

避免單例的濫用

單例模式當然好用,但感受有點過分,將接口設計成單例入口前須要考慮一下:

  1. 這個類表達的含義真的只能有一個實例麼?(如UIApplication)仍是隻是爲了好調用而已?
  2. 這個單例持有的內存一直存在
  3. 是否能用類方法代替?
  4. 這個單例對象是否能成爲另外一個單例對象的屬性?若是是,應該做爲屬性

隱藏繼承關係中的私有接口

感謝@像條狗在飛在留言中提出的問題,問題大概能夠總結爲:當子類須要使用父類的一個私有屬性(方法)時,須要把這個屬性(方法)放到父類的header中,但暴露給子類的同時暴露給了外部調用者,如何解決?

個人方案是:創建一個私有header,使用類擴展定義父類須要暴露給子類的屬性(方法),而後在各自的.m文件中引用,如:

有Father類和Son類,繼承關係,能夠考慮建一個如FatherPrivate.h的私有header:

1
2
3
4
5
// FatherPrivate.h
@interface Father ()
@property (nonatomic, copy) NSString *privateThingSonNeed;
- (void)privateMethodNeedsSonOverride;
@end

同時在Father.m和Son.m中同時import這個私有header,這樣,Father和Son內部對於定義的屬性和方法都是透明的,而對外部是隱藏的(由於兩個類的header中都沒有import這個私有header)


總結

  • @implementation合成了Class,而非@interface@interface@protocol的強類型升級版,它們和Category都表示了相近的含義
  • 咱們應該善於面向接口編程,劃清邊界,將類的實現隱藏在調用者所見以外,使主調和被調者之間保持最少知識原則
  • @interface自己就是最好的文檔

References

http://en.m.wikipedia.org/wiki/Interface_(object-oriented_programming)
http://zh.wikipedia.org/wiki/%E9%B8%AD%E5%AD%90%E7%B1%BB%E5%9E%8B


原創文章,轉載請註明源地址,blog.sunnyxx.com

1


原創文章,轉載請註明原地址:blog.sunnyxx.com 
對博主有意思?新浪微博@我就叫Sunny怎麼了 
or 微信搜索訂閱號sunnyxx或掃下面的逗比狗 

相關文章
相關標籤/搜索