美文网首页
六 OC 底层原理 类的结构

六 OC 底层原理 类的结构

作者: 可乐冒气 | 来源:发表于2020-12-14 23:06 被阅读0次

    前言

    当一个相同的类创建多个对象时,那么这么对象的类是不是创建了多个呢

    Class d1 = [Desk class];
    Class d2 = [Desk alloc].class;
    Class d3 =  object_getClass([Desk alloc]);
    Class d4 = [Desk alloc].class;
    NSLog(@"%p - %p - %p - %p", d1, d2, d3, d4);
    
    --------------
    Room[42703:2055484] 0x1000080f0 - 0x1000080f0 - 0x1000080f0 - 0x1000080f0
    
    

    类对象地址显然相同, 所以 类在内存空间中只存了一份

    回顾

    上篇我们知道类在底层一个继承于 objc_object 的对象,也就是object_class 结构体

    //截取其中一部分
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() const {
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    
        void setInfo(uint32_t set) {
            ASSERT(isFuture()  ||  isRealized());
            data()->setFlags(set);
        }
    }
    

    我们知道 objc_class 第一个内存是 isasuperclass 存储的父类信息,占据8个字节,cache 缓存占用 16 字节, bits 存储了类的方法,属性,协议等信息的地方,具体的注释表示 class_data_bits_t 相当于 class_rw_t 指针加上 rr/alloc 的标识

    class_data_bits_t结构体返回了 class_rw_t 指针,那 class_rw_t 又是什么呢?

    就在代码旁简单谢谢介绍
    
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
    
        explicit_atomic<uintptr_t> ro_or_rw_ext;
        // 当前所属类 第一个子类
        Class firstSubclass;
        // 当前所有类 的兄弟类
        Class nextSiblingClass;
    
    private:
        // ro_or_rw_ext_t会有两种情况:
        // 1): 值是 class_ro_t *
        // 2): 值是 class_rw_ext_t *,
        //          而 class_ro_t * 作为 class_rw_ext_t 的 const class_ro_t *ro 成员变量保存
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
        const ro_or_rw_ext_t get_ro_or_rwe() const {
              return ro_or_rw_ext_t{ro_or_rw_ext};
        }
        ... 此处省略
    }
    // class_rw_ext_t 其中 rw 可以理解为 read write
    struct class_rw_ext_t {
        // 存放只读类型
        const class_ro_t *ro;
        // 方法列表
        method_array_t methods;
        // 属性列表
        property_array_t properties;
        // 协议列表
        protocol_array_t protocols;
        // 所属类名
        char *demangledName;
        // 版本号
        uint32_t version;
    };
    
    // class_ro_t 其中 ro 可以理解为 read only
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
        ... 此处省略
    };
    
    

    可以看出 class_rw_t 存放者类的属性 协议 方法等信息

    现在我们验证一下类的成员变量 属性存储的位置

    准备

    @interface TObject : NSObject
    {
        NSString *tname;
    }
    @property (nonatomic, copy) NSString* toName;
    
    + (void) classMethodTest;
    - (void) instanceMethodTest;
    @end
    
    @implementation TObject
    +(void)classMethodTest{
        NSLog(@"%s",__func__);
    }
    -(void)instanceMethodTest{
        NSLog(@"%s",__func__);
    }
    @end
    

    成员变量 / 属性 存储的位置

    接下我 我们就要利用 lldb 调试 一步一步验证

    1. 我们先要获取类对象的内存地址,通过类结构分析 class_data_bits_t bits 字段之前 还有32 字节
      所以我们需要通过内存便宜获取 bits的内存地址
      在这我们介绍一个命令 x/5gx 其中第一个 x 表示读取内存,5表示分成5段,g 表示一个单元8个字节,x 表示16进制, 所以其中第5个地址 就是 bits 的地址
    (lldb) p obj1.class
    (Class) $0 = TObject
    (lldb) x/5gx $0
    0x1000021f0: 0x00000001000021c8 0x0000000100333140
    0x100002200: 0x00000001018335c0 0x0001802400000007
    0x100002210: 0x00000001006656a4
    (lldb) 
    
    1. 我们现在需要将 地址强转为 class_data_bits_t 类型,并通过 data() 函数 获取 class_rw_t
    (lldb) p (class_data_bits_t*)0x100002210
    (class_data_bits_t *) $1 = 0x0000000100002210
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x000020a000000000
    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975648
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    1. 此时我们调用class_rw_t 结构体中的 properties() 获取属性列表,可以看到属性名称 toName
    (lldb) p $3.properties()
    (const property_array_t) $4 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          list = 0x0000000100002198
          arrayAndFlag = 4294975896
        }
      }
    }
    (lldb) p $4.list
    (property_list_t *const) $5 = 0x0000000100002198
    p *$5
    (property_list_t) $6 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1
        first = (name = "toName", attributes = "T@\"NSString\",C,N,V_toName") // 其中 C 代Copy,N 代表 nonatomic
      }
    }
    
    1. 此时 我们的成员变量又去哪了呢,接下来 我们接着调用 class_rw_t 的 ro属性,并获取 ro 的 ivar
    (lldb) p $3.ro()
    (const class_ro_t *) $9 = 0x00000001000020a0
    (lldb) p $9->ivars
    (const ivar_list_t *const) $10 = 0x0000000100002150
    (lldb) p *$10
    (const ivar_list_t) $12 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x00000001000021c0  // 偏移量
          name = 0x0000000100000eb9 "tname" // 名称
          type = 0x0000000100000f8a "@\"NSString\"" // 类型
          alignment_raw = 3 // 8字节对齐
          size = 8 // 内存占用大小
        }
      }
    }
    

    由此可见 我们的 属性是存在 class_rw_t 中的 属性列表中的,而成员变量是存储在 class_ro_t 中的 ivars ,成员变量列表中的

    实例方法 / 类方法 的存储位置

    1. 接着上面的调用,我们先获取class_rw_t的方法列表 methods(),在读取 列表信息
    (lldb) p $3.methods()
    (const method_array_t) $7 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000020e0
          arrayAndFlag = 4294975712
        }
      }
    }
    (lldb) p $7.list
    (method_list_t *const) $8 = 0x00000001000020e0
    (lldb) p *$8
    (method_list_t) $9 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 3
        first = {
          name = "instanceMethodTest"
          types = 0x0000000100000f34 "v16@0:8"
          imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
        }
      }
    }
    (lldb) 
    

    由此可见我们的实例方法确实是存储在 class_rw_t 中的方法列表中的

    1. 现在找找我们的类方法存到哪了, 先看看 class_ro_t 中的baseMethodList
    lldb) p $3.ro()
    (const class_ro_t *) $10 = 0x0000000100002098
    (lldb) p $10->baseMethodList
    (method_list_t *const) $12 = 0x00000001000020e0
    (lldb) p *$12
    (method_list_t) $13 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 3
        first = {
          name = "instanceMethodTest"
          types = 0x0000000100000f34 "v16@0:8"
          imp = 0x0000000100000db0 (Objc_alloc`-[TObject instanceMethodTest])
        }
      }
    }
    (lldb) 
    

    我们看到 在 class_ro_t 中的方法列表中,还是存储着我们的实例方法信息
    那我们的类方法到底去哪了呢,通过 isa 的继承关系 推导,会不会在我们的元类中呢

    1. 现在我们开始获取元类地址,然后获取元类的 class_rw_t 中的方法列表中,流程和上面的是一样的
    (lldb) p objc_getMetaClass(object_getClassName(obj1))
    (Class) $14 = 0x00000001000021a8
    (lldb) x/5gx $14
    0x1000021a8: 0x00000001003330f0 0x00000001003330f0
    0x1000021b8: 0x0000000101925020 0x0001e03500000007
    0x1000021c8: 0x00000001006ce2e4
    (lldb) p (class_data_bits_t*)0x1000021c8
    (class_data_bits_t *) $15 = 0x00000001000021c8
    (lldb) p $15->data()
    (class_rw_t *) $17 = 0x00000001006ce2e0
    (lldb) p *$17
    (class_rw_t) $18 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975536
      }
      firstSubclass = nil
      nextSiblingClass = 0x00007fff89e08cf8
    }
    (lldb) p $18.methods
    (const method_array_t) $19 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100002078
          arrayAndFlag = 4294975608
        }
      }
    }
      Fix-it applied, fixed expression was: 
        $18.methods()
    (lldb) p $19.list
    (method_list_t *const) $20 = 0x0000000100002078
    (lldb) p *$20
    (method_list_t) $21 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 1
        first = {
          name = "classMethodTest"
          types = 0x0000000100000f34 "v16@0:8"
          imp = 0x0000000100000d80 (Objc_alloc`+[TObject classMethodTest])
        }
      }
    }
    (lldb) 
    

    由此可见 我们的类方法是存储在对象所属类的元类当中的方法列表

    总结

    这是一张类的结构图


    image.png
    • 方法
      • 实例方法确实是存储在 class_rw_t 中的methods中的
      • 类方法存储在 当前对象所属类的元类中的 class_rw_t 中的methods
    • 属性
      • 对象属性 -> class_rw_t中的properties
      • 成员变量 -> class_rw_t -> class_ro_t *ro -> ivars

    补充 - 验证以上

    1. 例1 通过class_getInstanceMethod 获取类的实例方法
    Class class = TObject.class;
    const char *className = class_getName(class);
    Class metaClass = objc_getMetaClass(className);
     // ---  class_getInstanceMethod
    Method method1 = class_getInstanceMethod(class, @selector(instanceMethodTest));
    Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethodTest));
    
    Method method3 = class_getInstanceMethod(class, @selector(classMethodTest));
    Method method4 = class_getInstanceMethod(metaClass, @selector(classMethodTest));
            
    NSLog(@"class_getInstanceMethod - %p-%p-%p-%p",method1,method2,method3,method4);
    ===========
     TObjc[4080:382660] class_getInstanceMethod - 0x100003d51-0x0-0x0-0x100003d39
    

    class_getInstanceMethod返回的是类的实例方法,如果类或者父类中没有找到,就返回nil

    /***********************************************************************
    * class_getInstanceMethod.  Return the instance method for the
    * specified class and selector.
    **********************************************************************/
    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        // This deliberately avoids +initialize because it historically did so.
    
        // This implementation is a bit weird because it's the only place that 
        // wants a Method instead of an IMP.
    
    #warning fixme build and search caches
            
        // Search method lists, try method resolver, etc.
        lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    
    #warning fixme build and search caches
    
        return _class_getMethod(cls, sel);
    }
    
    

    以上 method1 和 method4 是有值的,进一步验证了 实例方法存在类中,类方法存在元类中,
    也给了我们另一个定义 也就是说 类方法,对于元类来说 其实就是一个实例方法

    1. 例2 获取 类方法 class_getClassMethod
    // ---  class_getClassMethod
    Method method5 = class_getClassMethod(class, @selector(instanceMethodTest));
    Method method6 = class_getClassMethod(metaClass, @selector(instanceMethodTest));
    
    Method method7 = class_getClassMethod(class, @selector(classMethodTest));
    Method method8 = class_getClassMethod(metaClass, @selector(classMethodTest));
            
    NSLog(@"class_getClassMethod - %p-%p-%p-%p",method5,method6,method7,method8);
    ========
    2020-12-15 21:54:56.966095+0800 TObjc[4080:382660] class_getClassMethod - 0x0-0x0-0x100003d39-0x100003d39
    

    class_getClassMethod 这个方法 其实就是使用元类 获取实例方法

    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
    
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    Class getMeta() {
      if (isMetaClass()) return (Class)this;
      else return this->ISA();
    }
    

    其实都是走的class_getInstanceMethod 函数,method7 之所以会有值,也是因为 getMeta()这个函数返回元类的原因, 然而当 传入的是元类的话 就会返回本身,这也就意味着 method7method8其实是一样的

    3.例3 获取方法实现class_getMethodImplementation

    // 寻找方法实现 class_getMethodImplementation
    IMP imp1 = class_getMethodImplementation(class, @selector(instanceMethodTest));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethodTest));
    
    IMP imp3 = class_getMethodImplementation(class, @selector(classMethodTest));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethodTest));
    NSLog(@" class_getMethodImplementation %p-%p-%p-%p",imp1,imp2,imp3,imp4);
    
    =======
    2020-12-15 21:54:56.966114+0800 KCObjc[4080:382660]  class_getMethodImplementation 0x100003ab4-0x199ce6600-0x199ce6600-0x100003a7c
    

    其中有一个很重要的东西 _objc_msgForward 消息转发

    IMP class_getMethodImplementation(Class cls, SEL sel)
    {
        IMP imp;
    
        if (!cls  ||  !sel) return nil;
    
        imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
    
        // Translate forwarding function to C-callable external version
        if (!imp) {
            return _objc_msgForward;
        }
    
        return imp;
    }
    
    

    所以 imp2imp3 是没有找到的,只是进行了消息转发,所以打印都是有值的

    class_getInstanceMethod:返回的是类的实例方法,如果在传入的类或者类的父类中没有找到指定的实例方法,则返回空。
    class_getClassMethod:获取类方法,本质上还是调用 class_getInstanceMethod,不过在这其中的参数 变成了元类,如果传入的类或者父类中 对应的元类中没有找到,那就返回空
    class_getMethodImplementation: 寻找方法实现,如果在对应的类,或者元类中没有找到,那就进行消息转发

    相关文章

      网友评论

          本文标题:六 OC 底层原理 类的结构

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