iOS底层原理探索篇 主要是围绕底层进行
源码分析
-LLDB调试
-源码断点
-汇编调试
,让本身之后回顾复习的😀😀设计模式目录以下:bash
咱们先来看下面这段代码:ui
TCJPerson *p1 = [TCJPerson alloc];
TCJPerson *p2 = [p1 init];
TCJPerson *p3 = [p1 init];
复制代码
看完代码,有一个疑问就是:p一、p二、p3有什么联系呢? 为了弄清此问题,那就看运行结果呗: spa
alloc
和
init
到底作了什么呢? 咱们先来看看
alloc
的实现:下面介绍三种方式来查看他的实现.(查看过程需用真机查看,由于模拟器查找的是x86_64环境和arm64是不同的)
objc_alloc
在
libobjc.A.dylib
这个动态库里面.
第一步点左下角的**+号按钮以后,在点击Symbolic Breakpoint**设计
第二部添加alloc符号断点:3d
以后过掉断点以后会显示以下:
如何操做以下图箭头所示:
以后会显示下图所示:
这时咱们能够看到在22行有objc_alloc
,那在此处打下断点按住control键和图上键头所指的键结果所下:
以后咱们继续以前的操做(按住control键和箭头所指的键)结果以下:
经过这三种方式咱们能够知道objc_alloc
在libobjc.A.dylib
这个动态库里面,那接下来咱们来经过alloc
的源码来分析.在这以前,咱们用寄存器来读取一下:那么什么是寄存器呢? 寄存器就是应该存储一些指针的一些东西,由于汇编它就是利用寄存器,用的妥妥的.过掉第一个断点(37行断点),来到alloc
断点以下:
x0~x7
用于程序调用的参数传递,
x0
是第一个参数的传递者也是返回的时候返回值的存储地方.所以咱们通常读x0就能够了.以后过掉
alloc
断点来到
_objc_rootAlloc
断点:
objc_msgSend
打上断点,来到断点处:
打开可编译的objc756.2源码,经过前面的探究咱们能够看到,在调用alloc以前还调用了objc_alloc,咱们打下断点一步一步去看,图以下:
咱们先来到这个断点之处,而后全局搜索objc_alloc
,以下图打上断点,以后咱们过掉上图的断点,回来到下面的断点之处,这时咱们看到allocWithZone
返回的是false
.在下图中我作了详细的解释.
那么为何objc_alloc
这个流程只会走一次呢?请看下图
从上图中能够看到符号绑定的操做是在fixupMessageRef
这个方法里面实现的.而fixupMessageRef
的调用又是在_read_images里面调用的.也就是dyld读取咱们的镜像文件的时候.然而,在咱们读取镜像文件的时候,系统会判断是否须要FIXUP,若是须要的话,咱们就会调用fixupMessageRef
,而后在fixupMessageRef
内部判断当前的消息sel是不是SEL_alloc,若是是的话就替换其IMP为objc_alloc.其中FIXUP只会走一次,也就是说objc_alloc只会走一次. 以后会继续走callAlloc方法:在这个方法中以下图所示allocWithZone返回false,以后在走alloc方法.
objc_alloc
只会走一次:
先说说为何会走objc_alloc
?由于在LLVM
的底层会调用CGF.EmitCallOrInvoke
在说说objc_alloc
只会走一次:
call
-objc_alloc
->none
没有返回对象;经过LLVM
源码能够看到:
none
时,return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType))
复制代码
也返回为none
,那么就会进行下面的条件判断
if (Optional<llvm::Value *> SpecializedResult =
tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,Sel, Method, isClassMessage))
复制代码
而此时并无进入
return RValue::get(SpecializedResult.getValue())
复制代码
可是此时调用了objc_alloc
方法,以后会继续走
return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID, Method)
复制代码
调用alloc方法.
此方法内部有一系列的判断条件,其中因为方法canAllocFast()的内部调用了bits.canAllocFast(),其返回值为固定值false,因此能够肯定以后建立对象会走class_createInstance方法
进入class_createInstance
方法,其内部调用了_class_createInstanceFromZone
方法,并在其中进行size计算,内存申请,以及isa初始化:
咱们先来看看对象size
的计算,经过方法cls->instanceSize(extraBytes),计算出size,其中64位系统下,对象大小采用8字节对齐,可是实际申请的内存最低为16字节,也就是说系统分配内存按照16字节对齐分配
obj = (id)calloc(1, size)
obj->initInstanceIsa(cls, hasCxxDtor)
alloc
的做用 咱们来对这两个核心内容分析一下: 建立指针,申请内存
calloc
函数建立了一个指针,这个指针是怎么建立的,这个源码在:libmalloc
.(具体的分析在OC对象原理(二) 内存对齐探索&malloc源码分析文中有写) 最后根据不一样的条件,使用calloc
或者malloc_zone_calloc
进行内存申请,而且初始化isa
指针,至此size
大小的对象obj
已经申请完成,而且返回.init
方法什么事情都没有作。那为何
init
会什么都不作呢? 其实这是一种设计模式,本身思考一下,平常开发过程当中,咱们会在什么状况下,进行
init
方法的使用。
—— 重写 在重写默认初始化的时候,咱们能够根据本身的需求,进行各类个性化的设置。 工厂设计,
父类没有执行,交给子类去实现。
至此,咱们在回到前面的问题?就很好的知道p一、p二、p3他们的内存地址为何同样了吧.
进入new
方法:
最后附上alloc流程图一张