OC对象创建过程 - alloc、init & new
什么是 alloc 、init 和 new?
先来看看我们经常初始化一个对象的方法
NSobject *object1 = [[NSobject alloc] init]; 这里面其实是调用了两个方法,类方法alloc 和 对象方法init。那这里就有个疑问:alloc和init分别做什么呢?苹果为什么会这么设计呢?针对这个问题,通过以下的demo来进行探究。
NSObject *obj1 = [NSObject alloc];
NSObject *obj2 = [obj1 init];
NSObject *obj3 = [obj1 init];
LGNSLog(@"%@ - %p - %p",obj1,obj1,&obj1);
LGNSLog(@"%@ - %p - %p",obj2,obj2,&obj2);
LGNSLog(@"%@ - %p - %p",obj3,obj3,&obj3);
打印结果
<NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9158
<NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9150
<NSObject: 0x6000036a4380> - 0x6000036a4380 - 0x7ffee7fc9148
有这个打印结果可以发现obj1、obj2和obj3指向的内存地址以及他们的类型是一样的,不一样的还是变量本事的地址。因此我们可以推测,对象在alloc方法中就已经分配了内存地址和类信息的绑定。而init方法则不会改变对象的内存地址。实际上init是苹果为了方便开发者统一对象初始化方法而设计的工厂方法,开发者可以根据需要重写init方法做自己定义的初始化操作。
为了进一步验证我们的推测,可以直接阅读苹果官方源码:官方源码地址,这里面很多版本,我选用的781这个版本。
- alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
// 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*/);
}
可以看出alloc方法通过先调用_objc_rootAlloc函数,并通过_objc_rootAlloc调用callAlloc函数实现了包括内存分配和类型绑定的操作,跟我们的推测是一致的。callAlloc路径比较长,这里不一一列出,实际上里面做了很多事情,后面单独分析。
-
init
接下来通过源码分析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;
}
在这里我们可以看到init方法实际上什么也没做,返回的是对象本身,跟我们的推测是一致的。
-
new
接下来我们看看new方法:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
这里我们看到new方法实际上也是直接调用的callAlloc函数和init方法进行初始化的。那么简化成OC代码其实 [NSObject new]等价于[[NSObject alloc] init]的。但是我们为什么不推荐使用new方法呢?主要还是为了防止有些对象自定义了初始化方法,而new方法内部只是简单的调用了init方法,这样就有可能会漏掉一些初始化信息;而且从代码设计角度讲也不利于代码风格的统一。
alloc到底做了什么,怎么做?
由前面的分析我们可以知道init、new方法都很容易理解,但是alloc调用流程却相对复杂,那么alloc方法除了内存分配和类绑定还做了哪些事情?对象内存是如何分配的?以及如何进行类型绑定的?接下来我们继续对源码进行分析,了解alloc方法调用流程。
-
alloc流程图
根据源码的分析,我们来大概绘制一下alloc方法调用的流程图:
屏幕快照 2020-09-11 上午10.57.12.png
可以说alloc流程还是比较复杂的,这里我们对一些比较重要的函数进行分析。
-
callAlloc
有源码可知alloc内部实际上是先调用_objc_rootAlloc函数,然后_objc_rootAlloc只是调用callAlloc函数并返回。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));
}
以上代码可以看出callAlloc里面会调用_objc_rootAllocWithZone函数,这个函数实际上只是直接调用_class_createInstanceFromZone并返回,代码如下:
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);
}
-
_class_createInstanceFromZone
在这个过程我们发现创建对象的核心函数是_class_createInstanceFromZone,源码如下:
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
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 {
// alloc 开辟内存的地方
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
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);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
分析代码发现里面主要调用有三个函数instanceSize、calloc和initInstanceIsa。instanceSize用来计算对象的大小size:
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;
}
随后calloc根据size开辟出一个内存空间并返回内存地址,这个内存是16字节对齐的(关于内存对齐参考内存对齐),它是对象的地址:
obj = (id)calloc(1, size);
这时候的obj还没有指向任何类型,就是一个普通的指针。获得生成的对象obj之后会调用initInstanceIsa(或initIsa)函数进行对象和类的绑定,实际上不管是调用initInstanceIsa或initIsa,它们最后都会调用objc_object::initIsa方法:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
const char *mangledName = cls->mangledName();
if (strcmp("MyObject", mangledName) == 0) {
if(!cls->isMetaClass()){//避免元类的影响
printf("我来了 MyObject");//定位要调试的类
}
}
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;
}
通过代码可以就看出这个函数实际上就是设置isa指针,让isa指针指向对象的class信息以及初始化引用计数为1(newisa.extra_rc = 1)。点击了解isa指针的更多信息。