类的结构分析

256 阅读7分钟

类的结构

在项目中搜索objc_class :, 找到 Class 的结构,以下是关于 class 的定义

typedef struct objc_class *Class; //Class是来自于objc_class结构体类型
struct objc_class : objc_object { //同时objc_class是来自于objc_object对象(万物皆对象)
    // Class ISA; // 8Byte   源码中Class ISA被注释,所以隐藏比来源于继承父类.见下代码块(已被苹果公司废弃)
    Class superclass; // 8Byte
    cache_t cache;    // 16Byte 不是8Byte         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { //其包含类的一些信息
        return bits.data();
    }
}

由此可见Class 是由ISA,superclass,cache,bits组成的结构体

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE; //废弃
/* Use `Class` instead of `struct objc_class *` */

isa 就是 class ?

为什么说在外面 isa 就是 class ?在源码中我们得知

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK); //强制转型为 Class 数据结构体
#endif
}

关于 bits 位置

我们又是如何知道cache_t cache是 16 字节?

struct cache_t {
    struct bucket_t *_buckets; // 8字节   注意:这里是*_buckets,因为有*,所以是指针!指针占 8 字节!
    mask_t _mask;  // 4字节  类型是typedef uint32_t mask_t  int 占 4 字节
    mask_t _occupied; // 4字节  同理类型是typedef uint32_t mask_t   int 占 4 字节
}

关于struct bucket_t的源码,我们可知为何是 8 字节

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif
}

综上所述,在struct objc_class中, Class ISA占 8 字节,Class superclass占 8 字节,cache_t cache占 16 字节.Class 是由ISA,superclass,cache,bits组成的结构体,那剩下的 bits 我们如何找到.

LGPerson.h
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;

LGperson.m
#import "LGPerson.h"

@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}


@end
main.m
LGPerson *person = [LGPerson alloc];

(lldb): x/4gx person.class   //查看 person.class 内存结构
0x100002328: 0x001d800100002301 0x0000000100b00140  //这个是 person.class 的首地址,也就是我们上面说的objc_class,为了得到 bits,前面经历了 Class ISA,Class superclass,cache_t cache总共 32 字节,32 转为 16 进制是 20,所以根据首地址,我们要加上 0x20,0x100002348
0x100002338: 0x0000000100f5e3f0 0x0000000200000003  
(lldb) p (class_data_bits_t *)0x100002348  //注意因为 bits 不是对象,不能用 po ,要进行强转,得到$1
(class_data_bits_t *) $1 = 0x0000000100002348 //得到其指针
 p $1->data()  //因为 bits 内部结构含有 rw 实现 rw,即 class_rw_t -> data,调用 data
(class_rw_t *) $2 = 0x0000000101a19020

类的存储

为什么我们要找 bits,通过源码可以知道,结构体class_rw_t包含了类中methods,properties以及protocols信息,源码如下


struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro; //ro 存在于 rw 结构体中

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
}

打印$2,我们就可以得到下面的结构信息内容

(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002278
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x00000001000021c8
        arrayAndFlag = 4294975944
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100002260
        arrayAndFlag = 4294976096
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000 <no value available>
}

在这里需要注意:

class_rw_t 是在运行时来拓展类的一些属性、方法和协议等内容。

class_ro_t 是在编译时就已经确定了的,存储的是类的成员变量、属性、方法和协议等内容

所以应该取 ro,即查到所有属性和方法

在上面代码块中我注释到ro 存在于 rw 结构体中,所以我们只查看class_ro_t在源码中的体现

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;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

取值 ro

(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002278
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f82 "\x02"
  name = 0x0000000100001f79 "LGPerson"
  baseMethodList = 0x00000001000021c8
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100002218
  weakIvarLayout = 0x0000000000000000 <no value available>
  baseProperties = 0x0000000100002260
}
(lldb) p $5.baseProperties
(property_list_t *const) $6 = 0x0000000100002260
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "nickName", attributes = "T@"NSString",C,N,V_nickName")
  }
}

但不要高兴太早,即使找到了 nickName,但是仍旧找不到hobby.为什么这么说,因为在$7 中,count = 1. 仅存在唯一 nickName.

注意!注意!注意! --- ivars

(lldb) p $5.ivars
(const ivar_list_t *const) $8 = 0x0000000100002218
(lldb) p *$8
(const ivar_list_t) $9 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000022f8
      name = 0x0000000100001e88 "hobby"
      type = 0x0000000100001f9f "@"NSString""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $9.get(1)
(ivar_t) $10 = {
  offset = 0x00000001000022f0
  name = 0x0000000100001e8e "_nickName"
  type = 0x0000000100001f9f "@"NSString""
  alignment_raw = 3
  size = 8
}

所以属性和成员变量在不同地方. 但是为什么 ivars 中即存在成员变量也存在属性?因为属性也会编译成 _成员变量,可能你会问属性和成员变量有什么区别?

属性 = getter + setter + _成员变量

我们找到了属性和成员变量,接下来瞧一瞧方法在bits哪里

(lldb) p $4->baseMethodList
(method_list_t *const) $11 = 0x00000001000021c8
(lldb) p *$11
(method_list_t) $12 = {
  entsize_list_tt<method_t, method_list_t, 3> = { //注意method_list_t,下面会提供源码,清晰理解method_list_t结构
    entsizeAndFlags = 26
    count = 3
    first = {
      name = "nickName"
      types = 0x0000000100001f8c "@16@0:8"
      imp = 0x0000000100001c30 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
    }
  }
}

附上method_list_t结构体

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

此时该环境下,我已提前注释掉了- (void)sayHello;+ (void)sayHappy;那为什么count = 3?

(lldb) p $12.get(0)
(method_t) $15 = {
  name = "nickName"  //get
  types = 0x0000000100001f8c "@16@0:8"
  imp = 0x0000000100001c30 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $12.get(1)
(method_t) $16 = {
  name = "setNickName:"  //set
  types = 0x0000000100001f94 "v24@0:8@16"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
(lldb) p $12.get(2)
(method_t) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f84 "v16@0:8"
  imp = 0x0000000100001ca0 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}

这就验证了我们上面说的 属性 = getter + setter + _成员变量, 属性是包含getter与setter方法的.

那么看一下类方法与实例方法的区别,打开- (void)sayHello;+ (void)sayHappy,重新断点,lldb.

(lldb) x/4gx person.class
0x1000023a0: 0x001d800100002379 0x0000000100b00140
0x1000023b0: 0x0000000100f6b540 0x0000000200000003
(lldb) p (class_data_bits_t *)0x1000023c0
(class_data_bits_t *) $1 = 0x00000001000023c0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001018077a0
(lldb) p $2->ro
(const class_ro_t *) $3 = 0x00000001000022f0
(lldb) p $3->baseMethodList
(method_list_t *const) $4 = 0x0000000100002228
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "sayHello"
      types = 0x0000000100001f84 "v16@0:8"
      imp = 0x0000000100001ba0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}
(lldb) p $5.get(0)
(method_t) $6 = {
  name = "sayHello"
  types = 0x0000000100001f84 "v16@0:8"
  imp = 0x0000000100001ba0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
(lldb) p $5.get(1)
(method_t) $7 = {
  name = "nickName"
  types = 0x0000000100001f8c "@16@0:8"
  imp = 0x0000000100001c00 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $5.get(2)
(method_t) $8 = {
  name = "setNickName:"
  types = 0x0000000100001f94 "v24@0:8@16"
  imp = 0x0000000100001c30 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}
(lldb) p $5.get(3)
(method_t) $9 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f84 "v16@0:8"
  imp = 0x0000000100001c70 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}

由此可见 count = 4,打印 4 个方法,我们看到了实例方法sayHello,setter,getter,C++析构函数,但并没有找到sayHappy.证明实例方法与类方法并没有放在一起.

所以下面我们探索一下方法在类中的存储,利用 runtime 提供的 API 尝试调用

void testInstanceMethod_classToMetaclass(Class pClass){

    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}
输出:
2019-12-24 17:37:16.038499+0800 LGTest[7851:1660992] 0x100002240-0x0-0x0-0x1000021d8
2019-12-24 17:37:16.038712+0800 LGTest[7851:1660992] testInstanceMethod_classToMetaclass

我们从testInstanceMethod_classToMetaclass方法中得到了类和元类中的实例方法和类方法

sayHello在中的指针是0x100002240

sayHello在元类中的指针是0x0

sayHappy在中的指针是0x0

sayHappy在元类中的指针是0x1000021d8

由此可见,sayHello 实例方法存在于类对象中的内存中;sayHappy 类方法存在于元类对象中的内存中


总结

  • 通过探寻Class 的结构得知objc_class是来自于objc_object对象( 万物皆对象 )
  • Class 是由ISA,superclass,cache,bits组成的结构体
  • 通过return (Class)(isa.bits & ISA_MASK)得知isa强制转型为 Class 数据结构
  • class_rw_t 是在运行时来拓展类的一些属性、方法和协议等内容。
  • class_ro_t 是在编译时就已经确定了的,存储的是类的成员变量、属性、方法和协议等内容
  • 属性 = getter + setter + _成员变量
  • 实例方法存在于类对象中的内存中
  • 类方法存在于元类对象中的内存中