Runtime

Runtime简称运行时,是一套底层的 C 语言 API,oc运行时最主要的就是消息机制。
对于C语言,在编译的时候会检查函数。决定调用哪个。
对于OC的函数,只有在真正运行的时候确定函数调用。OC作为动态语言,它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
类结构:

struct objc_object {
private:
   isa_t isa;
}

struct objc_class : objc_object {
   // Class ISA; // 继承自objc_object 
   Class superclass;  //父类
   cache_t cache;             // 缓存
   class_data_bits_t bits;    // 方法列表、属性列表。协议列表。等等
}

cache_t 方法缓存

image.png
struct  **cache_t** {
    struct  **bucket_t**  *_buckets; // 一个散列表,[bucket_t,bucket_t],
    mask_t _mask; //可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1
    mask_t _occupied;//被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
}

typedef unsigned int uint32_t;
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

typedef unsigned long  uintptr_t;
typedef uintptr_t cache_key_t;

struct bucket_t {
private:
    cache_key_t _key;
    IMP _imp;
}

需要注意的是:
1、类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份
2、从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。
3、类的方法缓存大小有没有限制?目前没有。。
4、为什么类的方法列表不直接做成散列表呢,做成list,还要单独缓存,多费事?
 散列表是没有顺序的,Objective-C的方法列表是一个list,是有顺序的;Objective-C在查找方法的时候会顺着list依次寻找,并且category的方法在原始方法list的前面,需要先被找到,如果直接用hash存方法,方法的顺序就没法保证。
list的方法还保存了除了selector和imp之外其他很多属性
散列表是有空槽的,会浪费空间


class_data_bits_t

class_data_bits_t属于class_rw_t的封装
class_rw_t结构体又是对class_ro_t的封装,rw代表可读写、ro代表只读。
包括:
class_ro_t,//封装了类的基本原始信息成员变量,属性,方法和遵守协议等,格式为数组
protocols,
properties,
methods,
协议、属性、方法列表三者是二维数组; methods的元素是一个类或者它的分类的方法列表methodList,而methodList的元素是method_t。

method_t

{
SEL name:代表方法名称。
const char* types:返回值+参数+参数+…,不可变的字符类型指针。比如v@:,v代表返回值是void,@代表第一个参数是id类型的self,:代表第二个参数SEL。
IMP imp:无类型的函数指针,对应的是函数
}
总体结构引用一张图:


image.png

消息转发流程

调用查找阶段-转发阶段-动态解析

查找过程:
实例方法调用。首先根据isa指针找到它的类对象,查找类对象的缓存中是否有对应SEL的IMP方法实现,如果没有,则在当前类对象的实例方法列表,二分查找(有序情况)或遍历查找(无序情况)同名的方法实现IMP,如果找到,填充到缓存中,并返回selector,如果没有找到,根据类对象的superClass指针查找上一级缓存、方法列表。直到根类,如果没找到则进入消息转发流程。
类方法一样。只是调用地方不同。

消息转发

void resolveMethodTest(void){
    NSLog(@"----------%s",__func__);
}
/**为一个类动态提供方法实现  实际还未进入方法转发
 respondsToSelector:和instancesRespondToSelector:
也会调用resolveInstanceMethod:获取返回值
 如果此方法返回NO则进入方法转发。。
一般我们不操作这个方法因为系统底层很多都需要它操作。返回yes。。*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s",__func__);
//    if (sel == NSSelectorFromString(@"studentMethod")) {
//        class_addMethod([self class], sel, resolveMethodTest, "v@:");
//        return YES;
//    //获取sayMaster方法的imp
//    IMP imp = class_getMethodImplementation(self, @selector(resolveMethodTest));
//    //获取sayMaster的实例方法
//    Method sayMethod  = class_getInstanceMethod(self, @selector(resolveMethodTest));
//    //获取sayMaster的丰富签名
//    const char *type = method_getTypeEncoding(sayMethod);
//    //将sel的实现指向sayMaster
//    return class_addMethod(self, sel, imp, type);
//    }
    return [super resolveInstanceMethod:sel];
}
/**允许你指定特有的对象。进行消息转发。从对象出发去查找消息*/
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == NSSelectorFromString(@"studentMethod")) {
//        return Student.class; //调用classMethod
//        return [[Student alloc] init]; //调用instanceMethod
        return nil; //继续转发
    }
    return [super forwardingTargetForSelector:aSelector];
}
/**methodSignatureForSelector和forwardInvocation关联。。
提供方法签名调用。。
forwardinvocation可以更改target也可以更改方法*/

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if (aSelector == NSSelectorFromString(@"studentMethod")) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//    avoidcrash内部采取catch方式拦截
    @try {
        if (anInvocation.selector == @selector(studentMethod)) {
            anInvocation.target = Student.class;
            anInvocation.selector = @selector(say);
            [anInvocation invoke];
        }else{
            [super forwardInvocation:anInvocation];
        }
    } @catch (NSException *exception) {
    } @finally {
    }
}

其中# Type Encodings


image.png
  • (void)methodName{}
    ”v@:“ —>
    v是返回值void
    @代表id方法的第一个参数self
    :方法第二个参数sel

参考:
https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html
https://www.jianshu.com/p/c339d331e23e

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容