鍍金池/ 教程/ iOS/ 使用對(duì)象 - Working with Objects
使用對(duì)象 - Working with Objects
使用塊 - Working with Blocks
自定義現(xiàn)有的類(lèi) - Customizing Existing Classes
使用協(xié)議 - Working with Protocols
定義類(lèi) - Defining Classes
賦值與集合 - Values and Collections
關(guān)于 Objective-C
命名規(guī)則 - Conventions
錯(cuò)誤處理 - Dealing with Errors
數(shù)據(jù)封裝 - Encapsulating Data

使用對(duì)象 - Working with Objects

在一個(gè) Objective-C 應(yīng)用中大部分的工作是由于消息跨越對(duì)象的生態(tài)系統(tǒng)被送回和送達(dá)而產(chǎn)生的。這些對(duì)象中的一部分是由 Cocoa 或者 Cocoa Touch 所提供的類(lèi)的實(shí)例,一部分是你自己的類(lèi)的實(shí)例。

前一章描述了來(lái)為一個(gè)類(lèi)定義接口和實(shí)現(xiàn)的語(yǔ)法,其中也包括了實(shí)現(xiàn)包含為響應(yīng)消息而需要被執(zhí)行的代碼的方法的語(yǔ)法。這一章解釋了如何給一個(gè)對(duì)象發(fā)送這樣一條消息,并涵蓋了 Objective-C 的一些動(dòng)態(tài)特征,包括動(dòng)態(tài)類(lèi)型和決定哪個(gè)方法應(yīng)當(dāng)在運(yùn)行時(shí)被調(diào)用的能力。

在一個(gè)對(duì)象能夠被使用前,它必須結(jié)合為它的屬性所做的內(nèi)存分配和內(nèi)部值的初始化以被正確地創(chuàng)建。這一章描述了如何嵌套方法調(diào)用來(lái)分配和初始化一個(gè)對(duì)象來(lái)保證它是被正確配置的。

對(duì)象發(fā)送并且接收消息

盡管在 Objective-C 的對(duì)象間中有多種不同的方法來(lái)發(fā)送消息,到目前為止最普遍的方法是使用方括號(hào)的基礎(chǔ)語(yǔ)法,像這樣:

    [someObject doSomething];

左邊的引用,在這個(gè)案例中的 someObject,是消息的接收者。右邊的消息,doSomething,是訪問(wèn)接收者的方法的名稱(chēng)。換句話說(shuō),當(dāng)上面這行代碼被執(zhí)行的時(shí)候,someObject 將被發(fā)送消息 doSomething

前一章描述了如何為一個(gè)類(lèi)創(chuàng)建一個(gè)借口,像這樣:

@implementation XYZPerson : NSObject  
- (void)sayHello;    
@end

和怎樣為創(chuàng)建那個(gè)類(lèi)的實(shí)現(xiàn),像這樣:

@implementation XYZPerson  
- (void)sayHello {  
    NSLog(@"Hello, world!");  
}
@end

注解:這個(gè)例子使用了一個(gè) Objective-C 的字符串字面量,@“Hello, world!”。字符串是在 Objective-C 中幾個(gè)允許為它們的創(chuàng)建而使用的速記文字語(yǔ)法的其中之一。規(guī)定@“Hello, world!”概念上等同于“An Objective-C string object that represents the string Hello, world!.”

字面量和對(duì)象創(chuàng)建將在這一章之后的對(duì)象是被動(dòng)態(tài)創(chuàng)建的中被進(jìn)一步地解釋。

假設(shè)你已經(jīng)得到了一個(gè) XYZPerson 的對(duì)象,你可以像這樣給它發(fā)送 sayHello的消息:

[somePerson sayHello];

發(fā)送一條 Objective-C 的消息概念上非常像調(diào)用一個(gè)函數(shù)。圖2-1為消息“sayHello”展示了實(shí)際的程序流程。

圖 2-1 基本的消息程序流程

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/programflow1.png" alt="2-1" />

為了指定一條消息的接收者,理解指針在 Objective-C 中怎樣被用來(lái)指向?qū)ο笫欠浅V匾摹?/p>

使用指針來(lái)跟蹤對(duì)象

C 和 Objective-C 使用變量來(lái)跟蹤數(shù)值,就像其他編程語(yǔ)言一樣。

在標(biāo)準(zhǔn)C語(yǔ)言中有許多基本的標(biāo)量變量被定義,包括整數(shù)型,浮點(diǎn)數(shù)型和字符型,它們像這樣被聲明和賦值:

    int someInteger = 42;  
    float someFloatingPointNumber = 3.14f;  

局部變量,是在一個(gè)方法或函數(shù)中被聲明的變量,像這樣:

- (void)myMethod {  
    int someInteger = 42;  
}

被限定在方法中被定義的作用域中。

在這個(gè)例子中,someIntegermyMethod 中被聲明為一個(gè)局部變量。一旦執(zhí)行到方法的閉合括號(hào),someInteger 將不再可存取。當(dāng)一個(gè)局部的標(biāo)量變量(像一個(gè) int 或者一個(gè) float)消失,數(shù)值也將消失。

Objective-C對(duì)象,相比之下,則分配得稍許不同。對(duì)象通常比方法調(diào)用的簡(jiǎn)單作用域生命更長(zhǎng)。特別的,一個(gè)對(duì)象經(jīng)常需要比那些被創(chuàng)建來(lái)跟蹤它的原始變量要存活更久的時(shí)間。因此,一個(gè)對(duì)象的存儲(chǔ)空間是動(dòng)態(tài)地被分配和解除分配的。

注解:如果你習(xí)慣于使用棧和堆,則當(dāng)對(duì)象在堆上被分配時(shí),一個(gè)局部變量是在棧上被分配的。

這需要你使用 C語(yǔ)言中的指針(指向存儲(chǔ)空間的地址)來(lái)追蹤它們?cè)诖鎯?chǔ)空間中的位置,像這樣:

- (void)myMethod {
    NSString *myString = // get a string from somewhere...
    [...]
}

盡管指針變量 myString(星號(hào)指示它的指針)的作用域受 myMethod 作用域的限制,它在存儲(chǔ)空間中所指的實(shí)際的字符串對(duì)象在作用域外可能會(huì)有更長(zhǎng)的生存時(shí)間。舉例來(lái)說(shuō),它可能已經(jīng)存在,或者你可能需要在其他的方法調(diào)用中傳遞對(duì)象。

你可以給方法參數(shù)傳遞對(duì)象

如果你在發(fā)送一條消息時(shí)需要傳遞一個(gè)對(duì)象,你為方法參數(shù)中的一個(gè)提供一個(gè)對(duì)象指針。前一章描述了聲明只有一個(gè)參數(shù)的方法的語(yǔ)法:

- (void)someMethodWithValue:(SomeType)value;

因此聲明一個(gè)帶有字符串對(duì)象的方法的語(yǔ)法,看起來(lái)是像這樣的:

- (void)saySomething:(NSString *)greeting;

你可以像這樣實(shí)現(xiàn) saySomething: 的方法:

- (void)saySomething:(NSString *)greeting {  
    NSLog(@"%@", greeting);  
}

指針 greeting 表現(xiàn)得像一個(gè)局部變量并且被限制在 saySomething: 的作用域中。盡管它所指的真實(shí)的字符串對(duì)象先于方法調(diào)用存在,并且將在方法完成后繼續(xù)存在。

注解NSLog()使用說(shuō)明符來(lái)指示替代單元。就像C標(biāo)準(zhǔn)函數(shù)庫(kù)中的 printf()函數(shù)。記錄到控制臺(tái)的字符串是通過(guò)插入提供的值(剩余的參數(shù))來(lái)修改格式字符串(第一個(gè)參數(shù))的結(jié)果。

在 Objective-C 中有一個(gè)額外的可替代的單元,%@,用來(lái)指示一個(gè)對(duì)象。在運(yùn)行時(shí),這個(gè)說(shuō)明符將隨著調(diào)用 descriptionWithLocale:方法(如果它存在)或者提供的對(duì)象上的 description 方法中的一個(gè)而被替換。description 方法由 NSObject 實(shí)現(xiàn)用來(lái)傳回類(lèi)和對(duì)象的存儲(chǔ)空間,但是許多 Cocoa 和 Cocoa Touch 的類(lèi)將它重寫(xiě)來(lái)提供更多有用的消息。在 NSString 的例子中,description 方法簡(jiǎn)單地返回了它所代表的字符串字符。

想要獲取更多有關(guān) NSLog()NSString 類(lèi)的說(shuō)明符使用的消息,可參見(jiàn) String Format Specifiers

方法可以返回值

就像通過(guò)方法參數(shù)傳遞值,方法直接返回值是有可能的。在這章中到現(xiàn)在每一個(gè)展示出的方法都有一個(gè)返回值類(lèi)型 void。這個(gè)C語(yǔ)言的關(guān)鍵詞 void 意味著一個(gè)方法不返回任何東西。

規(guī)定返回值類(lèi)型為 int 意味著方法返回一個(gè)標(biāo)量整形數(shù)值:

- (int)magicNumber;

方法的實(shí)現(xiàn)使用C語(yǔ)言中的 return 聲明來(lái)指示在方法在結(jié)束執(zhí)行之后被傳回的值,像這樣:

- (int)magicNumber {  
    return 42;
}

忽略一個(gè)方法會(huì)返回值的事實(shí)是完全可以接受的。在這個(gè)例子中 magicNumber 方法除了返回一個(gè)值不做其他任何有用的事,但是像這樣調(diào)用方法沒(méi)有任何錯(cuò)誤:

    int interestingNumber = [someObject magicNumber];

你可以用相同的方法從方法中返回對(duì)象。舉個(gè)例子,NSString 類(lèi),提供了一個(gè) uppercaseString 方法:

- (NSString *)uppercaseString;

當(dāng)一個(gè)方法返回一個(gè)純數(shù)值時(shí)做法相同,盡管你需要使用一個(gè)指針來(lái)追蹤結(jié)果:

    NSString *testString = @"Hello, world!";  
    NSString *revisedString = [testString uppercaseString];

當(dāng)這個(gè)方法調(diào)用返回時(shí),revisedString 將會(huì)指向一個(gè)代表 Hello,world!NSString 對(duì)象。

記住當(dāng)實(shí)現(xiàn)一個(gè)方法來(lái)返回一個(gè)對(duì)象時(shí),像這樣:

- (NSString *)magicString {  
NSString *stringToReturn = // create an interesting string...
    return stringToReturn;
}

盡管指針 stringToReturn 出了作用域,字符串對(duì)象繼續(xù)存在當(dāng)它被作為一個(gè)返回值被傳遞時(shí)。

在這種狀況下有很多存儲(chǔ)空間管理的考慮:一個(gè)返回了的對(duì)象(在堆中被創(chuàng)建)需要存在足夠長(zhǎng)的時(shí)間使它被方法的原有調(diào)用者使用,但不是永久存在,這是因?yàn)槟菢訒?huì)導(dǎo)致內(nèi)存泄露。在很大程度上,具有自動(dòng)引用計(jì)數(shù)(ARC)特征的 Objectiive-C 編譯程序會(huì)為你照顧到這些需要考慮的地方的。

對(duì)象能給他們自己發(fā)送消息

無(wú)論何時(shí)你正在寫(xiě)一個(gè)方法的實(shí)現(xiàn),你可以獲得一個(gè)重要的隱藏值, self。概念上,self 是一個(gè)指向"已經(jīng)接收到這個(gè)消息的對(duì)象"的方法。它是一個(gè)指針,就像上文的值 greeting,可以被用來(lái)在現(xiàn)在接收對(duì)象上調(diào)用方法。

你也許決定通過(guò)修改 sayHello 方法來(lái)使用上文的 saySomething:方法來(lái)重構(gòu) XYZPerson 的實(shí)現(xiàn),因此將 NSLog() 的調(diào)用移動(dòng)到不同的方法中。這將意味著你能進(jìn)一步增加方法,像 sayGoodbye ,那將每次聯(lián)系 saySomething: 方法來(lái)解決真實(shí)的問(wèn)候過(guò)程。如果你稍后想將每一個(gè)問(wèn)候在用戶接口中的每個(gè)文本框中展示出來(lái),你只需要修改 saySomething: 方法而不是仔細(xì)檢查并單獨(dú)地調(diào)整每一個(gè)問(wèn)候方法。

新的在當(dāng)前對(duì)象上使用 self 來(lái)調(diào)用消息的實(shí)現(xiàn)將會(huì)是這樣的:

@implementation XYZPerson  
- (void)sayHello {  
     [self saySomething:@"Hello, world!"];
}
- (void)saySomething:(NSString *)greeting {  
    NSLog(@"%@", greeting);  
}
@end

如果你用這種更新過(guò)的實(shí)現(xiàn)把消息 sayHello 發(fā)送給對(duì)象 XYZPerson,實(shí)際的程序流程將會(huì)在圖2-2中顯示。

圖 2-2 和自己通信時(shí)的程序流程

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/programflow2.png" alt="2-2" />

對(duì)象可以調(diào)用被它們的超類(lèi)實(shí)現(xiàn)的方法

在 Objective-C 中對(duì)你來(lái)說(shuō)有另外一個(gè)重要的關(guān)鍵詞,叫作 super。發(fā)送一條消息給 super 是調(diào)用一個(gè)被進(jìn)一步完善繼承鏈的超類(lèi)所定義的方法的途徑。super 最普遍的用法是重寫(xiě)一個(gè)方法的時(shí)候。

比方說(shuō)你想要?jiǎng)?chuàng)建一個(gè)新類(lèi)型的 person 類(lèi),“shouting person”類(lèi),它每一句問(wèn)候都用大寫(xiě)字母展示出來(lái)。你可以復(fù)制整個(gè) XYZPerson 類(lèi)并修改每個(gè)方法中的每個(gè)字符串使它們是大寫(xiě)的。但是最簡(jiǎn)單的方法是創(chuàng)建一個(gè)新的繼承自 XYZPerson 的類(lèi),只要重寫(xiě) saySomething: 方法這樣它就會(huì)以大寫(xiě)的形式展現(xiàn)出來(lái),像這樣:

@interface XYZShoutingPerson : XYZPerson
@end  
@implementation XYZShoutingPerson  
- (void)saySomething:(NSString *)greeting {  
    NSString *uppercaseGreeting = [greeting uppercaseString];  
    NSLog(@"%@", uppercaseGreeting);
}
@end

這個(gè)例子聲明了一個(gè)額外的字符串指針,uppercaseGreeting,并且將發(fā)給初始對(duì)象 greeting 的消息 uppercaseString message返回的值賦給它。正如你早一些所見(jiàn)到的,這將成為一個(gè)將原始字符串中的每個(gè)字符轉(zhuǎn)換為大寫(xiě)新的字符串對(duì)象。

因?yàn)?sayHelloXYZPerson 實(shí)現(xiàn),而 XYZShoutingPerson 是用來(lái)繼承 XYZPerson 的,你也可以在 XYZShoutingPerson對(duì)象上調(diào)用 sayHello 對(duì)象。當(dāng)你在 XYZShoutingPerson對(duì)象上調(diào)用 sayHello 對(duì)象時(shí),[self saySomething:...]的調(diào)用將使用重寫(xiě)過(guò)的實(shí)現(xiàn)并且將問(wèn)候顯示為大寫(xiě),實(shí)際的程序流程圖在圖2-3中顯示。

圖 2-3 對(duì)于一個(gè)覆寫(xiě)方法的程序流程

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/programflow3.png" alt="2-3" />

新的實(shí)現(xiàn)并不是理想的,是因?yàn)槿绻愦_實(shí)稍后決定修改 saySomethingXYZPerson 實(shí)現(xiàn),用用戶接口元素來(lái)展示問(wèn)候而不是通過(guò) NSLog(),你也將需要修改 XYZShoutingPerson 的實(shí)現(xiàn)。

一個(gè)更好的想法將會(huì)是改變 saySomethingXYZShoutingPerson 版本來(lái)調(diào)用超類(lèi)(XYZPerson)實(shí)現(xiàn)來(lái)處理實(shí)際的問(wèn)候:

@implementation XYZShoutingPerson
- (void)saySomething:(NSString *)greeting {  
    NSString *uppercaseGreeting = [greeting uppercaseString];
    [super saySomething:uppercaseGreeting];
}
@end

由于給對(duì)象 XYZShoutingPerson 發(fā)送消息 sayHello 而來(lái)的實(shí)際的程序流程如圖2-4所示。

圖 2-4 和超類(lèi)通信時(shí)的程序流程

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/programflow4.png" alt="2-4" />

對(duì)象是被動(dòng)態(tài)創(chuàng)建的

正如在這章早些時(shí)候描述的,給 Objective-C 對(duì)象的存儲(chǔ)空間的分配是動(dòng)態(tài)的。創(chuàng)建一個(gè)對(duì)象的第一步是確認(rèn)有足夠的存儲(chǔ)空間,不僅是對(duì)被一個(gè)對(duì)象的類(lèi)所定義的屬性來(lái)說(shuō),也要滿足在它的繼承鏈中在每一個(gè)超類(lèi)上所定義的屬性。

根類(lèi) NSObject 提供了一個(gè)類(lèi)方法,alloc,為你處理這一過(guò)程:

+ (id)alloc;

注意到這個(gè)方法的返回值類(lèi)型為 id。這在 Objective-C 中是一個(gè)特殊的關(guān)鍵詞,表示“一些類(lèi)型的對(duì)象”。它是一個(gè)對(duì)象的指針,就像(NSObject *),但是又是特別的因?yàn)樗皇褂眯翘?hào)。它在這章的稍后,Objective-C 是一種動(dòng)態(tài)語(yǔ)言中會(huì)被更仔細(xì)地描述。

alloc方法有另外一個(gè)重要的任務(wù),就是通過(guò)將存儲(chǔ)空間設(shè)為零來(lái)清空為對(duì)象的屬性而分配的存儲(chǔ)空間。這避免了一個(gè)尋常的問(wèn)題,即存儲(chǔ)空間含有之前曾存儲(chǔ)的垃圾。但是這不足以完全初始化一個(gè)對(duì)象。

你需要將對(duì) alloc 的調(diào)用和對(duì)另一個(gè) NSObject 的方法 init 的調(diào)用結(jié)合起來(lái):

- (id)init;

init方法被類(lèi)使用以來(lái)確認(rèn)它的屬性在創(chuàng)建時(shí)有合適的初始值,它將在下一章被詳細(xì)介紹。

注意到 init 也返回 id。

如果一個(gè)方法返回一個(gè)對(duì)象指針,將那個(gè)方法的調(diào)用作為接收者嵌套進(jìn)另一個(gè)方法的調(diào)用時(shí)有可能的,由此在一個(gè)聲明中結(jié)合了多個(gè)消息的調(diào)用。正確分配和初始化一個(gè)對(duì)象的方法是在對(duì) init 的調(diào)用中嵌套對(duì) init 的調(diào)用,像這樣:

    NSObject *newObject = [[NSObject alloc] init];

這個(gè)例子設(shè)置了 newObject 對(duì)象來(lái)指向一個(gè)新被創(chuàng)建的 NSObject 實(shí)例。

最內(nèi)部的調(diào)用第一個(gè)被實(shí)現(xiàn),所以 NSObject 類(lèi)被送到返回一個(gè)新被分配的 NSObject 實(shí)例的 alloc 方法。這個(gè)返回的對(duì)象之后被作為 init 消息的接收者被使用,它自己返回對(duì)象并賦給 newObject 指針,正如在圖2-5中顯示的那樣。

圖 2-5 嵌套 allocinit 消息

http://wiki.jikexueyuan.com/project/programming-with-objective-c/images/nestedallocinit.png" alt="2-5" />

注解init 返回一個(gè)由 alloc 創(chuàng)建的不同的對(duì)象是有可能的,所以正如展示的那樣,嵌套調(diào)用是最好的嘗試。

永遠(yuǎn)不要在沒(méi)有將任何指針賦給對(duì)象的情況下初始化一個(gè)對(duì)象。作為一個(gè)例子,不要這樣做:

    NSObject *someObject = [NSObject alloc];
    [someObject init];

如果對(duì) init 的調(diào)用返回了一些其他的對(duì)象,你將留下一個(gè)初始被分配過(guò)但從沒(méi)有初始化的對(duì)象的指針。

初始化方法可以攜帶參數(shù)

一些對(duì)象需要用需要的值來(lái)初始化。一個(gè) NSNumber 對(duì)象,舉例來(lái)說(shuō),必須用它需要代表的數(shù)值來(lái)創(chuàng)建。

NSNumber類(lèi)定義了幾種初始化方法,包括:

- (id)initWithBool:(BOOL)value; 
- (id)initWithFloat:(float)value;  
- (id)initWithInt:(int)value;  
- (id)initWithLong:(long)value;

帶有參數(shù)的初始化方法的調(diào)用和普通的 init 方法是一樣的———一個(gè) NSNumber 對(duì)象像這樣被分配和初始化:

    NSNumber *magicNumber = [[NSNumber alloc] initWithInt:42];

類(lèi)工廠方法是分配和初始化的一個(gè)選擇

正如在前邊的章節(jié)中所提到的,一個(gè)類(lèi)也可以定義工廠方法。工廠方法提供了傳統(tǒng)的 alloc]init] 過(guò)程的不許嵌套兩個(gè)方法的選擇。

NSNumber 類(lèi)定義了幾個(gè)類(lèi)工廠方法來(lái)匹配它的初始化方法,包括:

+ (NSNumber *)numberWithBool:(BOOL)value;  
+ (NSNumber *)numberWithFloat:(float)value;  
+ (NSNumber *)numberWithInt:(int)value;  
+ (NSNumber *)numberWithLong:(long)value;  

一個(gè)工廠方法像這樣被使用:

    NSNumber *magicNumber = [NSNumber numberWithInt:42];

這實(shí)際上和之前使用 alloc] initWithInt:] 的例子相同。類(lèi)工廠方法通常指直接調(diào)用 alloc 和相關(guān)的 init 方法,它為方便使用而被提供。

如果初始化不需要參數(shù)那么使用 new 來(lái)創(chuàng)建一個(gè)對(duì)象

創(chuàng)建一個(gè)類(lèi)的實(shí)例時(shí)使用類(lèi)方法 new 是有可能的。這個(gè)方法由 NSObject 提供并且在你自己的超類(lèi)中不需要被覆寫(xiě)。

這實(shí)際上和調(diào)用沒(méi)有參數(shù)的 allocinit 是一樣的:

    XYZObject *object = [XYZObject new];  
    // is effectively the same as:  
    XYZObject *object = [[XYZObject alloc] init];  

文字提供了一個(gè)簡(jiǎn)潔的對(duì)象——?jiǎng)?chuàng)建語(yǔ)法

一些類(lèi)允許你使用簡(jiǎn)潔的,文字的語(yǔ)法來(lái)創(chuàng)建實(shí)例。

你可以創(chuàng)建一個(gè) NSString 實(shí)例,舉例來(lái)說(shuō),使用一個(gè)特殊的文字記號(hào),像這樣:

    NSString *someString = @"Hello, World!";

這實(shí)際上和分配,初始化一個(gè) NSString 或者使用它的類(lèi)工廠方法中的一個(gè)相同:

    NSString *someString = [NSString stringWithCString:"Hello, World!"
                                              encoding:NSUTF8StringEncoding];

NSNumber類(lèi)也允許各種各樣的文字:

    NSNumber *myBOOL = @YES;  
    NSNumber *myFloat = @3.14f;  
    NSNumber *myInt = @42;  
    NSNumber *myLong = @42L;  

此外,這些例子中的每一個(gè)實(shí)際上和使用相關(guān)的初始化方法或者一個(gè)類(lèi)工廠方法相同。

你也可以使用一個(gè)框表達(dá)式來(lái)創(chuàng)建一個(gè) NSNumber,像這樣:

    NSNumber *myInt = @(84 / 2);

在這種情況中,表達(dá)式被用數(shù)值表示,而且一個(gè) NSNumber 實(shí)例伴隨著結(jié)果被創(chuàng)建。

Objective-C 也支持文字來(lái)創(chuàng)建不可變的 NSArrayNSDictionary 對(duì)象;這些將在 Values and Collections 中進(jìn)一步討論。

Objective-C 是一種動(dòng)態(tài)語(yǔ)言

正如之前所提到的,你需要使用一個(gè)指針來(lái)追蹤存儲(chǔ)空間中的一個(gè)對(duì)象。因?yàn)?Objective-C 的動(dòng)態(tài)特征,你為那個(gè)指針使用什么特定的類(lèi)型都沒(méi)有關(guān)系——當(dāng)你給它發(fā)送消息時(shí),正確的方法將總是在相關(guān)的對(duì)象上被調(diào)用。

id型定義了一個(gè)通用的對(duì)象指針。當(dāng)聲明一個(gè)變量時(shí)使用 id 是有可能的,但你會(huì)失去關(guān)于對(duì)象編譯時(shí)的信息。

考慮以下的代碼:

    id someObject = @"Hello, World!";  
    [someObject removeAllObjects];

在這種情況下,someObject 將指向一個(gè) NSString 實(shí)例,但是編譯器不知道任何事,而事實(shí)上它是對(duì)象的某一類(lèi)型。消息 removeAllObjects 由一些 Cocoa 或者 Cocoa Touch 對(duì)象(例如 NsMutableArray)定義所以編譯器不會(huì)抱怨,盡管這個(gè)代碼因?yàn)橐粋€(gè) NSString 對(duì)象不能響應(yīng) removeAllObjects 而會(huì)在運(yùn)行時(shí)會(huì)生成一個(gè)異常。

使用一個(gè)靜態(tài)類(lèi)型來(lái)重寫(xiě)編寫(xiě)代碼:

    NSString *someObject = @"Hello, World!";  
    [someObject removeAllObjects];

意味著編譯器將會(huì)由于 removeAllObjects 沒(méi)有在任何它所知道的公共的 NSString 接口中被聲明而生成一個(gè)錯(cuò)誤。

因?yàn)閷?duì)象的類(lèi)是在運(yùn)行時(shí)被決定的,當(dāng)創(chuàng)建或者使用一個(gè)實(shí)例時(shí)你無(wú)論給變量指定什么類(lèi)型都沒(méi)有差別。要使用這章早些時(shí)候描述的 XYZPersonXYZShoutingPerson 類(lèi),你可能要使用下面的代碼:

    XYZPerson *firstPerson = [[XYZPerson alloc] init];  
    XYZPerson *secondPerson = [[XYZShoutingPerson alloc] init];  
    [firstPerson sayHello];  
    [secondPerson sayHello];  

盡管 firstPersonsecondPerson 作為 XYZPerson 的對(duì)象都是靜態(tài)類(lèi)型的,secondPerson 在運(yùn)行時(shí)將會(huì)指向一個(gè) XYZShoutingPerson 對(duì)象。當(dāng) sayHello 方法在每一個(gè)對(duì)象上被調(diào)用時(shí),正確的實(shí)現(xiàn)將會(huì)被使用;對(duì)于 secondPerson,這意味著 XYZShoutingPerson 的版本。

確定對(duì)象相等

如果你需要確定一個(gè)對(duì)象是否和另一個(gè)對(duì)象相同,記住你在使用指針是重要的。

標(biāo)準(zhǔn)的C語(yǔ)言等號(hào)運(yùn)算符 == 被用來(lái)檢測(cè)兩個(gè)變量的值是否相同,像這樣:

    if (someInteger == 42) {  
        // someInteger has the value 42  
    }

當(dāng)處理對(duì)象的時(shí)候,運(yùn)算符 == 被用來(lái)檢測(cè)兩個(gè)單獨(dú)地指針是否指向一個(gè)相同的對(duì)象:

    if (firstPerson == secondPerson) {
        // firstPerson is the same object as secondPerson
    }

如果你需要檢測(cè)兩個(gè)對(duì)象是否代表相同的數(shù)據(jù),你需要調(diào)用一個(gè)像 isEqual: 的方法,從 NSObject 可以獲得:

    if ([firstPerson isEqual:secondPerson]) {
        // firstPerson is identical to secondPerson
    }

如果你需要比較一個(gè)對(duì)象是否比另一個(gè)對(duì)象代表了更大或更小的值,你不能使用標(biāo)準(zhǔn)C語(yǔ)言的比較運(yùn)算符 > 和 <。相反,像 NSNumber,NSStringNSDate 這樣的基本類(lèi)型,提供了一個(gè)方法 compare:

    if ([someDate compare:anotherDate] == NSOrderedAscending) {
        // someDate is earlier than anotherDate
    }

使用 nil

在你聲明它們的時(shí)候初始化純量變量總是一個(gè)好主意,否則它們的初始值將包含前一個(gè)堆棧的垃圾內(nèi)容:

    BOOL success = NO;
    int magicNumber = 42;

這對(duì)對(duì)象指針來(lái)說(shuō)不是必要的,因?yàn)榫幾g器將自動(dòng)把變量設(shè)為 nil 如果你不規(guī)定任何其他的初始值:

    XYZPerson *somePerson;
    // somePerson is automatically set to nil

nil 的值對(duì)初始化一個(gè)對(duì)象指針來(lái)說(shuō)是最安全的,如果你沒(méi)有另一個(gè)值來(lái)使用的話。因?yàn)樵?Objective-C 中發(fā)送消息給 nil 是完全可以接受的。如果你確實(shí)給 nil 發(fā)送了消息,顯然什么都不會(huì)發(fā)生。

注解:如果你期待從發(fā)送給 nil 的消息中獲得一個(gè)返回值,返回值將會(huì)是對(duì)象返回類(lèi)型的 nil 型,數(shù)字類(lèi)型的 0, BOOL 類(lèi)型的 NO。返回的結(jié)構(gòu)擁有所有初始化為 0 的成員。

如果你需要檢查確認(rèn)一個(gè)對(duì)象不是 nil(一個(gè)變量指向存儲(chǔ)空間中的一個(gè)對(duì)象),你可以使用標(biāo)準(zhǔn)C語(yǔ)言中的不等運(yùn)算符:

    if (somePerson != nil) {
        // somePerson points to an object
    }

或者簡(jiǎn)單地提供變量:

    if (somePerson) {
        // somePerson points to an object
    }

如果 somePerson 變量是 nil,它的邏輯值是 0(false)。如果它有地址,它就不是零,所以被認(rèn)為是 true。

相同的,如果你需要檢查一個(gè) nil 變量,你可以使用相等運(yùn)算符:

    if (somePerson == nil) {
        // somePerson does not point to an object
    }

或只是使用 C 語(yǔ)言邏輯非操作符:

    if (!somePerson) {
        // somePerson does not point to an object
    }

練習(xí)

1.在你的項(xiàng)目里從上一章最后的練習(xí)中打開(kāi) main.m 文件并且找到 main() 函數(shù)。作為對(duì)于任何用 C 語(yǔ)言寫(xiě)的可執(zhí)行的程序,這個(gè)函數(shù)代表了你應(yīng)用的起點(diǎn)。

使用 allocinit 創(chuàng)建一個(gè)新的 XYZPerson 實(shí)例,然后調(diào)用 sayHello 方法。

注解:如果編譯器沒(méi)有自動(dòng)提示你,你將需要在 main.m 的頂部導(dǎo)入頭文件(包含 XYZPerson 接口)

2.實(shí)現(xiàn)在這一章早些時(shí)候展示的方法 saySomething:,并且重新編寫(xiě)方法 sayHello 來(lái)使用它。添加各種其他的問(wèn)候并且在你之前創(chuàng)建的每一個(gè)實(shí)例上調(diào)用它們。

3.為 XYZShoutingPerson 類(lèi)創(chuàng)建新的類(lèi)文件,設(shè)置成繼承自 XYZPerson。

覆寫(xiě) saySomething: 方法來(lái)展示大寫(xiě)的問(wèn)候,并且測(cè)試在 XYZShoutingPerson 實(shí)例上的行為。

4.實(shí)現(xiàn)在前一章你聲明的 XYZPerson 類(lèi) person 工廠方法,來(lái)返回一個(gè)被正確分配和初始化的 XYZPerson 類(lèi)的實(shí)例,然后在 main() 中使用方法來(lái)代替嵌套的 allocinit

提示:嘗試使用 [[self alloc] init] 而不是使用 [[XYZPerson alloc] init]。

在一個(gè)類(lèi)工廠方法中使用 self 意味著你在指向類(lèi)本身。

這意味著你不必在 XYZShoutingPerson 實(shí)現(xiàn)中覆寫(xiě) person 方法來(lái)創(chuàng)建正確的實(shí)例。通過(guò)檢查以下代碼來(lái)測(cè)試這個(gè):

    XYZShoutingPerson *shoutingPerson = [XYZShoutingPerson person];

創(chuàng)建正確類(lèi)型的對(duì)象。

5.創(chuàng)建一個(gè)新的局部的 XYZPerson 指針,但是不包括任何其他賦值。 使用一個(gè)轉(zhuǎn)移指令(if 聲明)來(lái)檢查變量是否自動(dòng)被賦為 nil。