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