iOS开发Runtime的理解与应用

2018-04-09  本文已影响0人  小呀小苹果呀

一、Runtime概念

1.动态类型:
即运行时再决定对象的类型。简单说就是id类型,任何对象都可以被id指针所指,只有在运行时才能决定是什么类型。像内置的明确的基本类型都属于静态类型(int、NSString等)。静态类型在编译的时候就能被识别出来。所以,若程序发生了类型不对应,编译器就会发出警告。而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行的时候才会根据语境来识别。所以这里面就有两个概念要分清:编译时跟运行时。
2.动态绑定:
基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。比如我们一般向一个NSObject对象发送-respondsToSelector:或者 -instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类 的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方 法,也即类的实现是可以动态绑定的;isKindOfClass也是一样的道理。
3.动态加载:
所谓动态加载就是我们做开发的时候icon图片的时候在Retina设备上要多添加一个张@2x的图片,当设备更换的时候,图片也会自动的替换。

二、Runtime数据结构

1.Class

Objective-C类是由Class类型来表示,Class 其实是指向 objc_class 结构体的指针

typedef struct objc_class *Class;

我们可以从<objc/runtime.h>里面看到类的定义

struct object_class{
    Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
     Class super_class                        OBJC2_UNAVAILABLE;  // 父类
     const char *name                         OBJC2_UNAVAILABLE;  // 类名
     long version                             OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
     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;
2.id

id 是一个结构体指针类型,它可以指向 Objective-C 中的任何对象。从<objc/objc.h>中可以看出id其实是指向objc_object结构体的指针。objc_object只有一个成员变量 isa,对象可以通过 isa 指针找到其所属的类,这也是为什么id可以表示任意对象的原因。isa 是一个 Class 类型的成员变量。
注意:在KVO中,isa在运行时会被修改,指向一个中间类,对于编译器而言,isa的指向才是最真实的类型。

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

当我们向一个Objective-C对象发送消息时,运行时库会根据
实例对象的isa指针找到这个实例对象所属的类。Runtime库会在类的方法列表由super_class指针找到父类的方法列表直至根类NSObject中去寻找与消息对应的selector指向的方法,找到后即运行这个方法。

3.元类(meta-Class)

类自身也是一种对象,可以叫做“类对象”。类对象包含一个指向其类的一个isa指针( Class _Nonnull isa )

为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。
所以,调用类方法的这个类对象的isa指针指向的就是meta-class。当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

下图是一个经典的类及相应meta-class类的一个继承体系图解:

image.png

从图中可以看出:
1.每个实例对象的类都是类对象,每个类对象的类都是元类对象,每个元类对象的类都是根元类(root meta class的isa指向自身)。即任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
2.类对象的父类最终继承自根类对象NSObject,NSObject的父类为nil
3.元类对象(包括根元类)的父类最终继承自根类对象NSObject

三、方法和消息

1.SEL
typedef struct objc_selector *SEL;
SEL sel1 = @selector(func1);
SEL sel2 = NSSelectorFromString(func2);

SEL本质上是一个指向方法的指针。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识即SEL,每一个方法都对应着一个SEL。所以即使返回值类型或参数类型不同,方法名相同也会报错。

2.IMP
id (*IMP)(id, SEL,...)

IMP的本质是函数指针,指向方法实现的地址,直接通过IMP就可以找到各个方法。这样效率更高,因为绕过了消息传递阶段,直接定位。
SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的IMP。

消息发送和转发流程可以概括为:消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。

3.Method
typedef struct objc_method *Method
struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法实现
}

可以看出method包含SEL和IMP,在实现方法交换时,主要原理就是交换SEL和IMP的映射关系。

参考:https://www.jianshu.com/p/46dd81402f63
https://www.jianshu.com/p/adf0d566c887
https://www.jianshu.com/p/13457a27624c

上一篇下一篇

猜你喜欢

热点阅读