iOS 类的底层探索(下)

126 阅读3分钟

上篇文章我们探索了类的本质,发现类的本质是 objc_class,类对象里存储了成员变量、实例方法、属性、协议等,并存在 class_rw_tclass_ro_t中,那么本篇来探索一下 rw 和 ro,最后用 runtime 提供的 API 获取一下成员变量、方法等。

1. rw 和 ro 的本质

class_ro_t

class_ro_t存储了当前类在编译期就已确定的 成员变量实例方法属性协议。 这是一块纯净的内存空间,不允许被修改

class_ro_t 可以进行移除(内存不够的时候),再需要的时候可以再从磁盘中加载

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;
    
    ......
};

根据上篇文章可知,在 class_ro_t 中,方法列表为 baseMethods、协议列表 baseProtocols、属性列表为 baseProperties,成员变量为ivars

class_rw_t

class_rw_t 是运行时生成的,类一经使用就会变成 class_rw_t,它会将class_ro_t里面的东西“拿”过来,再把需要动态修改的分类、属性和方法拷贝进来(成为rwe)。它是可读可写的

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

    class_rw_ext_t *ext() const {

        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);

    }


    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);

    }

    const method_array_t methods() const {

        auto v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;

        } else {

            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};

        }

    }

    const property_array_t properties() const {

        auto v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;

        } else {

            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};

        }

    }

    const protocol_array_t protocols() const {

        auto v = get_ro_or_rwe();

        if (v.is<class_rw_ext_t *>()) {

            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;

        } else {

            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};

        }

    }
    
    ...

};

class_rw_t 存放于 objc_class 中的 bits (class_data_bits_t) 中,在运行 runtimerealizeClass 方法时,会生成 class_rw_t 结构体,该结构体包含类 class_ro_t,并且更新了 data 部分,换成了 class_rw_t 结构体的地址。类的 realizeClass 运行之前:

Screen Shot 2022-11-03 at 18.14.55.png 类的 realizeClass 运行之后:

Screen Shot 2022-11-03 at 18.13.13.png 可以说 class_rw_tclass_ro_t 的超集,实际访问的方法、属性也都是访问 class_rw_t 里的内容

class_rw_ext_t

class_rw_ext_t 可以减少内存消耗,苹果在 WWDC2020 里面说过,只有大约 10% 左右的类需要动态修改。所以只有 10% 左右的类里面需要生成 class_rw_ext_t 这个结构体,可以节约很大一部分内存。需要生成 class_rw_ext_t 时,将 ro 里面的东西内容需要修改的内容拷贝出来。

Screen Shot 2022-11-03 at 23.30.10.png class_rw_ext_t 生成的条件:

  • 用过 runtimeAPI 进行动态修改的时候
  • 有分类的时候,且分类和本类都是非懒加载类的时候。实现了 +load 方法即为非懒加载类

class_ro_t, class_rw_t, class_rw_ext_t 之间的关系:

image.png

2.通过 runtime 的 API 探索类的数据结构

获取类的成员变量

// 获取类的成员变量
- (void)getIvarList:(Class)class {
    unsigned int outCount = 0;
    Ivar *ivars = class_copyIvarList(class, &outCount);
    
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        const char *cName = ivar_getName(ivar);
        const char *cType = ivar_getTypeEncoding(ivar);
        NSLog(@"name = %s, type = %s", cName, cType);
    }

    // arc 只管理 OC 对象,ivars 是 malloc 出来的
    free(ivars);

}

获取类的属性

// 获取类的属性
- (void)getPropertyList:(Class)class {
    unsigned int outCount = 0;
    objc_property_t *properties = class_copyPropertyList(class, &outCount);

    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = properties[i];
        const char *cName = property_getName(property);
        const char *cType = property_getAttributes(property);

        NSLog(@"name = %s, type = %s", cName, cType);
    }
    free(properties);
}

获取方法列表

// 获取方法列表
- (void)getMethodList:(Class)class {
    unsigned int outCount = 0;
    Method *methods = class_copyMethodList(class, &outCount);

    for (int i = 0; i < outCount; i ++) {
        Method method = methods[i];
        NSString *name = NSStringFromSelector(method_getName(method));
        const char *cType = method_getTypeEncoding(method);

        NSLog(@"name = %@, type = %s", name, cType);
    }
    free(methods);
}

打印结果如下:

image.pngv24@0:8@16 为例:

  • v: 返回值为 void
  • 24 参数总共 24 个字节
  • @类型从 0 开始 (消息的接收者)
  • : 类型从 8 开始(消息的方法名)
  • @类型从 16 开始(方法的参数)

拓展

打印方法的地址:

// 方法的地址
- (void)methodTest:(Class)class {
    const char *className = class_getName(class);
    Class metaClass = objc_getMetaClass(className);

    Method method1 = class_getInstanceMethod(class, @selector(instanceMethod));
    Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
    Method method3 = class_getInstanceMethod(class, @selector(classMethod));
    Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));

    NSLog(@"%p--%p--%p--%p", method1, method2, method3, method4);
}

打印结果如下:

image.png 说明实例方法存在类里,类方法存在元类里

注意:

为什么不用class_getClassMethod获取类方法?源码发现 class_getClassMethod 走的也是 class_getInstanceMethod,说明元类不是为了存储类方法,因为底层没有类方法和实例方法的概念

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

打印方法的实现

// 方法的实现
- (void)methodImpTest:(Class)class {
    const char *className = class_getName(class);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(class, @selector(instanceMethod));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethod));
    IMP imp3 = class_getMethodImplementation(class, @selector(classMethod));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod));

    NSLog(@"%p--%p--%p--%p", imp1, imp2, imp3, imp4);
}

image.png 注意

为什么元类可以打印出实例方法的实现,类可以打印出类方法的实现? 源码发现 imp 不存在会走 _objc_msgForward 消息转发

IMP class_getMethodImplementation(Class cls, SEL sel)

{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE |         LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

结论

  • 类的 class_ro_t 中存储了成员变量,类对象的 class_rw_t 中存储了实例方法属性协议

  • 类方法存储在元类里