Objective-C——消息、Category和Protocol


面向對象永遠是個可以吐槽的話題,從開始提出到推崇備至,到充滿質疑,一路走來讓人唏噓不已。面向對象的思想可謂歷史悠久,20世紀70年代的Smalltalk可以說是面向對象語言的經典,直到今天我們依然將這門語言視為面向對象語言的基礎。
面向對象是大部分編程語言的基本特性,像C++、Java、Objective-C這樣的靜態語言,Ruby、Python這樣的動態語言都是面向對象的語言。但是如何編寫面向對象的程序卻一直是困擾人們的話題,即使是Smalltalk,也有人認為這是一個有缺陷的面向對象的語言實現。
 
我在2010年翻譯過的一篇InfoQ的文章,《面向對象編程──走錯了路》中提到,面向對象編程的三個原則是:基於消息傳遞機制,對象分離和多態。文章中還舉了Erlang例子,認為Erlang具備了這些原則,“所以可能是唯一的面向對象語言”。除了之前提到的三個特征,單繼承和動態類型也被引用為面向對象語言的“絕對需求”。基於這些考慮,文章指出,Smalltalk在實現對象思想時的“錯誤”──例如,只關注狀態和行為,在類和基於映像的語言里缺乏良好的並發模型和消息機制。
 
這篇文章中的核心就是,面向對象思想中除了對象的狀態、行為,還應該關注其並發機制、消息機制,后者更為重要。這一點事實上是我在接觸了Objective-C之后才有了更深入的體會。
 
Ojbective-C的語法設計主要基於Smalltalk,除了提供傳統的面向對象編程特性之外,還增加了很多類似動態語言Ruby、Python才具有的特性,例如動態類型、動態加載、動態綁定等等,同時強化了消息傳遞機制和表意(Intention Revealing Interface)接口的概念。
 
—消息—
消息傳遞模型(Message Passing)是Objective-C語言的核心機制。在Objective-C中,沒有方法調用這種說法,只有消息傳遞。在C++或Java中調用某個類的方法,在Objective-C中是給該類發送一個消息。在C++或Java里,類與類的行為方法之間的關系非常緊密,一個方法必定屬於一個類,且於編譯時就已經綁定在一起,所以你不可能調用一個類里沒有的方法。而在Objective-C中就比較簡單了,類和消息之間是松耦合的,方法調用只是向某個類發送一個消息,該類可以在運行時再確定怎么處理接受到的消息。也就是說,一個類不保證一定會響應接收到的消息,如果收到了一個無法處理的消息,那么程序就是簡單報一個錯。甚至你可以向一個值為nil的空對象發送消息,系統都不會出錯或宕掉。這種設計本身也比較符合軟件的隱喻。
 
在表意接口(Intention Revealing Interface)方面,Objective-C也是設計的比較出色的語言。面向對象語言的特性之一就是通過API把實現封裝起來,為上層建築提供服務。但是需要注意的一點就是,你封裝的API最好能夠讓調用者看到接口描述就知道怎么使用。如果為了使用一個API必須要去研究它的實現,那么就失去了封裝的意義。Objective-C通過顯式的API描述,讓開發者不自覺的寫出滿足表意接口的API,比如下圖中的API描述。

 
上圖中描述了一個傳統意義的實例方法,但和Java或C++不同的是,其方法關鍵字由多個字符串組成,在這個例子是insertObject和atIndex,(id)anObject和(NSUInterger)index分別表示參數類型和參數名稱。整個方法看上去就像一個英語句子,我們可以很容易的知道,這個方法就是在索引為index處插入一個對象。如果你是從其他語言轉到Objective-C,那么開始的時候會感覺這種寫法有些繁復,但是一旦理解並習慣了你會感受到其巨大的好處,這種寫法會強制你寫出優美易讀的代碼和API,而且有了XCode強大的提示功能,再長的方法也是一蹴而就。
 
下面我們來說說多態和繼承。
 與Java一樣,Objective-C一樣不支持多重繼承,但是通過類別(Category)和協議(Protocol)可以很好的實現代碼復用和擴展。
 
—Category—
首先我們來談談Category。
 
Objective-C提供了一種與眾不同的方式——Category,可以動態的為已經存在的類添加新的行為。這樣可以保證類的原始設計規模較小,功能增加時再逐步擴展。使用Category對類進行擴展時,不需要訪問其源代碼,也不需要創建子類。Category使用簡單的方式,實現了類的相關方法的模塊化,把不同的類方法分配到不同的分類文件中。
 
實現起來很簡單,我們舉例說明。
SomeClass.h @interface SomeClass : NSObject{ } -(void) print; @end 
 
這是類SomeClass的聲明文件,其中包含一個實例方法print。如果我們想在不修改原始類、不增加子類的情況下,為該類增加一個hello的方法,只需要簡單的定義兩個文件SomeClass+Hello.h和SomeClass+Hello.m,在聲明文件和實現文件中用“()”把Category的名稱括起來即可。聲明文件代碼如下:
 
#import "SomeClass.h"
 
@interface SomeClass (Hello)
-(void)hello;
@end
實現文件代碼如下
#import "SomeClass+Hello.h" @implementationSomeClass (Hello) -(void)hello{ NSLog (@"name:%@ ", @"Jacky"); } @end 
其中Hello是Category的名稱,如果你用XCode創建Category,那么需要填寫的內容包括名稱和要擴展的類的名稱。這里還有一個約定成俗的習慣,將聲明文件和實現文件名稱統一采用“原類名+Category”的方式命名。
調用也非常簡單,毫無壓力,如下:
首先引入Category的聲明文件,然后正常調用即可。
#import "SomeClass+Hello.h" SomeClass * sc =[[SomeClass alloc] init]; [sc hello] 
執行結果是:
nameJacky 
 
Category的使用場景:
1、當你在定義類的時候,在某些情況下(例如需求變更),你可能想要為其中的某個或幾個類中添加方法。
2、一個類中包含了許多不同的方法需要實現,而這些方法需要不同團隊的成員實現
3、當你在使用基礎類庫中的類時,你可能希望這些類實現一些你需要的方法。
 
遇到以上這些需求,Category可以幫助你解決問題。當然,使用Category也有些問題需要注意,
1、Category可以訪問原始類的實例變量,但不能添加變量,如果想添加變量,可以考慮通過繼承創建子類。
2、Category可以重載原始類的方法,但不推薦這么做,這么做的后果是你再也不能訪問原來的方法。如果確實要重載,正確的選擇是創建子類。
3、和普通接口有所區別的是,在分類的實現文件中可以不必實現所有聲明的方法,只要你不去調用它。
 
用好Category可以充分利用Objective-C的動態特性,編寫出靈活簡潔的代碼。
 
—Protocol— 
下面我們再來看Protocol。
Protocol,簡單來說就是一系列不屬於任何類的方法列表,其中聲明的方法可以被任何類實現。這種模式一般稱為代理(delegation)模式。你通過Protocol定義各種行為,在不同的場景采用不同的實現方式。在iOS和OS X開發中,Apple采用了大量的代理模式來實現MVC中View和Controller的解耦。
 
定義Protocol很簡單,在聲明文件(h文件)中通過關鍵字@protocol定義,然后給出Protocol的名稱,方法列表,然后用@end表示Protocol結束。在@end指令結束之前定義的方法,都屬於這個Protocol。例如:
@protocol ProcessDataDelegate <NSObject> @required - (void) processSuccessful: (BOOL)success; @optional - (id) submitOrder: (NSNumber *) orderid; @end
 
以上代碼可以單獨放在一個h文件中,也可以寫在相關類的h文件中,可以視具體情況而定。該Protocol包含兩個方法,processSuccessful和submitOrder。這里還有兩個關鍵字,@required和@optional,表示如果要實現這個協議,那么processSuccessful方法是必須要實現的,submitOrder則是可選的,這兩個注解關鍵字是在Objective-C 2.0之后加入的語法特性。如果不注明,那么方法默認是@required的,必須實現。
 
那么如何實現這個Protocol呢,很簡單,創建一個普通的Objective-C類,取名為TestAppDelegate,這時會生成一個h文件和m文件。在h文件中引入包含Protocol的h文件,之后聲明采用這個Protocol即可,如下:
@interface TestAppDelegate : NSObject<ProcessDataDelegate>; @end
用尖括號(<...>)括起來的ProcessDataDelegate就是我們創建的Protocol。如果要采用多個Protocol,可以在尖括號內引入多個Protocol名稱,並用逗號隔開即可。例如<ProcessDataDelegate,xxxDelegate>
 
m文件如下:
@implementation TestAppDelegate - (void) processSuccessful: (BOOL)success{ if (success) { NSLog(@"成功"); }else { NSLog(@"失敗"); } } @end 
由於submitOrder方法是可選的,所以我們可以只實現processSuccessful。
 
Protocol一般使用在哪些場景呢?Objective-C里的Protocol和Java語言中的接口很類似,如果一些類之間沒有繼承關系,但是又具備某些相同的行為,則可以使用Protocol來描述它們的關系。不同的類,可以遵守同一個Protocol,在不同的場景下注入不同的實例,實現不同的功能。其中最常用的就是委托代理模式,Cocoa框架中大量采用了這種模式實現數據和UI的分離。例如UIView產生的所有事件,都是通過委托的方式交給Controller完成。根據約定,框架中后綴為Delegate的都是Protocol,例如UIApplicationDelegate,UIWebViewDelegate等,使用時大家可以留意一下,體會其用法。
 
使用Protocol時還需要注意的是:
1、Protocol本身是可以繼承的,比如:
@protocol A -(void)methodA; @end
@protocol B <A>
     -(void)methodB; @end

如果你要實現B,那么methodA和methodB都需要實現。 

2、Protocol是類無關的,任何類都可以實現定義好的Protocol。如果我們想知道某個類是否實現了某個Protocol,還可以使用conformsToProtocol進行判斷,如下:
[obj conformsToProtocol:@protocol(ProcessDataDelegate)] 
 
好吧,具體的語言特性這次就介紹這么多。從某種意義上來說,Objective-C是一門古老的語言,發明於1980年。1988年,喬布斯的Next公司獲得了Objective-C語言的授權,並開發出了Objective-C的語言庫和NEXTSTEP的開發環境。NextStep是以Mach和BSD為基礎,Objective-C是其語言和運行庫,后來的事大家都清楚,蘋果買了Next,喬布斯回歸蘋果,開始神奇的蘋果振興之路,NextStep成了Max OS X的基礎。以后發展越來越好,Objctive-C成了Apple的當家語言,現在基本上是Apple在維護Objctive-C的發展。
 
在蘋果的AppStore推出之前,Objective-C一直相對小眾,但是其優秀的語言特性似乎一直在為后面的爆發積蓄力量,當蘋果平台級的應用出現之后,Objective-C開始大放異彩,靜態語言的效率和動態語言的特性得到眾多程序員的喜愛,目前它已經以火箭般的速度躥升TIOBE語言排行版第四位。
 
對於喜愛蘋果技術的技術人員來說,Objective-C是你必須深入了解和值得學習的一門語言,希望以后有機會多寫一些相關的文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM