iOS Runtime与消息转发机制

Runtime机制

一.runtime的源码部分解读

首先贴上Runtime代码,参照着代码进行runtime的学习

Runtime代码 (使用版本为objc4-646.tar)

这里,我们先了解一下Runtime里面包含了一些什么,这里我参考了一下简书里面的一篇比较不错的文章.已经各大论坛里面详细的文章

当我们调用一个函数的时候,编译器会转化为

objc_msgSend(receiver,message);
//假如消息中含有参数,会转化为如下形式:
[receiver message:arg...];
objc_msgSend(receiver,message,arg1,arg2,...);

1.id到底是什么

那我们首先来了解一下objc_msgSend到底是个什么东西,其实,他本质上是个
id objc_msgSend(id self,SEL op...)
那这里又会给出id的概念
typedef struct objc_object *id;

2.objc_object是什么

struct objc_object {
  Class isa OBJC_ISA_AVAILABILITY;
};

objc_object含有isa指针,isa指针的作用就是找到对象的所属的类

这里参考简书中提到的isa指针注意的东西,isa指针在代码运行的时候并不总指向实例所属的类,所以不能单单依靠isa来确定类型,可以通过 class方法来确定。因为在KVO实现原理中,isa指针指向的是一个该类(基类)的派生类(补充kvo的实现原理:当你第一次观察某个对象的时候,runtime会创建一个基于该类的派生类,然后将isa指针指向这个派生类,在这个派生类中重写了被观察属性的setter方法,当一个属性的值改变时,在重写的setter 方法里面来实现通知机制,此时的isa指针指向的是派生类而不是基类)


3.Class类型是什么

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

它里面还有一个isa指针,这里因为Objective-C本身也是对象,为了区别对象和类,Objc创建了Meta Class的东西,类对象所属的类叫做元类。

我们熟悉的类方法,就来自于元类,可以理解为类方法就是类对象的实例方法,每一个类只有一个类对象,每一个类对象只有一个类,当发出一个[NSObject new]的消息时,事实上是把这个类发送给了类对象,这个类对象是元类的一个实例,而这个元类也是一个根元类(root meta class),所有的元类最终都指向根元类为自身的父类。所有的元类的方法列表中都有能够响应消息的方法,所以当[NSObject new]消息发送给类对象的时候,objc_msgSend就会去方法列表中找到相应的方法,找到了就去响应


4.Cache是什么

Cache为方法调用的性能进行优化,也就是在方法调用的时候会首先从这个缓存列表中找,如果找到就执行,没有找到就通过isa指针在类的方法列表中寻找,找到之后执行并把这个方法添加到缓存列表里,以供下一次调用,当下一次调用的时候直接从缓存列表中找,这样提高了性能,不用通过isa指针去查找。  

它有指向超类的super_class指针.其中Objc_ivar_list是成员变量objc_ivar列表; objc_method_list是objc_method方法列表

其中的属性具体这里不做介绍了


二.消息转发机制

1.过程

其实,但我们在消息转发的过程中,我们需要经历以下几个步骤
(1)检测selector是否执行,比如有了垃圾回收机制就不需要retain和release等方法选择器
(2)检测targer是否为nil,如果为nil,便忽略
  (3)  上面都执行成功了,就开始查找这个方法实现的IMP,先从Cache中查找,如果找到了就执行
  (4)  如果找不到了,就在类方法列表中查找
(5)如果在类的方法列表中找不到就去父类的方法列表,一直找到NSObject为止
(6)如果找不到,就开始进行动态解析



首先,如果要了解Runtime机制,我们不得不先说一下iOS的消息转发机制,来看下面的这一段代码

- (void)testMessageSend {
    UILabel *label = [[UILabel alloc] initWithFrame:self.view.bounds];
    label.backgroundColor = [UIColor redColor];
    [self.view addSubview:label];
    
    [self performSelector:@selector(setTextColor:) withObject:nil afterDelay:1];
}


我们写这么一个测试方法 因为是在控制器下调用,而控制器肯定不会有setTextColor这个方法,所以,正常情况下,运行该方法后,肯定会导致系统奔溃,我们来看一下奔溃日志

[ViewController setTextColor:]: unrecognized selector sent to instance 0x7fdd1a508040'

打印错误的结果,很显然,没有找到该方法

原因是因为消息发送给了不能处理它的对象,然而有时我们并不希望出现这种奔溃的现象,那么它在这一系列工程中发生了一下什么,如何使其不奔溃呢

首先,编译器将代码[self setTextColor ] 转化为objc_msgSend(obj,@selector(setTextColor)); 在objc_msgSend函数中,首先通过obj的isa指针找到obj对应的class.在Class中先去cache中通过SEL查找对象函数method,若cache中为找到到,再去methodList中查找,若methodlist中未找到,则去superClass中查找。若能找到,则将method加入到cache中,方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

当在当前类和父类中都找不到对应的方法实现时,则


1.首先对象在收到无法解读的消息后,首先将调用其所属的下列类方法

+ (BOOL)resolveInstanceMethod:(SEL)sel

参数为SEL类型的对象,是一个方法选择器,返回值为BOOL类型,表示这个类是否能新增一个实例来处理选择器的方法。继续往下执行级之前,本类有机会新增一个处理此选择器的方法。加入尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用下一个方法

+ (BOOL)resolveClassMethod:(SEL)sel

如果不想让程序cash掉,那么我们把要实现的代码写好,等着运行的时候动态的插在类里面就可以了。此方案一般用来实现@dynamic属性


2.如果在这里没有得到处理,那么接收者还有第二次机会能处理位置的方法,这一步中,运行期系统会问它:能不能把这条消息转给其他接收者来处理,对应所执行的方法如下

- (id)forwardingTargetForSelector:(SEL)aSelector

方法的参数表示未知的选择子,若当前接收者能找到被援对象,则将其返回,若找不到就返回nil。在一个对象内部,可能还有一系列其他的对象,该对象可经由此方法能够处理某选择字的相关内部返回,这样,在外界看来,好像是对象亲自处理了这些消息似得。


3.最后的不久措施,系统唯一能做的就是启用完整的消息转发机制了。首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中。在出发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象,方法如下

-(void)forwardInvocation:(NSInvocation *)invocation
这个方法实现很简单:只需改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与"备援接收者"方案实现的法等效,所以很少有人采用这么简单的实现放啊是。


如果在这几步里面做任何操作,那么将会奔溃




参考文章:http://www.jianshu.com/p/ea1743715609 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值