类的底层探索(下)

类的底层探索(下)

类中的成员变量

从前面的分析中我们知道,我们在rw中读取到了方法列表、属性列表、协议列表,那成员变量呢?它在什么位置?
class_rw_t结构中有:

// objc-runtime-new.h
// 获取ro
const class_ro_t *ro() const {
	auto v = get_ro_or_rwe();
	if (slowpath(v.is<class_rw_ext_t *>())) {
		return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
	}
	return v.get<const class_ro_t *>(&ro_or_rw_ext);
}

我们可以关注一下class_ro_t这个结构:

// objc-runtime-new.h
// ro结构
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name; // 类名
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods; // 方法列表
    protocol_list_t * baseProtocols; // 协议列表
    const ivar_list_t * ivars;  // 成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;    // 属性列表

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    const char *getName() const {
        return name.load(std::memory_order_acquire);
    }

    class_ro_t *duplicate() const {
        bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;

        size_t size = sizeof(*this);
        if (hasSwiftInitializer)
            size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);

        class_ro_t *ro = (class_ro_t *)memdup(this, size);

        if (hasSwiftInitializer)
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];

#if __has_feature(ptrauth_calls)
        // Re-sign the method list pointer.
        ro->baseMethods = baseMethods;
#endif

        return ro;
    }

    Class getNonMetaclass() const {
        ASSERT(flags & RO_META);
        return nonMetaclass;
    }

    const uint8_t *getIvarLayout() const {
        if (flags & RO_META)
            return nullptr;
        return ivarLayout;
    }
};

class_ro_t这个结构中,我们可以看到,它既有方法列表、属性列表、协议列表,还有成员变量列表ivars,那我们就尝试读取一下:
在这里插入图片描述
读取出来之后,我们可以看到,它和方法列表是一样的,使用了entsize_list_tt这个模板类,并且里面的count = 3,应该就是我们的三个成员变量:_hobby_name_age。我们也可以看一下ivar_list_tivar_t

// objc-runtime-new.h
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
    bool containsIvar(Ivar ivar) const {
        return (ivar >= (Ivar)&*begin()  &&  ivar < (Ivar)&*end());
    }
};

// objc-runtime-new.h
struct ivar_t {
#if __x86_64__
    // *offset was originally 64-bit on some x86_64 platforms. // *offset最初在一些x86_64平台上是64位的。
    // We read and write only 32 bits of it. //我们只读写其中的32位
    // Some metadata provides all 64 bits. This is harmless for unsigned 
    // little-endian values. //有些元数据提供全部64位。这对于unsigned little-endian值是无害的。
    // Some code uses all 64 bits. class_addIvar() over-allocates the 
    // offset for their benefit. //有些代码使用全部64位。class_addIvar()为它们的利益超额分配偏移量。
#endif
    int32_t *offset;
    const char *name;
    const char *type;
    // alignment is sometimes -1; use alignment() instead //对齐有时是-1;使用alignment()
    uint32_t alignment_raw;
    uint32_t size;

    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

那我们就可以读取一下ivar_list_t中的三个值:
在这里插入图片描述
我们读取出来发现,这就是我们已知的三个成员变量。

rw ro rwe

那为什么在rw中有methods等信息,在ro也要有呢?
我们可以先从 WWDC2020 开始介绍:
WWDC2020中,苹果工程师介绍了clean memorydirty memory,也就是 “干净内存” 和 “脏内存” 。
clean memory就是加载后不会改变的内存,class_ro_t就是clean memoryro就是read-only(只读)。
dirty memory就是进程运行时会发生变化的内存,class_rw_t就是dirty memoryrw就是read-write(读写)。
dirty memoryclean memory昂贵的多,只要进程在运行,就必须存在。但是clean memory是可以移除的,节省内存,需要的时候,系统会在磁盘里读取出来。
在这里插入图片描述
dirty memory可以分离出那些永远不会变的数据,可以把大部分的类数据存储为clean memory。当一个类首次被使用,运行时会为它分配额外的存储容量,这个运行时分配的存储容量是 class_rw_t,用于读取-编写数据。在这个数据结构中,我们存储了只有在运行时才会生成的新信息。
在这里插入图片描述
但为什么方法和属性也在只读数据中时,我们这里还要有方法和属性呢?
在这里插入图片描述
因为这些东西在运行时是可以被更改的。例如category被加载时,它可以向类中添加新的方法,而且程序员可以使用运行时 API 动态地添加它们。因为 class_ro_t 是只读的,所以我们需要在 class_rw_t 中追踪这些东西。
但是这样做会占用很大的内存,毕竟系统中会有两份内容一样的东西。
经过检查实际设备上的使用情况,大约只有 10% 的类真正地更改了它们的方法。而且只有 Swift 类会使用这个 demangled name 字段,并且 Swift 类并不需要这一字段,除非有东西询问它们的 Object-C 名称时才需要。
所以,可以拆掉那些平时不用的部分,这将 class_rw_t 的大小减少了一半。拆出来的部分叫做 class_rw_ext_t
在这里插入图片描述
class_rw_ext_t在源码中的结构:

// objc-runtime-new.h
// rwe的结构域
struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;         //方法列表
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

对于那些需要改变的类,我们扩展出class_rw_ext_t,供其使用。
在这里插入图片描述
这样大约90%的类是不需要的,这样就节省了内存。
并且我们在class_rw_t的结构代码中也可以看到这些变化:

// 方法列表
const method_array_t methods() const {
	auto v = get_ro_or_rwe();
	if (v.is<class_rw_ext_t *>()) { // 有rwe时使用rwe
		return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
	} else {// 没有就是ro
		return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
	}
}

在获取methods时,判断了如果有rwe就读取rwe,没有就是ro
当然对于这些结构的变化,我们不需要去关心,只是用官方 API 就可以获取这些内容。

至此,我们探究了class_data_bits_t

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值