Objective-C runtime

来源:互联网 发布:mac修改桌面图标大小 编辑:程序博客网 时间:2024/05/22 00:09

文章转载自:http://tech.glowing.com/cn/objective-c-runtime/

Objective-C

Objective-C扩展了C语言,并加入了面向对象的特性和SmallTalk式的消息传递机制。而这个扩展的核心就是一个用C和汇编语言写的runtime库。它是Objective-C面向对象的基石。
Objective-C是一门动态的语言,这意味着他不仅需要一个编译器,也需要一个运行时系统来动态地创建类和对象、进行消息的转发。理解Objective-C的Runtime机制可以帮助我们更好地了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解runtime,要先了解它的核心-消息传递(Messaging)。

消息传递(Messaging)

Alan Kay 曾多次强调SmallTalk的核心不是面向对象,面向对象只是the lesser idea,消息传递才是the big idea。
在很多语言中,比如C,调用一个方法其实就是跳的内存中的某一点开始执行一段代码。并没有任何的动态特性,因为这在编译时就决定好了。而在Objective-C中[object foo]语法并不会立刻执行foo这个方法的代码。它是在运行时给object发送一条叫foo的消息。这个消息,也许会由object来处理,也许会转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

事实上,在编译时你写的Objective-C函数调用的语法都会被翻译成一个C的函数调用-objc——msgSend()。比如下面这两行代码就是等价的:

[array insertObject:foo atIndex:5];objc_msgSend(array,@selector(insertObject:atIndex:),foo,5);

消息传递的关键藏于objc_object中的isa指针和objc_class中的class dispatch table。

objc_object,objc_class以及objc_method

在Objective-C中,类、对象和方法都是一个C的结构体,从objc/objc.h头文件中我们可以找到它们的定义:

struct objc_object {    Class isa OBJC_ISA_AVAILABILITY;};struct objc_class {    Class isa OBJC_ISA_ACAILABILITY;#if !__OBJC2__     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;#endif};struct objc_method_list {    struct objc_method_list *obsolete;    int method_count;#ifdef __LP64__    int space;#endif;    /* variable length structure */    struct objc_method method_list[1];};struct objc_method {    SEL method_name;    char *method_types; /* a string representing argument/return types */    IMP method_imp;};

objc_method_list本质是一个有objc_method元素的可变长度的数组。一个objc_method结构体中有函数名,也就是SEL,有表示函数类型的字符串,以及函数的实现IMP。
重这些定义中可以看出发送一条消息也就objc_msgSend做了什么事。举objc_msgSend(obj,foo)这个例子来说:
1.首先,通过obj的isa指针找到它的class;
2.在class的method list中找foo;
3.如果class中没找到foo,继续往它的superclass中找;
4.一旦找到foo这个函数,就去执行它的实现IMP。
但这种实现有个问题,效率低。每一个class往往只有20%的函数会被经常调用,可能占总调用次数的80%。每个消息都需要便利一次objc_method_list并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情。在找到foo之后,把foo的method_name作为key,method_imp作为value给存起来。当再次受到foo消息的时候,可以直接在cache里找到,避免去遍历objc_method_list

动态方法解析和转发

在上面的例子中,如果foo没有找到会发生什么?通常情况下程序会在运行时挂掉并抛出unrecognized selector sent to…的异常。但在抛出前,Objective-C的运行时会给你三次拯救的机会:
1.Method resolution
2.Fast forwarding
3.Normal forwarding

Method Resolution

首先,Objective-C运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES,那运行时系统就会重新启动一次消息发送的过程。还是以foo为例,你可以这么实现:

void fooMethod(id obj,SEL _cmd) {    NSLog(@"Doing foo");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL {    if(aSEL == @selector(foo:)) {        class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");        return YES;    }    return [super resolveInstanceMethod];}

Core Data 有用到这个方法。NSManagedObjects中properties的getter和setter就是在运行时动态添加的。

如果resolve方法返回NO,运行时就会移到下一步:消息转发(Message Forwarding)。
PS: iOS 4.3加入了很多新的runtime方法,主要都是以imp为前缀的方法,比如imp_implementationWithBlock()用block快速创建一个imp。
上面的例子可以重写成:

IMP fooIMP = imp_implementationWithBlock(^(id _self){    NSLog(@"Doing foo");});class_addMethod([self class],aSEL,fooIMP,"v@:");

Fast Forwarding

如果目标对象实现了-forwardingTargetForSelector:,runtime这时候就会调用这个方法,给你把这个消息转发给其他对象的机会。

- (id)forwardingTargetForSelector:(SEL)aSelector {    if(aSelector == @selector(foo:)){        return alternateObject;    }    return [super forwardingTargetForSelector:aSelector];}

只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Forwarding。
这里叫Fast Forwarding,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快一点。

Normal Forwarding

这一步是runtime最后一次给你挽救的机会。首先他会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

NSInvocation实际上就是对一个消息的描述,包括selector以及参数等信息。所以你可以在-forwardInvocation:里修改传进来的NSInvocation对象,然后发送-invokeWithTarget:消息给他,穿进去一个新的目标:

- (void)forwardInvocation:(NSInvocation*)invocation {    SEL sel = invocation.selector;    if([alternateObject respondsToSelector:sel]){        [invocation invokeWithTarget:alternateObject];    } else {        [self doesNotRecognizeSelector:sel];    }}

Cocoa里很多地方都利用到了消息传递机制来对语言进行扩展,如Proxies、NSUndoManager跟Responder Chain。NSProxy就是专门用来作为代理转发消息的;NSUndoManager截取一个消息之后再发送;而Responder Chain保证一个消息转发给合适的响应者。

总结

Objective-C中给一个对象发送消息会经过以下几个步骤:
1.在对象类的dispatch table 中尝试找到该消息。如果找到了,调到相应的函数IMP去执行实现代码;
2.如果没有找到,Runtime会发送+resolveInstanceMethod:或者+resolveClassMethod:尝试去resolve这个消息;
3.如果resolve方法返回NO,Runtime就发送-forwardingTargetForSelector:允许你把这个消息转发给另一个对象;
4.如果没有新的目标对象返回,Runtime就会发送-methodSignatureForSelector:-forwardingInvocation:消息。你可以发送-invokeWithTarget:消息来手动转发消息或者发送-doseNotRecognizeSelector:抛出异常。

0 0