objective-c下的消息機製
l 消息機製:
Objective-c下調用函數並不是采用的想C語言中的函數調用機製而是使用的一種消息傳遞的機製。下文將討論這兩種協議之間的區別。
傳統的函數調用機製是在程序編譯的階段就已經將子函數的引用地址編譯進了執行代碼,所以當編譯完成之後,函數名指向的就是函數的入口地址,而調用函數將直接導向至函數的入口地址,從而直接開始執行函數。
而Objective-c采用的是一種消息傳遞機製,即以消息的形式將要執行的函數傳遞給對應的對象,此時對象在自己的方法列表中查找對應的函數,如果找到了就執行,如果沒有就在父類中查找。在這樣的機製下,程序在編譯的時候,要執行哪個函數其實是沒有確定的,這也就是所謂的動態調用。其具體的操作如下。
在objective-c下,當編譯器編譯到一個函數調用的時候,編譯器會調用一個obj_msgSend函數:
id objc_msgSend(id theReceiver, SELtheSelector, ...)
theReceiver是接收該消息的對象,SELtheSelector則是要調用函數的選擇器(選擇器的具體介紹詳見下文),後麵還可以跟多個函數傳入的參數。在程序執行的時候,theReceiver對象會在自己的方法列表中找尋SELtheSelector指向的函數,如果沒有找到就繼續查找其父類,找到之後,執行該函數。以上就是objective-c下的消息機製。可以看出,在編譯之後,具體要調用哪些函數並沒有確定。
l 選擇器:
在消息傳遞函數obj_msgSend函數中有一個選擇器,那麼選擇器是什麼呢?其作用相當於函數指針,現在我看到的大多說用法都是在調用某些函數需要傳遞一個函數指針 參數時,使用@selector。它會在當前類裏麵查找selector後麵所跟的函數,返回一個SEL類型的值。Objective-C在編譯的時候,會根據方法的名字(包括參數序列),生成一個用 來區分這個方法的唯一的一個ID,這個ID就是SEL類型的。我們需要注意的是,隻要方法的名字(包括參數序列)相同,那麼它們的ID都是相同的。就是說,不管是超類還是子類,不管是有沒有超類和子類的關係,隻要名字相同那麼ID就是一樣的。除了函數名字和ID,編譯器當然還要把方法編譯成為機器可以執行的代碼。其實簡單地理解可以將SEL類型理解為一個char*,因為它的確可以用char*的形式打印出來:
SEL hah=@selector(message);
NSLog(@"%s",(char*)hah);
SEL類型的聲明有多種方法,除了SEL=@selector(方法名)這種方式以外,還有另一種方法:
SEL 變量名 = NSSelectorFromString(方法名字的字符串);
而方法:
NSString *變量名 = NSStringFromSelector(SEL參數);
可以獲得選擇器對應方法的名稱。
在Objective-c下,方法的定義如下:
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;//方法名稱
char *method_types;//方法參數類型
IMP method_imp;//方法實現的函數指針
};
其中的method_imp參數就是指向函數的指針,其定義如下:
typedef id (*IMP)(id, SEL, ...);
而id實際隻是一個Class對象,其名字位isa,每一個NSObject對象都有一個isa成員:
typedef struct objc_object {
Class isa;
} *id;
關於Class類,其定義如下:
struct objc_class {
struct objc_class super_class; /*父類*/
const char *name; /*類名字*/
long version; /*版本信息*/
long info; /*類信息*/
long instance_size; /*實例大小*/
struct objc_ivar_list *ivars; /*實例參數鏈表*/
struct objc_method_list **methodLists; /*方法鏈表*/
struct objc_cache *cache; /*方法緩存*/
struct objc_protocol_list *protocols; /*協議鏈表*/
};
到這裏一切都明了了,這個IMP對象實際上指向的是函數的執行地址,同時還傳入了一個接收對象的id(self指針)以及對應SEL和對應的參數。同時還會返回一個id。
l 實例:
也許你會問,這樣的機製相對於以前的函數調用機製來說有什麼優勢呢?在我看來,最重要的就是其實現了函數的動態調用,我們不需要在一開始編寫代碼的時候就決定在某個特定的時候需要執行什麼函數。而是可以在之後的某個時候再決定要執行的函數。看下麵這個實例:
NSMutableArray *list2=[NSMutableArray arrayWithObjects:@"four",@"five",@"six",nil];
SEL sel=NSSelectorFromString(@"count");
NSLog(@"items of list2:%@ \n it has %lu elements",list2,[list2 performSelector:sel]);
隻需要在一個特定的時候傳入函數的名稱,我們就可以動態調用函數。這就是該機製的優勢。
最後更新:2017-04-03 15:22:03