在平常开发中我们会经常用到自定义类,并且会调用其alloc+init方法来创建一个实例对象。但是这个过程到底发生了什么,对象是如何创建的,如果其占用的内存大小一直没有深究过,今天就来好好掰扯掰扯~~
一、对象创建初探
定义一个LGPerson类,什么属性都不添加,然后分别打印经过alloc和init后的对象地址:
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
LGNSLog(@"%@ - %p - %p",p1,p1,&p1);
LGNSLog(@"%@ - %p - %p",p2,p2,&p2);
LGNSLog(@"%@ - %p - %p",p3,p3,&p3);
输出如下:
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936198
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936190
<LGPerson: 0x600002135480> - 0x600002135480 - 0x7ffee0936188
- p1,p2,p3都指向同一个对象,说明init并不会开辟新的内存。
- 第一个%p 打印指针信息,所以也是0x600002135480。
- 第二个%p 打印指针的地址,即p1,p2,p3在栈上的地址,因为它们在相邻的变量所以在栈上地址是连续的,另外在64位系统上指针占8个字节,所以它们之间地址相差8。
二、分析alloc内部流程
那0x600002135480
这块内存是如何开辟并初始化的呢,想要知道具体的实现流程我们可以借助ios开源runtime代码来进行动态调试,具体项目搭建可以参考这里最新macOS 10.15下objc4-779.1源码编译调试。在开源项目上进行测试代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *objc1 = [LGPerson alloc];
LGPerson *objc2 = [LGPerson alloc];
NSLog(@"Hello, World! %@ - %@",objc1,objc2);
}
return 0;
}
objc1对象创建流程:
跟踪调试[LGPerson alloc]
方法执行过程,整个堆栈调用情况如下:
最终会调用到这个
_class_createInstanceFromZone
,它就是我们oc类对象创建时最终调用的方法,其中有几个关键步骤,解释一下:
//步骤一:确认创建类需要开辟的内存大小
size = cls->instanceSize(extraBytes);
//步骤二:开辟内存
obj = (id)calloc(1, size);
//步骤三:把isa信息填充到上面申请到的内存中
obj->initInstanceIsa(cls, hasCxxDtor);
objc2对象创建流程:
在LGPerson *objc2 = [LGPerson alloc];打个断点,然后执行到此处后再到_class_createInstanceFromZone打个断点,整个堆栈调用情况如下:
这里发现LGPerson类第二次调用alloc方法是比第一次流程少了
callAlloc
、[NSObject alloc]
、objc_rootAlloc
,跟踪代码发现两次创建对象callAlloc
方法中执行流程不一样:
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
//第一次LGPerson类调用alloc时,该if方法没有进
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);
}
//第一次LGPerson类调用alloc时,会执行到此处通过objc_msgSend调用了alloc,后续流程又会回到当前方法中来
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
第一个LGPerson对象创建时,系统底层还未加载过LGPerson类到内存中来,所以调用
cls->ISA()->hasCustomAWZ()
方法不成立,会进入到下面的objc_msgSend流程中。在objc_msgSend方法流程中会把LGPerson类信息读取进来,并进行缓存,以及一些类的初始化(整体流程我们后面在起一篇文章详细分析下)。所以,当创建第二个objc2对象时,类信息已经加载到内存中了,此时会直接进入到_objc_rootAllocWithZone
方法中。
三、instanceSize方法分析
前面分析到对象创建有三个关键步骤,其中第一步就是确定对象需要开辟的内存大小,先看一下instanceSize
方法的实现:
// 1.调用类的instanceSize方法
size = cls->instanceSize(extraBytes);
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
//2.直接进入此处,因为类信息被加载到内存中后会被缓存
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;
}
//cache对象的
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
//3.通过_flags来确定对象创建需要的内存大小
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
//4.进一步处理,并进行16字节对齐
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
void setFastInstanceSize(size_t newSize)
{
// Set during realization or construction only. No locking needed.
uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
uint16_t sizeBits;
// Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
// to yield the proper 16byte aligned allocation size with a single mask
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
sizeBits &= FAST_CACHE_ALLOC_MASK;
if (newSize <= sizeBits) {
newBits |= sizeBits;
}
_flags = newBits;
}
#define FAST_CACHE_ALLOC_MASK 0x1ff8
#define FAST_CACHE_ALLOC_MASK16 0x1ff0
#define FAST_CACHE_ALLOC_DELTA16 0x0008
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
神奇的代码,暂还没读懂啥意思~
四、init和new
当然我们在alloc创建出来一个对象后一般都会调用init方法,或者直接通过调用new方法来进行对象的创建。那它们具体是做了什么呢,看一下init和new的源码实现:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
此处的init什么都没做,只是self返回,所以一般我们都在子类中重写init方法并调用
self = [super init]
。
而new则先调用了callAlloc
方法,然后调用init,相当于调用了alloc
+init
。
最后
关于对象的创建流程,本文中只分析到对象大小创建这一步,关于calloc
调用、isa信息初始化的分析就留着后面再继续吧~~
未完待续...
网友评论