示例: 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);
}