OC对象(一)-- alloc和init底层到底在干嘛
OC对象(二)-- 内存对齐和calloc中的16字节对齐
OC对象(三)-- isa结构分析
本文使用的源码是objc4-787.1
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方法中调用了_objc_rootInit方法,最后返回的就是self。所以可以说init中什么都没有做。
用个demo验证一下:
DZObject *obj1 = [DZObject alloc];
DZObject *p1 = [obj1 init];
DZObject *p2 = [obj1 init];
NSLog(@"%@ - %@ - %@", obj1, p1, p2);
NSLog(@"%p - %p - %p", obj1, p1, p2);
NSLog(@"%p - %p - %p", &obj1, &p1, &p2);
说明:
- DZObject是自定义的类,继承自NSObject。
- alloc一个实例obj1
- 再分别定义实例p1和p2,通过obj1调用init方法
- 分别打印实例对象、指针、和指针地址
打印结果如图:
对象和指针都是相同的,指针地址不同。内存中结构如图:
init方法是苹果提供的工厂方法,对实例对象没有进行任何处理。开发者可以自定义init的实现,比如初始化一些属性数据的工作。
alloc
alloc流程图
图中粉色部分是alloc的核心部分,主要进行了三部操作
- cls->instanceSize:计算需要开辟的内存空间,内部实现是个算法,在最新源码的算法是16字节对齐。
- calloc:用计算好的size作为参数,调用calloc方法,开辟内存空间。
- obj->initInstanceIsa:初始化示例的isa
instanceSize实现(字节对齐算法)
函数方法调用流程图入下:
核心代码如下:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
计算过程示例:例如x=4,如下表:
16进制 | 2进制 |
---|---|
x=4 | 0000 0100 |
+ | |
15 | 0000 1111 |
= | |
19 | 0001 0011 |
& | |
~15 | 1111 0000 |
16 | 0001 0000 |
经过算法后,得到的结果都是16的倍数(可以使用大于16的数字算一遍)。这就是16字节对齐。
new
经常会用new方法直接示例对象,例如
DZObject *obj = [DZObject new];
看看new的底层源码实现:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
通过源码可以看到,new做了alloc和init合并的事。==但是不建议直接使用new。如果一个自定义类中添加了initWithXXX:方法,方法中做了初始化的相关逻辑。那么直接调用new,只是调用了系统提供的init方法。==
NSObject alloc
当用NSObject alloc的时候,走的流程与上面的一样。其实alloc的真正调用的是objc_alloc方法,验证方式:在alloc下断点,打开汇编,运行:
可以看到汇编代码调用的是objc_alloc,接下来看看它的源码实现:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
因此alloc的真正的流程图是这样的:
扩展 - 那为什么调用alloc方法时会先调用objc_alloc函数呢?
苹果使用LLVM编译工具,在编译期间对alloc方法进行了特殊处理,可以理解为是系统级别的hook方法,也就是将alloc方法和objc_alloc函数进行交换。
展示一下部分llvm源码中tryGenerateSpecializedMessageSend
函数中的部分实现:
再来看看tryGenerateSpecializedMessageSend
的调用地方:
大致的思路是,系统将alloc标记为特殊消息发送。一个类调用alloc的时候系统会去调用objc_alloc。在objc_alloc源码中通过objc_msgSend再次发送一次alloc消息(这次不会被系统标记成特殊消息,正常调用alloc方法)。