OC底层原理之-0C对象(上)对alloc理解

1,452 阅读5分钟

上图打印的结果为: 通过结果可知p1,p2,p3的对象地址都是一样的,指针指向地址同样也是一样的,但是p1,p2,p3的指针地址不同。 通过上面打印可以做如下猜测:alloc对对象进行了创建和内存分配,而init只是创建指针指向已创建好的内存地址。猜测对不对,我们看下alloc和init的代码执行过程。 我们通过这样设置断点: 运行(刚开始将alloc断点放过,等运行到我们p1的alloc位置再打开),下一步,发现断点进入到_objc_rootAlloc 还用同样的方法设置_objc_rootAlloc断点,发现进入下图: 这方法我们看到有两个看得懂的方法_objc_rootAllocWithZone,以及objc_msgSend 我们发现这种方法来看流程缺点很明显,首先是慢,每进入一个方法都要打相对应的断点。还有就是中间很多内容看不到,不适合。上面只是提供一个看内部实现的方法,最好的还是看苹果开源的代码 我们下载objc源码,看到_objc_rootAlloc进入callAlloc,但实际运行的时候,我们发现跟我们上面直接断点看到的是有出入的,它直接进的objc_alloc,后面补充:之所以没有进alloc而是进了objc_alloc,查资料说的是在编译期的时候如果符号绑定失败了就会触发一个这样的修复操作,调用fixupMessageRef方法,明显的能看到 if (msg->sel == SEL_alloc) , msg->imp = (IMP)&objc_alloc,将alloc方法和objc_alloc方法进行交换。(后面分析发现,我们断点打的alloc,并没有打objc_alloc断点,其实都是从objc_alloc开始,后面用消息发送方式去调用了alloc方法,来到我们alloc断点位置) 我们继续往下走,发现此时进入callAlloc直接进入objc_msgSend方法,调用的消息转发,消息转发去调用alloc方法 继续往下走,断点走到这了(其实先走的alloc方法后跳到下面方法) 继续(注意上面图中的callAlloc方法的传参:false/checkNil/,true/allocWithZone/跟第一次objc_alloc方法中callAlloc方法传参:true/checkNil/,false/allocWithZone/)通过语义可知:第一次传参是需要检查是否为nil,没有重写allocWithZone,第二次传参是不需要检查是否为nil,但是重写了allocWithZone. 红框字面意思快速跟慢速,可以理解为是否编译优化(感觉就是debug跟release不知道对不对)下一步,进入_objc_rootAllocWithZone 继续下一步进入:_class_createInstanceFromZone(主角登场了) 为啥说是主角呢?我们先总览整个方法 整个方法包含开辟内存,内存很isa绑定。继续下一步,红框内的方法是给size赋值,这个size我们在整个方法里看到是开辟内存空间大小的。我们看下这个方法instanceSize看看size怎么来的 看下这个方法,注意红框,红框内容是size不足16字节,就返回16字节,这就确定内存空间的size最小是16字节。 我们进入hasFastInstanceSize方法看看 其中__builtin_constant_p(extra)判断extra是否为编译常数,整个方法查资料得到的是这个方法判断是否有缓存,具体怎么判断后面知道了再补充。 回到instanceSize方法,如果有缓存就直接返回缓存就会调用返回size,看下fastInstanceSizefan方法,注意红框内容 红框的方法是个算法,就是为了保证返回的内存地址大小永远是16字节的倍数。这就是传说中的内存对齐(之所以内存对齐是为了提高CPU读取效率,这也是苹果的规定) 回到_class_createInstanceFromZone打印返回的内存 发现返回内存是32字节(为啥是32字节?因为有属性这个自定义对象有两个字符串属性,一个int对象,应该是:8+8+8+4 = 28,取16的倍数为32),继续 obj = (id)calloc(1, size);这个方法为开辟内存为32字节的内存空间 我们验证下这个方法是否是创建了内存 执行这个方法前打印obj,结果:LGPerson 此时在打印obj为内存地址 执行红框后的方法,发现再打印obj就是我们常打印的结果,所以说initInstanceIsa就是将内存地址跟LGPerson的isa指针进行绑定,此时obj的alloc完成。 总结下如下图 alloc流程(我们看到自定义对象在判断是否存在自定义allocWithZoon是不存在的) 我们看下如果是NSObject对象是什么情况 调试发现在判断是否存在自定义allocWithZoon判断是存在的 后面就一样了 为什么自定义对象的自定义allocWithZoon是不存在的,而NSObject存在呢? 我们看第一次调用的objc_alloc,其中checkNil为true,allocWithZone为false,因为第一次进来fastpath(!cls->ISA()->hasCustomAWZ())中的自定义对象cls还没有指针isa,所以为false。走objc_msgSend,会再次调用callAlloc方法,但是此时的参数checkNil为false,allocWithZone为true。因为之前调用alloc方法,通过send_message方式调用了initialize类方法,自定义对象isa指针已经存在了(自定义类不存在自定义的allocWithZoon,但它父类NSObject是有的)。 但是NSObject类不同,因为他是系统类,在编译的时候,它的isa指针已经存在了,所以为true(后面感觉不对) 补充:initialize方法会在第一次初始化该类之前调用。NSObject在运行的时候,别处已经初始化过了,所以在我们对NSObject进行alloc时,initialize已经调用,isa指针已经存在 这也就是为什么自定义对象会走两边callAlloc,而NSObject只走一遍的原因。 补充:通过上面的解释,可以确定,相同类多次alloc,第一次fastpath(!cls->ISA()->hasCustomAWZ())为false,但之后都应该为true,也就是不在走_objc_rootAlloc方法。之后的验证也证明这样的猜测是对的 上面就是对OC对象alloc的理解,如果理解有什么问题,望指正,谢谢!