Objective-C 2.0(一)Objects & Messaging

Objective-C 2.0(一)Objects & Messaging:2009年08月11日星期二

首先,本Objective-C 2.0系列文是精簡及中文化apple developer網站Objective-C 2.0 Programming Language 內容而來的。先討論以下幾個題目:

Objects, Classes, and Messaging.

Declared Properties.

Categories, extensions.

Protocols.

Selectors.

以上章節,在Objective-C 2.0 Programming Language 均可找到同名的章節,本文只討論Objects及一些 Messaging的概要。

(A)問:Objective-C run time重要嗎?

答:重要,因為Objective-C將許多事由compile及link的過程延遲至run time才執行,如此,可dynamically link所需的Object,以達到最高flexibility。詳見 Objective-C 2.0 Runtime Programming Guide.

(B)問:什麼是Object?

答:Object包含有method及instance variable。 method就像一般程式的function call,instance variable就是一般的variable。

(C)問:id是什麼?

答:Objective-C一個特別的data type。id就是:

typedef struct objc_object {

Class isa;

} *id;

而Class本身就是個pointer:

typedef struct objc_class *Class;

所以isa稱為isa pointer。

至此,我們可定義一個id object如下:

id anObject;

nil 即為null object,也就是id值為0,id、nil及其他basic type object都在objc/objc.h裡面。

objects均dynamically typing,也就是說,在程式執行時(run time)才最後決定該object的type。

(D)問:object會自動消失嗎?

答:不會,寫程式時自己需deallocate object,並使用refernce counting來記錄該object是否仍被使用。Objective-C 2.0 Programming Language 另有garbbage clollection方式清除object,iPhone不用這方法。 所以, 只需看memory management programming guide,確實維持refernce counting及deallocate object。

(E)問:如何用message來指令object做事?

答:[receiver message];

receiver是個object名稱。

message即為一串 method:argument。method 是該object的method,也就是一個function call。argument(參數)就是該function call所需的參數。 method和一般function call不同,function call用function的名字就決定call那一個function,而method則是由method名及message共同決定去呼叫那一個method,因而,在此處,用selector來稱呼method的名字。method後跟一個冒號":"、"method:",稱為keyword。冒號":"後面則緊跟著傳送給method的argument,

看幾個例子:

[myRectangle setOrigin:20.0]; //定義一個中心在20.0的長方形

[myRectangle setOrigin:30.0 :50.0]; // 定義一個中心在(30.0, 50.0)的長方形,setOrigin: : 是合法的語法,但並不好。

[myRectangle setOriginX:30.0 Y:50.0]; // 定義一個中心在(30.0, 50.0)的長方形,setOriginX: Y: 這方法較好,因為清楚指明X軸及Y軸

(F)問:倒底[receiver message];在Objective-C 2.0 Programming Language是如何運作的?

答:請看Objective-C run time message這一章節。 重點如下:

第一步,compiler將message中的selector抽離出來,並將[receiver message];轉比成system call objc_msgSend的形式,如下--

objc_msgSend(receiver, selector);

第二步,加入argument,如下--

objc_msgSend(receiver, selector, arg1, arg2, .....);

第三步,在run time,先找到selector相對的method,重點來了:

以上述[myRectangle ....]這例子來說,setOrigin這名字的method有許許多多,例如圓、橢圓...等都有setOigin這method,在run time,如何找到對的method?

答案就是以myRectangle這個object所屬的class為準。

接著,將call這個class的這個method(當然,同時將argument傳給這個method)。所謂method,也就是C語言中的procedure、function罷了。

最後,method會將return value回傳給 objc_msgSendobjc_msgSend再將此return value當作自己的return value回傳。

所以,一個message就完成了,而且,message有return value。

message能如以上三步完成的重點是,每一個class都有:

指向superclass的pointer。

class dispatch table:這dispatch table內有一堆[method selector,相對於此method、此class的procedure位址],當產生一個object後,該object就佔了一塊位址(memory),這塊memory中有個pointer,指向該object所屬的class,這個pointer就是本文之前提到的isa pointer。 因此,isa pointer非常重要,可是,我們不用去管它,因為所有的object都來自(inherit)於NSObject或NSProxy這兩個object,isa pointer自動產生了。下圖來自Objective C run time programming guide - messaging,充份圖示了class dispatch table

(G)問:使用message時,常看到"self","self"是什麼?

答:當objc_msgSend找到相對於method的procedure後,它會傳argument,而有兩個"hidden argument"也會傳給procedure,

一是receiving object

另一是selector for the method

這兩個hidden argument是在compile時放入程式。未來,在method裡,"self"就是指receiving object。

同時"_cmd"指的是selector。如下例,有個名為strange的message:

- strange

{

id target = getTheReceiver();

SEL method = getTheMethod();

if ( target == self || method == _cmd )

return nil;

return [target performSelector:method];

}

(H)問:用 [receiver message];可以有return value嗎?

答:可以,例:

BOOL isFilled;

isFilled = [myRectangle isFilled];

其原理如前(F)第三步所述。

(I)問:用 [receiver message];可以nested嗎?

答:可以,例:

[myRectangle setPrimaryColor:[otherRect primaryColor]];

(J)問:像java的method可用"."operator,objective C 可以使用嗎?

答:可以,通常用在object的accessor method。跟Declared properties feature合用,詳情在日後"Dot Syntax"會講到。

(K)問:可以送message給nil(空戶)嗎?

答:可以,如下例:

Person *motherInLaw = [[aPerson spouse] mother];

如果aPerson尚未婚,那麼[aPerson spouse]自然是個nil object,後面mother這個message送給nil object後,仍是回覆(return value)nil(0),因而motherInLaw這pointer的值是nil。

(L)問: [receiver message];例子中,message可以看到receiver的instance variable嗎?

答:當然可以。例如,(I)例子中,

[myRectangle setPrimaryColor:[otherRect primaryColor]];

primaryColor這message可以看到otherRect中所有的instance variables。

(M)問:objective C裡的message和standard C裡的function call 或procedure倒底有何不同?

答:objective C裡的message有同質異形(polymorphism)功能,意思是:

同一個名字的message對上不同的object,會有不同結果,這在standard C裡是不可想像的,在standard C裡,同一個function只有一種功能,舉例說明同質異形(polymorphism)功能如下:

[myRectangle draw];裡的draw message會畫個方形。

[myCircle draw];裡的draw message會畫個圓形。

同一個名字的draw會因object myRectangle、myCircle的不同作出不同的結果, 這就是同質異形(polymorphism)。

這一整套的概念就是dynamic binding, receiver和message都在run time才決定,並不像standard C在compile time都決定了,這連java都比不上。一切都得歸功於 [receiver message];這一招!!詳見Objective-C 2.0 run time programming guidedynamic method resolution

(N)(.)dot operator 和([])square bracket operator相似性?

答:如(J)所述的(.)dot operator和([])square bracket operator相似性舉例如下:

第一例--

(.)dot operator

myInstance.value = 10;

printf("myInstance value: %d", myInstance.value);

([])square bracket operator

[myInstance setValue:10]; //注意setValue中的Value與(.)dot operator中的value同,但V一定要大寫

printf("myInstance value: %d",[ myInstance value]);

再看個例子:

第二例--

(.)dot operator

Graphic *graphic =[[Graphic alloc] init];

NSColor *color = graphic.color;

CGFloat xLoc = graphic.xloc;

BOOL hidden = graphic.hidden;

int textCharacterLength = graphic.text.length;

if (graphic.textHidden != YES) {

graphic.text = @"Hello";

}

([])square bracket operator

Graphic *graphic =[[Graphic alloc] init];

NSColor *color = [graphic color];

CGFloat xLoc = [graphic xloc];

BOOL hidden =[ graphic hidden];

int textCharacterLength = [[graphic text] length];

if ([graphic isTextHidden ]!= YES) {

[graphic setText: @"Hello"];

}

(O)以上兩個例子的結果完全相同,如(J)所述的(.)dot operator通常用在object的accessor method。跟Declared properties feature合用,倒底什麼是特性(property)?

答:如(N)第二例,graphic這個object有很多特性(property),例如:顏色(color)、x-location(xloc)、名字(text)、名字長度(.text.length)...,這些用來描繪graphic的性質,就是特性(property),在Declared properties feature中有詳述。

 

練習:本練習的目的是自己寫下objective-C的程式,建立class,使用object,以便未來可以看得懂iPhone SDK中的系統class。

為避免apple developer網站Objective-C 2.0 Programming Language的繁複及使用網上可以下載的程式範例,以下資訊取材於Objective-C Beginner's Guide的中文英文網站,

(P)問:網上有現成的Objective-C 2.0程式範例嗎?

答:有,到Objective-C Beginner's Guide的中文英文網站下載即可。下載後,如果是用apple Xcode開發環境,需小幅度改寫程式。自現在起本文均以Objective-C Beginner's Guide的中文網站的內容為主。

(Q)問:如何建立class?

答:共分為@interface及@implement及合成在一起三個步驟,先簡述如下:

(一)@interface

型式:

@interface ClassName : ItsSuperclass {

instance variable declarations

}

method declarations

@end

說明:

@interface ....@end即為@interface的全部,其中分為 instance variable declarations 及 method declarations 兩項。 在@interface ClassName : ItsSuperclass {這一行的 ItsSuperclass指定其superclass,若不寫,只有@interface ClassName {,那麼,其superclass為NSObject,NSObject中的NS倒底是什麼東東?

原來,iPhone OS和Mac OS均是由steve Jobs、NeXT公司的NeXTStep OS改寫而來的, NS就是NeXT Step兩字的第一個字母,其實這OS已改名為OpenStep了,但NSObject並未跟著改成OSObject,NSObject就一直延用。

instance variable declarations內的variable預設權限均為protected。

method declarations的型式為

scope (return Type) methodName:(parameter1 Type) parameter1Name;

詳見@interface說明

(二)@implement

型式:

@implementation ClassName : ItsSuperclass{

instance variable declarations

}

method definitions

@end

或者....

#import "Classname.h"

@implementation ClassName

method definitions

@end

說明:

幾乎和@interface相同。注意,若用#import "Classname.h",則ItsSuperclass及instance variable declarations都可省略。

現在來看一個例子。這個例子是印出分數,例如,分子(numerator)是1,分母(denominator)是3,印出:

The fraction is 1/3

下載的練習中找到Fraction folder。用Xcode打開Fraction、, Fraction.m 、main.m,試試看,不過,這個例子是用在linux環境,若要在Xcode內使用,作如下的修改(假設先用Xcode New Project,project name為fractionObjc):

將main.m內的程式放到fractionObjcAppDelegate.m的applicationDidFinishLaunching內。

其他的都OK。在這個例子中主要有五個method:

-(void) print { printf( "%i/%i", numerator, denominator ); }

-(void) setNumerator: (int) n { numerator = n; }

-(void) setDenominator: (int) d { denominator = d; }

-(int) denominator { return denominator; }

-(int) numerator { return numerator; }

它們都在Fraction.m內。詳細的解說請參考Objective0C Beginner's Guide創建Class原文

本段class的練習已作成Xcode可執行的project,名稱為fractionObjc。

(R)問:如果method中想多傳幾個參數,該怎麼做?

答:在Objective-C Beginner's Guide的中文網站內,有多重參數一節可參考。

型式:

method定義時:

method:labelForparam2:labelForparam3:labelForparam4:

method呼叫時:

[obj method:param1 labelForparam2:param2 labelForparam3: param3 labelForParam4:param4]

或者(因為並非一定要label)

[obj method:param1 :param2 : param3 :param4]

說明:基本上是method::::這個型式,每個:跟一個parameter。在java及C++中似乎都沒這種用法,不容易習慣,method::::::是來自於Smalltalk。

現在來舉一個例子,仍用分數、Fraction這個例子,本例子是要將分子(numerator)分母(denominator)一起設定,而不是分別用setNumerator及 setDenominator設定(本例取自Objective-C Beginner's Guide的中文網站):

在Fraction.h中:

-(void) setNumerator: (int) n andDenominator: (int) d;

在Fraction.m中:

-(void) setNumerator: (int) n andDenominator: (int) d { numerator = n; denominator = d; }

假設frac2是個Fraction object,呼叫時:

[frac2 setNumerator: 1 andDenominator: 5];

本段多重參數練習,已作成Xcode可執行的project,名稱為multipleObjc。

(S)問:在message裡常看到init,如[[object message] init];這是什麼?

答:這是Objective-C裡面Class的預設constructor(建構子),不同於java(java是與Class同名的method),init只是個 -(id)init 罷了。比較有趣的是自訂的constructor。在Objective-C 2.0 Beginner's Guide裡有關自訂的constructor章節仍用Fraction作例子,十分簡潔。如下:

Fraction.h:

-(Fraction*) initWithNumerator: (int) n denominator: (int) d;

Fraction.m:

-(Fraction*) initWithNumerator: (int) n denominator: (int) d { self = [super init]; if ( self ) { [self setNumerator: n andDenominator: d]; } return self; }

呼叫:

Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];

細看Fraction.m中的程式。

-(Fraction*) initWithNumerator: (int) n denominator: (int) d {

self = [super init];

if ( self ) { [self setNumerator: n andDenominator: d]; }

return self;

}

[super init];是什麼?

(1) 這就說來話長了。首先,

先了解init,init是預設constructor(建構子),要了解"[super init];",必需去看NSObjec的init,打開Xcode,Help/Documentation,按左邊的Apple iPhone OS 3.0/iPhone OS 3.0 Library,右上角start with打入NSObject,就會搜尋出一列NSObject的相關資訊,此時有上下二欄,上欄是大綱,下欄是某大綱項目的詳細說明,共有六項,

P NSObject

C NSObject

C NSObject(UINibLoadingAdditions)

K NSObjectIDAttributeType

G NSObjectlnaccessibleException

G NSObjectNotAvailableException

click C NSObject,再看下欄,scroll一下,找到 Tasks/Creating,Copying,and Deallocating Objects, 可看init在第四個,click init,即可了解部份[super init]。

(2)要完全了解[super init],也必需了解alloc, 用找init相同方法,找到 Tasks/Creating,Copying,and Deallocating Objects, 可看alloc在第二個,click alloc,即可了解alloc和init如何交互作用。

(3)基本上,[super init]一定在designated initializer(也就是 initWithNumerator)出現,而init 和alloc會配對出現在程式中,先看簡單的,例如,

Fraction *frac3 = [[Fraction alloc] init];

alloc會找一塊memory給這個object,初始化(C)中提到isa pointer,並將其他instance variable值設為0,然後傳回一個object instance( 也就是指向這memory的pointer)。

init接著呼叫super class的init ([super init]) ,將instance variable值設好,傳回一個initialize好的receiver。

[super init]是呼叫receiver的superclass的init,並傳回一個initialize好的receiver。。

(4)這些原理看似簡單,卻不容易,必需充份了解self,super的意義。以最粗淺的解釋,self就是java的this,super則是superclass。詳情即需去讀上面(1)(2)所講到的NSObject的init及alloc。再加上Objective-C Beginner's Guide的中文網站自訂的constructor例子,同時讀Objective-C 2.0 Programming LanguageMessages to self and super。 一起看,才能了解。

 

(5)先去試做以下提及的自訂的constructor練習,做完,再來看看是否真了解[super init]了,看一下initWithNumerator中的[super init],

呼叫 initWithNumerator

Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10];

而 initWithNumerator是這樣子的:

-(Fraction*) initWithNumerator: (int) n denominator: (int) d {

self = [super init];

if ( self ) { [self setNumerator: n andDenominator: d]; }

return self;

}

在Fraction class的initWithNumerator這個constructor中有[super init]字眼。

再閱讀上面(3)一次,尤其是

"Fraction *frac3 = [[Fraction alloc] init]; "這一行,

其實這一行和"Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; "這一行類似。

接著,看

alloc....這一行

由這一行得知, [Fraction alloc]會傳回一個object instance of Class Fraction,注意,這是個object(已有memory), 所以, [[Fraction alloc] init];成了"[object instance of Class Fraction init];",而object instance of Class Fraction即為receiver。

同理, [[Fraction alloc] initWithNumerator: 3 denominator: 10];會傳回一個object instance of Class Fraction,注意,這是個object(已有memory), 所以, [[Fraction alloc] initWithNumerator: 3 denominator: 10];成了"[object instance of Class Fraction initWithNumerator: 3 denominator: 10];",而object instance of Class Fraction即為receiver。

再看

init....這一行

現在,object instance of Class Fraction是receiver。由這一行得知,init回傳一個initialize好的receiver。因而就是回傳一個object instance of Class Fraction。

因此,initWithNumerator中的[super init]回傳的是一個object instance of Class Fraction,再assigns給self,

self = [super init];

所以,此時self即是一個object instance of Class Fraction,做了那麼多事情,看來完全是廢話,因為,即便不做"self = [super init];",self本來就是receiver(詳情見本文(G)項),為什麼要做"self = [super init];"呢?主要是要init superclass,而且要防範init superclass失敗,當init superclass失敗時, [super init]回傳值是nil,造成initWithNumerator回傳nil,而"Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; "中的frac3即得到nil值,如此,就知道失敗了。

self = [super init];這一行相當不易了解,希望至此已充份明白了。

本段自訂的constructor練習,已作成已作成Xcode可執行的project,名稱為constructorsObjc。