OC 对象 "+alloc" 方法内部流程

AllocMethodflowChart.png

示例: SomeClass 对象 alloc 方法调用

SomeClass *sc = [[SomeClass alloc] init]

重点1. 对象调用 alloc,底层 C 接口 objc_alloc_init

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}

这里要注意的是,alloc 核心方法都在 callAlloc, init 调用需要看各自类的实现,系统根类(NSObject)没有太多实现, 下面是 init 的实现。

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    // 根类仅仅返回对象本身
    return obj;
}

说起 alloc/init,不得不提下他们的聚合版本 new,实际上他的内部实现等同于 alloc + init,但是为何不推荐使用呢?

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

在UIKit 或 Foundation 框架中,很多类会将 first design 方法替换成自定义 initWithXXX 方法,使用 new 将很难发现这些类初始化失败的原因,导致代码可读性降低,查错困难。

小插曲

Xcode 10 和 Xcode 11 在 debug 时,堆栈不太一致

Xcode 11 中不会在堆栈中显示调用 objc_alloc_init,只有注释中说明了这一步骤。

对于这个问题,目前的说法是源码开源得不够充分。以下这段代码虽然未调用到,但其逻辑也是耐人寻味。 大致猜测是这个方法等于交换单次的Method Swizzling

重点2. 计算要开辟的内存大小

这里需要注意,oc 对象本身需要的内存大小基础上,会进行 16 字节对其,目的是为了进行读取速度优化,空间换时间。实际上大多只需要 8 字节就能存储完成,如下为 16 字节对其代码。

size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        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
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }
// 16 字节对其算法
static inline size_t align16(size_t x) {
      // 抹掉末尾的0,只保留 16 的倍数
    return (x + size_t(15)) & ~size_t(15); 
}

你可能感兴趣的:(OC 对象 "+alloc" 方法内部流程)