前言
在本文中,我們介紹建立和使用物件的方式。由於 Cocoa 或 GNUstep 已經有許多現成的物件可用,即使還不會建立新的類別,也可以直接使用已有的類別。
物件的基本在訊息傳遞
Objective-C 物件最基本的使用方式在於對物件傳遞訊息 (message)。其虛擬碼如下:
[object message];
這行指令的意思為對 object
物件傳遞 message
訊息。
上述指令約略等同於以下 C++ 虛擬碼:
object->method();
由此可知,Objective-C 的訊息約略等同於 C++ 或 Java 的方法 (method)。但兩者在行為上差異甚大,詳見後文說明。
如果想要傳遞參數,可參考以下虛擬碼:
[object messageWith: object_1];
[object messageWithFirst: object_1 andSecond: object_2];
[object messageWith: object_1, object_2, object_3];
在大部分情形下,每個訊息只會有一個參數。如果要傳遞兩個參數,就要傳遞兩個訊息。有少數訊息會有多個參數。
等效的 C++ 虛擬碼如下:
object->methodWith(object_1);
object->methodWith(object_1, object_2);
方法間的參數是以逗點隔開,所以可以傳入多個參數。
建立和使用物件
Cocoa 或 GNUstep 中,有少數資料型態不是物件,像是 NSInteger
:
NSInteger n = 10;
但大部分資料型態是物件,需配置記憶體,並在資料型態上使用指標:
NSNumber *n = [[NSNumber alloc] initWithInt: 10];
由於物件型態在內部實作是結構體,所以要用指向結構體的指標才能使用動態記憶體配置。我們會在後文說明在 Objective-C 中管理記憶體的方式。
這行指令的意思是先配置空的 NSNumber
物件,再對該物件初始化。另外一種直接建立 NSNumber
物件的指令如下:
NSNumber *n = [NSNumber numberWithInt: 10];
這兩行指令的差別在於前者是對 NSNumber
物件 初始化,後者是對 NSNumber
類別 建立物件。Objective-C 的類別本身也是一種物件,所以兩者共享相同的語法。
我們來看一個稍微複雜的例子。這個例子從標準輸入取得資料,將資料轉為 NSString
型態,轉換時去除空白和換行字元:
/* Create a file handle for STDIN */
NSFileHandle *input = \
[NSFileHandle fileHandleWithStandardInput];
/* Get some data from the file handler */
NSData *data = [input availableData];
/* Get string from the raw data and
trim trailing spaces from it */
NSString *str = \
[[[NSString alloc] initWithData:data \
encoding: NSUTF8StringEncoding] \
stringByTrimmingCharactersInSet:\
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
這個例子實際上只有三行。但第三行比較長,所以用折行的方式寫成多行。
第一行建立 NSFileHandle
型態物件 input
,建立該物件傳入 fileHandleWithStandardInput
訊息,表示由標準輸入建立物件。
第二行建立 NSData
型態物件 data
,此物件由先前 input
物件的資料而建立。
第三行建立 NSString
型態物件 str
。由於這行比較長,我們把這行拆解成細部動作:
/* Allocate memory for `str` */
NSString *str = [NSString alloc];
/* Init `str` object with `data` */
str = [str initWithData:data \
encoding: NSUTF8StringEncoding];
/* Trim the trailing spaces of `str` */
str = [str stringByTrimmingCharactersInSet:\
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
一開始先配置空的 str
物件。然後由 data
物件將 str
物件初始化。在初始化時指定編碼為 NSUTF8StringEncoding
。最後,對 str
物件去除空白和換行字元。
Objective-C 是動態型態語言
Objective-C 可以寫成動態型態語言,因為有萬用物件型態 id
。我們將先前的例子改寫如下:
id input = \
[NSFileHandle fileHandleWithStandardInput];
id data = [input availableData];
id str = \
[[[NSString alloc] initWithData:data \
encoding: NSUTF8StringEncoding] \
stringByTrimmingCharactersInSet:\
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
在這個時候,程式仍然可以成功地編譯和運行。但這樣寫會造成程式可讀性下降,應該把 id
保留在需要動態物件時態時使用。
為什麼物件型態使用 id
時不需使用指標?根據蘋果公司釋出的 libobjc 原始碼來看,id
的實際宣告如下:
typedef struct objc_object {
struct objc_class* class_pointer;
} *id;
由此可知,id
是指向結構體的指標型態,所以不需要額外使用指標。有時候我們以為要死記的程式知識,其實可以多花一點時間來理解。
訊息和方法在行為上的差異
訊息和方法除了在語法上略有差異外,真正重要的差別在於兩者在運行期的不同。
方法是靜態的,在編譯期就決定了。如果不同物件間要共用同一個方法,得透過繼承 (多重繼承、界面、mixin 等) 獲得共享的資料型態,才能共用同一方法。
然而,訊息是動態的,在運行期才決定是否可用。所以,每個物件只要各自實作特定訊息即可。由於 Objective-C 的物件系統會在運行期確認該訊息是否可執行,根本不需要透過繼承共享特定訊息。(參考這裡)
由此可知,Objective-C 表面上是靜態型態的編譯語言,但在行為上相當於動態型態語言。靈活的代價是編譯器比較難優化 Objective-C 程式碼。在相同的演算法下,C++ 所寫的程式碼通常會比 Objective-C 所寫的程式碼快。