位元詩人 [Objective-C] 程式設計教學:如何建立和使用物件 (Objects)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在本文中,我們介紹建立和使用物件的方式。由於 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 所寫的程式碼快。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。