iOS疑问之alloc到底做了些什么

194 阅读4分钟

探索alloc。我们需要从alloc底层来探索一番。

Student * student = [Student alloc] 来查看,找到alloc。 alloc是的源码需要到 opensource.apple.com/tarballs/ 找到

image.png 找到最新版objc4-818.2下载。build Setting -> Base SDK -> macOS command+b编译成功(M1电脑会报错”task_restartable_ranges_register failed“)。

image.png 注释调即可。alloc后面到底是什么,即将展现在眼前。 找到alloc

+ (id)alloc {
    return _objc_rootAlloc(self);
}

符号断点发现会执行objc_alloc

id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

有感觉了callAlloc 里面有点东西。 参数

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

注意:

__builtin_expect 编译器对代码进行优化,减少指令跳转带来的性能下降
#define fastpath(x) (__builtin_expect(bool(x), 1)) // x很可能为真,真值判断
#define slowpath(x) (__builtin_expect(bool(x), 0))// x很可能为假,假值判断

通过 if (slowpath(checkNil && !cls)) return nil; 可知道 会直接 return nil;继而执行_objc_rootAlloc _objc_rootAlloc 继续深入

id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

继续 fastpath(!cls->ISA()->hasCustomAWZ()) 这里是代表是否有自定义allocWithZone,没有 进入_objc_rootAllocWithZone 继续进入到 _class_createInstanceFromZone, ASSERT(cls->isRealized()); 是否已经实现 来看”zone“内存空间的开辟

if (zone) {
//分配大小为 1 * size 的新指针;块被清除;区域必须为非空
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size); 
    }

size 为所需开辟内存空间大小 size = cls->instanceSize(extraBytes); 进行计算 结合”zone“ 已经对cls申请了内存,还一步指针 fast:内存指针isa bool fast = cls->canAllocNonpointer();

if (!zone && fast) {
// 进行关联
        obj->initInstanceIsa(cls, hasCxxDtor);
   } else {
        // Use raw pointer isa on the assumption that they might be
        //使用原始指针isa的前提是它们可能是
        // doing something weird with the zone or RR.
        //对区域或RR做一些奇怪的事情
        obj->initIsa(cls);
    }

上述代码是对cls类与obj进行关联 alloc流程结束了。 有一些问题还是不明白, 1、calloc是个很核心的地方 2、size = cls->instanceSize(extraBytes);如何计算的呢,了解了这里是不是能够真正的做一下内存优化。 进入instanceSize(size_t extraBytes) extraBytes额外字节。传0

inline size_t instanceSize(size_t extraBytes) const {
//是否快速计算内存
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;  
        return size;
    }

cache.fastInstanceSize 快速计算内存 16字节对齐

size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));
        // __builtin_constant_p gcc内建函数,判断一个值是否为编译时常数,extra为常数返回1.否则返回0
        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            // 16为对齐算法
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

16位字节对齐

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

x = 15 15+16 = 31 >> 0000 0000 0001 1111 & 1111 1111 1111 0000 = 0000 0000 0001 0000 对齐16字节 然而为什么要16字节对齐。好奇宝宝发问。 1、为什么要16位呢,其实理论上可以 2、4、8、16、32对齐。苹果原先是8字节对齐的。后面改为16字节 2、为什么对齐。一句话总结,节省不必要的内存,合理利用内存,符合规范。 2.1、内存字节对齐的原则。 1、数据成员对齐规则:struct或者union的数据成员,第一个数据放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始,子成员包括,数据、结构体等。 2、数据成员为结构体:如果一个结构里面有些结构体成员,则结构体成员要从其内部最大元素大小的整数倍开始存储 3、结构体的整体对齐规则:结构体的总代下,sizeof的结果,必须是其内部最大成员的整数倍,不足补齐。 2.2、字节对齐原因 1、内存通常是有一个个字字组成,cpu读取数据是以块为单位的,并不是字节为单位,减少存取次数来降低cpu开销 2、一个class中第一个属性isa占8字节,属性另外8字节,没用属性也会保留。不保留会导致isa错乱。 3、16字节对齐后加快cpu读取速度,同事访问也安全。 对象为 objc_object的结构体,结构体是连续存放的,所以可以对结构体进行强转。

非快速计算内存,8字节对齐计算,不够16补到16

calloc申请内存

**(lldb) po obj 0x00000001095042c0

此处没用于isa绑定。所以和平时po的class不一样。 执行obj->initInstanceIsa(cls, hasCxxDtor);后就是我们所用的class了 isa如何于cls关联呢

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa()); //检测 类的实例需要原始isa
    ASSERT(hasCxxDtor == cls->hasCxxDtor());
    initIsa(cls, true, hasCxxDtor);
}

dtor是用来判断当前class或者supreclass是否有。cxx_desctruct(自动释放)函数的实现 initIsa 实现cls 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;
    }
    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}

initIsa 实现了关联 附上流程图

流程图-2.png