欢迎你们继续阅读iOS原理探索系列篇章(后续陆续完善补充)算法
在序章中,初步探索了iOS程序启动流程,可是不少东西都涉及系统底层库的加载,以及一系列晦涩难懂的资料,因此为了由浅入深的总结梳理,我这里就从咱们最熟悉的陌生人对象
开始。缓存
做为程序猿,没对象不可怕,可怕的是咱们竟然只能本身new
一个对象出来,咱们新建对象的方式有两种,以下: bash
想要知道对象建立方法在底层到底作了哪些事情,咱们就须要去看源代码的实现,正常咱们使用的方式就是跳转到方法定义去查看实现,以下图所示,咱们最多能到NSObject
的方法声明,没法去查看具体的源代码了,可是咱们仍是有其余的方式能够去查看源码实现。 app
小断点调试 正常咱们打完断点以下图: 函数
control
键,再看断点,能够看到出现了变化以下图:
control
的同时,点击中间的下箭头,能够看到程序来到汇编:
alloc
方法时,系统底层调用了objc_alloc
,因此咱们能够添加符号断点objc_alloc
查看底层库
alloc
底层库是libobjc.A.dylib
库。 符号断点调试 一样咱们能够直接添加符号断点alloc
来查看底层库实现,以下图: 源码分析
alloc
的调用
libobjc.A.dylib
库实现。 开启Debug Workflow
模式 经过前两种方式咱们能够看到,底层都是汇编,由于OC是高级语言,最终能被机器识别的仍是汇编语言,因此咱们能够直接开启查看汇编代码模式,以下图: post
objc_alloc
control
+下箭头,继续进入到objc_alloc
的断点,继续往下走,看到以下,一样可以看到objc_alloc
libobjc.A.dylib
源码分析Apple其实开源了不少源码给咱们,libobjc
也是其中之一,想要直接查看源码实现能够按照以下操做:优化
- 官方objc源码下载,最新版本是
objc4-779.1
- 官方源码调试编译请查看Cooci老司机的这篇文章最新macOS 10.15下objc4-779.1源码编译调试
这里我也下载编译了最新的779.1,而后咱们能够查看源代码,可是咱们能够看到源代码很是多,大部分人一开始看确定很懵逼,因此咱们不用所有文件去看,只须要看咱们的目标代码便可。 ui
就如同咱们在断点调试中已经知道, alloc
方法最终底层都是到NSObject
的alloc
方法,因此咱们能够直接找到NSObject
的头文件,查看alloc
方法的实现,也能够全局搜索alloc {
,以下图: this
接下来咱们就能够逐步去查看alloc
的底层实现原理了。
alloc
流程分析咱们查看_objc_rootAlloc
的具体实现能够看到以下代码,其实从注释能够很清晰的看到,全部建立类的初始化方法最终都是调用了callAlloc
函数。
接下里咱们来具体分析下callAlloc
函数到底作了作什么,以下,因为咱们目前基本上都是在objctive-c 2.0
环境下,因此只须要看__OBJC2__
这个判断下的代码便可。
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
// 判断当前类是否存在
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// 这是判断一个类是否有自定义的 +allocWithZone 实现。
//hasCustomAWZ : hasCustomAllocWithZone
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));
}
NEVER_INLINE
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
,在实现函数中咱们能够看到,咱们先为obj
申请分配了内存空间,而且绑定地址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());
//hasCxxCtor() 是判断当前 class 或者 superclass 是否有 .cxx_construct 构造方法的实现。
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
//hasCxxDtor() 是判断判断当前 class 或者 superclass 是否有 .cxx_destruct 析构方法的实现。
bool hasCxxDtor = cls->hasCxxDtor();
//具体标记某个类是否支持优化的isa.
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 {
/**
* void *calloc(size_t __count, size_t __size)
* 在内存的动态存储区中分配 __count 个长度为 __size 的连续空间
*/
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 对象isa的初始化 以及绑定 内存空间
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);
} 
复制代码
alloc
原理分析_objc_rootAlloc
,咱们经过代码调试发现,这里alloc
点击进入的为_objc_rootAlloc
其实咱们断点后发现进入的是objc_alloc
。(关于这个缘由,我后面会单独分析下,这里暂时不展开,由于最终你们调用的都是callAlloc
这个函数)id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
// 这里传入了一个`Class`参数
typedef struct objc_class *Class;
// 咱们看下objc_class的结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
...
// 再看看objc_object的结构
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
} OBJC2_UNAVAILABLE;
复制代码
因为咱们新建的是一个objc_class
类,objc_class
又继承于objc_class
,objc_class
存储了isa
的一个Class
结构体,指向当前是什么类。
关于callAlloc
函数
if (slowpath(checkNil && !cls)) return nil;
断定当前类是否存在//__builtin_expect 经常使用于 if-else 的判断为了优化判断的速度。
//bool(x) 为真的可能性更大,if 下的代码执行的可能性更高
#define fastpath(x) (__builtin_expect(bool(x), 1))
//bool(x) 为假的可能性更大,else 下的代码执行的可能性更高
#define slowpath(x) (__builtin_expect(bool(x), 0))
复制代码
if (fastpath(!cls->ISA()->hasCustomAWZ()))
的判断
hasCustomAWZ
其实就是 hasCustomAllocWithZone
的意思,因此这里用来断定继承了NSObject/NSProxy
的类才会进入这里。
关于_class_createInstanceFromZone
函数 能够看到_objc_rootAllocWithZone
里面最终调用的就是_class_createInstanceFromZone
hasCxxCtor()
bool hasCxxCtor = cls->hasCxxCtor(); hasCxxCtor()
是判断当前class
或者 superclass
是否有.cxx_construct
构造方法的实现。
hasCxxDtor()
bool hasCxxDtor = cls->hasCxxDtor(); hasCxxDtor()
是判断判断当前class
或者superclass
是否有.cxx_destruct
析构方法的实现。
canAllocNonpointer()
bool fast = cls->canAllocNonpointer();
具体标记某个类是否支持优化的isa.
instanceSize()
size_t size = cls->instanceSize(extraBytes); 获取类的大小(传入额外字节的大小)
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;
}
复制代码
内存对齐(alignedInstanceSize()
) 在unalignedInstanceSize
方法中 data()->ro->instanceSize
获取类所占用空间的大小,实际上是在MacO
的data
段的ro
中的获取类所占用的大小。
关于字节对齐OC
是8字节对齐,在 word_align
这个方法中计算了字节对齐。
static inline uint32_t word_align(uint32_t x) {
//字节对齐
return (x + WORD_MASK) & ~WORD_MASK;
}
复制代码
假设传入的参数: x = 9
x + WORD_MASK = 9 + 7 = 16
WORD_MASK 二进制 :0000 0111 = 7(4+2+1)
~WORD_MASK : 1111 1000
16二进制为 : 0001 0000
1111 1000
0001 0000
·········
0001 0000 = 16
因此 x = 16(原始值:9) 也就是 8的倍数对齐,即 8 字节对齐
也能够用相似位运算来实现此算法
好比 (x >> 3) <<3 也能够实现对等功能
复制代码
initInstanceIsa()
上一步咱们获取到了obj的内存空间,接下来使用 obj->initInstanceIsa(cls, hasCxxDtor)
绑定isa
指针,说明这块内存空间是为谁开辟的。 而后返回obj
。new
和init
init
方法,以下,能够看到其实init
方法什么都没作,只是返回了对象自己// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (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;
}
复制代码
new
其实最终调用了objc_opt_new
,本质上就至关于[[cls alloc] init]
id
objc_opt_new(Class cls)
{
#if __OBJC2__
if (fastpath(cls && !cls->ISA()->hasCustomCore())) {
return [callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/) init];
}
#endif
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(new));
}
复制代码
以上就是对alloc
的初步总结,中间有些细节问题可能没有彻底覆盖到,好比对象的析构函数,构造函数等,后面会在类的初始化分析中详细分析,敬请期待~