Runtime

作者: buding_ | 来源:发表于2024-06-12 14:12 被阅读0次

    Objective-C是一门动态性非常强的编程语言;
    Objective-C的动态性是由Runtime API提供的;
    Runtime API提供的接口基本都是C语言的,源码由C/C++ /汇编语言编写;

    isa详解

    • instance的isa指向class
      当调用对象方法时,通过instance的isa找到class,继而在class中寻找对应的方法,并进行调用
    • class的isa指向meta-class
      当调用类方法时,通过class的isa找到meta-class,继而在meta-class中找到对应的方法,并进行调用
    isa的数据结构

    在arm64架构之前,isa就是普通的指针,存储着class/meta-class的地址;
    在arm64架构后,对isa进行了优化,变为了union结构,使用位域来存储更多的信息;

    union isa_t
    {
      Class cls;
      uintptr_t bits;
      struct {
        #define ISA_MASK 0x000000fffffff8U
        ....
          uintptr_t nonpointer : 1;  //0,代表普通指针 1,代表优化过 使用位域
          uintptr_t has_assoc : 1;  //是否设置过关联对象,如果没有释放会更快
          uintptr_t has_cxx_dtor : 1;  //是否有c++的析构函数,如果没有释放会更快
          uintptr_t shiftcls : 33;//存储着class meta-class对象的内存地址
          uintptr_t magic : 6; //用于调试时分辨对象是否完成初始化  
          uintptr_t weakly_referenced : 1; //是否有被弱引用,如果没有释放会更快         
          uintptr_t deallocating : 1;  //对象是否正在释放    
          uintptr_t has_sidetable_rc  : 1;  //引用计数器是否过大,如果过大 引用计数会存储在一个Slide Table中        
          uintptr_t extra_rc : 19;  //里面存储的值是引用计数器-1
      }
    }
    
    对isa进行 &ISA_MASK的运算后才得到class/meta-class的地址;
    使用不同的掩码即可得到不同的信息;
    故优化后isa能存储了更加多的信息
    
    tip: 对于class/meta-class 的地址最后三位都是0;
    通过lldb中使用: p/x person->isa  //获取地址信息
    x 0x...  //获取存储的信息
    

    class的结构

    class中主要有 isa、 superclass、 属性、 对象方法、 协议、 成员变量
    meta-class其实是一种特殊的class,结构与class一致

    struct objc_class
    {
      Class isa;
      Class superclass;
      cache_t cache;  //方法缓存
      class_data_bits_t bits;  //用于存储具体的类信息
    }
    通过bits & FAST_DATA_MASK得到class_rw_t
    struct  class_rw_t 
    {
      uint32_t flags;
      uint32_t version;
      const class_ro_t *ro;
      method_list_t *methods;  //方法列表,二维列表,包含了分类
      property_list_t *propertries;  //属性列表,二维列表,包含了分类
      const protocol_list_t *protocols;  //协议列表,二维列表,包含了分类
      Class firstSubclass;
      Class nextSiblingClass;
      char *demangledName;
    }
    struct class_ro_t
    {
      uint32_t flags;
      uint32_t instanceStart;
      uint32_t instanceSize;  //instance占用的内存空间
      const uint8_t * ivarLayout;
      const char *name;  //类名
      method_list_t *baseMethodList; //方法列表,一维数组
      property_list_t *baseProtocols;  //协议列表,一维数组
      const ivar_list_t * ivars; //成员变量列表
    
    }
    
    详细看看method_list_t里面的信息
    struct method_t 
    {
      SEL name;  //函数名
      const char *types;  //编码(包含函数的返回值 参数等信息)
      IMP imp; //指向函数的指针
    }
    SEL代表方法/函数名,一般叫做选择器,底层结构跟char *类似
    可以通过sel_getName和NSStringFromSelector()转成字符串
    不同类中相同名字的方法,所对应的方法选择器是相同的
    
    objc_class中的cache_t cache 是用来缓存曾经调用的方法,它的结构是一个散列表
    struct cache_t
    {
      struct bucket_t *buckets; //散列表
      mask_t _mask;  //散列表长度-1
      mask_t _occupied; //已经缓存方法数量
    }
    
    struct bucket_t {
      cache key_t _key; //SEL作为key  
      IMP _imp;  //函数的内存地址
    }
    
    存储时, 
    使用@selector(methodName) & _mask 得出索引值,将bucket_t存入该索引下;
    如果该索引下已经存在bucket_t,则索引-1地寻找下一个为空的
    缺点:通过牺牲空间来换取效率
    
    获取时,
    通过进行 @selector(methodName) & _mask 得出匹配方法的索引; 
    ! 当得出相同的索引 则索引-1 继续寻找匹配的方法;
    
    同时这也是散列表构建的核心(散列表,也称为哈希表或哈希映射)。
    

    msg_send的原理

    msg_send就是OC中方法调用的底层实现,即OC的消息机制,给receiver(方法调用者)发送一条消息(selector方法名)
    msg_send有三大阶段:消息发送、动态方法解析、消息转发;

    1. 消息发送
    对象方法: 
      先通过isa指针找到类对象,从类对象的方法列表中寻找匹配的方法;
      若类对象中找不到,则通过superclass指针找到类对象的父类,从其方法列表中寻找匹配的方法;
      当全部父类均未找到匹配的方法,则进行下一个阶段-动态方法解析
    
    类方法:
      先通过isa指针找到元类对象,从元类对象的方法列表中寻找匹配的类方法;
      若元类对象中找不到,则通过superclass指针找到元类对象的父类,从其方法列表中寻找匹配的方法;
      当全部父类均未找到匹配的方法,则进行下一个阶段-动态方法解析
    
    消息发送阶段流程图.png
    1. 动态方法解析
      通过开发者实现 +resolveInstanceMethod / +resolveClassMethod进行动态添加方法;
      然后再次走一遍消息发送流程;
    //开发者手动实现 --
    + (BOOL)resolveInstanceMethod:(SEL)sel {
        if (sel == @selector(test)) {
            // 获取其他方法
            Method otherMethod = class_getInstanceMethod(self, @selector(other));
            //动态添加test方法实现
            class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
            
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    + (BOOL)resolveClassMethod:(SEL)sel {
        if (sel == @selector(test2)) {
            //获取元类
            Class metaClass = objc_getMetaClass("Person");
            //获取其他方法
            Method otherMethod = class_getClassMethod(self, @selector(other2));
            //动态添加实现
            class_addMethod(metaClass, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
            return YES;
        }
        return [super resolveClassMethod:sel];
    }
    
    1. 消息转发
      通过开发者实现 forwardingTargetForSelector 返回处理该消息的其他对象;
      若无返回或者无处理,则调取 methodSignatureForSelector返回处理该消息的方法签名,以及 forwardInvocation如何处理
    1-消息转发的第一层处理
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        if (aSelector == @selector(test)) {
            return [[Cat alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    2-消息转发的第二层处理
    //NSMethodSignature方法签名,包含返回值类型、参数类型
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        if (aSelector == @selector(test)) {
            return [[[Cat alloc] init] methodSignatureForSelector:@selector(test)];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    //NSInvocation封装一个方法调用,包括方法调用者、方法名、方法参数
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        [anInvocation invokeWithTarget:[[Cat alloc] init]];
    }
    
    对于类方法的消息转发,以上方法均有对应类方法(流程一致)
    
    消息转发流程图.png

    super

    super调用方法时,底层实现是调取 obj_msgSendSuper({self, [Person class]}, @selector(xxx)),故方法的接收者还是self

    @implementation Student
    
    - (instancetype)init {
        if (self = [super init]) {
            NSLog(@"%@",[self class]);  // Student
            NSLog(@"%@",[self superclass]); // Person
            //obj_msgSendSuper({self, [Person class]}, @selector(class))
            NSLog(@"%@",[super class]); // Student
            NSLog(@"%@",[super superclass]); // Person
        }
        return self;
    }
    
    @end
    
    
    @implementation NSObject
    - (Class)class {
        return object_getClass(self);
    }
    - (Class)superclass {
        return class_getSuperclass(object_getClass(self));
    }
    

    class

    Person *p = [[Person alloc] init];
    NSLog(@"%d",[p isKindOfClass:[Person class]]);  //1
    NSLog(@"%d",[p isMemberOfClass:[Person class]]);    //1
    
    NSLog(@"%d",[Person isKindOfClass:[Person class]]); //0
    NSLog(@"%d",[Person isMemberOfClass:[Person class]]);   //0
            
    NSLog(@"%d",[Person isKindOfClass:[NSObject class]]);   //1 -- 基类的元类的superclass = 基类
    NSLog(@"%d",[Person isMemberOfClass:object_getClass(Person.class)]);    //1
    
    
    • isKindOfClass的实现是使用 object_getClass,传入对象获取到的是类对象,传入类对象获取到的是元类对象;
    • 基类的元类的superclass = 基类
    @interface Person : NSObject
    @property (nonatomic, copy) NSString *name;
    - (void)print;
    @end
    @implementation Person
    - (void)print {
        NSLog(@"my name is %@",self.name);
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        NSString *v = @"123";
        id cls = [Person class];
        void *obj = &cls;
        [(__bridge id)obj print];
        //输出 ->  my name is 123
    }
    

    看完上面代码,我们提出两个问题:

    • q1: 为什么obj可以调用print成功?
    • q2: 为什么self.name 会取出123?
    q1:
    正常的对象调用方法,
     Person *p = [[Person alloc] init];
     [p print];
     这里面 p 指向-> Person的对象, 包括[isa _name], 其中前八位就是isa
     再通过 isa 指向 -> Person class , 从而找到print方法进行执行,
     简化而言走向如下 p -> isa -> Person class -> print
     
     而
     id cls = [Person class];
     void *obj = &cls;
     obj 调用方法时,取出其中前八位恰恰就是cls, 而cls又为 Person class的地址, cls充当了isa的作用,
     obj -> cls -> Person class
     
     通过类比可得出第二种方式本质与正常对象调用一致,故可以执行print成功
    
    
    q2
    栈空间中,分配的地址是从高到低的
     - (void)test {
         NSString *a = @"1"; //0x16d9abaa8
         NSString *b = @"2"; //0x16d9abaa0
         NSString *c = @"3"; //0x16d9aba98
         NSLog(@"%p %p %p", &a, &b, &c);
     }
     
     2 对象调用方法,通过地址去取成员变量
     Person *p的对象中, 前8位是isa 后8位是_name;
     故取self.name时,通过isa指针+8访问到_name;
     
     3 例子中代码
     NSString *v = @"123";
     id cls = [Person class];
     void *obj = &cls;
     所创建的地址值, obj --+8--> cls --+8--> v
     故print方法中访问self.name时, 取isa+8的地址,即cls+8的地址 = NSString *v
    
    

    runtime应用

    //动态创建一个类
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    //注册一个类
    void objc_registerClassPair(Class cls)
    //销毁一个类
    void objc_disposeClassPair(Class cls)
    
    //设置isa指向的Class
    void object_setClass(id obj, Class cls)
    //获取isa指向的class
    void object_getClass(id obj)
    
    //判断一个OC对象是否为Class
    BOOL object_isClass(id obj)
    //判断一个OC对象是否为MetaClass
    BOOL object_isMetaClass(id obj)
    //获取父类
    Class class_getSuperclass(Class cls)
    
    -- 实例变量
    //获取一个实例变量
    Ivar class_getInstanceVariable(Class cls, const char *name)
    
    //拷贝实例变量列表(最后需要调用free释放)
    Ivar *class_copyIvarList(Class cls, unsigned int* outCount)
    --想要查看某个类的内部成员可以使用这个方法
    
    //设置和获取成员变量
    void object_setIvar(id obj, Ivar ivar, id value)
    id object_getIvar(id obj, Ivar ivar)
    
    //动态添加成员变量(已经注册的类是不能动态添加成员变量)
    BOOL class_addIvar(Class cls, const char* name, size_t size, uint8_t alignment, const char *types)
    
    //获取成员变量的相关信息
    const char* ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
    
    -- 方法
    //获取一个实例方法 类方法
    Method class_getInstanceMethod(Class cls, SEL name)
    Method class_getClassMethod(Class cls, SEL name)
    
    //方法实现相关
    IMP class_getMethodImplementation(Class cls, SEL name)
    IMP method_setImplementation(Method m, IMP imp)
    void method_exchangeImplementation(Method m1, Method m2)
    
    //拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class cls, unsinged int *outCount)
    
    //动态添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    
    //动态替换方法
    BOOL class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    
    //
    SEL method_getName(Method m)
    IMP method_getImplementation(Method m)
    const char* method_getTypeEncoding(Method m)
    unsigned int method_getNumberOfArguments(Method m)
    char *method_copyReturnType(Method m)
    char *method_copyArgumentType(Method m, unsigned int index)
    
    -- 选择器相关
    const char *sel_getName(SEL sel)
    SEL sel_registerName(const char *str)
    //用block作为方法的实现
    IMP imp_implementationWithBlock(id block)
    id imp_getBlock(IMP anImp)
    BOOL imp_removeBlock(IMP anImp)
    
    什么是runtime? 平时项目中有用过么?
    • OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
    • OC的动态性就是由runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态相关的函数
    • 平时编写的OC代码,底层都是转成了Runtime API 进行调用

    具体的应用:

    • 利用关联对象(AssociatedObject)给分类添加属性
    • 遍历类的所有成员变量(如 修改非公开的成员变量、字典转模型、自动归解档)
    • 交互方法实现(如 交互系统的方法)
    • 利用消息转发机制解决方法找不到的问题

    相关文章

      网友评论

          本文标题:Runtime

          本文链接:https://www.haomeiwen.com/subject/ysfnfjtx.html