初步学习objective-c的Runtime机制

前言

一段繁忙之后,项目进入休整期,没有太忙的事情需要处理,所以想在现阶段多学一些东西,于是就想了解一下OC的底层,一方面是提高自己的能力,一方面是方便日后开发时使用,另外也为了使用Method Swizzling打一下基础。

引言

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

正文

消息传递机制

就像是引言中提到的,了解Runtime机制,首先要先了解什么才是消息传递。
学习过C语言的都能够理解,C语言的函数调用是将程序跳转到内存中的某个位置开始并开始执行对应的代码行。而OC中,[object xx(方法名)]并不会直接调用对应的代码,而是在运行时给object对象发送了一条消息,消息名为xx,这个消息有可能直接由object处理,也可能被转发给另一个object,还有可能不被任何对象处理,多条不同的消息可以被同一个方法实现,以上这些都是在运行时决定的。
通常来说,编译的时候,方法调用的代码会被翻译成C的函数调用语句,如下面的两句是等价的

[array insertObject:obj atIndex:2];

objc_msgSend(array, @selector(insertObject:atIndex:), obj, 2);

objc_object,objc_class以及objc_method

在OC中,类、对象和方法都是一个C的结构体,从objc/objc.h头文件中,我们能看到以下定义:

struct objc_object {  
    Class isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class {  
    Class isa  OBJC_ISA_AVAILABILITY;
#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_object的isa指针和objc_class中的class dispatch table有密切关系。objc_method_list本质是一个存储objc_method元素的可变长度数组,而objc_method结构体中包含的有:函数名(即SEL),有表示函数类型的字符串(详见TypeEncoding),以及函数的实现IMP。
结合定义,我们能够理解objc_msgSend做了什么事情,我们以objc_msgSend(obj,xx)为例:
1. 首先,通过obj的isa指针找到它的class;
2. 在class的method list找xx;
3. 如果class中没有找到xx,继续往他的superClass中找xx;
4. 一旦找到xx这个函数,就去执行他的实现IMP。
如果每次都是走这些流程,那么这势必是一种比较低效率的方法,因为一个class中,只有20%的函数会被经常调用,而调用率达到80%,因此每当接收到一个消息就去遍历一次objc_method_list是不合理的。所以OC中还是用了另一个成员:objc_cache。当第一次找到xx之后,就把xx的method_name作为key,把xx的method_imp作为value存储起来,当再次调用xx的时候就直接在cache中找就可以了,减少了对objc_method_list的调用次数。

到了这里,你可能会有疑问,如果在objc_method_list中没有找到会发生什么呢?请看下面的内容:

动态方法解析与转发

如果没有在objc_method_list中找到对应的方法,那么通常情况下,程序会在运行时挂掉并抛出unrecognized selector sent to … 的异常,但是在异常抛出之前,oc的运行时会给三次拯救的机会:
1. Method resolution
2. Fast forwarding
3. Normal forwarding

Method resolution

首先,OC运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,可以在出现找不到方法的情况的时候提供一个函数实现。如果在这两个方法中添加了函数并返回YES,那么运行时系统将就会重新启动一次消息发送的过程。实现方式如下:

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

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就会在resolve方法返回NO时调用这个方法,以此来实现消息的转发。实现如下:

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

这个方法中,返回值只要不是nil或者self,就会使消息发送的过程被重启,不过对象会被换成返回的那个对象,否则就会继续进入下一步:Normal forwarding。
Fast和Normal的区别在于,Fast不会创建新的对象,而Normal会创建一个NSInvocation对象,所以Fast相对会更快一些。

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 保证一个消息转发给合适的响应者。
再次感谢顾鹏的私人博客,正是参阅了他的文章才进行了这样的总结,十分感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值