Object

iOS Runtime学习笔记(一) - 基础学习

2018-03-19  本文已影响83人  brownfeng

iOS Runtime学习笔记

Runtime就是运行时, 核心就是消息机制. 对OC的函数调用,是一个动态调用过程,只有在运行的时候runtime系统才能知道真正调用的哪一个函数(C语言在函数调用过程中, 编译时候就已经决定会调用哪个函数了).

可以去下载runtime的源码: Apple官方Runtime源码

iOS Runtime中实例对象和类的本质

iOS 中实例对象的本质

先摆出结论: OC是一门面向对象的编程语言, 在编译过程中, 编译器会将OC对象转化成结构体.

我们去中的objc.h中找到:

typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

以上出现了我们常用的Class, id等关键字的定义.

我们可以看到OC中实际的类Class, 会被编译成struct objc_class. 我们操作的类的对象实例是struct objc_object, 并且该结构体中有一个指针指向struct objc_class.

iOS OC中类的本质

OC对象的结构体中有一个Class指针能够理解, 因为要知道该对象是哪个类的对象.但是我们在objc-runtime-new.h中发现objc_class继承自objc_object的.

struct objc_class : objc_object {
    // Class ISA; // 继承了
    Class superclass;
    ...

runtime.h中, 我们看到OC类的结构体struct objc_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;
/* Use `Class` instead of `struct objc_class *` */

因此, 两个地方都告诉我们, OC中类Class中也有一个指针指向Class, 因此类Class本质上也是一个对象, 我们一般称为类对象, 这个指向的Class是就是元类(metaClass)的对象.

当我们调用对象方法时候, 会通过对象中的Class指针找到对应的Class,然后调用实例方法,同理当我们调用类方法时候, 会通过Class中的Class指针找到对应的meta Class,然后调用meta Class 中的方法.

OC一般会隐藏元类, 并且元类也是某个类的实例, 这个类我们一般称为根元类(root meta Class). 并且所有的元类的根元类都是一个, 并且根元类的元类是它自己. (实际中根元类是NSObject的元类)

下面是一个非常有名的图, 分表表示 isa 指针 和 super class指针.

对象-类-元类-根元类的关系

用一个实例表示这个过程如下:

NSString实例的isa指针链

iOS 中OC方法调用的本质

iOS方法的动态调用

我们一般将OC中的方法调用称为消息发送, 具体格式是[receiver message].例如:

NSMutableString *str = [[NSMutableString alloc] initWithString:@"hello"];
[str appendString:@" world"];

其中str就是receiver, appendString:就是message.

message.h头文件中如下方法,这个方法是runtime的核心方法,

void objc_msgSend(void /* id self, SEL op, ... */ )
objc_msgSend(receiever, selector, arg1, arg2, ...)

调用实例如下:

objc_msgSend(str, @selector(appendString:), @" world");

该消息方法为消息的动态绑定完成了以下工作:

为了使得objc_msgSend能完成通过selector查找receiver对应的IMP, 我们在上一节中提到的OC类和对象的结构就非常重要.

通过上一节的内容,我们知道一个OC类和OC对象有会有一个isa指针,指向他们各自的Class, 同时OC类还有一个super指针指向父类.

下图非常清晰的展示了这个指针链结构. 同时圆形表示对象object, 方形表示类class. 在object's class中, 会存储 <selector, address> 的键值对, 我们一般称为dispatch_table, 方便我们通过某个object的class查找selector对应的address(IMP)

image

具体过程就是通过isa指针找到对应的class struct, 然后在dispatch table里面查找selector对应的方法, 如果没有找到,那么通过super指针查找父类的dispatch table, 一直找下去, 直到NSObject类, 如果还没有找到,就调用NSObject的doesNotRecognizeSelector:方法, 然后报unrecognized selector错误.(实际中间还有消息转发等内容, 后面会讲到).

当然在消息查找的过程中, 会使用一个cache来加速这个过程. 其中, 对象方法(instance method)会保存到类对象(class object)的method list. 类方法(class method)会保存到meta class的 method list.

Selector, Message, Method的含义

Selector: 表示method的name. 我们一般见到的alloc,init,setObject:forKey:等等.一般用SEL表示SEL aSelector = @selector(doSomething:) 或者SEL aSelector = NSSelectorFormString(@"doSomething:")

Message: 消息是一个selector和参数一起被发送的给receiver

Method: 方法是selector和 implementation的集合, implementation是一个函数指针(IMP).

Method Signature: 表示一个method能够接受的参数类型和返回数据类型.

消息转发, unrecognized selector的补救

上一节中提到,对象通过消息这种机制查找struc objc中的方法地址.如果在整个isa/super指针链中找不到对应的selector-IMP, 那么说明该对象无法收到这个消息, 此时就会进入消息转发(message forwarding)的环节, 通过这种机制, 我们可以在消息转发的过程中告知对象如何处理该selector.

消息转发分成两个阶段: 1. 动态方法解析(dynamic method resolution), 2. 完整消息转发机制(full forwarding mechanism).

1. 动态方法解析

主要是询问receiver所属的类, 看它是否能动态添加方法, 处理该unrecognized selector.涉及一下方法, 分别是实例方法和类方法:

+ (BOOL)resolveInstanceMethod:(SEL)selector;
+ (BOOL)resolveClassMethod:(SEL)selector;
2. 完整的消息转发机制

其实本阶段也分成两个阶段:

1> 备份者机制(replacement receiver). 或者称为重定向

2> 真正的消息转发机制.

第一种情况,如果前面阶段都没有完成. 那么runtime会请求receiver的- (id)forwardingTargetForSelector:(SEL)selector方法,询问它是否有其他的receiver来帮助处理这个selector.

第二种情况. 此时只能启用真正的消息转发机制。完整的消息转发机制是这样的:

  1. 首先通过-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回一个NSInvocation对象, 这个方法中生成的NSInvocation作用在于把尚未处理的那条message有关的全部细节封装于这个NSInvocation对象中。此对象中包含选择子(selector)、目标(target)及参数。
  1. 在返回的NSInvocation对象不为nil时,消息派发系统(message-dispatch system)将亲自触发- (void)forwardInvocation:(NSInvocation *)anInvocation,把message dispatch给目标对象。

其中,有以下内容需要注意:

实现此方法时,如果发现调用操作不应该由本类处理,则需要沿着继承体系,调用父类的同名方法,这样一来,继承体系中的每个类都有机会处理这个调用请求,直至rootClass,也就是NSObject类。如果最后调用了NSObject的类方法,那么该方法还会继而调用doesNotRecognizeSelector以抛出异常(unrecognized selector send to instance xxx),此异常表明选择子最终也未能得到处理。消息转发到此结束。

具体的流程如下

参考文献

Apple官方Runtime开源源码

iOS runtime探究(一): 从runtime开始理解面向对象的类到面向过程的结构体

selector/Message/Method的关系

上一篇下一篇

猜你喜欢

热点阅读