Objective-C 2.0 with Cocoa Foundation--- 5,Class類型,選擇器Selector以及函數指針

原文鏈接:http://www.cnblogs.com/yaski/


5,Class類型,選擇器Selector以及指針函數 

本系列講座有着很強的前後相關性,如果你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏 

上一章筆者介紹了在Objective-C裏面繼承的概念。有了繼承的知識我們可以重複的使用很多以前生效的代碼,這樣就大大的提高了代碼開發的效率。在本章,筆者要向同學們介紹幾個非常重要的概念,Class類型, 選擇器Selector以及指針函數。 

我們在實際上的編程過程中,也許會遇到這樣的場景,那就是我們在寫程序的時候不能確切的知道我們需要使用什麼類,使用這個類的什麼方法。在這個時候,我們需要在我們的程序裏面動態的根據用戶的輸入來創建我們在寫程序不知道的類的對象,並且調用這個對象的實例方法。Objective-C爲我們提供了Class類型, 選擇器Selector以及指針函數來實現這樣的需求,從而大大的提高了我們程序的動態性能。

在Objective-C裏面,一個類被正確的編譯過後,在這個編譯成功的類裏面,存在一個變量用於保存這個類的信息。我們可以通過一個普通的字符串取得這個Class,也可以通過我們生成的對象取得這個Class。Class被成功取得之後,我們可以把這個Class當作一個已經定義好的類來使用它。

Selector和Class比較類似,不同的地方是Selector用於表示方法。 在Objective-C的程序進行編譯的時候,會根據方法的名字(包括參數列表)確定一個唯一的身份證明(實際上就是一個整數),不用的類裏面的相同名字相同聲明的方法的身份證明是一樣的。這樣在程序執行的時候,runtime就不用費力的進行方法的名字比較來確定是執行哪一個方法了,只是通過一個整數的尋找就可以馬上定位到相應的方法,然後找到相應的方法的入口地址,這樣方法就可以被執行了。

筆者在前面的章節裏面敘述過,在Objective-C裏面消息也就是方法的執行比C語言的直接找到函數入口地址執行的方式,從效率上來講是比較低下的。儘管Objective-C使用了Selector等招數來提高尋找效率,但是無論如何尋找的過程,都是要消耗一定的時間的。好在Objective-C是完全兼容C的,它也有指針函數的概念。當我們需要執行效率的時候,比如說在一個很大的循環當中需要執行某個功能的時候,我們可以放棄向對某一個對象發送消息的手段,用指針函數取而代之,這樣就可以獲得和C語言一樣的執行效率了。

說到這裏,可能有的同學已經有些茫然了。這些概念有些令人難以理解,但是它們確實是Objective-C的核心的功能。掌握了這些核心的功能之後,同學們可以很輕鬆的看懂蘋果的SDK裏面的很多東西含義,甚至可以自己動手寫一些蘋果沒有爲我們提供的功能。所以建議大家仔細研讀本章的內容,如果有什麼問題,可以發個帖子大家可以共同探討。

從筆者的觀點上來看,對於有Java或者C++或者其他面向對象的語言的經驗的同學來說,前面的從第1到第4章的內容也許有些平淡無奇。從第5章開始,我們將要逐漸的深入到Objective-C的核心部分。筆者的最終目的,雖然是向大家介紹iPhone開發的入門,但是筆者認爲了解了Objective-C的基本概念以及使用方法之後,熟悉iPhone的應用程序的開發將是一件水到渠成的輕鬆的事情。否則如果你直接就深入到iPhone的開發的話,在絕大多數時間你也許因爲一個小小的問題就會困擾你幾個小時甚至幾天,解決這些問題的唯一方法就是熟悉Objective-C和Cocoa Foundation的特性。

好了,說了很多我們從下面就要開始,我們的手法和前面幾章是一樣的,我們首先要介紹一下本章程序的執行結果。

5.1,本章程序的執行結果

 圖5-1,第5章程序的執行結果

在本章裏面,我們將要繼續使用我們在前面幾章已經構築好的類Cattle和Bull。爲了靈活的使用Cattle和Bull,我們將要構築一個新的類,DoProxy。在DoProxy裏面,我們將會引入幾個我們的新朋友,他們分別是BOOL,SEL,IMP,CLASS。通過這些新的朋友我們可以動態的通過設定文件取得Cattle和Bull的類,還有方法以及方法指針。下面將要介紹如何構築本章程序。同學們可以按照本章所述的步驟來構築,也可以通過從這裏下載。不過爲了熟悉代碼的寫作,筆者強烈建議大家按照筆者所述的步驟來操作。

5.2,實現步驟

第一步,按照我們在第2章所述的方法,新建一個項目,項目的名字叫做05-Hello Selector。如果你是第一次看本篇文章,請到這裏參看第二章的內容。

第二步,按照我們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的“Cattle.h”,“Cattle.m”,“Bull.h”還有“Bull.m” 導入本章的項目裏面。如果你沒有第4章的代碼,請到這裏下載。如果你沒有閱讀第4章的內容,請參看這裏

第三步,把鼠標移動到項目瀏覽器上面的“Source”上面,然後在彈出的菜單上面選擇“Add”,然後在子菜單裏面選擇“New Files”,然後在新建文件對話框的左側選擇“Cocoa Touch Classes”,然後在右側窗口選擇“NSObject subclass”,選擇“Next”,在“New File”對話框裏面的“File Name”欄內輸入“DoProxy.m”。在這裏筆者沒有給出圖例,在這裏新建文件的步驟和第3章的第二步到第四步相同,只是文件名字不一樣。第一次看到本篇 文章的同學可以參照第3章

第四步,打開“DoProxy.h”做出如下修改並且保存

#import <Foundation/Foundation.h>

#define SET_SKIN_COLOR @"setSkinColor:"
#define BULL_CLASS @"Bull"
#define CATTLE_CLASS @"Cattle"

@interface DoProxy : NSObject {
    BOOL notFirstRun;
    id cattle[
3];
    SEL say;
    SEL skin;
    
void(*setSkinColor_Func) (id, SEL, NSString*);
    IMP say_Func;
    Class bullClass;
}
- (void) doWithCattleId:(id) aCattle colorParam:(NSString*) color;
- (void) setAllIVars;
- (void) SELFuncs;
- (void) functionPointers;
@end

第五步,打開“DoProxy.m”做出如下修改並且保存

#import "DoProxy.h"
#import 
"Cattle.h"
#import 
"Bull.h"

@implementation DoProxy
- (void) setAllIVars
{
    cattle[
0= [Cattle new];
    
    bullClass 
= NSClassFromString(BULL_CLASS);
    cattle[
1= [bullClass new];
    cattle[
2= [bullClass new];
    
    say 
= @selector(saySomething);
    skin 
= NSSelectorFromString(SET_SKIN_COLOR);
}
- (void) SELFuncs
{
    [self doWithCattleId:cattle[
0] colorParam:@"brown"];
    [self doWithCattleId:cattle[
1] colorParam:@"red"];
    [self doWithCattleId:cattle[
2] colorParam:@"black"];
    [self doWithCattleId:self colorParam:
@"haha"];
}
- (void) functionPointers
{
    setSkinColor_Func
=(void (*)(id, SEL, NSString*)) [cattle[1] methodForSelector:skin];
    
//IMP setSkinColor_Func = [cattle[1] methodForSelector:skin];
    say_Func = [cattle[1] methodForSelector:say];
    setSkinColor_Func(cattle[
1],skin,@"verbose");
    NSLog(
@"Running as a function pointer will be more efficiency!");
    say_Func(cattle[
1],say); 
}
- (void) doWithCattleId:(id) aCattle colorParam:(NSString*) color
{
    
if(notFirstRun == NO)
    {
        NSString 
*myName = NSStringFromSelector(_cmd);
        NSLog(
@"Running in the method of %@", myName);
        notFirstRun 
= YES;
    }
    
    NSString 
*cattleParamClassName = [aCattle className];
    
if([cattleParamClassName isEqualToString:BULL_CLASS] || 
       [cattleParamClassName isEqualToString:CATTLE_CLASS])
    {
        [aCattle setLegsCount:
4];
        
if([aCattle respondsToSelector:skin])
        {
            [aCattle performSelector:skin withObject:color];
        }
        
else
        {
            NSLog(
@"Hi, I am a %@, have not setSkinColor!", cattleParamClassName);
        }
        [aCattle performSelector:say];
    }
    
else
    {
        NSString 
*yourClassName = [aCattle className];
        NSLog(
@"Hi, you are a %@, but I like cattle or bull!", yourClassName);
    }
}
@end

第六步,打開“05-Hello Selector.m” 作出如下修改並且保存

#import <Foundation/Foundation.h>
#import 
"DoProxy.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool 
* pool = [[NSAutoreleasePool alloc] init];
    DoProxy 
*doProxy = [DoProxy new];
    
    [doProxy setAllIVars];
    [doProxy SELFuncs];
    [doProxy functionPointers];
    
    [pool drain];
    
return 0;
}

第七步,選擇屏幕上方菜單裏面的“Run”,然後選擇“Console”,打開了Console對話框之後,選擇對話框上部中央的“Build and Go”,如果不出什麼意外的話,那麼應該出現入圖5-1所示的結果。如果出現了什麼意外導致錯誤的話,那麼請仔細檢查一下你的代碼。如果經過仔細檢查發現 還是不能執行的話,可以到這裏下載筆者爲同學們準備的代碼。 如果筆者的代碼還是不能執行的話,請告知筆者。

5.3,BOOL類型

我們現在打開“DoProxy.h”文件。“DoProxy.h”文件的第3行到第5行是三個預定義的三個字符串的宏。我們將在程序當中使用這3個宏,爲了實現代碼的獨立性,在實際的程序開發當中,我們也許考慮使用一個配置的文本文件或者一個XML來替代這些宏。但是現在由於筆者的主要目的是講解Objective-C的概念,爲了避免較多的代碼給大家帶來理解主題的困難,所以筆者沒有使用配置文件或者XML來表述這些可以設定的常量。

“DoProxy.h”的第7行對同學們來說也是老朋友了,是通知編譯器,我們需要聲明一個DoProxy類,從NSObject繼承。

我們在第8行遇到了我們的一個新的朋友,BOOL:

    BOOL notFirstRun;

 

我們定義了一個notFirstRun的實例變量,這個變量是布爾類型的。我們的實例方法doWithCattleId需要被執行多次,我們在第一次執行doWithCattleId的時候需要向控制輸出包含doWithCattleId的方法名字的字符串,關於這個字符串的內容,請參考圖5-1。

好的,我們現在需要看看在Objective-C裏面BOOL是怎樣定義的,我們把鼠標移動到BOOL上面,然後單擊鼠標右鍵選擇彈出菜單的“Jump to Definition”,然後Xcode會打開objc.h文件,我們看到下面的代碼:

typedef signed char             BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#define OBJC_BOOL_DEFINED


#define YES             (BOOL)1
#define NO              (BOOL)0

我們看到這段代碼,我們可以這樣理解,在Objective-C裏面,BOOL其實是signed char,YES是1,NO是0。我們可以這樣給BOOL賦值:

BOOL x = YES;
BOOL y 
= NO;

 

關於BOOL,實際上就是一個開關的變量,但是我們需要注意下面2點:

第一點,從本質上來說BOOL是一個8bit的一個char,所以我們在把其他比如說short或者int轉換成爲BOOL的時候一定要注意。如果short或者int的最低的8位bit都是0的話,儘管除了最低的8位以外都不是0,那麼經過轉換之後,就變成了0也就是NO。比如說我們有一個int的值是0X1000,經過BOOL轉換之後就變成了NO。 

第二點,Objective-C裏面的所有的邏輯判斷例如if語句等等和C語言保持兼容,如果數值不是0判斷爲真,如果數值是0那麼就判斷爲假,並不是說定義了BOOL值之後就變成了只有1或者YES爲真。所以下面的代碼的判斷都爲真:

if(0X1000)
if(2)
if(-1)

5.4,SEL類型

讓我們接着看“DoProxy.h”文件的下列代碼:

1     id cattle[3];
2     SEL say;
3     SEL skin;

其中id cattle[3]定義了一個數組用於存儲Cattle或者Bull對象。這一行代碼估計大家都很熟悉,筆者就不贅述了。像這樣的傳統的數組並不能完全滿足我們的需求,當我們需要做諸如追加,刪除等操作的時候,會很不方便。在隨後的章節裏面筆者將要向大家介紹傳統數組的替代解決方案NSArray。

 

上一段代碼的第二行和第三行是本節所關注的,就是SEL類型。Objective-C在編譯的時候,會根據方法的名字(包括參數序列),生成一個用 來區分這個方法的唯一的一個ID,這個ID就是SEL類型的。我們需要注意的是,只要方法的名字(包括參數序列)相同,那麼它們的ID都是相同的。就是 說,不管是超類還是子類,不管是有沒有超類和子類的關係,只要名字相同那麼ID就是一樣的。除了函數名字和ID,編譯器當然還要把方法編譯成爲機器可以執 行的代碼,這樣,在一個編譯好的類裏面,就產生了如下圖所示方法的表格示意圖(本構造屬於筆者推測,沒有得到官方證實,所以圖5-2爲示意圖僅供參考,我們可以暫時認爲是這樣的)。

 

圖5-2,方法的表格示意圖 

請注意setSkinColor後面有一個冒號,因爲它是帶參數的。由於存在這樣的一個表格,所以在程序執行的時候,我們可以方便的通過方法的名字,獲取到方法的ID也就是我們所說的SEL,反之亦然。具體的使用方法如下:

1     SEL 變量名 = @selector(方法名字);
2     SEL 變量名 = NSSelectorFromString(方法名字的字符串);
3     NSString *變量名 = NSStringFromSelector(SEL參數);

其中第1行是直接在程序裏面寫上方法的名字,第2行是寫上方法名字的字符串,第3行是通過SEL變量獲得方法的名字。我們得到了SEL變量之後,可以通過下面的調用來給一個對象發送消息:

[對象 performSelector:SEL變量 withObject:參數1 withObject:參數2];

這樣的機制大大的增加了我們的程序的靈活性,我們可以通過給一個方法傳遞SEL參數,讓這個方法動態的執行某一個方法;我們也可以通過配置文件指定需要執行的方法,程序讀取配置文件之後把方法的字符串翻譯成爲SEL變量然後給相應的對象發送這個消息。

從效率的角度上來說,執行的時候不是通過方法名字而是方法ID也就是一個整數來查找方法,由於整數的查找和匹配比字符串要快得多,所以這樣可以在某種程度上提高執行的效率。 

5.5,函數指針

在講解函數指針之前,我們先參看一下圖5-2,函數指針的數值實際上就是圖5-2裏面的地址,有人把這個地址成爲函數的入口地址。在圖5-2裏面我們可以通過方法名字取得方法的ID,同樣我們也可以通過方法ID也就是SEL取得函數指針,從而在程序裏面直接獲得方法的執行地址。或者函數指針的方法有2種,第一種是傳統的C語言方式,請參看“DoProxy.h” 的下列代碼片斷:

1     void(*setSkinColor_Func) (id, SEL, NSString*);
2     IMP say_Func;

其中第1行我們定義了一個C語言裏面的函數指針,關於C語言裏面的函數指針的定義以及使用方法,請參考C語言的書籍和參考資料。在第一行當中,值得我們注意的是這個函數指針的參數序列:

第一個參數是id類型的,就是消息的接受對象,在執行的時候這個id實際上就是self,因爲我們將要向某個對象發送消息。

第二個參數是SEL,也是方法的ID。有的時候在消息發送的時候,我們需要使用用_cmd來獲取方法自己的SEL,也就是說,方法的定義體裏面,我們可以通過訪問_cmd得到這個方法自己的SEL。 

第三個參數是NSString*類型的,我們用它來傳遞skin color。在Objective-C的函數指針裏面,只有第一個id和第二個SEL是必需的,後面的參數有還是沒有,如果有那麼有多少個要取決於方法的聲明。 

現在我們來介紹一下Objective-C裏面取得函數指針的新的定義方法,IMP。

上面的代碼的第一行比較複雜,令人難以理解,Objective-C爲我們定義了一個新的數據類型就是在上面第二行代碼裏面出現的IMP。我們把鼠標移動到IMP上,單擊右鍵之後就可以看到IMP的定義,IMP的定義如下:

typedef id                      (*IMP)(id, SEL, );

這個格式正好和我們在第一行代碼裏面的函數指針的定義是一樣的。

我們取得了函數指針之後,也就意味着我們取得了執行的時候的這段方法的代碼的入口,這樣我們就可以像普通的C語言函數調用一樣使用這個函數指針。當然我們可以把函數指針作爲參數傳遞到其他的方法,或者實例變量裏面,從而獲得極大的動態性。我們獲得了動態性,但是付出的代價就是編譯器不知道我們要執行哪一個方法所以在編譯的時候不會替我們找出錯誤,我們只有執行的時候才知道,我們寫的函數指針是否是正確的。所以,在使用函數指針的時候要非常準確地把握能夠出現的所有可能,並且做出預防。尤其是當你在寫一個供他人調用的接口API的時候,這一點非常重要。

5.6,Class類型

到目前爲止,我們已經知道了對應於方法的SEL數據類型,和SEL同樣在Objective-C裏面我們不僅僅可以使用對應於方法的SEL,對於類在Objective-C也爲我們準備了類似的機制,Class類型。當一個類被正確的編譯過後,在這個編譯成功的類裏面,存在一個變量用於保存這個類的信息。我們可以通過一個普通的字符串取得 這個Class,也可以通過我們生成的對象取得這個Class。Class被成功取得之後,我們可以把這個Class當作一個已經定義好的類來使用它。這樣的機制允許我們在程序執行的過程當中,可以Class來得到對象的類,也可以在程序執行的階段動態的生成一個在編譯階段無法確定的一個對象。

因爲Class裏面保存了一個類的所有信息,當然,我們也可以取得一個類的超類。關於Class類型,具體的使用格式如下:

 

1     Class 變量名 = [類或者對象 class];
2     Class 變量名 = [類或者對象 superclass];
3     Class 變量名 = NSClassFromString(方法名字的字符串);
4     NSString *變量名 = NSStringFromClass(Class參數);

第一行代碼,是通過向一個類或者對象發送class消息來獲得這個類或者對象的Class變量。

第二行代碼,是通過向一個類或者對象發送superclass消息來獲得這個類或者對象的超類的Class變量。

第三行代碼,是通過調用NSClassFromString函數,並且把一個字符串作爲參數來取得Class變量。這個在我們使用配置文件決定執行的時候的類的時候,NSClassFromString給我們帶來了極大的方便。

第四行代碼,是NSClassFromString的反向函數NSStringFromClass,通過一個Class類型作爲變量取得一個類的名字。

當我們在程序裏面通過使用上面的第一,二或者第三行代碼成功的取得一個Class類型的變量,比如說我們把這個變量名字命名爲myClass,那麼我們在以後的代碼種可以把myClass當作一個我們已經定義好的類來使用,當然我們可以把這個變量作爲參數傳遞到其他的方法當中讓其他的方法動態的生成我們需要的對象。

 

 

5.7,DoProxy.h裏面的方法定義

DoProxy.h裏面還有一些實例方法,關於方法的定義的格式,同學們可以參照第三章。我們現在要對DoProxy.h裏面定義的方法的做一下簡要的說明。

1 - (void) doWithCattleId:(id) aCattle colorParam:(NSString*) color;
2 - (void) setAllIVars;
3 - (void) SELFuncs;
4 - (void) functionPointers;

第一行的方法,是設定aCattle,也就是Cattle或者Bull對象的屬性,然後調用saySomething方法,實現控制檯的打印輸出。

第二行的方法,是把我們定義的DoProxy類裏面的一些變量進行賦值。

第三行的方法,是調用doWithCattleId方法。

第四行的方法,是調用了函數指針的方法。

好的,我們把DoProxy.h的內容介紹完了,讓我們打開DoProxy.m。

5.8,DoProxy.m的代碼說明

有了DoProxy.h的說明,同學們理解DoProxy.m將是一件非常輕鬆的事情,讓我們堅持一下把這個輕鬆的事情搞定。由於篇幅所限,筆者在這裏的講解將會省略掉非本章的內容。

DoProxy.m代碼如下:

 1 #import "DoProxy.h"
 2 #import "Cattle.h"
 3 #import "Bull.h"
 4 
 5 @implementation DoProxy
 6 - (void) setAllIVars
 7 {
 8     cattle[0= [Cattle new];
 9     
10     bullClass = NSClassFromString(BULL_CLASS);
11     cattle[1= [bullClass new];
12     cattle[2= [bullClass new];
13     
14     say = @selector(saySomething);
15     skin = NSSelectorFromString(SET_SKIN_COLOR);
16 }
17 - (void) SELFuncs
18 {
19     [self doWithCattleId:cattle[0] colorParam:@"brown"];
20     [self doWithCattleId:cattle[1] colorParam:@"red"];
21     [self doWithCattleId:cattle[2] colorParam:@"black"];
22     [self doWithCattleId:self colorParam:@"haha"];
23 }
24 - (void) functionPointers
25 {
26     setSkinColor_Func=(void (*)(id, SEL, NSString*)) [cattle[1] methodForSelector:skin];
27     //IMP setSkinColor_Func = [cattle[1] methodForSelector:skin];
28     say_Func = [cattle[1] methodForSelector:say];
29     setSkinColor_Func(cattle[1],skin,@"verbose");
30     NSLog(@"Running as a function pointer will be more efficiency!");
31     say_Func(cattle[1],say); 
32 }
33 - (void) doWithCattleId:(id) aCattle colorParam:(NSString*) color
34 {
35     if(notFirstRun == NO)
36     {
37         NSString *myName = NSStringFromSelector(_cmd);
38         NSLog(@"Running in the method of %@", myName);
39         notFirstRun = YES;
40     }
41     
42     NSString *cattleParamClassName = [aCattle className];
43     if([cattleParamClassName isEqualToString:BULL_CLASS] || 
44        [cattleParamClassName isEqualToString:CATTLE_CLASS])
45     {
46         [aCattle setLegsCount:4];
47         if([aCattle respondsToSelector:skin])
48         {
49             [aCattle performSelector:skin withObject:color];
50         }
51         else
52         {
53             NSLog(@"Hi, I am a %@, have not setSkinColor!", cattleParamClassName);
54         }
55         [aCattle performSelector:say];
56     }
57     else
58     {
59         NSString *yourClassName = [aCattle className];
60         NSLog(@"Hi, you are a %@, but I like cattle or bull!", yourClassName);
61     }
62 }
63 @end

第10行代碼是通過一個預定義的宏BULL_CLASS取得Bull的Class變量。

第11和12行代碼是使用bullClass來初始化我們的cattle實例變量數組的第2和第3個元素。

第14行是通過@selector函數來取得saySomething的SEL變量。

第15行是通過向NSSelectorFromString傳遞預定義的宏SET_SKIN_COLOR來取得setSkinColor的SEL變量。

第22行,筆者打算“戲弄”一下doWithCattleId,向傳遞了不合適的參數。

第26行,筆者取得了傳統的C語言的函數指針,也是使用了第5.5節所述的第一種取得的方法。

第28行,筆者通過5.5節所述的第二種取得的方法得到了函數指針say_Func

第29行和31行分別執行了分別在第26行和28行取得的函數指針。

第35行是一個BOOL型的實例變量notFirstRun 。當對象被初始化之後,確省的值是NO。第一次執行完畢之後,我們把這個變量設定成爲YES,這樣就保證了花括號裏面的代碼只被執行一次。

第37行我們通過_cmd取得了doWithCattleId這個方法名字用於輸出。當然同學們在設計方法的提供給別人使用的時候,爲了防止使用方法的人把這個方法本身傳遞進來造成死循環,需要使用_cmd這個系統隱藏的變量判斷一下。筆者在這裏沒有做出判斷,這樣寫從理論上來說存在一定的風險。

第42行,我們通過向對象發送className消息來取得這個對象的類的名字。 

第43行和第44行,我們通過NSString的方法isEqualToString來判斷取得的類的名字是否在我們事先想象的範圍之內,我們只希望接受Bull或者Cattle類的對象。

第46行,本來我們想通過SEL的方式來進行這個牛股的設定,但是由於它的參數不是從NSObject繼承下來的,所以我們無法使用。我們會有辦法解決這個問題的,我們將在後面的章節裏面介紹解決這個問題的方法。

 

 

 

第47行的代碼,有一個非常重要NSObject的方法respondsToSelector,通過向對象發送這個消息,加上一個SEL,我們可以知道這個對象是否可以相應這個SEL消息。由於我們的Cattle無法相應setSkinColor消息,所以如果對象是Cattle類生成的話,if語句就是NO所以花括號裏面的內容不會得到執行。

第59行,我們通過類的名字發現了一個假冒的Cattle,我們把這個假冒的傢伙給揪出來,然後實現了屏幕打印。

5.9,本章總結

本章給同學們介紹了幾個新的數據類型,以及使用方法,這些數據類型分別是BOOL,SEL,Class,IMP。

本章的內容很重要,希望同學們花一點時間仔細的理解一下。應該說,本章的內容有些令人難以理解,或者說知道了SEL,Class,IMP之後也許不知道如何使用,遇到什麼情況的時候需要使用。不過在學習Objective-C的初級階段,不知道這些也沒有關係,但是SEL,Class,IMP的概念需要掌握,否則當你遇到別人寫的質量比較高的代碼或者蘋果官方的技術文檔的時候,你會覺得理解起來比較吃力。

本章內容也是理解下一章內容的基礎,下一章我們將要講述NSObject的奧祕。

謝謝大家! 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章