对象的本质

alloc底层原理这篇文章主要介绍了,当我们创建一个NSObject的子类的时候,调用alloc方法的流程、类创建的对象实际占用的内存大小分析以及系统分配的内存大小分析、影响对象内存大小的因素等问题。

结构体内存对齐这篇文章主要介绍了,当我们创建结构体的时候对于相同的成员数以及成员类型,顺序不同导致占用内存大小不一致的原因分析、系统对于alloc方法的防Hook操作、alloc函数内存开辟过程。

那么今天这篇文章一起探索下,当我们创建一个NSObject的子类的时候,调用alloc方法的时候,系统是如何把创建的类对象和isa进行绑定的。

initIsa函数

通过alloc底层原理这篇文章我们可以发现,类clsisa进行绑定是在initIsa函数中实现的,那么我们就看下initIsa函数的实现,代码如下:

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

然后继续进入initIsa(cls, false, false);的实现函数中,发现函数实现中有条件分支,先看下条件分支的判断条件,然后进行代码简化,SUPPORT_INDEXED_ISA的定义如下:

// 在将类存储在isa字段中的平台上定义SUPPORT_INDEXED_ISA=1作为类表的索引。注意,要与任何定义它的.s文件保持同步。确保也编辑objc-abi.h。
#if __ARM_ARCH_7K__ >= 2  ||  (__arm64__ && !__LP64__)
#   define SUPPORT_INDEXED_ISA 1
#else
#   define SUPPORT_INDEXED_ISA 0
#endif

可以看到默认走的是SUPPORT_INDEXED_ISA=1也就是说分支里面走的是true的判断。所以剔除多余代码后,精简代码如下:

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic是ISA_MAGIC_VALUE的一部分
        // isa.nonpointer是ISA_MAGIC_VALUE的一部分
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
        newisa.extra_rc = 1;
    }
    // 在某些情况下,这个写入必须在单个存储中执行(例如,当实现一个类时,因为其他线程可能同时尝试使用这个类)。Fixme使用原子来保证单存储和保证内存顺序。但不要太原子,因为我们不想破坏实例化
    isa = newisa;
}

当简化完成函数实现后,我们开始分析函数中的代码,首先就是创建了一个联合体(又称共用体)对象newisa,那么先去看下这个联合体isa_t的结构,代码如下:

union isa_t {
    // 析构函数,无参数
    isa_t() { }
    // 析构函数,有参数,设置位域信息
    isa_t(uintptr_t value) : bits(value) { }
    
    uintptr_t bits;

private:
    // 访问这个类需要自定义的ptrauth操作,所以强制客户端通过私有来访问setClass/getClass。
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // 在isa.h文件中进行了定义,isa位域中每个位置锁代表的含义进行了定义
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

在分析函数中代码前,我们先做下扩展知识,也就是俗话说的备战,了解相关知识点,才能更快更高效的读懂源码。

补充:

  1. uintptr_t:在很多地方都能看见使用这个关键字去定义变量,那么它到底是什么呢?带着好奇心,经过一顿操作,找到这么一段定义,而且是在Linux平台的usr/include/stdint.h头文件中进行的定义。具体代码如下:
    #if __WORDSIZE == 64
    # ifndef __intptr_t_defined
    typedef long int        intptr_t;
    #  define __intptr_t_defined
    # endif
    typedef unsigned long int   uintptr_t;
    #else
    # ifndef __intptr_t_defined
    typedef int         intptr_t;
    #  define __intptr_t_defined
    # endif
    typedef unsigned int        uintptr_t;
    #endif
    

    解读下来就以下几点:

    • 64位系统下long int类型起个别名叫intptr_tunsigned long int类型起个别名叫uintptr_t

    • 32位系统下int类型起个别名叫intptr_tunsigned int类型起个别名叫uintptr_t

    • 至于这么做的原因,应该就是为了提高程序在不同的系统环境下的可移植性。

  2. 位域:在C语言中允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单元的成员称为位域

结构体和联合体(共用体)的异同

  • 同:结构体联合体都是拥有一个或多个成员的结构型数据类型,而且可以相互嵌套包含。
  • 异:结构体中所有成员之间是可以共存的,能够同时赋值取值。联合体中所有成员之间是互斥的,某一时刻只能有一个值是真实有效的,当给一个成员赋值时其他成员立马就会变成垃圾数据,不是有效值。

MASK(面具)

在iOS开发中经常能够看见一些&(与)运算,~(取反)运算等等,在位域运算中经常会遇到&(与)运算或者位移运算,从而达到访问指定位置的值。运行objc源码的时候都是在mac运行,所以我们主要看下x86架构下的ISA_BITFIELD,如下所示:

#   define ISA_BITFIELD                                                        
      uintptr_t nonpointer        : 1;                                         
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      uintptr_t shiftcls          : 44;
      uintptr_t magic             : 6;                                         
      uintptr_t weakly_referenced : 1;                                         
      uintptr_t unused            : 1;                                         
      uintptr_t has_sidetable_rc  : 1;                                         
      uintptr_t extra_rc          : 8

x86架构下的ISA_BITFIELD中每个位置代表的信息如图所示:

x86架构下示意图:

image.png

arm64架构下示意图:

image.png

nonpointer在0位,表示是否对isa指针开启指针优化。0:纯isa指针,1:不止是类对象地址,isa包含了类信息、对象的引用计数等。

has_assoc在1位,表示关联对象标志位,0:没有,1:有。

has_cxx_dtor在2位,表示该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。

shiftclsx86架构中占用3~46位,表示存储类指针的值。开启指针优化的情况下,在arm64架构中占用3~35位。

magicx86架构中占用47~52位,在arm64架构中占用36~41位,用于调式器判断当前对象是真的对象还是没有初始化的空间。

weakly_referencedx86架构中占用53位,在arm64架构中占用42位,标志对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。

unusedx86架构中占用54位,在arm64架构中占用43位,标志对象是否正在释放内存。

has_sidetable_rcx86架构中占用55位,在arm64架构中占用44位,表示当对象引用计数大于10时,则需要借用该变量存储进位。

extra_rcx86架构中占用56~63位,在arm64架构中占用45~63位,当表示该对象的引用计数值时,实际上是引用计数值减1,例如:如果对象的引用计数为10,那么extra_rc9,如果引用计数大于10,则需要使用到has_sidetable_rc

initIsa函数实现

当了解这些扩展知识后,回到开始的isa_t结构体,主要是setClass函数,函数有一段注释,大概意思就是在isa中设置类字段。接受要设置的类和指向最终将使用isa的对象的指针。这是使指针正确签名所必需的。注意:此方法不支持设置索引的isa。当使用索引isa时,它只能用于设置原始isa的类。然后这个函数中重点代码为这一句uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));,因为我们要将结果指针作为函数指针调用,因此需要对其进行签名,也就是对传入的newCls进行签名。

然后回到开始的initIsa函数,当创建一个联合体对象newisa后,先进行了判断传入的nonpointer是否为false,从函数执行流程跟进来的时候,就能看见调用initIsa的时候写死的传入false,所以条件分支走进newisa.setClass(cls, this);,然后继续来到函数setClass的实现中,因为条件分支比较多,最笨的方法那就是挨个添加断点,看走的那个,如下图:

image.png

断点走的是最后的shiftcls = (uintptr_t)newCls >> 3;这句代码,上面补充中说了,这个值是用来存储类指针的值的。

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

推荐阅读更多精彩内容