前言
本文使用objc源码版本为:objc4-818.2
常见的对象初始化如下:
Foo *Obj0 = [[Foo alloc] init];
Foo *Obj1 = [Foo new];
其中,alloc init new三个关键字的意义可以理解为:
alloc: 创建对象并分配内存init: 对象初始化new: 理解为alloc + init,创建并初始化了对象
init
示例代码如下
Foo *obj = [Foo alloc];
Foo *obj1 = [obj init];
Foo *obj2 = [obj init];
调试分析
上文讲到init函数执行了对象初始化,这意味着init函数并不进行对象的创建工作。
运行示例代码,可以发现obj1和obj2的对象地址与obj相同,即init并没有实际创建对象。
汇编分析
断点在init语句汇编代码如下,可以发现init是通过objc_msgSend(消息发送)机制被执行的。
源码分析
- (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;
}
从上面源码可发现,init实际上什么都没有做就直接返回了,结合源码注释可以猜测,实际开发中[super init]是不能保证被调用到的,所以init中不敢放有意义的操作。
new
示例代码
Foo *obj0 = [Foo new];
Foo *obj1 = [Foo new];
调试分析
通过new创建出的是两个不同的对象,并且都完成了初始化。
汇编分析
通过汇编代码可发现,[Foo new]具体执行时,是通过函数地址调用了objc_opt_new函数,而不是通过消息发送向Foo发送new函数,这里是因为LLVM针对new, alloc等函数做了一层拦截优化。
在源码中找到
objc_opt_new的定义如下:
// Calls [cls new]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
// 当该类有默认的 new/self/class/respondsToSelector/isKindOfClass方法时
// 直接返回[[class alloc] init]
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
// 相当于[[class alloc] init]
return [callAlloc(cls, false/*checkNil*/) init];
}
#endif
// 通过消息发送执行new函数
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
源码分析
new的源码定义如下,上文objc_opt_new中通过消息发送执行new即调用这段代码中。
+ (id)new {
// 该函数不必展开 只需理解为[[class init] init]即可
return [callAlloc(self, false/*checkNil*/) init];
}
alloc
示例代码
Foo *obj0 = [Foo alloc];
Foo *obj1 = [Foo allocWithZone:nil];
调试分析
两次alloc创建出了两个不同的对象,且他们的age属性并没有被初始化为18。
alloc和allocWithZone的区分
allocWithZone:可以理解为历史包袱,下文中不进行单独分析。
汇编分析
通过汇编代码发现通过地址直接调用了objc_alloc函数(LLVM做了优化)。
查看
objc_alloc函数定义如下,callAlloc函数在下文中进行分析
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
// Calls [cls allocWithZone:nil].
id
objc_allocWithZone(Class cls)
{
return callAlloc(cls, true/*checkNil*/, true/*allocWithZone*/);
}
源码分析
callAlloc定义如下:
// 名词解释
// slowPath:告诉编译器该处条件判断更可能为false
// fastPath:告诉编译器该处条件判断更可能为true
// hasCustomAWZ: 是否有自定义的alloc实现(如果没有默认alloc实现则为false)
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
// 检查当前创建的Class是否为nil
if (slowpath(checkNil && !cls)) return nil;
// 当前Class是否有自定义的alloc方法(是否有默认alloc方法)
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// 重点!! 创建对象&&分配内存的具体函数
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
// allocWithZone的逻辑 不必关注
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
// 当前存在自定义alloc方法 通过消息发送机制调用自定义alloc方法
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
通过消息发送调用的alloc函数如下:
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
// 执行回callAlloc函数 不过此时不再检查Class是否为空
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
执行对象创建和分配内存的函数_objc_rootAllocWithZone:
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
// class or superclass has .cxx_construct/.cxx_destruct implementation
// 类是否有c++的构造/析构函数
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// !!计算创建对象需要的大小 会进行字节对齐!!
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// !!通过calloc函数分配内存空间!!
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
// 分配内存失败 调用对应的Handler函数
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
// !!初始化ISA!!
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);
}
// 没有c++构造函数则直接返回创建好的obj
if (fastpath(!hasCxxCtor)) {
return obj;
}
// 返回执行c++构造函数后的obj
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
函数内我们最关注的主要有三个步骤:
- 计算创建对象所需的大小:
size = cls->instanceSize(extraBytes); - 申请内存:
obj = (id)calloc(1, size); - 初始化 ISA:
obj->initInstanceIsa(cls, hasCxxDtor);
流程分析
alloc的流程相对较长 需要借助流程图进行梳理
(修正:_objc_rootAlloc(self)->objc_alloc改正为_objc_rootAlloc(self)->callAlloc)
本地调试时发现,即使没有自定义alloc,第一次进入callAlloc时,hasCustomAWZ()判断也为true;第二次进入callAlloc时,hasCustomAWZ()才返回false。这里结合源码分析该现象:
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
根据源码可发现该函数实际判断位信息FAST_CACHE_HAS_DEFAULT_AWZ,即是否有默认的alloc/allocWithZone实现。
而第一次进入callAlloc时,该位信息为0,表示没有默认的alloc实现,第二次进入callAlloc时,该位信息已经变成了1。
在setHasDefaultAWZ()处断点,发现两次进入callAlloc期间该函数确实被执行了,栈信息如下:
两处关键信息:
1.第一次进入callAlloc后,因为hasCustomAWZ()为true,走了objc_msgSend(alloc)的分支。
2.向Class发送消息时,如果该类还没有实现or初始化,则会进行实现&初始化,也就是在这时候,我们的类的FAST_CACHE_HAS_DEFAULT_AWZ位信息被设为了1。
综上,第一次进入callAlloc时hasCustomAWZ()返回true是因为我们的类这时候还未接收过任何消息,并没有初始化过,所以并没有默认的alloc实现。而此时通过消息发送机制调用该类的alloc,则会触发类的初始化,所以在第二次进入callAlloc时,hasCustomAWZ()返回了false。
我们可以通过在第一次调用[Foo alloc]前先向Foo类发送一条消息(执行一次函数调用)进行验证。
通过上图可证明,在第一次向
Foo类发送消息时,确实触发执行了setHasDefaultAWZ(),此时再执行callAlloc时,hasCustomAWZ()将返回false,直接执行_objc_rootAllocWithZone。