Objective-C 2.0(二)Class

Objective-C 2.0(二)Class:2009年08月14日星期五

本文,主要根據Objective-C Beginner's Guide的中文英文網站,有source code, 下載後稍加修改即可用在Xcode開發環境中(如何改?見Objective-C 2.0(一)Objects & Messaging(Q)(二))。

其次,以Apple的Objective-C 2.0 Programming Language 為輔,在此亦可找到同名的章節Defining a Class、Classes。Language summary也很重要,Class亦在其中。

Objective-C 2.0(一)Objects & Messaging的(Q)(R)(S)中已簡單講了一些Class,現在深入一些看Class。

(A)問:Class中的instance variable到處可用嗎?(或者說,instance variable 的scope規則是什麼?)

答:看看型式,說明,練習。詳見Objective-C Beginner's Guide的中文網站存取權限

型式

@public

int publicVar;

@private

int privateVar;

int privateVar2;

@protected

int protectedVar;

@package

int packageVar;

說明:以下說明取材自Objective-C 2.0 Programming Language的The scope of instance variables。

以上型式@public之後到下一個@...或}之前,均為public variable;@private、@protected, @package均同。

private variable在class內看得到,protectedvariables 在class及其subclass看得到,public variables則到處可用。

至於@package則是在包含這class的程式(大約就是A/P)、如public,但在此程式之外,就如private varibales。原文如下:

on 64bit, an @package variable instance variable acts like public inside the image that implements the class, but @private outside.

....This is most useful for instance variables in framework classes.

所以大約像Java的@package,在package裡像public,package外是private。

private、protected、prublic詳見下圖(取自Objective-C 2.0 Programming Language):

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

(B)問:有沒有辦法計算某一個Class被instance了幾次?

答:可以用class level的variables及class level的function。詳見Objective-C Beginner's Guide的中文網站Class Level Access

例,在declare 一個ClassA.h時,

static int count;

@interface ClassA: NSObject +(int) initCount; +(void) initialize; @end

上面的static int count; 即是class level的variable, +(int) initCount; +(void) initialize;均為class level的function。兩點注意:

  1. +(int) initCount; 雖是method,但因是class level function,故用+。
  2. +(void) initialize;有點像是Class 的"default constuctor", 在run time一開始,所有class內的+(void) initialize;都會被呼叫一次。

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

(C)問:當有異常狀況發生時,Objective C是否像java一樣可以處理?

答:有NSException Class,@try, @catch,, @throw,@finally可用。詳見AppleObjective-C 2.0 Programming LanguageException handlingthrow

型式:見Exception handling

Cup *cup = [[Cup alloc] init];

@try{

//放入可能出現 exception的程式

[cup fill];

}

@catch(CustomException *ce){//若未caught exception,不會進入這一行。用breakpoint可試出來。

//放入exception發生後的處理程式

@throw ce; //任何exception均可用@throw,由此直接跳出此function。

}

@catch (NSException *exception){

// 放入exception發生後的處理程式

[exception raise]; //只可用在NSException,由此直接跳出此function。

}

@catch (id ue) {

// 放入exception發生後的處理程式

//注意,由這兒直接進@finally

}

@finally {//這一行永遠不會到,直接進下一行

//不論exception是否發生,本段程式均執行

}

說明:見上段型式中的comment。有以下幾個重點:

  1. @catch若catch到exception,@catch這一行會執行,執行完,立刻進去做,做完,若無@throw,則跳入finally。若沒catch到exception,不會進@catch這一行執行。
  2. @finally那一行永遠不會進入,直接進body
  3. 若沒catch到exception或exception內無@throw,則進finally。

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

(D)問:dynamic binding是什麼?和id有何關係?

答:Objective-C 2.0(一)Objects & Messaging的(M)講過dynamic binding,[receiver message]中的receiver、message都在run time才決定。receiver在runtime決定的用法之一是靠id幫忙,例如,

define:id number; 再將不同的receiver assign 給number,因為id是個Class *,為眾Class之首,所以其object nunber可接受任何object。

舉例說明可以看Objective-C 2.0(一)Objects & Messaging的(M)。

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

(E)問:inheritance(繼承)是什麼?

答:當declare一個Class時(見Objective-C 2.0(一)Objects & Messaging的(Q)),可指定super class,也inherit(繼承)了super class的instance variable及method。注意,不可亂用superclass的自定constructor。詳見Objective-C Beginner's Guide的中文網的inheritance(繼承)

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

(F)問:神奇的dynamic binding(如上(D)所述)搞到最後,object會不會連自己是誰都搞不清楚了?

答:一定會,因此需有方法動態地知道object倒底是那個class?某個selector是否屬於這個object?...等等,也就是Dynamic types(動態識別)用的方法,詳見Objective-C Beginner's Guide的中文網的Dynamic types(動態識別)

介紹六種方法:isKindOfClass(object屬於某class或descendent?)、 isMemberOfClass(object屬於某class?)、 respondsToSelector(class定義了某selector?)、 instancesRespondToSelector(class可回應某selector?)、 performSelector(執行這個selector)。注意文法:responds及instances

以兩個為例來說明,

例如: isKindOfClass(屬於某class或descendent?),其型式為,

[objectName isKindOfClass:classObj]

實例:[sq isKindOfClass : [Square class]]

這個例子中唯一要說明的是[Square class],class是NSObject的method,回傳值是一個class物件。

例如:respondToSelector(class定義了某selector?),其型式為,

[ClassName respondsToSelector:selector]

實例:[Square respondsToSelector:@selector(setSize:)]

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

(G)問:做一個Class時,有時想偷偷寫個method自己用,不給別人用,行嗎?

答:使用category,詳見Objective-C Beginner's Guide的中文網的categories。因為,java有private/protected/public methods,而Objective-C沒有。所以有catogory來完成這個工作。

型式:舉例表示

FractionMath.h:

#import "Fraction.h"

@interface Fraction (Math)

@end

FractionMath.m:implement這些method。

@implementation Fraction (Math)

@end

說明:Fraction.h裡本來就有一些method,當用了@interface Fraction (Math)及@implementation Fraction (Math) 這些之後, 又多了幾個method。

那,為什麼可以有private method的效果呢?

答:如果.m裡有,但.h沒有,不就成功了?! 詳見Objective-C Beginner's Guide的中文網的categories裡MyClass.h及MyClass.h例子。

練習:本段class的練習已作成Xcode可執行的project,有兩個:

練習categories:名稱為categoriesObjc。

練習用categories達成private method:名稱為privateObjc。

注意:private method理論上如此,實際作時,SDK 3.0 Xcode的Objective-C compiler只對categories method產生warning,照常執行!!!

(H)問:Class擴充後,如何取代原來的Class?

答:用Posing(裝扮), 詳見Objective-C Beginner's Guide的中文網的posing

型式:假設ClassB是ClassA的subclass,並且加了些method或改寫了些method。

[ClassB poseAsClass:[ClassA class]];

說明,執行了這一行之後,所有的ClassB就成了ClassA。poseAsClass為NSObject的一部份。

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

注意:實作後發現,SDK 3.0 Xcode的Objective-C無法使用posing(run time error),poseAsClass這個method不存在。

(I)問:@protocol有什麼用?

答: 詳見Objective-C Beginner's Guide的中文網的protocol

型式:

在Printing.h中

@protocol Printing

-(void) print; // 未來需在class中implement的method

@end

在Fraction.h中

#import <Foundation/NSObject.h>

#import "Printing.h"

@interface Fraction: NSObject <Printing, NSCopying> //conforms to(服從) Printing及NSCopying(NSString的method)這兩個protocol

{int numerator; int denominator; }

....

@end

在Fraction.m中

#import "Fraction.h"

#import <stdio.h>

@implementation Fraction

....

-(void) print { printf( "%i/%i", numerator, denominator ); } //因為comforms to Printing protocol,必需implement其中所有的protocol

@end

在Complex.h中

#import <Foundation/NSObject.h>

#import "Printing.h"

@interface Complex: NSObject <Printing> //conforms to(服從) Printing這一個protocol,雖然是NSObject的subclass,但未conforms to(服從)NSCopying protocol

{ double real; double imaginary; }

@end

在Complex.m中

#import "Complex.h"

#import <stdio.h>

@implementation Complex

-(void) print { printf( "%_f + %_fi", real, imaginary ); } //因為comforms to Printing protocol,必需implement其中所有的protocol

@end

在main.m中

Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; //frac是Fraction,記得conforms to (服從) Printing及NSCopying(NSString的method)這兩個protocol

Complex *comp = [[Complex alloc] initWithReal: 5 andImaginary: 15]; // comp是Complex,記得conforms to(服從) Printing這一個protocol,雖然是NSObject的subclass,但未conforms to(服從)NSCopying protocol

id <Printing> printable; // printable 是conforms to Printing protocol的id

id <NSCopying, Printing> copyPrintable; //copyPrintable 是conforms to Printing及NSCopying protocol的id

printable = frac; // 可以,因為 printable及frac都 conforms to Printing protocol,frac多conform了NSCopying,但沒關係,因為不會有[printable NSCopying];

printable = comp;// 可以,因為 printable及frac都 conforms to Printing protocol。

copyPrintable = frac; // 可以,因為 printable及frac都 conforms to Printing 及NSCopying protocol,

copyPrintable = comp; //comp只conmform to Printing, 並未comform to NSCopying

if ( [frac conformsToProtocol: @protocol( NSCopying )] == YES )

if ( [comp conformsToProtocol: @protocol( NSCopying )] == YES )

說明:見型式中各行comment。

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

注意:在protocolObjcAppDelegate.m中有copyPrintable = comp;這一行,原先comment掉,因為"doesn't compile" ,實作後發現SDK3.0 Xcode的Objective-C可以compile,只是有warning。

(J)Protocol的理論 (取材自Mac Developer Center):protocol(草約、協定)有三大用法,不管那一種用法,基本原則是要和class/object分離,什麼意思?class/object有inheritance(繼承)、hierarchy(階級),protocol徹夜抽離,protocol只是一堆method的集合,當class想用它時,只需conform to(服從)它,用<>表示,如(I)中的@interface Fraction: NSObject <Printing, NSCopying>,這一行表示Fraction這個class是NSObject的subclass,並conform to ( 服從 ) Printing,及NSCopying這兩個protocol。所以,Printing、NSCopying中所定義的method,未來在Fraction class中都要implement。

protocol最大的好處就在於讓method脫離class。三大用法如下:

To declare methods that others are expected to implement。

例:(I)例中Printing.h內的method -(void) print;

To declare the interface to an object while concealing(隱藏) its class。

例:(I)例中 id <Printing> printable; 及 id <NSCopying, Printing> copyPrintable;

完全看不出printable及copyPrintable這兩個variable是那一個class的!好處?dynamic binding!

可以如此assign :---------> printable = frac;

也可以如此assign :------> printable = comp;

一切等run time。

To capture similarities among classes that are not hierarchically related。

例:(I)例中 Fraction及Complex兩個class都需要 -(void) print; 這method,但Fraction及Complex互相並無hierarchy(階級)關係,這時,用protocol將method -(void) print;抽出。以便讓Fraction及Complex兩個class都可conform to (服從)它,達到精簡、integrity兩大目的。

formal protocol:

型式:

@protocol MyProtocol

-(void) requiredMethod;

@optional

- (void) anOptionalMethod;

- (void) anotherOptionalMethod;

@required

- (void) anotherRequiredMethod;

@end

例:

@protocol MyXMLSupport

- initFromXMLRepresentation:( NSXMLElement *) XMLElement;

- ( NSXMLElement *) XMLRepresentation;

@end

informal protocol:直接放在category declaration。

例:往往放在NSObject。

@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation:( NSXMLElement *) XMLElement;

- ( NSXMLElement *) XMLRepresentation;

@end

adopting protocol:

@interface ClassNmae: ItsSuperclass < protocol list > ,protocol list separated by ,

例: @interface Formatter : NSObject < Formatting, Prettifying >

@interface ClassName ( CategoryName ) < protocol list >

Conforming to protocol:

if ( ! [receiver conformsToProtocol:@protocol(MyXMLSupport)] ){

//Object does not conform to MyXMLSupport protocol

// If you are expecting receiver to implement methods declared in the

 //MyXMLSupport protocol, this is probably an error

}

protocols within protocols:

@protocol ProtocolName < protocol list >

protocol list 裡的名字也成了ProtocolName的protocol。

例:

@protocol Paging < Formatting >

id <Paging> someObject;

someObject conforms to both protocol Paging and Formatting。

Refering other protocol:

#import "B.h"

@protocol A