runtime的理解(一)

主要内容

  • runtime 是什么
  • runtime 的消息机制

是什么

*  *OC 是在C语言的基础上进行的扩展和封装。对于C语言,调用未实现的函数在编译阶段就会报错,而对于OC语言,调用的只要是声明过的函数,在编译阶段不会报错,在运行阶段才会报错。这就是OC的动态性的一个体现,并且C语言是面向过程的语言,而OC是面向对象的编程语言,OC对C语言的这些扩展的核心就是 runtime。
*  *runtime 简称运行时,主要由C和汇编编写,是OC面向对象和动态机制的基础,其的核心是 消息机制。在使用的过程中,我们可以参考官方Api

消息机制

objc_class

*  *Cocoa Touch 中几乎所有的类中都继承自 NSObject 类(另一个是 NSProxy 类),在 NSObjcet 中存在一个 Class 的 isa 指针。

1
2
3
4
typedef struct objc_class *Class;
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

*  *Objective-C 的类也是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针。查看 objc/runtime.h 中 objc_class 结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct objc_class {
Class isa; // 指向metaclass

Class super_class ; // 指向其父类
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
struct objc_protocol_list *protocols; // 存储该类遵守的协议
}

方法选择器

*  ***@selector (SEL):是一个SEL方法选择器。SEL** 其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。一个方法对应着一个SEL,所以一个类中不能存在2个名称相同的方法。

消息传递

*  *例如执行代码: [person eat]; ,底层就是调用 [person performSelector:@selector(eat)]; 方法,然后编译器再将代码转化为objc_msgSend(person, @selector(eat));

  1. 在 objc_msgSend 函数中。首先通过 person 的isa指针找到 person 对应的 Class。在 class 中先去 cache 中通过 SEL 查找对应函数 method,如果找到则通过 method 中的函数指针跳转到对应的函数中去执行。
  2. 若 cache 中未找到。再去 methodList 中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
  3. 若 methodlist 中未找到,则去 superClass 中查找。若能找到,则将 method 加入到 cache 中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。若依然未找到,就要消息转发。
消息传递

消息转发

*  *消息转发包含动态方法解析、备用接收者、完整消息转发三部分,也是程序报错前最后的三次机会。

  1. 动态方法解析
    *  *在消息传递到根类仍然找不到方法的时候,消息转发,调用 +resolveInstanceMethod: 或者 +resolveClassMethod: 方法。重写此方法,并利用 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 方法来添加新的方法接受消息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    - (void)testDynamicMethod
    {
    [self performSelector:@selector(justTest:) withObject:@"testStr"];
    // [DITRuntimeViewController performSelector:@selector(justClassTest:) withObject:@"classTestStr"];
    }

    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
    if (sel == @selector(justTest:))
    {
    class_addMethod([self class], sel, (IMP)justTestMethod, "v@:@");
    return YES;
    }

    return [super resolveInstanceMethod:sel];
    }

    void justTestMethod(id self, SEL _cmd, id str) {
    NSLog(@"%@的%@方法动态实现了,参数为:%@", self, NSStringFromSelector(_cmd), str);
    }

    *  关于 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 方法,四个参数代表的意思,*cls : 表示给哪个类添加方法,这里是指当前控制器;SEL name: 表示要添加方法的 sel,即是传进来的参数 sel。IMP imp : 表示要添加的方法的实现,方法名可与未找到的方法的名字(sel)相同,方法的实现默认带有两个隐式参数, self : 方法调用者 _cmd : 调用方法的 SEL。types : 表示方法类型,需要用特定符号,系统提供的例子中使用的是**”v@:”**,各符号所代表的类型如下图:

    methodTypes
  2. 备用接收者
    *  *在调用 +resolveInstanceMethod: 或者 +resolveClassMethod: 方法返回NO的时候,就会调用 - forwardingTargetForSelector: 方法,重写此方法,返回备用接收者即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #pragma mark - 备用接受者
    - (void)testForwardingReceiver
    {
    [self performSelector:@selector(justTestForwarding:) withObject:@"testStr"];
    }

    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
    if (aSelector == @selector(justTestForwarding:))
    {
    return [[DITForwardingTest alloc] init];
    }

    return [super forwardingTargetForSelector:aSelector];
    }
  3. 完整消息转发
    *  *在调用 - forwardingTargetForSelector: 方法返回 nil 的时候,就会启用完整的消息转发机制。首先它会发送 -methodSignatureForSelector: 消息获得函数的参数和返回值类型。如果 -methodSignatureForSelector: 返回nil ,runtime 则会发出 -doesNotRecognizeSelector: 消息,程序报 unrecognized selector 错。如果返回了一个函数签名,runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #pragma mark - 完整消息转发
    - (void)testMethodSignature
    {
    [self performSelector:@selector(justTestMethodSignature:) withObject:@"testMethodSignatureStr"];
    }

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
    if (aSelector == @selector(justTestMethodSignature:))
    {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }

    return [super methodSignatureForSelector:aSelector];
    }

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
    SEL sel = anInvocation.selector;
    DITForwardingTest *methodSignatureTest = [[DITForwardingTest alloc] init];

    if ([methodSignatureTest respondsToSelector:sel])
    {
    [anInvocation invokeWithTarget:methodSignatureTest];
    }
    else
    {
    [anInvocation doesNotRecognizeSelector:sel];
    // [super forwardInvocation:anInvocation];
    }
    }
  4. 消息转发流程

    消息转发流程

Demo地址:Github