iOS底层探索之alloc(下)

前言

前两篇主要介探索了调用对象的alloc的底层的流程,定位到核心方法_class_createInstanceFromZone,该方法主要做了三件事:

  • 计算需要开辟的内存空间大小;
  • 申请内存;
  • 返回地址指针将类与isa关联;

本来主要探索 返回的地址指针将类与isa关联 的流程。

NONPOINTER_ISA

探索流程之前,先来了解一下NONPOINTER_ISA
目前 Apple的设备使用的都是64的cpu,寻址能力达到了0-2的64次方64位下程序的指针占用了8字节的内存,但是其实根本用不到这么大内存地址,所以苹果通过优化,存储指针的同时还存储了一些其他的信息,充分利用了这8字节的内存,这就是非指针型ISA(NONPOINTER_ISA),底层是一个isa_t的联合体。下面是整理之后的isa_t,由于代码运行在x86 64位环境下,以该环境下为例:

#   define ISA_MASK        0x00007ffffffffff8ULL //用于访问shiftcls存放的指针值
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1

//这是一个联合体
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
  
    Class cls;

public:
    //arm 64字段类似
    //这里用了位域去存储
    struct {
      //nonpointer的值0是纯的ISA指针,1是非指针型的NOPOINTER_ISA指针
      uintptr_t nonpointer        : 1;
      //是否有设置过关联对象
      uintptr_t has_assoc         : 1;     
      //是否有C++的析构函数(.cxx_destruct)
      uintptr_t has_cxx_dtor      : 1;   
      //存储着Class、Meta-Class对象的内存地址信息
      uintptr_t shiftcls          : 44;
      //用于在调试时分辨对象是否未完成初始化
      uintptr_t magic             : 6;  
      //是否有被弱引用指向过
      uintptr_t weakly_referenced : 1;   
      //未被使用
      uintptr_t unused            : 1;  
      //标记是否有sitetable结构用于存储引用计数
      uintptr_t has_sidetable_rc  : 1;   
      //标记对象的引用计数(首先会存储在该字段中,当到达上限后,在存入对应的引用计数表中)
      uintptr_t extra_rc          : 8 
    };
}

复制代码

联合体和位域

什么是联合体

联合体也称为共合体,同结构体一样,也是用来构造数据的一种类型。联合体的定义如下

union 联合体名{
    成员列表
};
复制代码

结构体联合体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,占用的内存等于最长的成员占用的内存,修改一个成员会影响其余所有成员,如果对一个成员赋值,就会把其他成员的值覆盖掉。

什么是位域

有些数据在存储的时候并不需要占用一个完整的字节,只需要1位或者几位就即可,比如 nonpointer字段的值为0和1,只需1位就能够存储,所以C语言提供了一种叫做位域的数据结构。
在结构体定义时,可以在某个成员变量跟上”:”加数字,来限定成员变量占用的位数,如:

struct {
      uintptr_t nonpointer        : 1;
      uintptr_t has_assoc         : 1;     
      ...
    };
复制代码

isa关联的流程

1.和alloc(上)一样,创建一个Person对象

int main(int argc, const char * argv[]) {
    Person *person1 = [Person alloc];
    return 0;
}
复制代码

2.将断点打在_class_createInstanceFromZone方法,来到这部分代码,此时会调用obj->initInstanceIsa(cls, hasCxxDtor)

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
        
    }
复制代码

3.进入initInstanceIsa后,会调用initIsa(cls, true, hasCxxDtor)initIsa主要是创建一个isa_t结构体对象,赋值之后再赋值给对象结构体的isa字段,完成关联。

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

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    isa = newisa;
}
复制代码

4.来看一看isa_tsetClass方法,里面有大量的IF宏判断,但此时环境下最终执行的是shiftcls = (uintptr_t)newCls >> 3,也就是将类地址的指针右移3位赋给shiftcls

inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
    ...
#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}
复制代码

为什么地址要右移3位呢?带着这个问题来看看取类的指针地址的时候是如何操作的。

5.有setClass,必然会有getClass,如下,ISA_MASK就是上面定义的宏常量。

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    ...
#   else
    clsbits &= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}

复制代码

6.通过lldb获取了 person1实例对象的isaPerson的类对象指针地址,

image.png
我们将此时isaISA_MASK转为二进制来进行与运算,会更直观

image.png
可以看到,& ISA_MASK其实就是抹去了其他位数上的值,由于在4.setClass方法中将地址右移了3位,此时低3位的0正好补上右移的3位,且由于是8字节对齐低三位为0,也不刽有数据丢失。

总结

至此,通过调试objc源码完成了对alloc流程的探索。整个流程探索下来,发现了更多值得探索的东西,探索的越多,愈发觉得自己了解的越少,底层探索才刚刚开始。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享