「OC」源码学习——objc_class的bits成员探究

「OC」源码学习------objc_class的bits成员探究

类模版

objc 复制代码
@interface GGObject : NSObject
{
    int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end

@implementation GGObject

- (void)speak {
    NSLog(@"%s", __func__);
}
+ (void)walk {
    NSLog(@"%s", __func__);
}

-(void) sayHello {
    NSLog(@"%s", __func__);
}

@end

objc_class的成员变量

在全局搜索搜索objc_class 的内容,我们看到内容如下

类本身自带的isa指针为八字节;其中superclass为class类型本质为isa指针,大小也为八字节;cache_t结构体的内容得在lldb之中进行分析,查看cache_t的结构体数据,使用p sizeof(cache_t)可以看到,cache_t的内存为16字节,结合我们前面所计算的,那么我们不难得出。只要我们在objc_class的首地址加上32字节就可以得到bits之中的信息

如何获取bits

既然已经知道平移32位就能获取bits的相关信息,那在lldb里面就不难操作了,我们进入class_data_bits_t的声明,看到data()方法

objc 复制代码
class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

大致意思是,将bit的内容转化为class_rw_t的类型输出

Class_rw_t

class_rw_t是由class_data_bits_t中的bits第3位到46位存储的。

接着在rw之中获取到对应的方法列表

在源码之中查看class_rw_t的定义

objc 复制代码
struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;

    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

    void changeFlags(uint32_t set, uint32_t clear) 
    {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
};

使用函数打印属性列表

c 复制代码
// 获取类的属性
void wj_class_copyPropertyList(Class pClass) {
    unsigned int outCount = 0;
    objc_property_t *perperties = class_copyPropertyList(pClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = perperties[i];
        const char *cName = property_getName(property);
        const char *cType = property_getAttributes(property);
        NSLog(@"name = %s type = %s",cName,cType);
    }
    free(perperties);
}

可以看到class_rw_t的结构是由method_array_tproperty_array_t,protocol_array_t

得到的结果如上:

  • T 表示 type
  • @ 表示 变量类型
  • C 表示 copy
  • N 表示 nonatomic
  • V 表示 variable 变量,即下划线变量、

使用LLDB调试出属性列表

lldb 复制代码
p/x cls
(Class) 0x0000000100008410 GGObject

(lldb) ex (class_data_bits_t *)0x0000000100008430
(class_data_bits_t *) $0 = 0x0000000100008430


(lldb) ex $0->data()
(class_rw_t *) $2 = 0x00006000036f9800

(lldb) ex *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000424
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSProcessInfo
}

(lldb) ex $3.properties()
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
    storage = (_value = 4295000336)
  }
}

(lldb) p/x 4295000336
(long) 0x0000000100008110

(lldb) ex (property_list_t *)0x0000000100008110
(property_list_t *) $5 = 0x0000000100008110

(lldb) p $5->count
(uint32_t) 5

(lldb) p $5->get(0)
(property_t)  (name = "name", attributes = "T@\"NSString\",C,N,V_name")

使用LLDB调试出方法列表

lldb 复制代码
(lldb) ex $3.methods()
(const method_array_t) $6 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
    storage = (_value = 4294982464)
  }
}

(lldb) p/x 4294982464
(long) 0x0000000100003b40

(lldb) ex (method_list_t *)0x0000000100003b40
(method_list_t *) $7 = 0x0000000100003b40


(lldb) ex *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 13)
}

(lldb) ex $8->get(0).small()
(method_t::small) $9 = {
  name = (offset = 18432)
  types = (offset = 940)
  imp = (offset = -2284)
}


(lldb)  ex $9.name
(RelativePointer<const void *, false>) $10 = (offset = 18432)

我们可以看到count=13,原因就是13 = (五个属性的getter/setter) + sayHello + speak + 析构函数.cxx_destruct
注意:不是每个类都有析构函数.cxx_destruct的!析构函数的作用是用来释放C++ 成员对象 和 @property 属性的,若类的实例变量为基本数据类型(如 intfloat),则不会生成 .cxx_destruct,因为无需 ARC 管理内存

剩余的协议列表其实就是使用以上方法调试出来,这里不过多赘述

class_ro_t

打印class_ro_t之中的成员变量列表

查看其成员列表:

objc 复制代码
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
    uint32_t reserved;
    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;
    }
};

class_ro_t 存储的大多是类在编译时就已经确定的信息。(区别于class_rw_t, 提供了运行时对类拓展的能力)。

三者的关系

ro:是在类第一次从磁盘被加载到内存中产生,它是一块纯净的内存clear memory(只读的) ,编译期生成的只读结构体,存储类的静态元数据,如方法列表、属性列表、协议列表、成员变量等。

当进程内存不够时候,ro可以被移除,在需要的时候再去磁盘中加载,从而节省更多内存。

rw:程序运行就必须一直存在,在进程运行时类一经使用后,runtime就会把ro写入新的数据结构dirty memory(读写的),这个数据结构存储了只有在运行时才会生成的新信息。(例如创建一个新的方法缓存并从类中指向它)

于是所有类都会链接成一个树状结构,这是通过First Subclass(子类)和Next Sibling Class(兄弟类)指针实现的,这就决定了runtime能够遍历当前使用的所有类。

为了验证runtime会把ro的数据写入rw,我们可以使用lldb进行调试,得出rorw的方法列表指向的地址都指向了相同的位置

复制代码
(lldb)ex *$1
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000424
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSProcessInfo
}
(lldb) ex *$2
(const class_ro_t) $4 = {
  flags = 388
  instanceStart = 8
  instanceSize = 48
  reserved = 0
   = {
    ivarLayout = 0x0000000100003ed7 "\""
    nonMetaclass = 0x0000000100003ed7
  }
  name = {
    std::__1::atomic<const char *> = "GGObject" {
      Value = 0x0000000100003ece "GGObject"
    }
  }
  baseMethods = (_value = 4294982464)
  baseProtocols = (_value = 0)
  ivars = 0x0000000100008048
  weakIvarLayout = 0x0000000000000000
  baseProperties = (_value = 4295000336)//ro之中方法列表的地址
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p/x 4294982464
(long) 0x0000000100003b40

(lldb)ex $3->methods()
(const method_array_t) $8 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
    storage = (_value = 4294982464)//rw之中方法列表的地址
  }
}

为社么需要把ro之中的内容复制到rw之中呢?其实在寒假之前学过,我们可以通过分类的方法去重写原本类中的对象方法,那么由于ro是只读的属性,所以需要rw来负责追踪。

另外从ro将方法列表复制到rw_ext的源码如下

objc 复制代码
    // 此处仅声明 extAlloc 函数
    //(此函数的功能是进行 class_rw_ext_t 的初始化)
    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
    
    // extAlloc 定义位于 objc-runtime-new.mm 中,主要完成 class_rw_ext_t 变量的创建,
    // 以及把其保存在 class_rw_t 的 ro_or_rw_ext 中。
    class_rw_ext_t *
    class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
    {
        // 加锁
        runtimeLock.assertLocked();
        
        // 申请空间
        auto rwe = objc::zalloc<class_rw_ext_t>();
        
        // class is a metaclass
        // #define RO_META (1<<0)
        // 标识是否是元类,如果是元类,则 version 是 7 否则是 0
        rwe->version = (ro->flags & RO_META) ? 7 : 0;

        // 把 ro 中的方法列表追加到 rw(class_rw_ext_t) 中
        //(attachLists 函数等下在分析 list_array_tt 时再进行详细分析)
        method_list_t *list = ro->baseMethods();
        if (list) {
        
            // 是否需要对 ro 的方法列表进行深拷贝,默认是 false
            if (deepCopy) list = list->duplicate();
            
            // 把 ro 的方法列表追加到 rwe 的方法列表中
            //(attachLists 函数在分析 list_array_tt 时再进行详细分析)
            //(注意 rwe->methods 的有两种形态,可能是指向单个列表的指针,
            // 或者是指向列表的指针数组(数组中放的是列表的指针))
            rwe->methods.attachLists(&list, 1);
        }

        // See comments in objc_duplicateClass property lists and
        // protocol lists historically have not been deep-copied.
        // 请参阅 objc_duplicateClass 属性列表和协议列表中的注释,历史上尚未进行过深度复制。
        
        // This is probably wrong and ought to be fixed some day.
        // 这可能是错误的,可能会在某天修改。
        
        // 把 ro 中的属性列表追加到 rw(class_rw_ext_t)中
        property_list_t *proplist = ro->baseProperties;
        if (proplist) {
            rwe->properties.attachLists(&proplist, 1);
        }

        // 把 ro 中的协议列表追加到 rw(class_rw_ext_t) 中
        protocol_list_t *protolist = ro->baseProtocols;
        if (protolist) {
            rwe->protocols.attachLists(&protolist, 1);
        }
        
        // 把 ro 赋值给 rw 的 const class_ro_t *ro,
        // 并以原子方式把 rw 存储到 class_rw_t 的 explicit_atomic<uintptr_t> ro_or_rw_ext 中
        set_ro_or_rwe(rwe, ro);
        
        // 返回 class_rw_ext_t *
        return rwe;
    }

由于每一个的类对象都有其对应的rw,但是上述这样做的结果会导致占用相当相当多的内存,因为据苹果官方统计只有大约10%的类需要真正地去修改它们的方法。

那么如何缩小这些结构呢?于是就设计出了rwe,从而减少rw的大小。

rwe:是在category被加载或者通过 Runtime API 对类的方法属性协议等等进行修改后产生的,它保存了原本rw中类可能会被修改的东西(Methods / Properties / Protocols / Demangled Name),它的作用是给rw瘦身

在方法实现中来做区分,如果有rw_ext的类,其列表就错那个rw_ext中获得,如果没有,从ro中读取。

参考文章

Objective-C 类的底层探索

iOS八股文(四)类对象的结构(下)

iOS-底层原理 08:类 & 类结构分析

iOS 从源码解析Runtime (十二):聚焦objc_class(class_rw_t 内容篇)

相关推荐
鸿蒙布道师1 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
吃货界的硬件攻城狮1 小时前
【STM32 学习笔记】ADC数模转换器
笔记·stm32·单片机·学习
菜鸟破茧计划2 小时前
C++ 算法学习之旅:从入门到精通的秘籍
c++·学习·算法
海尔辛2 小时前
学习黑客 MAC 地址深入了解
学习·macos·php
I烟雨云渊T2 小时前
iOS 模块化开发流程
ios
喜欢吃燃面2 小时前
C++:扫雷游戏
c语言·c++·学习
小浪学编程2 小时前
C#学习7_面向对象:类、方法、修饰符
开发语言·学习·c#
猴子请来的逗比4892 小时前
http重新为https
网络协议·学习·http·https
刘小哈哈哈3 小时前
Lost connect to debugger on ‘iphone‘
ios·iphone
I烟雨云渊T3 小时前
iOS蓝牙技术实现及优化
macos·ios·cocoa