在平常的开发中使用[[NSObject alloc] init]去创建一个对象是再正常不过的事,但是alloc和init底层分别做了什么事情却很少去研究,抱着这样的疑问我查阅了相关资料和objc的源码。
首先我们需要从苹果官方开源代码列表找到需要查看的源码,这里我用到的是最新的objc-756.2版本。下载到本地后需要进行编译调试才能使用的,这里我参考了Cooci大神的iOS_objc4-756.2 最新源码编译调试。
1.alloc源码分析
源码调试完毕后就可以开始分析流程了
首先搜索alloc { ,注意这里是花括号,这样我们得到结果
点击后看到代码
继续跟进去
再点进去可以看到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];
}
这个方法里其实有很多点要说,不过这次重点是alloc创建对象的流程,其他的之后再补充。
我们在这里下一个断点
然后跟进去
再跟进去可以看到_class_createInstanceFreomZone()方法的源码:
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());
// 一次读取类的信息位以提高性能
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) {
// 分配1块大小为size的连续内存
obj = (id)calloc(1, size);
if (!obj) return nil;
// 初始化对象的isa
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;
}
因为最后一句 return obj; 我们可以得知obj就是最后返回的创建好的对象,那么obj在何时创建的呢,我们先在这里下个断点:
跟着断点往下走,在6524行和6526行分别 po obj 可以得到结果:
由此我们可以知道calloc()函数为对象开辟了一块大小为size的连续内存空间,而initInstanceIsa()函数通过初始化isa将对象和当前的class关联起来。简化来说就是这两行代码创建了对象。那么此时还有其他问题,内存空间的大小size是如何确定的呢?还有这个initInstanceIsa()函数里面到底做了什么?
我们先来看size的问题
从_class_createInstanceFreomZone()方法的源码中可以看到
size_t size = cls->instanceSize(extraBytes);这说明size的大小是由instanceSize()函数确定的,点进去查看源码:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}此时extraBytes是0,暂时可以不用管,这里可以看到即使size<16,也会开辟一块大小为16字节的内存,继续查看alignedInstanceSize()的源码:
// Class’s ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}由函数名可以得知word_align()的作用是字节对齐,先点进unalignedInstanceSize()函数里查看源码:
// May be unaligned depending on class’s ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}这里作用其实就是从类的data数据段的ro里拿到数据信息,至于ro是什么会在之后研究,现在只知道这里会返回一个uint32_t类型的size,然后再查看word_align()的源码:
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}这里用了一个很巧妙的算法,WORD_MASK在64位操作系统下是7,通过计算可以知道word_align()函数的作用就是8字节对齐。
通过这一系列的分析我们可以知道size的大小是8的倍数并且最小是16。
因为isa是很重要的一点,所以initInstanceIsa()函数里面具体做了什么还有isa的结构等等博主会新起一篇文章来记录研究过程。
从上面的代码和分析中可以看到alloc大体上做了很多事情,并且最终返回了创建的对象,那么问题来了,init究竟做了什么事情呢?
2.init和new的作用
首先查看init的源码:
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}我们可以看到,无论是类方法还是对象方法init所做的都只是将alloc创建的对象返回。那么苹果官方为什么这么设计呢?其实也不难理解,这是一种工厂设计模式,为了开发者在日常的开发中根据不通的业务需求来重写init。
再来看看new:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}显然,new相当于 alloc + init 。
3.总结
我们通过最常见的alloc函数源码分析出了对象创建的大体流程,下面用一张流程图总结一下alloc创建对象的过程:
其实这之中还有很多没分析到的和不足的地方,我会在以后逐渐补充进来。