• 1

  • 482

  • 收藏

手撕iOS底层09 -- 类的结构深入剖析

1个月前

手撕iOS底层09 -- 类的结构深入剖析

通过前几篇文章 ,了解了对象内存里的布局, 从对象isa找到类 objc_class,那今天这篇文章主要探讨下类的内存布局


0x00 - 内存偏移

首先还是复习一个基础知识内存偏移,再引出今天的主要内容。

int a[4] = {1,2,3,4};
int *pa = a;
for(int i = 0; i < 4; i++) {
  int tmp = pa + i;
  print("%d \n", *tmp);
}
复制代码

以上是一段简单的c代码, 创建一个数组, 数组名是数组的首地址, 第一个元素也是这个数组的首地址。

然后把数组的首地址赋值给一个指向 和数组元素相同类型的指针变量. int *pa = &a[0] 简写:int *pa = a

之后可以直接通过对指针变量+++1的操作,访问数组的元素。

通过对数组的内存偏移访问内存中相邻元素的数据,可以引申出在结构体中通过这种方式访问相邻数据。通过这种方式方便我们直观的去探索底层结构。


0x01 - 内存平移探索class_data_bits_t bits;

struct objc_object {
private:
    isa_t isa;
  .....
}

struct objc_class : objc_object { // 继承自objc_object
    // Class ISA;       // 8字节
    Class superclass;   // 8字节
    cache_t cache;      // 16字节        // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags 属性 方法 协议 //8
		class_rw_t *data() const {
        return bits.data();
    }
  	.....
}

typedef struct objc_class *Class;

复制代码

objc_class中, 想通过内存偏移得到bits的数据结构, 就要知道前几个成员变量 ISA, superclass cache 的类型大小。

  • 因为Class是指针类型,所以 ISAsuperclass的大小都是8,所以着重分析下cache_t类型的大小:
#ifndef _UINT32_T
#define _UINT32_T
typedef unsigned int uint32_t;
#endif /* _UINT32_T */

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED // 表示运行的环境 模拟器 或者 macOS
    explicit_atomic<struct bucket_t *> _buckets; //8 指针占8字节
    explicit_atomic<mask_t> _mask; // 4 通过上边的宏得知是uint32_t 占4个字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 //表示运行环境是arm64架构64位的真机
    explicit_atomic<uintptr_t> _maskAndBuckets; // typedef unsigned long uintptr_t;  8个字节
    mask_t _mask_unused; // 4 
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4 // 表示运行环境是arm64架构非64位的真机
    // _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<uintptr_t> _maskAndBuckets; // 8
    mask_t _mask_unused; // 4

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags; // 2
#endif
    uint16_t _occupied; //2
  .......

复制代码
  • 通过运行源码, cache结构体内部走CACHE_MASK_STORAGE_OUTLINED执行这个宏编译。
  • _buckets + _mask + _flags + _occupied = 8+4+2+2 = 16

想通过内存偏移得到bits, 就要在地址的基础上偏移ISA+superclass + cache = 8+8+16 = 32 ⚠️ 因为要在地址上相加,地址16进制,所以把32要转成16进制为20


// 测试代码
@interface Test : NSObject
{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickname;
- (void)hello;
+ (void)happy;
@end
复制代码

  • 通过得到Test.class类的内存布局,通过首地址平移到bits字段,通过调用bitsdata()方法,获得class_rw_t *这个指针类型,再打印,因为没有其它类继承这个类,所以firstSubclass的值是nil

  • 在最新objc4-781的版本里, class_rw_t类型做了许多优化和调整。

  • 获取bits里的class_rw_t数据里的methods,通过打印可以看到上边的测试代码里- (void)hello;实例方法和属性gettersetter方法。⚠️但是没有看到+ (void)happy;类方法。类方法存在那里?

  • 获取properties()Test类里只有nickname一个,⚠️那么成员变量hobby存在那里?

0x02 - 成员变量探索

class_rw_t中, 有一个获取class_ro_t的方法const class_ro_t *ro() const, 通过对ro()方法的调用,得到class_ro_t *

struct class_ro_t {
....
    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;

....
}
复制代码

以上内存平移的方法来打印出了类的成员变量, 通过{}定义对方法它在objc_class---->bits---->class_rw_t----->class_ro_t里的ivars。包括成员变量,还包括属性生成的带下划线的成员变量

通过@property定义的属性objc_class---->bits---->class_rw_t----->properties()-----> list 得到属性列表


0x03 - 类方法探索

在上边的实例方法探索阶段,有一个疑问,就是类方法存储在哪里?🤔️, 这一小节探索类方法, 在之前的文章中, 我们知道有元类这么一个概念,类对象的isa指向元类, 元类是用来存储类信息的,所以打印元类的bits,看看是否有类方法?

@interface LGPerson : NSObject<LGPerson_Protocol02>
{
    NSString *hobby;
    NSString *oooo;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *otherName;
@property (nonatomic, strong) NSString *otherName2;

@property (class) NSObject *objInstance;


- (void)sayHello;
- (void)sayCode;
- (void)say1;
- (void)say2;
+ (void)sayBye;
+ (void)sayNB;

- (void)sayNB;
+ (void)sayHappay;
@end
复制代码

通过LLDB以上的打印输出, 先找到元类的地址, 地址+20偏移到class_data_bits_t, 找到class_rw_t, 调用method_array_t methods(),拿到方法列表,打印出所有的类方法

  • 类的实例方法存在类的bits中,包括属性gettersetter方法
  • 类的类方法存在元类bits中,

欢迎大佬留言指正😄,码字不易,觉得好给个赞👍 有任何表达或者理解失误请留言交流;共同进步;

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

ios

482

相关文章推荐

未登录头像

暂无评论