做为 iOS
开发者,咱们天天打交道最多的应该就是对象了,从面向对象设计的角度来讲,对象的建立以及初始化是最基础的内容。那么,今天咱们就一块儿来探索一下 iOS
中最经常使用的 alloc
和 init
的底层是怎么实现的吧。html
对于第三方开源框架来讲,咱们去剖析内部原理和细节是有必定的方法和套路能够掌握的。而对于 iOS
底层,特别是 OC
底层,咱们可能就须要用到一些开发中不是很经常使用的方法。程序员
咱们这个系列主要的目的是为了进行底层探索,那么咱们做为 iOS
开发者,须要关注应该就是从应用启动到应用被 kill
掉这一整个生命周期的内容。咱们不妨从咱们最熟悉的 main
函数开始,通常来讲,咱们在 main.m
文件中打一个断点,左侧的调用堆栈视图应该以下图所示:macos
要获得这样的调用堆栈有两个注意点:设计模式
- 须要关闭
Xcode
左侧Debug
区域最下面的show only stack frames with debug symbols and between libraries
- 须要增长一个
_objc_init
的符号端点
咱们经过上面的调用堆栈信息不可贵出一个简单粗略的加载流程结构sass
咱们如今心中创建这么一个简单的流程结构,在后期分析底层的时候咱们会回过头来梳理整个启动的流程。app
接下来,让咱们开始实际的探索过程。框架
咱们直接打开 Xcode
新建一个 Single View App
工程,而后咱们在 ViewController.m
文件中调用 alloc
方法。ide
NSObject *p = [NSObject alloc];
咱们按照常规探索源码的方式,直接按住 Command
+ Control
来进入到 alloc
内部实现,但结果并不是如咱们所愿,咱们来到的是一个头文件,只有 alloc
方法的声明,并无对应的实现。这个时候,咱们会陷入深深的怀疑中,其实这个时候咱们只要记住下面三种经常使用探索方式就能迎刃而解:函数
具体操做方式为 Control
+ in
post
这里的
in
指的是左侧图片中红色部分的按钮,其实这里的操做叫作 Step into instruction
。咱们能够来到下图这里
咱们观察不可贵出咱们想要找的就是 libobjc.A.dylib
这个动态连接库了。
具体操做方式为打开 Debug
菜单下的 Debug Workflow
下的 Always Show Disassembly
接着咱们仍是下代码断点,而后一步一步调试也会来到下图这里:
咱们先选择 Symbolic Breakpoint
,而后输入 objc_alloc
,以下图所示:
至此,咱们获得了 alloc
实现位于 libObjc
这个动态库,而恰好苹果已经开源了这部分的代码,因此咱们能够在 苹果开源官网 最新版本 10.14.5 上下载便可。最新的 libObc
为 756。
libObjc
源码咱们下载了 libObjc
的源码到咱们的电脑上后是不能直接运行的,咱们须要进行必定的配置才能实现源码追踪流程。这一块内容不在本文范围内,读者可参考 iOS_objc4-756.2 最新源码编译调试。
配置好 libObjc
以后,咱们新建一个命令行的项目,而后运行以下代码:
NSObject *myObj = [NSObject alloc];
而后咱们直接下符号断点 objc_alloc
,而后一步步调试,先来到的是 objc_alloc
// Calls [cls alloc]. id objc_alloc(Class cls) { return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/); }
而后会来到 callAlloc
方法,注意这里第三个参数传的是 false
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { // 判断传入的 checkNil 是否进行判空操做 if (slowpath(checkNil && !cls)) return nil; // 若是当前编译环境为 OC 2.0 #if __OBJC2__ // 当前类没有自定义的 allocWithZone if (fastpath(!cls->ISA()->hasCustomAWZ())) { // No alloc/allocWithZone implementation. Go straight to the allocator. // 既没有实现 alloc,也没有实现 allocWithZone 就会来到这里,下面直接进行内存开辟操做。 // fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary // 修复没有元类的类,用人话说就是没有继承于 NSObject // 判断当前类是否能够快速开辟内存,注意,这里永远不会被调用,由于 canAllocFast 内部 // 返回的是false 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]; }
由于咱们在 objc_init
中传入的第三个参数 allocWithZone
是 true
,而且咱们的 cls
为 NSObject
,那么也就是说会这里直接来到 return [cls alloc]
。咱们接着往下走会来到 alloc
方法:
+ (id)alloc { return _objc_rootAlloc(self); }
而后咱们接着进入 _objc_rootAlloc
方法内部:
// 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*/); }
是否是有点似曾类似,没错,咱们第一步进入的 objc_init
也是调用的 callAlloc
方法,可是这里有两个参数是不同的,第二个参数 checkNil
是否须要判空直接传的是 false
,站在系统角度,前面已经在第一次调用 callAlloc
的时候进行了判空了,因此这里不必再次进行判空的了。第三个参数 allocWithZone
传的是 true
,关于这个方法,我查阅了苹果开发者文档,文档解释以下:
Do not overrideallocWithZone:
to include any initialization code. Instead, class-specific versions ofinit...
methods.
This method exists for historical reasons; memory zones are no longer used by Objective-C.
译:不要去重载allocWithZone
并在其内部填充任何初始化代码,相反的,应该在init...
里面进行类的初始化操做。
这个方法的存在是有历史缘由的,内存zone
已经再也不被Objective-C
所使用的。
按照苹果开发者文档的说法,其实 allocWithZone
本质上和 alloc
是没有区别的,只是在 Objective-C
远古时代,程序员须要使用诸如 allocWithZone
来优化对象的内存结构,而在当下,其实你写 alloc
和 allocWithZone
在底层是一模模同样样的。
好的,话题扯远了,咱们接着再次进入到 callAlloc
方法内部,第二次来到 callAlloc
的话,在 !cls->ISA()->hasCustomAWZ()
这里判断 cls
没有自定义的 allocWithZone
实现,这里的判断实质上是对 cls
也就是 object_class
这一结构体内部的 class_rw_t
的 flags
与上一个宏 RW_HAS_DEFAULT_AWZ
。通过笔者测试,在第一次进入 callAlloc
方法内部的时候, flags
值为 1 ,而后 flags
与上 1<<16
结果就是 0 ,返回过去也就是 false
,而后在 hasCustomAWZ
这里取反以后,返回的就是 true
,而后再一取反,天然就会跳过 if
里面的逻辑;而第二次进入 callAlloc
方法内部的时候, flags
值是一个很大的整数,与上 1<<16
后结果并不为0 ,因此 hasDefaultAWZ
会返回 true
,那么 hasCustomAWZ
这里就会返回 false
,那么返回到 callAlloc
的时候天然就会进入 if
里面的逻辑了。
这里插一句,在咱们 OC 的类的结构中,有一个结构叫class_rw_t
,有一个结构叫class_ro_t
。其中class_rw_t
是能够在运行时去拓展类的,包括属性,方法、协议等等,而class_ro_t
则存储了成员变量,属性和方法等,不过这些是在编译时就肯定了的,不能在运行时去修改。
bool hasCustomAWZ() { return ! bits.hasDefaultAWZ(); } bool hasDefaultAWZ() { return data()->flags & RW_HAS_DEFAULT_AWZ; }
而后咱们会来到 canAllocFast
的判断,咱们继续进入该方法内部
if (fastpath(cls->canAllocFast()))
bool canAllocFast() { assert(!isFuture()); return bits.canAllocFast(); } bool canAllocFast() { return false; }
结果很显然,这里 canAllocFast
是一直返回 false
的,也就是说会直接来到下面的逻辑
id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj;
咱们再次进入 class_createInstance
方法内部
id class_createInstance(Class cls, size_t extraBytes) { return _class_createInstanceFromZone(cls, extraBytes, nil); } static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { // 对 cls 进行判空操做 if (!cls) return nil; // 断言 cls 是否实现了 assert(cls->isRealized()); // Read class's info bits all at once for performance // cls 是否有 C++ 的初始化构造器 bool hasCxxCtor = cls->hasCxxCtor(); // cls 是否有 C++ 的析构器 bool hasCxxDtor = cls->hasCxxDtor(); // cls 是否能够分配 Nonpointer,若是是,即表明开启了内存优化 bool fast = cls->canAllocNonpointer(); // 这里传入的 extraBytes 为0,而后获取 cls 的实例内存大小 size_t size = cls->instanceSize(extraBytes); // 这里 outAllocatedSize 是默认值 nil,跳过 if (outAllocatedSize) *outAllocatedSize = size; id obj; // 这里 zone 传入的也是nil,而 fast 拿到的是 true,因此会进入这里的逻辑 if (!zone && fast) { // 根据 size 开辟内存 obj = (id)calloc(1, size); // 若是开辟失败,返回 nil if (!obj) return nil; // 将 cls 和是否有 C++ 析构器传入给 initInstanceIsa,实例化 isa obj->initInstanceIsa(cls, hasCxxDtor); } else { // 若是 zone 不为空,通过笔者测试,通常来讲调用 alloc 不会来到这里,只有 allocWithZone // 或 copyWithZone 会来到下面的逻辑 if (zone) { // 根据给定的 zone 和 size 开辟内存 obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { // 根据 size 开辟内存 obj = (id)calloc(1, size); } // 若是开辟失败,返回 nil if (!obj) return nil; // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. // 初始化 isa obj->initIsa(cls); } // 若是有 C++ 初始化构造器和析构器,进行优化加速整个流程 if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } // 返回最终的结果 return obj; }
至此,咱们的 alloc
流程就探索完毕,但在这其中咱们仍是有一些疑问点,好比,对象的内存大小时怎么肯定出来的, isa
是怎么初始化出来的呢,不要紧,咱们下一篇接着探索。这里,先给出笔者本身画的一个 alloc
流程图,限于笔者水平有限,有错误之处望读者指出:
分析完了 alloc
的流程,咱们接着分析 init
的流程。相比于 alloc
来讲, init
内部实现十分简单,先来到的是 _objc_rootInit
,而后就直接返回 obj
了。其实这里是一种抽象工厂设计模式的体现,对于 NSObject
自带的 init
方法来讲,其实啥也没干,可是若是你继承于 NSObject
的话,而后就能够去重写 initWithXXX
之类的初始化方法来作一些初始化操做。
- (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; }
先秦荀子的劝学中有言:
不积跬步,无以致千里;不积小流,无以成江海。
咱们在探索 iOS
底层原理的时候,应该也是抱着这样的学习态度,注意点滴的积累,从小作起,聚沙成塔。下一篇笔者将对本文留下的两个疑问进行解答: