原文地址:http://www.cnblogs.com/tangbinblog/archive/2012/11/08/2760060.html
Objective-C 2.0 的運行時環境叫做Morden Runtime,iOS 和Mac OS X 64-bit 的程序都運行在
這個環境,也就是說Mac OS X 32-bit 的程序運行在舊的Objective-C 1.0 的運行時環境Legacy
Runtime,這裏我們只講解Morden Runtime。
同運行時交互主要在三個不同的地方,分別是A.Objective-C 源碼(譬如:你定義的Category
中的新方法會在運行時自動添加到原始類)、B.NSObject 的方法(isMemberClassOf 等動態判
定的方法)、C.運行時函數。由於前兩者在第一篇文檔中講解過,這裏我們講一下運行時函
數的相關內容。
(1.)isa指針:
NSObject 中有一個Class isa 的指針類型的成員變量,因爲我們的對象大都直接或者間接的從
NSObject 繼承而來,因此都會繼承這個isa 成員變量,isa 在運行時會指向對象的Class 對象,
一個類的所有對象的Class 對象都是同一個(JAVA 也是如此),這保證了在內存中每一個類
型都有唯一的類型描述。這個Class 對象中也有個isa 指針,它指向了上一級的父類的Class
對象。
在明白了這個isa 之後,你就可以明白在繼承的時候,A extends B,你調用A 的方法a(),首
先A 的isa 到A 的Class 對象中去查找a()方法,找到了就調用,如果沒找到,就驅使A 的Class
對象中的isa 到父類B 的Class 對象中去查找。
(2.)SEL 與IMP:
第一篇文檔中,我們提到了方法選擇器SEL,它可以通過如下兩種方式獲得:
(SEL) @selector(方法的名字)
(SEL) NSSelectorFromString(方法的名字的字符串)
另外,你還可以通過(NSString*) NSStringFromSelector(SEL)函數來獲取SEL 所指定的方法名稱
字符串。
其實Objective-C 在編譯的時候,會依據每一個定義的方法的名字、參數序列,生成一個唯
一的整數標識,這個標識就是SEL。因此,在運行時查找方法都是通過這個唯一的標識,而不是通過方法的名字。
Objective-C 又提供了IMP 類型,IMP 表示指向實現方法的指針(函數指針),通過它,你可
以直接訪問一個實現方法,從而避免了[xxx message]的靜態調用方式,需要首先通過SEL 確定方法,然後再通過IMP 找到具體的實現方法,最後再發送消息所帶來的執行效率問題。
一般,如果你在多次循環中反覆調用一個方法,用IMP 的方式,會比直接向對象發送消息
高效一些。
例:
Person.m:
#import "Person.h"
@implementation Person
@synthesize name;
@synthesize weight;
-(Person*) initWithWeight: (int) w
{
self=[super init];
if (self)
{
weight=w;
}
return self;
}
-(void) print: (NSString*) str
{
NSLog(@"%@ %@",str,name);
}
-(void) dealloc
{
[self setName:nil];
[super dealloc];
}
@end
main.m:
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Person *person=[[Person alloc] initWithWeight:68];
person.name=@"Jetta";
SEL print_sel=NSSelectorFromString(@"print:");
IMP imp=[person methodForSelector: print_sel];
imp(person,print_sel,@"*********");
[pool drain];
return 0;
}
這裏我們看到要獲得IMP 的指針,可以通過NSObject 中的methodForSelector: (SEL)方法,訪
問這個指針函數,我們使用imp(id,SEL,argument1,… …),第一個參數是調用方法的對象,第
二個方法是方法的選擇器對象,第三個參數是可變參數,表示傳遞方法需要的參數。
(3.)objc_msgSend函數:
通過isa 指針的講解,我們知道Objective-C 中的方法調用是在運行時纔去綁定的,再進一步
看,編譯器會把對象消息發送[xxx method]轉換爲objc_msgSend(id receiver,SEL selector,參數…)
的函數調用。因此上面例子中的print 方法你也可以像下面這樣調用:
objc_msgSend(person,print_sel,@"++++++++");
當然,這是編譯器要做的事情,你在寫代碼的時候,是不需要直接使用這種寫法的。
綜合isa、SEL、IMP 的講解,實際上objc_msgSend 的調用過程就應該是這樣的:
A.首先通過第一個參數的receiver,找到它的isa 指針,然後在isa 指向的Class 對象中使用
第二個參數selector 查找方法;
B.如果沒有找到,就使用當前Class 對象中的新的isa 指針到上一級的父類的Class 對象中查
找;
C.當找到方法後,再依據receiver 的中的self 指針找到當前的對象,調用當前對象的具體實
現的方法(IMP 指針函數),然後傳遞參數,調用實現方法。
D.假如一直找到NSObject 的Class 對象,也沒有找到你調用的方法,就會報告不能識別發送
消息的錯誤。
(4.)動態方法解析:
我們在Objective-C 2.0 的新特性中的屬性訪問器一節中,實際忽略了一個內容,那就是動態
屬性。Objective-C 2.0 中增加了@dynamic 指令,表示變量對應的屬性訪問器方法,是動態實
現的,你需要在NSObject 中繼承而來的+(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定
動態實現的方法或者函數。
例:
Person.h:
@interface Person : NSObject
{
NSString *name;
float weight;
}
@property (retain,readwrite) NSString* name;
@property (readonly)float weight;
@property float height;
-(Person*) initWithWeight: (int) weight;
-(void) print: (NSString*) str;@end