iOS - 类结构分析

我们都知道,一个类可以创建多个不同实例对象,类自己也是对象(类对象),那么类在内存中会存在几份呢?看下面结果

Class class1 = [MGPerson alloc].class;
Class class2 = [MGPerson alloc].class;
Class class3 = [MGPerson class];
Class class4 = object_getClass([MGPerson alloc]);

得出的结果是 class1class2class3class4的地址一样

image.png

也就是类对象并不像实例对象一样在内存中有多份存在,只占用一份内存。

总结:类(类对象)在内存中只有一份存在;

类的内存地址分析

通过控制台输出类的地址是 0x0000000100002330

(lldb) p/x MGPerson.class
(Class) $0 = 0x0000000100002330 MGPerson

然而当我们用类的isa & 0x7ffffffffff8得到地址0x0000000100002308, po输出也是MGPerson, 可以通过上一节isa 和 类的关系查看isa分析

image.png

看到这里会发现和前面的分析结论类在内存中只有一份 相矛盾,真的是这样的吗?答案是否定的,看到的0x0000000100002308并不是类的内存地址,而是元类, 是MGPerson中的isa指向的元类。

元类的说明:
  • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自元类。
  • 元类本身没有名称的, 由于与类相关联,所以使用了同类名一样 的名称。

接着分析MGPerson类对象的元类的地址,输出内存地址为:

(lldb) x/4gx 0x0000000100002308
0x100002308: 0x00007fff8e0c50f0 0x00007fff8e0c50f0
0x100002318: 0x00000001007040c0 0x0001e03500000007

继续元类的isa & 0x7ffffffffff8得到地址0x00007fff8e0c50f0, po输出是NSObject

image.png

元类的isa指向根元类, 那么根元类isa指向谁? 继续输出 0x00007fff8e0c50f0指向的地址


(lldb) x/4gx 0x00007fff8e0c50f0
0x7fff8e0c50f0: 0x00007fff8e0c50f0 0x00007fff8e0c5118
0x7fff8e0c5100: 0x00000001005153c0 0x0004e03100000007
image.png

可以发现根元类isa指向的还是自己,即 0x00007fff8e0c50f0。

至此可以得出实例对象元类根元类的关系图如下:

image.png

总结:

  • 实例对象(Instance of Subclass) 的 isa 指向类(class)

  • 类对象(class) isa 指向 元类(Meta class)

  • 元类(Meta class)的isa 指向 根元类(Root metal class)

  • 根元类(Root metal class) 的isa 指向自己,形成闭环,这里的根元类就是NSObject

类(Class)

我们定义的对象都是继承于NSObject对象,可以通过isa 和 类的关系中找到类的结构体(objc_class)定义。

struct objc_class : 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

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        ASSERT((set & clear) == 0);
        data()->changeFlags(set, clear);
    }

    ...
}

可以看出 objc_class 继承于 objc_object, 发现有一个isa变量,可以得出所有的对象都有一个isa指针。

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;
  ... 
}

总结:

  • 所有的对象, 元类都有isa变量
  • 所有的对象都继承于objc_object

类结构分析

struct objc_class : 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

  // 获取bits存放的数据信息
    class_rw_t *data() const {
        return bits.data();
    }
    ...
}

通过上面分析可以看出类(class)中有4个属性分别是:

  • isa: 继承自objc_objectisa,占用8个字节;
  • superclass: Class类型, 也是objc_class结构体,是一个结构体指针,占用8个字节;
  • cache: 存放缓存的指针,以及缓存的函数,占用16字节;
  • bits: 类型为 class_data_bits_t ,存储了类中更详细的信息(例如:属性,方法,协议);

【cache_t类型】内存大小 16字节

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic<mask_t> _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //是指针,占8字节
    mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
   
  ...

#if __LP64__
    uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
    uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

   ....
}

class_data_bits_t结构分析

通过上面分析类的结构可知 类内存地址(首地址)平移32字节(即十六进制0x20)即可查找到 bits

image.png

步骤解析:

    1. 获取类的首地址 0x00000001000013d8
    1. 通过首地址偏移32字节(即0x20)得到 bits的内存地址
    1. 强转成 class_data_bits_t类型, 读取bits.data()存放的数据信息,即$3;
    1. 打印输出 $3 的内容 (class_rw_t) $4, 类型是class_rw_t,也是一个结构体类型;

class_rw_t 结构

在64位架构CPU下,bits 的第3到第46字节存储class_rw_t 。class_rw_t 中存储 flags 、witness、firstSubclass、nextSiblingClass, methods, properties, protocols 以及class_rw_ext_t

struct class_rw_ext_t {
    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 中存储着class_ro_tmethods (方法列表)properties (属性列表)protocols (协议列表)等信息。
class_ro_t 中也存储了baseMethodList (方法列表)baseProperties (属性列表)baseProtocols (协议列表) 以及 实例变量、类的名称、大小 等等信息。

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

通过源码获取属性列表、方法列表、协议列表等

image.png
  • 通过获取属性列表property_array_t可以拿到属性
image.png

image.png

注:p *($6 + 1)p *($6 + 2)可以获得属性列表的中的下一个数据

或者通过$7.get()获取各个属性

image.png

注意:properties()只能获取到属性列表, 如果成员变量并不在这里面存放着,而是存放在 ivars成员变量列表中。

  • 通过函数methods()获取 method_array_t获取方法列表
image.png

步骤解析:

  • p $4.methods() 获取方法列表list, 即得到的$6
  • p *$6拿到方法列表中的第一个方法,即 ‘sayHello’
  • p $7.get(1)p $7.get(2)等是获取方法列表中的其他方法。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容