iOS进阶专项分析(一)、alloc和init的底层实现
引子:
一个经典的面试问题:
Objective-C中alloc和init的区别是什么?
或者是问:下面这块代码执行后打印的结果
BMPerson * person = [BMPerson alloc];
BMPerson * p1 = [person init];
BMPerson * p2 = [person init];
NSLog(@"\n打印地址 %p - %p - %p", person, p1, p2);
执行一下结果发现这三个对象的地址是相同的
2020-06-17 15:47:20.525517+0800 TextProject[12922:914030]
打印地址 0x6000004b8680 - 0x6000004b8680 - 0x6000004b8680
为什么呢?接下来我来详细分析一下alloc和init底层究竟干了些什么
一、alloc底层探索
点击alloc方法在NSObject.h
文件中只能看到方法声明,从这个声明我们能看出 alloc这个方法是一个类方法,会返回一个 instancetype 类型的值。但是看不到具体信息了
+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
想要看alloc底部具体实现,我们需要去苹果官方文档open source里面下载 objc开放的源码 苹果开放文档传送门
下载完成打开objc4-750
部分,直接搜索 alloc {
就能找到实现部分 (注意搜索输入的alloc
和{
之间有空格)。
+ (id)alloc {
return _objc_rootAlloc(self);
}
发现了一个方法 _objc_rootAlloc
,继续点击该方法定义,找到 callAlloc
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
继续点击callAlloc
,发现该方法的实现代码
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
分析这部分代码,抛开优化的部分,我们不难挑出核心代码
id obj = class_createInstance(cls, 0);
return obj;
继续进入方法 class_createInstance
查看方法实现
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
我们发现 _class_createInstanceFromZone
里面 调用了cls->instanceSize
calloc
以及 obj->initIsa
, 即 开辟空间以及初始化isa
, 至此 obj
初始化完成,直接把 obj
返回出去。
到这里alloc的流程已经比较清楚了,alloc通过我们的类创建了实例对象
二、init底层探索
那么问题来了,既然alloc
都已经把实例变量创建好了,为什么还需要再调用init
呢?
我们接下来在objc750
源码中搜索 init {
,发现了init
这个实例方法调用了 _objc_rootInit
这个函数。
- (id)init {
return _objc_rootInit(self);
}
分析一下这里的调用,加上我们平时创建实例都是类似于 [[BMPerson alloc]init]
,所以此时的self
就是我们在alloc
里已经创建完成的实例对象,既然知道了此时的 self
是谁了,那么接下来我们继续进入 _objc_rootInit
这个函数,
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// 实际上,将很难依靠此功能。
// Many classes do not properly chain -init calls.
// 许多类没有属性 - 我们需要在init中按照业务需求自定义实现。
return obj;
}
进入之后我们发现,_objc_rootInit
这个方法里面其实什么也没做,直接把传入的self
给返回了出去。接下来我们看注释,发现了苹果设计时候的用意。init
就是苹果提供给我们自定义的。这里体现了一个工厂设计模式
:父类没有执行,提供给子类自定义
三、总结
1、alloc
&init
底层调用流程图:
2、alloc
方法底层已经完成开辟空间和创建类的实例变量了,init
是专门为NSObject
预留出来的,体现出工厂设计模式
,即父类没有执行,提供给子类自定义
。