C++開發者快速學習Objective-C語言核心語法

折酷吧發表於2014-11-27

本文將Objective-C討論了語言的核心語法。這部分開始詳述一些具體的語法。正如你期待的一樣,涉及到了定義和類。

類並不是特殊的

在Smalltalk中,類是具有一些特性的物件。在Objective-C中也一樣。一個類是一個物件,物件回應訊息。Objective-C和C++都分離了物件分配和初始化。

在C++中,物件分配通過新的操作。在Objective-C中,這樣的操作是通過給類傳送分配訊息—呼叫malloc()或者一個等價。

C++中的初始化是通過呼叫一個與類同名的函式。Objective-C並沒有區別初始化方法和其他方法,但出於慣例預設的初始化方法就是初始化。

當你宣告一個方法讓例項去回應,宣告通常已“-”開頭,並且“+”用作類的方法。在文件中對這些訊息使用一些字首是很普遍的,所以你也可以說+alloc和-init來暗示alloc是傳給一個類,init傳給例項。

類在Objective-C中,正如在其他一些面嚮物件語言,都是物件工廠。大多數類不用自行實現+alloc,而是從他們的父類中繼承。在 NSObject中,父類在大多數Objective-C程式中,+alloc方法呼叫+allocWithZone:.使NSZone作為一個引數,一 個C結構包含物件分配的一些策略。回顧19世紀80年代,當Objective-C用在NeXTstep來實現裝置驅動和只有8MB記憶體25MHZ的 CPU機器的GUI上面時,NSZone對優化非常重要。同時,這或多或少的被Objective-C程式設計師所忽視。(很有可能成為象NUMA構架一樣流 行,更普遍。)

眾多優秀的特性之一就是物件建立語義是由庫定義的並且語言不是類簇的思想。當你傳一個-init訊息給物件時,它返回一個初始化物件。這可能是你傳送訊息的那個物件,但不一定肯定就是。這和其他初始化程式一致的。很有可能一些公共類的特殊子類在不同資料上更有效。

實現這個特性的通用方法叫做isa-swizzling。正如我前述,Objective-C物件是C結構,這些結構第一個元素是指向類的指標。這 個元素是可存取的,正如其他例項變數一樣;你可以在執行時通過分配新值來改變物件的類。當然,如果你對物件的類設定在記憶體中有著不同的佈局,這些設定可能 嚴重錯誤。

然而,你可以通過一個父類來定義佈局和通過子集的集合定義行為,舉例來說,這個技術用在標準化字串類(NSString),它對不同的文字字符集、靜態事物和其它一些有著各種各樣的例項。

因為類是物件,你可以象操作物件一樣操作他們。舉例來說,你可以把他們放在集合。當我有一些輸入事件需要通過不同的類的例項來處理時我就使用這種格 式。你需要建立一個目錄對映事件命名到類,然後為每一個輸入事件例項化一個物件。如果你在一個庫中這麼做,它允許程式碼的使用者輕鬆的註冊屬於他們自己的句 柄。

型別和指標

Objective-C沒有公開允許在堆疊上定義物件。但並不是真的—很有可能在堆疊上定義物件,但有些困難,因為它破壞了對記憶體管理的一種假設。 結果,每一個Objective-C物件都是一個指標。一些型別由Objective-C定義;這些型別在頭部定義作為C型別。

在Objective-C中最普遍的3種型別就是id,Class和SEL。id就是指向Objective-C物件的指標,它等價於C語言中的void*,你可以對映任何物件指標型別指向它並且對映他指向其它的物件指標型別。

你可以傳任何訊息給id,但如果不支援的話會返回一個執行時異常。

類是指向Objective-C類的指標。類是物件,所以也可以接收訊息。類名是一種型別,不是可變的。識別符號NSObject是一個NSObject例項的型別,但也可作為訊息接受者。你可以獲得一個類,如下:

[NSObject class];

傳送一個+class訊息給NSObject類,然後返回一個指向代表類的類結構指標。

這對我們回顧是非常有用的[FS:PAGE],正如我們在這個系列第二部分看到的一樣。

第三種型別SEL,代表一個選擇器—一個代表方法名的抽象。你可以在編譯時通過@selector()直接建立,或在執行時通過C字串呼叫執行時 庫函式,或用OpenStep NSSelectorFromString()函式,這個函式給Objective-C字串一個選擇器。這個技術允許你通過名字呼叫方法。你可以在C中 通過使用類似dlsym(),但在C++中非常不同。在Objective-C中,你可以做的如下:

[object perfomSelector:@selector(doSomething)];

這等價於如下:

[object doSomething];

顯然,第二種格式速度稍微快些,因為第一種傳送兩個訊息。後面,我們會看到通過選擇器處理的一些細節。

C++沒有與id相同的型別。因為物件總是可以型別化的。在Objective-C,你可以選擇型別系統。下面的兩種都是有效的:

id object = @”a string”;  

NSString *string = @”a string”;

常量字串實際上是NSConstantString類的一個例項,NSConstantString類是NSString的子類。將它引用到 NSString* 使編譯時對訊息進行型別檢查和儲存公共例項變數(這在Objective-C從未使用過)。注意你可以通過如下改變這一設定:

NSArray *array = (NSArray*)string;

如果給陣列傳送訊息,編譯器將會檢查NSArray能接收的訊息。這並不是非常有用,因為物件是一個字串。如果傳送一個NSArray和NSString實現的訊息,可能會有作用。如果你傳送的訊息NSString沒有實現,一個異常將會丟擲。

強調Objective-C和C++的不同的這件事看起來比較奇怪。Objective-C有型別-值語法,而C++有型別-變數語法。在Objective-C,物件型別是物件專有的一種屬性。在C++,型別取決於變數的型別。

在C++中,當你指派一個指標指向一個物件到一個變數定義一個指向父類的指標,兩個指標可能沒有相同的數值(這可以通過多繼承實現,而Objective-C不支援這種。)

定義類

Objective-C類定義有一個介面和一個實現部分。與C++有相似的地方,但兩個稍微有些混。

Objective-C中的介面僅定義位並且明確的需要公開。對於實現的原因,這包括私有例項變數在大部分的實現中,因為你無法繼承一個類除非你知道它多大。最近的一些實現,象Apple的64位執行時則沒有這種限制。

Objective-C物件的介面如下:

@interface AnObject : NSObject  

{  

@private  

int integerivar  

@public  

id anotherObject;  

}  

+ (id) aClassMethod;  

- (id) anInstanceMethod:(NSString*)aString with:(id)anObject  

@end

第一行包含3個部分。識別符號AnObject 是新類的名字。冒號後面的名字是NSObject。(這是可選的,但每一個Objective-C 物件都應擴充NSObject)。在括號內的名字是協議——與Java中的介面相似——通過類來實現。

正如C++例項變數(C++中的域)可以存取修飾符,不象C++,這些修飾符以@為字首是為了避免與C識別符號衝突。

Objective-C不支援多繼承,所以只有一個父類。所以,物件第一部分的佈局總是與父類例項的佈局一致。這在過去常常定義為動態,意味著改變 類中例項變數需要它所有子類重新編譯。在較新的執行時這種限定並不要求,在存取例項例項變數上開支稍微大些。這種決策的另一個影響就是 Objective-C其他特性中的一個。

struct_AnObject  

{  
     @defs(AnObject);  
};

@def表示著對特定物件所有域都插入這種結構,所以struct_AnObject 和AnObject類的例項有著相同的記憶體結構。舉個例子來說,你可以通過這種規則可以直接存取例項變數。一個通常的用法就是允許C函式直接操作 Objective-C物件,是基於效能原因。
正如我前面暗示的,與這個特性相關的另一件事就是可以在堆疊上建立物件。因為結構和物件在[FS:PAGE]記憶體佈局中有著相同的結構,你可以簡單的建立 結構,設定他的指標指向正確的類,然後對映一個指標指向一個物件指標。然後你可以當做物件來使用,雖然你不得不小心沒有什麼可以保持指標不越界。(現實世 界中我從沒有使用這種方法,僅僅理論上可能。)

不象C++,Objective-C沒有私有的或受保護的方法。Objective-C物件上的任何方法可以被其他物件呼叫。如果你在介面中沒有宣告方法,就是非正式私有的。將會得到執行時警告:物件不回應這種訊息,但是你任然可以呼叫它。

介面和C中頭部宣告很相似。但它仍然需要一個實現,這並不奇怪,可以使用@implementation來定義。

@implementation AnObject  
+ (id) aClassMethod  
{  
    ...  
}  

- (id) anInstanceMethod:(NSString*)aString with:(id)anObject  
{  
    ...  
}  
@end

注意引數型別是特定的,在括號裡。這是從C重用對映語法來展示值對映到型別;他們可能不是型別。準確來說當對映時應用相同的規則。這意味著對映在不相容物件指標型別間會導致一個警告(不是錯誤)。

記憶體管理

傳統的,Objective-C不提供任何記憶體管理。在早期版本中,物件類實現一個+new方法呼叫malloc()來建立一個新物件。當使用完這 個物件,傳一個-free訊息。任何一個物件從NSObject繼承回應一個-retain和-release訊息。當你使用完這個物件,你傳一個 -free訊息。OpenStep新增了參考計算。

每一個從NSObject繼承的物件都回應-retain和-release訊息。當你想要保留一個指向物件的指標,你可以傳送一個-retain訊息。當你使用完以後,你可以傳送一個-release訊息。

這個設計有個細微問題。通常你不需要保持一個指向物件的指標,但是你也不想釋放。一個典型的例子在返回一個物件時候,呼叫者需要保持指向物件的指標,但你不想這麼做。

這個問題的解決方案就是NSAutoreleasePool類。加上-retain和-release,NSObject也回應 -autorelease訊息。當你傳送其中一個,和現前的自動釋放池一同註冊。當這個池物件被登出,它傳送一個-release訊息給每個物件,而物件 在這之前先收到-autorelease訊息。在OpenStep應用中,一個NSAutoreleasePool例項在迴圈開始的時候建立,在結束的時 候銷燬。你也可以建立屬於你自己的例項來自動釋放物件。

這個機制減少了一些C++所需的複製。其實也不值得這麼做,在Objective-C,易變性是物件的屬性,不是參考。在C++,有常量指標和非常量指標。不允許在常量物件上呼叫非常量方法。這保證不了物件不會被改變——僅僅因為你不想改變。

在Objective-C中,一個常態模式定義了一個不變的類和可變的子類。NSString就是一個典型例子;

它有一個可變的子類NSMutableString。如果你得到NSString並且想儲存下來,你可以傳一個-retain訊息並且不用複製操作 就可以儲存指標。相反地,你可以傳一個+stringWithString:message給NSString。不管這個引數是否可變都會檢查並返回原始 指標。

在Apple和GNU執行時,Objective-C都支援儲存性的垃圾回收,這會避免對-retain和-release的需要。在現存的框架中對語言的附加並不總是很好的支援的,並且在用的時候需要格外小心。

總結

既然我們已經瀏覽了Objective-C語言的核心,在這部分的總結我們將會看到更多的一些高階話題。

相關文章