objc@interface的设计哲学与设计技巧
本文原文發(fā)表自我的自建博客,cnblogs同步發(fā)表,格式未經(jīng)調(diào)整,內(nèi)容以原博客為準(zhǔn)。
?
我是前言
學(xué)習(xí)objc時(shí),尤其是先學(xué)過其他編程語言再來看objc時(shí),總會對objc的類聲明的關(guān)鍵字interface感到有點(diǎn)奇怪,在其它面向?qū)ο蟮恼Z言中通常由class關(guān)鍵字來表示,而interface在java中表示的卻大約相當(dāng)于objc的protocol,這個(gè)關(guān)鍵字的區(qū)別究竟代表了objc語言的設(shè)計(jì)者怎樣的思想呢,在objc類設(shè)計(jì)中需要注意哪些問題呢?接下來對這個(gè)問題進(jìn)行一些思考和探究.
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.
接口約定了對象間交互的屬性和方法,使得對象間無需了解對方就可以協(xié)作。
說的洋氣點(diǎn)就是解耦嘛,細(xì)心點(diǎn)也能發(fā)現(xiàn)Wiki中interface和protocol表示了相近的語義。
引用我和項(xiàng)目組架構(gòu)師討論有關(guān)interface的問題時(shí)他的說法:
interface就是一個(gè)object定義的可以被外界影響的方式
說著他指了下旁邊桌子上放著的一把傘,說,這把傘我可以打開它,打開這個(gè)動作就是它的一個(gè)interface,桌子旁邊還放著一個(gè)盒子,雖然它和傘都放在這張桌子上,但是它們之間永遠(yuǎn)不會互相影響,所以:
interface只存在于能互相影響的兩者間
@interface生成了class?
學(xué)習(xí)objc時(shí)最早接觸的就是怎么寫一個(gè)類了,從.h中寫@interface聲明類,再從.m中寫@implementation實(shí)現(xiàn)方法,所以,objc中寫一個(gè)@interface就相當(dāng)于c++中寫一個(gè)class。但這是真的么?
寫個(gè)小test驗(yàn)證一下: 有兩個(gè)類,Sark和Dark,Sark類只有.m文件,其中只寫@implementation;Dark類只有.h頭文件,其中只寫@interface,然后如下測試代碼:
| 1 2 | Class sarkClass = NSClassFromString(@"Sark"); Class darkClass = NSClassFromString(@"Dark"); |
NSClassFromString方法調(diào)用了runtime方法,根據(jù)類名將加載進(jìn)runtime的這個(gè)類找出來,沒有這個(gè)類就回返回空(Nil)。
結(jié)果是sarkClass存在,而darkClass為空,說明什么?是否說明其實(shí)@implementation才是真正的Class?
進(jìn)一步,不止能取到這個(gè)沒有@interface的類,還可以正常調(diào)用方法(因?yàn)槿f能的runtime)
如下面的測試代碼:
| 1 2 | Sark *sark = [Sark new]; [sark speak]; |
要是沒有@interface的聲明,類名,方法名都會報(bào)錯(cuò)說找不到,但是可以像下面一樣繞一下:
| 1 2 3 | Class cls = NSClassFromString(@"Sark"); id obj = [cls performSelector:NSSelectorFromString(@"new")]; [obj performSelector:NSSelectorFromString(@"speak")]; |
其實(shí),從rewrite后的objc代碼可以發(fā)現(xiàn),對于消息的發(fā)送,恰恰就是會被處理成類似上面的代碼,使用字符串mapping出Class,selctor等再使用objc_msgSend()進(jìn)行函數(shù)調(diào)用,如下面所示:
| 1 2 3 | // 經(jīng)過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; } |
通過對比可以發(fā)現(xiàn),@interface對objc類結(jié)構(gòu)的合成并無決定性作用,加上無決定性是因?yàn)槿绻麤]有@interface會丟失一些類自省的原始數(shù)據(jù),如屬性列表和協(xié)議列表,但對于純粹的對象消息發(fā)送并無影響。
所以說,可以得出這么一個(gè)結(jié)論,objc中@interface就是為了給調(diào)用者看的,是和調(diào)用者的一個(gè)protocol,沒錯(cuò),就是protocol。
對比@interface和@protocol
與其把@implementation扯進(jìn)來不如對比下@protocol
我理解objc的@interface和@protocal間唯一的區(qū)別就是是否和一個(gè)類型綁定,這讓我想起來鴨子類型(Duck typing),?wiki鏈接
“當(dāng)看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子?!?/p>
Duck type在objc的體現(xiàn)無疑就是@protocol了,我們常用id<XXXDelegate> delegate的方式聲明一個(gè)delegate,我們無需care這貨到底是什么類型,我們只知道他能干什么就可以work了。同樣的功能我也可以使用XXXDelegate *delegate的方式來定義,只不過這樣的話這個(gè)類又需要耦合一個(gè)XXXDelegate類型,而這個(gè)delegate類是它原本并不需要關(guān)心的。
所以說,@interface是@protocol的強(qiáng)類型升級版。
舉個(gè)NSObject的栗子最合適:
| 1 2 3 | @interface NSObject <NSObject> {Class isa; } |
NSObject之所以成為NSObject,絕大多數(shù)都是<NSObject>協(xié)議定義的方法,實(shí)體類@interface定義的唯一一個(gè)變量isa指針,為了繼承鏈和消息傳遞。
除了<NSObject>協(xié)議外,NSObject還有很多Category來補(bǔ)充它的功能,其實(shí)仔細(xì)想想,Category更像protocol,一個(gè)補(bǔ)充協(xié)議,同樣不能添加實(shí)例變量,但是和@interface一樣需要與Class綁定。
進(jìn)一步來講,自從屬性能自動合成變量之后,在頭文件@interface中寫大括號聲明實(shí)例變量的情況越來越少(可以參見近幾個(gè)版本iOS SDK中類頭文件里這種寫法幾乎消失),因此,@interface和@protocol的差別進(jìn)一步縮小。
類與接口的設(shè)計(jì)原則 - 電視和遙控器
我喜歡將Class和interface的關(guān)系比喻成電視+遙控器,那么objc中的消息機(jī)制就可以理解成:
用戶(caller)通過遙控器(interface)上的按鈕(methods)發(fā)送紅外線(message)來操縱電視(object)
所以,有沒有遙控器,電視都在那兒,也就是說,有沒有interface,class都是存在的,只是這種存在并沒有意義,就好像這個(gè)電視沒人會打開,沒人會用,沒人能看,一堆廢鐵擺在那兒。
對比簡潔的遙控器,一個(gè)擁有很多按鈕的老式電視遙控器,我們經(jīng)常會用到的按鈕能有幾個(gè)呢?
所以,在設(shè)計(jì)一個(gè)類的interface的時(shí)候,如同在設(shè)計(jì)遙控器應(yīng)該有怎樣功能的按鈕,要從調(diào)用者的角度出發(fā),區(qū)分邊界,應(yīng)該時(shí)刻有以下幾點(diǎn)考慮:
objc的@interface設(shè)計(jì)技巧Tips
看過不少代碼,從@interface設(shè)計(jì)上多少就能看出作者的水平,分享下我對于這個(gè)問題的一些拙見。
只暴露外部需要看到的
比如,有如下一個(gè)類(這個(gè)類無意義,主要關(guān)注寫法):
| 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 |
這個(gè)interface出現(xiàn)的問題:
合理分組子功能
- 將相同功能的一組屬性或方法寫在一起
使用這個(gè)類或者對其進(jìn)行修改時(shí),一般都是從功能上找,所以把同一功能模塊的一組屬性或方法寫在一塊
- 純操作方法的子功能(無需向類添加變量)使用Category分塊
- 在頭文件中也可以使用類擴(kuò)展將interface按功能分區(qū)
Category里不能添加實(shí)例變量,但是類擴(kuò)展可以,一般都在.m中作為私有interface使用,同樣在頭文件里作為分區(qū)使用,如,ReactiveCocoa中的RACStream.h
避免頭文件污染
首先,類實(shí)現(xiàn)內(nèi)部.m文件中使用的其他interface應(yīng)該在.m文件import,如果也寫在header中就會造成對調(diào)用者的污染;當(dāng)interface中出現(xiàn)其他Class或protocol時(shí),可以使用前置聲明@class XXX,?@protocol XXX;當(dāng)模塊(一組類)內(nèi)部間需要有一些定義(如常量、類型)而又不需要模塊使用者知道時(shí),使用一個(gè)內(nèi)部頭文件在模塊中使用。
避免接口過度設(shè)計(jì)
考慮調(diào)用者的使用方便是很必要的,過火了反而增加了復(fù)雜度:
| 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; // 無數(shù)多個(gè) // @end |
提供了一組這樣的方法,調(diào)用者可能只能用到其中的一個(gè),那這樣倒不如只留一個(gè)接口。
避免單例的濫用
單例模式固然好用,但感覺有點(diǎn)過度,將接口設(shè)計(jì)成單例入口前需要考慮一下:
總結(jié)
- @implementation合成了Class,而非@interface,@interface是@protocol的強(qiáng)類型升級版,它們和Category都表示了相近的含義
- 我們應(yīng)該善于面向接口編程,劃清邊界,將類的實(shí)現(xiàn)隱藏在調(diào)用者所見之外,使主調(diào)和被調(diào)者之間保持最少知識原則
- @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
原創(chuàng)文章,轉(zhuǎn)載請注明源地址,blog.sunnyxx.com
轉(zhuǎn)載于:https://www.cnblogs.com/sunnyxx/p/3674596.html
總結(jié)
以上是生活随笔為你收集整理的objc@interface的设计哲学与设计技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 扩展js string 方法
- 下一篇: 在centos 下安装配置基于gitos