編譯處理指令
既然Objective-C是面向對象的程序語言,所以理應支持可重用的數據和函數的封裝體,即類。
類是在結構體的基礎上發展的產物,結構體只能處理數據,在結構體之上增加對該數據處理的函數,就構成類的概念。類使程序總能提供對數據專門處理函數的安全調用,使得一系列的機能作為一個子系統供安全且重復的使用。
像結構體一樣,類在使用之前必須先聲明,但是Objective-C並沒有像其他面向對象語言那樣提供聲明類的專用關鍵字或者語法,而是用編譯處理指令來實現,特征是類聲明語句都須以@符號開始。
類聲明的編譯處理指令以@interface開始,以@end結尾,在這之間代碼便是類變量的定義和方法的聲明。類的聲明和定義比其他語言復雜,這會讓剛開始學習Objective-C的人非常困惑(准確的說,其他語言如Java只需定義類而不用聲明,而Objective-C需要先聲明再定義)。
@interface 類名 : 父類名 {
實例變量定義
...
}
方法聲明
@end
這就是Objective-C類聲明的語法結構,其中實例變量是供類內部使用的變量,和結構體的成員變量相似,但是實例變量不能從類的外部使用,原則上只能被類內部的方法使用(當然只是原則上)。類可以沒有實例變量,這時{}可以省略。
類的方法(注意和類方法的區別)是專屬該類的方法,與普通函數的區別是:類的方法可以操作類內部的實例變量。
緊接類名后邊的是父類名,也就是說可以指定類的父類,構成繼承。繼承使類可繼承使用父類的機能,而僅定義父類沒有的機能。比如父類是抽象的哺乳動物類,如果定義貓類的時候,可以繼承哺乳動物這個父類,而在貓類中只定義貓區別與普通哺乳動物類的特有功能即可。
父類可以不指定,這時編譯器會為類提供一個缺省的類,即根類。
Objective-C的根類隨具體編譯器的不同而有所不同,GCC編譯器中是Object,Mac OS X的Cocoa編譯環境中則是NSObject,除非自己開發根類時可以不指定父類,一般情況下都要采用系統提供的根類作為父類。僅在根類開發時,類的定義可以如下(沒有父類):
@interface 類名
{
實例變量聲明
...
}
類方法聲明
@end
為什么所有的類都必須繼承自一個共同的根類,那是因為根類提供了類正常動作所必須的一些基本機能,比如內存的取得和釋放,如果沒有根類,那么所有的類都要自己去完成這些基本的工作,會使類非常的復雜而不能專注於具體業務(后面還會具體說明)。
類中實例變量的聲明和普通C語言的變量聲明沒有什么區別,但是類方法的聲明則差別很大,語法如下:
-(返回值類型) 方法名 : 臨時參數列…; |
首先,最左邊的減號,代表該方法是類的實例方法,如果是加號則代表該方法為類方法。這里須記住一點:普通定義類的時候,方法前用減號即實例方法即可,類方法是可以不用生成實例而直接調用的方法。實例方法和類方法的區別后面會詳細說明。
其次,返回值類型要放在()中,如果函數需要參數的時候,參數的類型也要放到()中,然后緊接參數名,如果有多個參數,則用逗號隔開。
C語言函數的缺省返回值類型是int型,在Objective-C中新追加了一個id型的對象類型作為缺省函數的返回值類型。雖然返回值可以不指定,但一般不建議這么做。如果沒有返回值,則要用(void)做明確說明。
@interface Test : Object
- (void)method;
@end
在這個Test類聲明中,聲明了一個沒有返回值,沒有參數的方法method,類和方法以及變量的命名規則和C語言一樣,以字母或下划線開頭。但是習慣上類名的首字母大寫,方法名則全部小寫。
到這里,類的聲明已經做好了,但是方法method只是聲明了名稱和類型,具體實現的代碼怎么寫呢?事實上,Objective-C將類的聲明和定義完全分開,在類的聲明中,只能定義實例變量和方法名及類型,具體的實現要用到@implementation這個編譯處理指令中進行。
@implementation 類名
實例方法定義
...
@end
@implementation這個編譯指令具體定義@interface中聲明的方法,聲明過的方法,必須在這里具體定義。
類的實例化
類經過聲明和定義,但是還不能直接使用,使用類之前必須分配具體的內存領域且進行適當的初始化。根據類的聲明具體分配一塊內存,這個過程叫實例化,而具體分配的這塊內存,叫做實例或者對象。
C++或者Java中為我們提供好了new運算符,可以自動由類生成實例並完成初始化,而Objective-C實例化竟然也要類自己完成(即需要類給出實例化自己的方法)。實例化需要調查生成對象的大小,申請內存完成復雜的初始化,這些任務對普通用戶太難了,不過幸好編譯環境為我們提供了根類(Object)來幫助完成這些功能,這也是為什么所有的類都必須繼承自根類的原因(否則實例化方法要自己寫)。
在根類Object中,定義了實例化根類以及繼承自根類的類的類方法alloc,一般的實例方法,必須先有實例才能調用,但是類方法,沒有實例也可以使用。所以類方法alloc雖然沒有實例,也是可以正常調用的。鑒於alloc是用來生成實例的類方法,所以也常常也被稱作工廠方法。
+alloc; |
這是Object中alloc的聲明,+開頭,說明是一個類方法,缺省返回值為id類型,沒有參數。
id型是代表對象的廣義類型,什么類型的對象都可以放入id型的對象中。
下面,就要調用alloc類方法,生成實例了,但是怎樣寫呢?如果熟悉C++,可能會首先想到下面的調用方式:
id obj =類名.alloc(); |
遺憾的是,這種寫法是錯誤的,和大多數面向對象語言的調用方式不同,Objective-C有自己獨特的調用方式:
[類名 類方法名:參數序列...] |
如果是調用實例方法的時候,要將類名換成實例名,其中[ ]在Objective-C中被稱為消息,這是因為在Objective-C中,從對象調用方法的時候,不是直接調用,而是向對象發送特定的消息,對象接收到消息后,根據消息內容啟動相應的方法,這種方式雖然帶來了程序的柔軟性,但是比C語言的調用方式帶來額外的消耗,因為程序執行時要耗費時間檢索特定的方法然后啟動。
#import <stdio.h>
#import <objc/Object.h>
@interface Test : Object
- (void)method;
@end
@implementation Test
- (void)method {
printf("Kitty on your lap\n");
}
@end
int main() {
id obj = [Test alloc];
[obj method];
return 0;
}
上面的程序是一個Objective-C簡單但完整的類實例例子,@interface完成類的聲明,繼承自根類Object。@implementation完成類的定義,具體定義method的實現方法。
類聲明和定義准備好之后,在main函數中,首先定義id型的對象變量obj,然后調用Test類的父類Object的類方法alloc生成實例,最后調用實例obj的實例方法method,實現輸出打印。
另外,如果不定義obj變量(或者說Test的實例只在這里使用一次就不用了),也可以像下面的寫法:
[[Test alloc] method] |
注:像上面的程序片段一樣,雖然對類的聲明和定義的具體位置沒有要求,但是習慣上把類的聲明放到頭文件中,類的定義放到和類名同名的.m文件中。