如何建立和使用 Objective-C 物件 (Objects)

    前言

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

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Yahoo
    【追蹤本站】
    Facebook Facebook Twitter