Runtime(一)基础知识

本文介绍Runtime的基础知识

文章目录

一、如何理解OC是动态语言,Runtime又是什么?
二、分析Runtime中的数据结构
三、深入理解Rutime消息发送原理
四、Runtime消息转发原理总结

一、如何理解OC是动态语言,Runtime又是什么?
源代码转换为可执行的程序,通常要经过三个步骤: 编译、链接、运行。不同的编译语言,在这三个步骤中所进行的操作又有些不同。
**静态语言:**如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。
**动态语言:**如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。
我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。
Runtime是一套底层纯C语言API,OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。

二、分析Runtime中的数据结构

  1. 数据结构分析

新建一个工程,工程中引入头文件:

#import <objc/runtime.h>
#import <objc/message.h>

“Command +鼠标点击”,进入Runtime的源码文件,接下来分析OC代码在C中对应的结构,看看我们平时写oc代码时创建的类、对象、方法、协议在Runtime中是怎样的存在。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY; // objc_class 结构体的实例指针

#if !__OBJC2__
    Class _Nullable super_class  // 指向父类的指针 
    const char * _Nonnull name          // 类的名字       
    long version           // 类的版本信息,默认为 0   
    long info           // 类的信息,供运行期使用的一些位标识                                   
    long instance_size	// 该类的实例变量大小;
    struct objc_ivar_list * _Nullable ivars // 该类的实例变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists	 // 方法定义的列表
    struct objc_cache * _Nonnull cache     // 方法缓存
    struct objc_protocol_list * _Nullable protocols  // 遵守的协议列表
#endif

先大概浏览一下Runtime的.h文件中的结构和方法,再具体分析都有什么作用

2. Runtime中的概念解析
2.1 objc_msgSend

所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。objc_msgSend(receiver,selector);[receiver selector]; 对应的 C 函数。

2.2 Class(类)

objc_class 结构体 定义了很多变量:自身的所有实例变量(ivars)、所有方法定义(methodLists)、遵守的协议列表(protocols)等。objc_class 结构体 存放的数据称为 元数据(metadata)。
objc_class 结构体 的第一个成员变量是 isa 指针,isa 指针 保存的是所属类的结构体的实例的指针,这里保存的就是 objc_class 结构体的实例指针,而实例换个名字就是 对象。换句话说,Class(类) 的本质其实就是一个对象,我们称之为 类对象。

2.3 Object(对象)

这里的 id 被定义为一个指向 objc_object 结构体 的指针。从中可以看出 objc_object 结构体 只包含一个 Class 类型的 isa 指针。
换句话说,一个 Object(对象)唯一保存的就是它所属 Class(类) 的地址。当我们对一个对象,进行方法调用时,比如 [receiver selector];,它会通过 objc_object 结构体的 isa 指针 去找对应的 objc_class 结构体,然后在 objc_class 结构体 的 methodLists(方法列表) 中找到我们调用的方法,然后执行。

2.4 Meta Class(元类)

对象(objc_object 结构体) 的 isa 指针 指向的是对应的 类对象(objc_class 结构体)。那么 类对象(objc_class 结构体)的 isa 指针 又指向什么呢?
objc_class 结构体 的 isa 指针 实际上指向的的是 类对象 自身的 Meta Class(元类)。

那么什么是 Meta Class(元类)?
Meta Class(元类) 就是一个类对象所属的 类。一个对象所属的类叫做 类对象,而一个类对象所属的类就叫做 元类。
2.5 方法(Method)

struct objc_method {
    SEL _Nonnull method_name   //方法的名字
    char * _Nullable method_types    //参数的类型
    IMP _Nonnull method_imp	//就是函数的地址
} 

三、深入理解Rutime消息发送原理
1、消息转发的基本原理
Objective-C 语言 中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。
我们来看看方法调用 [receiver selector]; 在『编译阶段』和『运行阶段』分别做了什么?

编译阶段:[receiver selector]; 方法被编译器转换为:

objc_msgSend(receiver,selector) (不带参数)
objc_msgSend(recevier,selector,org1,org2,…)(带参数)

运行时阶段:消息接受者 recever 寻找对应的 selector。

通过 recevier 的 isa 指针 找到 recevier 的 Class(类);
在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);
如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector;
如果在 Class(类) 中没有找到这个 selector,就继续在它的 superClass(父类)中寻找;
一旦找到对应的 selector,直接执行 recever 对应 selector 方法实现的 IMP(方法实现)。
若找不到对应的 selector,消息被转发或者临时向 recever 添加这个 selector 对应的实现方法,否则就会发生崩溃。

2、消息转发
在这里插入图片描述

2.1消息动态解析
举例:创建个MessageSend类,声明一个方法,.m文件不写实现,然后调用这个方法,

@interface MessageSend : NSObject
- (void)sendMessage:(NSString *)msg;
@end

毫无意外的crash了,报错

2018-08-03 23:21:20.950576+0800 RunTime[844:370505] -[MessageSend sendMessage:]: unrecognized selector sent to instance 0x282bf0ce0

下面动态添加方法,即调用另一个方法
先添加一个方法

void dynamicMethodIMP(id self, SEL _cmd, NSString *msg) {
    NSLog(@"%@转发到这里----%@====%@", self, NSStringFromSelector(_cmd), msg);
}

然后重写系统的resolveClassMethod:(SEL)或者resolveInstanceMethod:(SEL)方法,我们声明的属性方法,所以重写这个:

+ (BOOL)resolveInstanceMethod:(SEL)sel {//实例
    
    /*@param        给哪个类添加方法
     *@param sel    方法列表中的名字,方法编号
     *@param imp    方法实现
     *@param        参数
     * @return      如果添加方法成功返回 YES,否则返回 NO
     */
    //BOOL class_addMethod(Class cls, SEL name, IMP imp, const char * _Nullable types);
    
   if (sel == @selector(receiveMessage:)) {        
   return class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
   }
    //也可以通过方法名进行转发
//    NSString *msgName = NSStringFromSelector(sel);
//    if ([msgName isEqualToString:@"sendMessage:"]) {
//        return  class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
//    }
    return [super resolveInstanceMethod:sel];;
}

具体的参数类型可查查看官方文档:传送门
运行结果:

2018-08-03 23:30:19.783188+0800 RunTime[851:371928] <MessageSend: 0x280c30720>转发到这里----receiveMessage:====message

可以看到程序运行并且成功调用了dynamicMethodIMP方法,说明消息转发成功。
2.2消息接受者重定向
如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector:,Runtime 就会调用这个方法,允许我们将消息的接受者转发给其他对象。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage:"]) {
       return [[PersonMessage alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

PersonMessage.m中

- (void)sendMessage:(NSString *)msg{
    NSLog(@"PersonMessage");
}

结果:

2018-08-04 00:43:37.241994+0800 RunTime[909:382058] PersonMessage

2.3消息重定向
如果经过消息动态解析、消息接受者重定向,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 methodSignatureForSelector: 方法获取函数的参数和返回值类型。

如果 methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。

/*消息重定向*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    // 获取函数的参数和返回值类型,返回签名
    if ([NSStringFromSelector(aSelector) isEqualToString:@"sendMessage:"]) {
        //调用forwardInvocation:,如果返回nil调用doesNotRecognizeSelector:
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;   // 从 anInvocation 中获取消息
    
    PersonMessage *p = [[PersonMessage alloc] init];
    
    if([p respondsToSelector:sel]) {   // 判断 Person 对象方法是否可以响应 sel
        [anInvocation invokeWithTarget:p];  // 若可以响应,则将消息转发给其他对象处理
    } else {
        [self doesNotRecognizeSelector:sel];  // 若仍然无法响应,则报错:找不到响应方法
    }
}

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"doesNotRecognizeSelector");
}
  1. 消息发送以及转发机制总结
    调用 [receiver selector]; 后,进行的流程:

编译阶段:[receiver selector]; 方法被编译器转换为:

objc_msgSend(receiver,selector) (不带参数)
objc_msgSend(recevier,selector,org1,org2,…)(带参数)

运行时阶段:消息接受者 recever 寻找对应的 selector

通过 recevier 的 isa 指针 找到 recevierclass(类);
在 Class(类) 的 cache(方法缓存) 的散列表中寻找对应的 IMP(方法实现);
如果在 cache(方法缓存) 中没有找到对应的 IMP(方法实现) 的话,就继续在 Class(类) 的 method list(方法列表) 中找对应的 selector,如果找到,填充到 cache(方法缓存) 中,并返回 selector
如果在 class(类) 中没有找到这个 selector,就继续在它的 superclass(父类)中寻找;
一旦找到对应的 selector,直接执行 recever 对应 selector 方法实现的 IMP(方法实现)。
若找不到对应的 selector,Runtime 系统进入消息转发机制。

运行时消息转发阶段:

动态解析:通过重写 +resolveInstanceMethod: 或者 +resolveClassMethod:方法,利用 class_addMethod方法添加其他函数实现;
消息接受者重定向:如果上一步添加其他函数实现,可在当前对象中利用 -forwardingTargetForSelector: 方法将消息的接受者转发给其他对象;
消息重定向:如果上一步没有返回值为 nil,则利用 -methodSignatureForSelector:方法获取函数的参数和返回值类型。

如果 -methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 -forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值