《OC底层系列五》-类的结构分析

前言

  • 通过上一篇 《OC底层系列四》-isa&superclass分析》中我们分析了isa和superclass的走向,知道了:
    • OC类的isa(其位域成员shiftcls)存储着其元类的信息
    • NSObject类的superclass指向nil,NSObject元类的superclass指向NSObject类
  • 今天对类的结构进行分析,探究类的属性、成员变量、实例方法、类方法在底层的实现。

目录

目录.png

1、简介

本文主要从结合底层源码,结合lldb来分析探究类的属性、成员变量、实例方法、类方法在底层的实现。

2、类的结构分析

OC类的底层为objc_class的结构体,对类进行探索,即分析objc_class包含成员的和作用。
781源码中关于objc_class定义如下:

// objc-runtime-new.h
struct objc_class : objc_object {
    // Class ISA; 继承自objc_object,包含isa
    Class superclass;  // 父类指针
    cache_t cache;             // formerly cache pointer and vtable,用于缓存指针和 vtable 
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
                               // class_rw_t 指针加上 rr/alloc 的标志
                               // bits 用于存储类的方法、属性、遵循的协议等信息的地方
    // 返回一个 class_rw_t 类型的结构体变量
    // Objective-C 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
     class_rw_t *data() const {
        return bits.data();
    }
...
}

我们要探究的类的属性、方法等都在bits里面,为了能够获取到bit的内存信息,我们需要计算出isa、superclass、cache占的大小,然后运用内存偏移获取到bits内存信息。

2.1、isa

isa继承自objc_object,因此第1个成员为isa,前面我们分析过其占8字节

2.2、superclass

superclass为Class类型,即一个指向objc_class结构体的指针,占8字节

// objc.h
typedef struct objc_class *Class;

2.3、cache

是一个cache_t的成员,cache_t定义关键代码如下如下:

struct cache_t {
// 非ARM设备
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets;  // 结构体指针,8字节
    explicit_atomic _mask; // mask_t即uint32_t类型,4字节

// ARM64位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _maskAndBuckets;  // uintptr_t即unsigned long类型,8字节
    mask_t _mask_unused; // mask_t即uint32_t类型,4字节

// 非ARM64位,即ARM32位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic _maskAndBuckets; // uintptr_t即unsigned long类型,8字节
    mask_t _mask_unused; // mask_t即uint32_t类型,4字节
#else
#error Unknown cache mask storage type.
#endif
  
// uint16_t即unsigned short类型,占2字节

// 64位系统下
#if __LP64__
    uint16_t _flags;
#endif

    uint16_t _occupied;

cache_t是一个结构体,缓存指针和函数表,由注释可以知道,无论是ARM64、ARM32还是模拟器环境下,其成员所占字节依次为8、4、2、2字节,根据内存对齐(参考带你深入理解iOS-内存对齐)可以知道,cache占16字节

2.4、bits

  • 综上我们可以知道,将类的首地址偏移32字节即可得到bits的内存地址信息。
  • bits是一个class_data_bits_t类型的结构体指针,根据内存偏移可以得出class_data_bits_t的地址为:
    • 方式一:通过**p/x Person.class **计算得出类首地址然后+32得出。
    • 方式二:使用x/6gx 获取其类首地址开始6个8字节可以得出其内存地址

对Person和main做了修改,重新运行了项目,对应的内存地址发生了变换,但是不影响对bits的分析:

// Person.h
@interface Person : NSObject
{
    NSString *_title;
}

@property(nonatomic,copy) NSString *name;

- (void)updateTitle:(NSString *)title;

@end

// Person.m
@implementation Person

- (void)updateTitle:(NSString *)title {
    _title = title;
}

@end

// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *p1 = [[Person alloc] init];
        p1.name = @"jack";
        [p1 updateTitle:@"Teacher"];
        
        NSLog(@"Hello, World!,p1:%p",[p1 class]);
        
    }
    return 0;
}

使用方式二,log处添加断点,lldb验证如下:

// 打印Person首地址开始的6个8字节内存空间存储的值
(lldb) x/6gx Person.class
// 1-16字节
0x100008208: 0x00000001000081e0 0x0000000100333028
// 16-32字节
0x100008218: 0x0000000102028c10 0x0001802400000007
// 32-48字节,0x100008208即bits的内存地址,0x100008228即bits的内存地址
0x100008228: 0x0000000102028654 0x00000001000ac900
// 强转为class_data_bits_t指针
(lldb) p (class_data_bits_t *)0x100008208
(class_data_bits_t *) $1 = 0x0000000100008228
// 获取bits的data()
(lldb) p/x  $1->data()
(class_rw_t *) $2 = 0x0000000101b04670
// 打印bits中的data信息
(lldb) p *$2
class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = {
      Value = 4295000232
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

我们获取了objc_classdata()方法返回的内容,但是没有看到属性列表、方法等,data()返回一个class_rw_t的结构体指针,关键代码定义如下

// objc-runtime-new.h
struct class_rw_t {
...
    // 存储类的成员变量
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is())) {
            return v.get()->ro;
        }
        return v.get();
    }

  // 存储类的实例方法
  const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->methods;
        } else {
            return method_array_t{v.get()->baseMethods()};
        }
    }

    // 存储类的属性
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->properties;
        } else {
            return property_array_t{v.get()->baseProperties};
        }
    }

    // 存储类的协议
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->protocols;
        } else {
            return protocol_array_t{v.get()->baseProtocols};
        }
    }
// 类方法存储在元类中
}

笔者已经把探索的结果写在了上面的注释里,下面通过lldb来进行验证。

2.4.1 属性

class_rw_tproperties() 方法返回一个property_array_t继承自list_array_tt的c++类,类的属性就存储在list中。

class list_array_tt {
...
private:
    union {
        List* list;
        uintptr_t arrayAndFlag;
    };
...
}

lldb验证:

(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = {
      Value = 4295000232
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

// 返回类中的属性列表信息,一个property_array_t类
(lldb) p $3.properties()
(const property_array_t) $4 = {
  list_array_tt = {
     = {
      list = 0x00000001000081a0
      arrayAndFlag = 4295000480
    }
  }
}

// 返回数组list的指针
(lldb) p $4.list
(property_list_t *const) $5 = 0x00000001000081a0

// 获取数组list存储的信息
(lldb) p *$5
(property_list_t) $6 = {
  entsize_list_tt = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}

// 获取第一个属性
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
// 获取第二个属性,越界,说明不存在
(lldb) p $6.get(1)
Assertion failed: (i < count), function get, file /Users/a002/PrivateReposity/github/xxerz/objc4/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 通过上面的lldb打印结果,可以知道类的属性存储路径为:
    Person类属性列表->objc_class.bits->class_rw_t.properties()->property_list_t.list->list,通过objc_class中的bits获取到类的属性列表
2.4.2 成员变量

通过class_rw_t中的ro()方法获取,ro()返回一个class_ro_t结构体指针,其class_ro_t结构体中的ivars存储类的成员变量列表
class_ro_t定义如下:

// objc-runtime-new.h
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;

验证ivars中存储着类的成员变量:

// 获取class_rw_t中的ro()返回值,返回一个结构体指针
lldb) p $3.ro()
(const class_ro_t *) $8 = 0x00000001000080a8
// 打印class_ro_t结构体的内容
(lldb) p *$8
(const class_ro_t) $9 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100003f7f "\x02"
  name = 0x0000000100003f78 "Person"
  baseMethodList = 0x00000001000080f0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008158
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000081a0
  _swiftMetadataInitializer_NEVER_USE = {}
}
// 获取类的成员变量列表的指针
(lldb) p $9.ivars
(const ivar_list_t *const) $10 = 0x0000000100008158
(lldb) p *$10
// 获取成员变量列表
(const ivar_list_t) $11 = {
  entsize_list_tt = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000081d0
      name = 0x0000000100003f1a "_title"
      type = 0x0000000100003f89 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
// 获取第一个成员变量
(lldb) p $11.get(0)
(ivar_t) $12 = {
  offset = 0x00000001000081d0
  name = 0x0000000100003f1a "_title"
  type = 0x0000000100003f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
// 获取第二个成员变量
(lldb) p $11.get(1)
(ivar_t) $13 = {
  offset = 0x00000001000081d8
  name = 0x0000000100003f21 "_name"
  type = 0x0000000100003f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  • 通过p $9.ivars获取到了成员变量列表的指针。
  • p *$10中获取成员变量列表,其存储在ivar_list_t结构体中,count=2说明有2个成员变量,依次打印为_title,_name
  • 我们可以得到Person类的成员变量存储路径为:
    Person类成员变量列表->objc_class.bits->class_rw_t.ro()->class_ro_t.ivars->ivars
2.4.3 实例方法

class_rw_tmethods()返回该一个method_array_t类,其list成员中可以获取到类的实例方法。
验证过程如下:

// 获取class_rw_t中的methods返回值,返回一个method_array_t的类,继承自list_array_tt
(lldb) p $3.methods()
(const method_array_t) $14 = {
  list_array_tt = {
     = {
      list = 0x00000001000080f0
      arrayAndFlag = 4295000304
    }
  }
}

// 获取method_array_t中的list指针
(lldb) p $14.list
(method_list_t *const) $15 = 0x00000001000080f0

// 打印list内容,list即存储着类的实例方法信息
(lldb) p *$15
(method_list_t) $16 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    // 有4个实例方法  
    count = 4
    first = {
      name = ".cxx_destruct"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
    }
  }
}

(lldb) p $16.get(0)
(method_t) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
}
(lldb) p $16.get(1)
(method_t) $18 = {
  name = "name"
  types = 0x0000000100003fa0 "@16@0:8"
  imp = 0x0000000100003dd0 (objc4-test`-[Person name])
}
(lldb) p $16.get(2)
(method_t) $19 = {
  name = "setName:"
  types = 0x0000000100003f95 "v24@0:8@16"
  imp = 0x0000000100003e00 (objc4-test`-[Person setName:])
}
(lldb) p $16.get(3)
(method_t) $20 = {
  name = "updateTitle:"
  types = 0x0000000100003f95 "v24@0:8@16"
  imp = 0x0000000100003d80 (objc4-test`-[Person updateTitle:])
}
  • 通过p *$15获取到了类的实例方法列表** method_list_t**。
  • p *$15打印方法列表信息,由count=4得知有4个方法,依次为
    -[Person .cxx_destruct]、-[Person . name]、-[Person . setName:]、-[Person .updateTitle:]
2.4.4 类方法
  • 类的实例方法存储在类中,那么类的类方法存储在哪里?
  • 前面分析isa走位的时候提到了元类类的isa指向元类
    类的实例的isa指向类,且类的实例方法存储在类的bits中
    我们可以推测类的类方法存储在元类的bits里
    验证如下:
// 获取类的指针
lldb) p/x Person.class
(Class) $0 = 0x0000000100008208 Person

// 获取类的内存信息
(lldb) x/4gx 0x0000000100008208
0x100008208: 0x00000001000081e0 0x0000000100333028
0x100008218: 0x0000000100693f70 0x0001802400000007
// 获取元类的指针
(lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000081e0

// 获取元类的内存信息
(lldb) x/6gx 0x00000001000081e0
0x1000081e0: 0x0000000100333000 0x0000000100333000
0x1000081f0: 0x0000000100693ff0 0x0001e03500000007
// 0x100008200即元类的bits指针
0x100008200: 0x0000000100693e94 0x00000001000081e0

// 强转为class_data_bits_t *类型
(lldb) p (class_data_bits_t *)0x100008200
(class_data_bits_t *) $2 = 0x0000000100008200

// 获取元类的bits信息
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100693e90

// 打印元类的bits信息
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic = {
      Value = 4295000128
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff900cdcd8
}
// 获取元类的示例方法列表,返回一个method_array_t类
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt = {
     = {
      list = 0x0000000100008088
      arrayAndFlag = 4295000200
    }
  }
}

// 获取method_array_t中list的内容,返回一个method_list_t结构体指针
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100008088
// 打印list内容
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt = {
    entsizeAndFlags = 26
    // 有一个实例方法
    count = 1
    first = {
      name = "eat"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003d70 (objc4-test`+[Person eat])
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "eat"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003d70 (objc4-test`+[Person eat])
}

  • 结果证明了类的类方法存储在其元类的bits里Person元类中的示例方法只有一个,为+[Person eat]

3、总结

  • 本文使用lldb命令分析了类的属性、成员变量、实例方法、类方法在OC底层如何获取。
  • 类的属性:通过@property定义的属性,存储在类的bits里,路径为
    objc_class.bits->class_data_bits_t.data()->class_rw_t.properties()->property_array_t.list->list
  • 类的成员变量:通过{}定义的成员变量,存储在类的bits里,路径为
    objc_class.bits->class_data_bits_t.data()->class_rw_t.ro()->class_ro_t.ivars->ivars
  • 类的实例方法:存储在类的bits里路径为
    objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list
  • 类的类方法:存储在元类bits里,路径为
    该类的元类的objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list

你可能感兴趣的:(《OC底层系列五》-类的结构分析)