objc@interface的设计哲学与设计技巧
本文原文發表自我的自建博客,cnblogs同步發表,格式未經調整,內容以原博客為準。
?
我是前言
學習objc時,尤其是先學過其他編程語言再來看objc時,總會對objc的類聲明的關鍵字interface感到有點奇怪,在其它面向對象的語言中通常由class關鍵字來表示,而interface在java中表示的卻大約相當于objc的protocol,這個關鍵字的區別究竟代表了objc語言的設計者怎樣的思想呢,在objc類設計中需要注意哪些問題呢?接下來對這個問題進行一些思考和探究.
interface?
先來段Wiki:
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.
接口約定了對象間交互的屬性和方法,使得對象間無需了解對方就可以協作。
說的洋氣點就是解耦嘛,細心點也能發現Wiki中interface和protocol表示了相近的語義。
引用我和項目組架構師討論有關interface的問題時他的說法:
interface就是一個object定義的可以被外界影響的方式
說著他指了下旁邊桌子上放著的一把傘,說,這把傘我可以打開它,打開這個動作就是它的一個interface,桌子旁邊還放著一個盒子,雖然它和傘都放在這張桌子上,但是它們之間永遠不會互相影響,所以:
interface只存在于能互相影響的兩者間
@interface生成了class?
學習objc時最早接觸的就是怎么寫一個類了,從.h中寫@interface聲明類,再從.m中寫@implementation實現方法,所以,objc中寫一個@interface就相當于c++中寫一個class。但這是真的么?
寫個小test驗證一下: 有兩個類,Sark和Dark,Sark類只有.m文件,其中只寫@implementation;Dark類只有.h頭文件,其中只寫@interface,然后如下測試代碼:
| 1 2 | Class sarkClass = NSClassFromString(@"Sark"); Class darkClass = NSClassFromString(@"Dark"); |
NSClassFromString方法調用了runtime方法,根據類名將加載進runtime的這個類找出來,沒有這個類就回返回空(Nil)。
結果是sarkClass存在,而darkClass為空,說明什么?是否說明其實@implementation才是真正的Class?
進一步,不止能取到這個沒有@interface的類,還可以正常調用方法(因為萬能的runtime)
如下面的測試代碼:
| 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出Class,selctor等再使用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?我們干過的事:
@implementation?我們干過的和可以干的事:
在@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的差別進一步縮小。
類與接口的設計原則 - 電視和遙控器
我喜歡將Class和interface的關系比喻成電視+遙控器,那么objc中的消息機制就可以理解成:
用戶(caller)通過遙控器(interface)上的按鈕(methods)發送紅外線(message)來操縱電視(object)
所以,有沒有遙控器,電視都在那兒,也就是說,有沒有interface,class都是存在的,只是這種存在并沒有意義,就好像這個電視沒人會打開,沒人會用,沒人能看,一堆廢鐵擺在那兒。
對比簡潔的遙控器,一個擁有很多按鈕的老式電視遙控器,我們經常會用到的按鈕能有幾個呢?
所以,在設計一個類的interface的時候,如同在設計遙控器應該有怎樣功能的按鈕,要從調用者的角度出發,區分邊界,應該時刻有以下幾點考慮:
objc的@interface設計技巧Tips
看過不少代碼,從@interface設計上多少就能看出作者的水平,分享下我對于這個問題的一些拙見。
只暴露外部需要看到的
比如,有如下一個類(這個類無意義,主要關注寫法):
| 1 2 3 4 5 6 7 8 | // Sark.h @interface SarkViewController : NSObject <NSXMLParserDelegate /*1*/, NSCopying> {NSString *_name; // 2IBOutlet UITextField *_nameTextField; // 2 } @property (nonatomic, strong) NSXMLParser *parser; // 3 - (IBAction)nameChangedAction:(id)sender; // 4 @end |
這個interface出現的問題:
合理分組子功能
- 將相同功能的一組屬性或方法寫在一起
使用這個類或者對其進行修改時,一般都是從功能上找,所以把同一功能模塊的一組屬性或方法寫在一塊
- 純操作方法的子功能(無需向類添加變量)使用Category分塊
- 在頭文件中也可以使用類擴展將interface按功能分區
Category里不能添加實例變量,但是類擴展可以,一般都在.m中作為私有interface使用,同樣在頭文件里作為分區使用,如,ReactiveCocoa中的RACStream.h
避免頭文件污染
首先,類實現內部.m文件中使用的其他interface應該在.m文件import,如果也寫在header中就會造成對調用者的污染;當interface中出現其他Class或protocol時,可以使用前置聲明@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 |
提供了一組這樣的方法,調用者可能只能用到其中的一個,那這樣倒不如只留一個接口。
避免單例的濫用
單例模式固然好用,但感覺有點過度,將接口設計成單例入口前需要考慮一下:
總結
- @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
轉載于:https://www.cnblogs.com/sunnyxx/p/3674596.html
總結
以上是生活随笔為你收集整理的objc@interface的设计哲学与设计技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 扩展js string 方法
- 下一篇: 在centos 下安装配置基于gitos