iOS Runtime机制原理 寓情于景 情景交融

一  什么是runtime

Objective-C是面向运行言(runtimeoriented language),就是它会尽可能的把编译行的逻辑到运行了你很大的灵活性,你可以按需要把消息重定向合适的象,你甚至可以交方法的实现,等

我们平时编写的 OC 代码,底层都是基于它来实现的。比如:

[receiver message];

//底层运行时会被编译器转化为:

objc_msgSend(receiver, selector)

//如果其还有参数比如:

[receiver message:(id)arg...];

//底层运行时会被编译器转化为:

objc_msgSend(receiver, selector, arg1, arg2, ...)

    有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。

    因此,编译器是不够的,我们还需要一个运行时系统(Runtimesystem)来处理编译后的代码。

    Runtime 基本是用 C 和汇编写的。


二、一个对象调用一个方法的全过程

  

举个栗子 :

1.我要找小P借本书,我通过指向小P位置的坐标(指针)找到她 ,他说书(方法)是她们部门(类)统一的 财产,要找组织上借(类),小P(通过isa指针)告诉我组织(类)的位置,并找到组织(类)的书架(methodLists方法列表),我用我的想要的书名(SEL 方法编号)在书架中寻找到那本书(IMP是函数指针,IMP中存了方法的地址),最后借到这本书。 

2.cache  我看完这本书后,把书还给了小P ,小P默默的把这本书放在了书架的特定位置,我再次跟她借这本书的时候,就不用耗费性能再去书架上找了,这个叫Class 的cache,所以在我借书的时候都会先从书架的特定位置缓存里拿,拿不到才会去methodlist里去找。

3.如果小P的组织中的缓存和Methodist中找不到这本书,那么我会到组织的上级组织 (类的父类)中的缓存和Methodlist中去找 ,找不到就再到父类的父类中去找 。一直到最高层的父类 NSObject为止。

4.如果一直没有找到这本书,

step 1.首先我先问小P,可不可以帮我买本新书。买就直接拿走那本新书;(动态方法解析,动态添加方法)

step 2.不给买我会看看原来的计划中有没有备案可以找到别人借书。(消息备用接受者)

step 3.   step 2不成功,我将看看原计划中是否有处理找不到书这种情况的备案。有备案调用备案方法,没有抛出异常。(启用完整的消息转发机制)


具体解释下例子里对应的专业术语

Class

typedef struct objc_class *Class;

Class 其实是指向 objc_class 结构体的指针。objc_class 的数据结构如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;   isa指针   (指向对象的类)
 
#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    方法列表   dispatch table          SEL 去寻找IMP
      OBJC2_UNAVAILABLE;
    struct objc_cache *cache                      缓存                     
    struct objc_protocol_list *protocols        协议列表                
#endif
 
} OBJC2_UNAVAILABLE;

从 objc_class 可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。

其中 objc_ivar_list 和 objc_method_list 分别是成员变量列表和方法列表:

// 成员变量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
 
// 方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
 
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}


objc_ivar_list 结构体用来存储成员变量的列表,而 objc_ivar 则是存储了单个成员变量的信息;

objc_method_list 结构体存储着方法数组的列表,而单个方法的信息则由 objc_method 结构体存储。


方法列表:


 ps: 1.(IMP 和SEL的区别  SEL:类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号,IMP函数指针保存了方法地址)  

     2.IMP SEL 的详细解释 IOS SEL 原理

     3. 我们可以动态修改 *methodList 的值来添加成员方法,这也是 Category 实现的原理


值得注意的时,objc_class 中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime 库创建了一种叫做 Meta Class(元类) 的东西,类对象所属的类就叫做元类。Meta Class 表述了类对象本身所具备的元数据。

我们所熟悉的类方法,就源自于 Meta Class。我们可以理解为类方法就是类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。

当你发出一个类似 [NSObjectalloc](类方法) 的消息时,实际上,这个消息被发送给了一个类对象(Class Object),这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类(Root Meta Class)的实例。所有元类的 isa 指针最终都指向根元类。

所以当 [NSObject alloc] 这条消息发送给类对象的时候,运行时代码 objc_msgSend() 会去它元类中查找能够响应消息的方法实现,如果找到了,就会对这个类对象执行方法调用



上图实线是 super_class 指针,虚线时 isa 指针。而根元类的父类是 NSObject,isa指向了自己。而 NSObject 没有父类。


息传递是如何工作的?


    在Objective-C中调用方法最终会翻译成调用方法实现的函数指针,并传递给这个方法实现一个对象指针、一个选择器和一组函数参数。每个Objective-C消息表达式都会转化为对objc_msgSend的调用,看下objc_msgSend的工作方式:

  • 1 首先去该类的方法cache中查找,如果找到了就返回它;
  • 2 如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP 返回,并将 它加入 cache 中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次 调用再次查找的开销。 
  • 3 如果在该类的方法列表中没找到对应的IMP,在通过该类结构中的super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中;
  • 4 如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议(后 面有专文讲述这个话题);
  • 5 如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。便利函数:我们可以通过 NSObject 的一些方法获取运行时信息或动态执行一些消息.

消息转发机制

当我们像一个对象发送消息[Receiver message],Receiver没有实现该消息,即[Receiver respondsToSelector:SEL]返回为NO情况下,其实系统不会立刻出现cash,这时Runtime system会对message进行转发。转发之后,如果该消息依然没有被执行就会出现Cash!Runtime System为我们提供了三种解决这种给对象发送没有实现消息方案。 
消息转发机制基本上分为三个步骤: 
1.
动态方法解析 
对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,我们可以在运行时通过class_addMethod函数动态添加未知消息到类里面,让原来没有处理这个方法的类具有了处理这个方法的能力。

2.
备用接收者 

如果在动态方法解析无法处理消息,则Runtime会继续调以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector
如果一个对象实现了这个方法,并返回一个非nil的对象,则返回的对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。 
使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。 

3.
完整转发 

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。 
此时会调用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

由于NSInvocation的初始化需要有一个方法签名NSMethodSignature,所以我们还需要实现下面的方法,该方法的返回值用于初始化NSInvocation的。

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
4.crash  如果以上都没有实现 系统会报错



以上文章一部分摘抄了其他大神的blog,一部分是自己学习完后自己对runtime的理解。感谢各位大神在网上无私的提供资料~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值