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_msgSend, objc_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 guide的dynamic 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 Language的Messages 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。