Objective-C的基本語法 -- by Chen-Hai, Teng

對 未曾在Mac OS X上開發的人而言,Objective-C是一個相當陌生的名詞,不過正如其名稱所述,Objective-C是在原有C語言上,加上物件,封裝,繼承, 多型等特性所擴充而成的一種物件導向語言。不過雖然同樣是由C擴展而來,Objective-C卻與C++相當不同,以下就概略介紹一下 Objective-C的語法(含2.0新增部分)。

類別的宣告
code 1
#import <UIKit/UIKit.h> //引入系統定義的標頭檔
#import "MyHeader.h" //引入自行定義的標頭檔

@interface
MyObject : NSObject
//宣告一物件類別,其名稱為「MyObject」,繼承自NSObject類別
{

id anyObject; //宣告一成員變數,其名稱為「anyObject」,其型別為id(任意物件型別)
}
/*code 1.1*/

+ (MyObject*)myClassMethod; //宣告一類別函式(Class Method),其名稱為「myClassMethod」,而傳回值之型別為MyObject。
- (id)init; //宣告一實體函式(Instance Method),其名稱為「init」,傳回值之型別為id。
- (id)initWithAnyObject:(id)anObject; //宣告一實體函式,其名稱為「initWithAnyObject:」,有一個參數「anObject」,此參數的型別為id。
- (void)myMethod:(unsigned)argument; //宣告一實體函式,其名稱為「myMethod:」,沒有傳回值(void)。
@end //類別宣告的結尾。
上面這一段程式碼,展示了Objective-C如何描述(宣告)一個物件。
其中「#import」就如同傳統C/C++的「#include」,用來告訴編譯器(compiler),要到那裡去找沒有在此檔案中宣告的型別名稱(如NSObject)。與#include不同之處在於使用#import不需要處理重複宣告的問題。

附記
在C/C++中,為了避免重複宣告,在每個標頭檔中,需要如下的寫法:
#ifndef __MY_HEADER__
#define __MY_HEADER__

//...實際內容...
...
#endif

使用#import則可省去這些功夫。

@interface MyObject : NSObject 宣告了一個繼承自NSObject的類別,其名稱為MyObject。這裡要注意的是,Objective-C並沒有多重繼承,而是使用protocol與category來實現類似多重繼承的功用。

在大括號中間,是MyObject類別用來宣告其成員變數的地方。要注意的是,除部分基本型別外,Objective-C並不支援靜態記憶體配置如下:
code 2
@interface
MyObject : NSObject

{
unsigned int myNumber; //靜態配置,但unsigned int為基本型別,可通過編譯。
NSObject myObject; //靜態配置,無法通過編譯!
NSObject* myObjectPointer; //動態配置,合法,但之後要記得配置記憶體!
}
@end

id anyObject述敍中,「id」這個關鍵字,其意義類似C/C++的「void*」,可相容於任意型別物件。

在大括號之後,緊接著的便是宣告類別的成員函式。
code 1.1
+ (MyObject*)myClassMethod;

- (id)init;
- (id)initWithAnyObject:(id)anObject;
- (void)
myMethod:(unsigned)argument;
Objective-C的函式宣告,由幾個部分組成-函式的種類,函式的結果(傳回值)的型態,函式的名稱,函式所用到的參數。
以 - (id)initWithAnyObject:(id)anObject 為例,「-」表示了函式的種類,第一個「(id)」表示了傳回值的型態,「initWithAnyObject:」表示了函式的名稱,冒號後面所接的 「(id)anObject」則代表了參數的型態與名稱。

在Objective-C中,有兩種函式-類別函式(Class Method)與實體函式(Instance Method)。函式名稱前若是「+」號,則代表此函式為類別函式,若是「-」號,則為實體函式。其使用上差別如下:
code 3
//使用類別函式
[MyObject myClassMethod]; // [<Class Name> <Class Method Name>];

//使用實體函式
MyObject* anObject = [MyObject alloc]; //產生一個實體物件,其名稱為anObject。
[anObject init]; //用實體函式「init」對物件做初始化動作。[<Object Name> <Instance Method Name>]
由code 3可知,實體函式,必需要先有一個實際的物件存在,才能夠使用,而類別函式只要宣告類別後,即可使用。
此外,上面的範例也點出了在Objective-C中,如何使用一個函式。

Objective-C的函式與C/C++類似,只能有一個或是沒有傳回值。如果函式不需傳回任何結果,可將傳回值的型態設為「void」。
另外,Objective-C的傳回值,若是物件型態-如NSObject*,或是id-則可以直接視為一物件看待,亦即我們可以寫出下列的表示式:
code 4
MyObject* anObject = [ [MyObject alloc] init]; //將alloc函式的傳回值,當作呼叫init的物件
最後,Objective-C的函式也可以有多個參數,其宣告如下:
code 5
- (void)myMethodWithFirstParam:(int)param1 secondParam:(int)param2; //函式名稱「myMethodWithFirstParam:SecondParam:」
當類別的宣告完成後,我們還必須加上「@end」述敍,表示類別的結尾。

類別的定義
在完成類別的宣告後,我們還必須將類別中所宣告的各個函式的內容實作出來:
code 6
//MyObject.m

#import "MyObject.h"
//引入類別宣告所在的標頭檔

@implementation
MyObject //開始實作MyObject的內容

+ (MyObject*)myClassMethod
{
//實作一個類別函式,此函式的目的在於產生並傳回一個會自動釋放記憶體的物件。
MyObject* anObject;
anObject = [[MyObject alloc] init]; //配置記憶體並初始化
return [anObject autorelease]; //使用autorelease釋放記憶體
}

- (id)init
{
//實作一個實體函式,用來對物件的成員初始化。
self = [super init];
if(self)
{
anyObject = nil;
}
return self;
}
...
@end
code 6簡單的展示了如何實作Objective-C的類別。純Objective-C的原始碼是以.m做為副檔名。與C++類似,Objective-C可以在同一個檔案內實作出多個不同的類別如下:
code 7
@implementation
MyObjectA //在此區塊間的內容都屬於MyObjectA

- (id)init //此函式屬於MyObjectA
{
}
@end

@implementation MyObjectB //在此區塊間的內容都屬於MyObjectB
- (id)init //此函式屬於MyObjectB
{
}
@end
與C++不同之處在於,Objective-C也可以將同一個類別的不同部分,實作於數個不同的檔案之上。

另外,由以上的實作內容也可以看到,Objective-C沿用了C語言的基本語法,如「=」,「if..else」等等。我們可以說除了物件導向相關的語法外,Objective-C其他方面的語法與C語言是一致的。

Objective-C 2.0的新語法
除了方才所提及的Objective-C語法,Apple在Objective-C 2.0中,還引入了一些新的語法,其中最直接會影響到我們理解程式碼的,便是「@property」的引入了。

在Objective-C 1.0中,若是要從物件外部,存取物件中的變數,你必需自行撰寫存取函式(Accessor)如下:
code 8
//MyObject.h

@interface
MyObject : NSObject {

id aProperty;
}
//aProperty的存取函式
- (id)aProperty; // 用來取得aProperty的值
- (void)setAProperty:(id)newProperty; //用來設定aProperty的值
@end
code 9
//MyObject.m

#import "MyObject.h"

@implementation
// 實作存取函式
- (id)aProperty
{
return aProperty;
}

- (void)setAProperty:(id)newProperty
{
if([newProperty isEqualTo:aProperty])
return;
if(aProperty)
[aProperty release];
aProperty = [newProperty retain];
}
@end
當我們要從外部取用aProperty時,我們會寫出如下的程式:
code 10
id 
oldProperty = nil;
id newProperty = nil;
//假設newProperty已經準備好
MyObject* myObject = [[MyObject alloc] init];

oldProperty = [[myObject aProperty] retain]; //將myObject中的aProperty取出,並交由oldProperty管理。
[myObject setAProperty:newProperty]; //將newProperty 設為新的aProperty。

但在Objective-C 2.0引入了@property語法之後,同樣的事情變成如下的程式碼:
code 11
//MyObject.h
@interface
MyObject : NSObject {

    id aProperty; //實際的變數(instance variable)
}
//property 語法: @property (存取的方式及特點) 型態 名稱
@property (retain) id aProperty; //宣告一個property,其名稱與實際變數相同,存取的方式為nonatomic 與 retain。
@end
code 12
//MyObject.m
#import "MyObject.h"

@implementation MyObject
@synthesize aProperty; //告知編譯器自動產生存取函式。
@end

現在,當我們要從外部取用aProperty時,程式碼變成了:
code 13
id oldProperty = nil;
id newProperty = nil;
//假設newProperty已經準備好
MyObject* myObject = [[MyObject alloc] init];
oldProperty = myObject.aProperty; //將myObject中的aProperty取出,並交由oldProperty管理。
myObject.aProperty = newProperty; //將newProperty 設為新的aProperty。
code 11~13示範了property如何宣告,實作,以及使用。

事實上,code 8~10與code 11~13(使用存取函式,使用@property),所做的事完全一樣。但可以看到,使用property,開發者所需要寫的程式少了很多-大部分的事 情都由編譯器與前置處理器(preprocessor)幫開發者處理掉了。在此,我們可以先粗略的說,property就是一個自動產生存取函式的機制。
當你使用 @property, @synthesize 來處理你的變數時,編譯器及前置處理器會自動幫你產生 「- (<變數型別>)<變數名稱>」 及 「- (void)set<變數名稱>:」兩個存取函式。

附記
本文中所介紹的Objective-C相關語法,目的只在於讓讀者能大致理解後續範例程式之內容。若想更深入瞭解Objective-C的全貌,還請參考以下資料:
  1. Learning Objective-C: A Primer
  2. The Objective-C 2.0 Programming Language
  3. Objective-C Pocket Reference (Andrew M. Duncan, O'Reilly)