目录
1. 背景
对iOS对象alloc方法进行了详细研究,目的是为了了解对象底层的本质、和对象在内存中的结构。如果你也有同样的兴趣?不要怀疑的阅读下去吧!~
2. 底层探索的三个方法
-
通过符号断点:
首先我们将断点打到
图片.pngZXPerson *p1 = [ZXPerson alloc];
这段代码来已此作为我们探索的入口。
开始编译运行之后我们来到断点,通过按住control
点击Setp into
之后,我们会进入到汇编页面。
在汇编顶部显示的alloc_test`objc_alloc
那么我们就清楚alloc
之后是调用的objc_alloc
这个函数,紧接着我们进行对这个函数添加符号断点继续调试。
图片.png 图片.png
图片.png
图片.png点击
continue
继续跟踪,发现新添加的符号断点已经断住了,我们从汇编界面分析在调用objc_alloc
之后,会调用一个叫做_objc_rootAllocWithZone
的函数,最后会调用一个objc_msgSend
的函数发送消息,而后就完成了alloc的流程。 -
通过汇编:
首先我们先通过Xcode设置开启汇编显示功能,
图片.png 这时候我们看到红框标示的那段语句,从后面的提示我们就可以得知,这段代码实际就是调用了objc_alloc的方法了。后续我们再根据objc_alloc增加符号断点即可。Debug
>DebugWorkFlow
>Always show Disassembly
之后运行。
ps:这里分享点汇编的小知识 我们看到的bl这个指令是属于 ARM 架构的汇编语法,他的功能是跳转到 0x104182564 这个地址去执行相关程序,并且把他下一步执行的地址0x1041821fc保存到 lr寄存器(又可以叫做x30寄存器) 中以便在objc_alloc执行ret命令之后可以回来继续往下执行
-
通过符号断点快速定位:
最后一种方法最暴力,当断点执行到
图片.pngZXPerson *p1 = [ZXPerson alloc];
时,我们直接添加alloc的符号断点即可马上定位。
那么,到此我们3中探索的方法就结束完了。
3. 如何进行源码调试
当我们知道探索方法以及入口之后,我们怎么能有效的进行代码跟踪呢?如果是下载源码进行静态分析显然让人觉得不是那么爽,如果可以做到就跟调试我们自己编写的程序一样那就太完美了吧。是否真的能实现呢?答案当时是可行的,下面我们就来搞起!
首先我们现需要去苹果开源网站去下载源码,根据我们上面探索的结果发现alloc的底层都是由objc来负责的,所以我们需要的就是objc4-818.2的源码。但是!当你兴冲冲的下载完毕打开项目并且编译时,你就发现根本编译不通过会有很多错误。怎么办?
-
方案一:大家可以参考这个文章来进行处理解决。解决源码顺利编译方法步骤
-
方案二(推荐):就是直接拿人家编译好的下载即可。最新macOS源码编译开源项目
图片.png
图片.png 以上都准备好之后我们来打开项目,我们创建一个target选择命令行工具。
图片.png 创建完成之后,将我们之前的ZXPerson
拷贝到target目录下面。
图片.png 在 图片.pngps:注意这里有一个坑点,就是在 Build Phases的 Compile Sources下把 main.m 文件夺挪到第一位来,要不可能不会触发断点,我不知道是不是我的Xcode(v12.5)问题,大家可以试一下
main.m
文件里编写ZXPerson
初始化的代码,同时加上断点。 图片.png 最后,在新建的target的build Settings
中搜索runtime
,找到Enable Hardened Runtime
选项,将其改为NO
;然后继续在Build Phases
的Dependencies
中引入objc
库。 图片.png 图片.png 接着激动人心的时刻来了,选则好target运行即可。当触发断点时点击Step into可以继续跟踪时我们就大功告成了!
图片.png 图片.png 图片.png 到这里咱们已经具备进行源码调试的能力了,下面就可以探索一下alloc的主线流程啦。
ps:注意有一个调试技巧,每次想跟踪对象时,先把除 ZXPerson *p1 = [ZXPerson alloc]; 之外的断点关闭,等断点断在 ZXPerson *p1 = [ZXPerson alloc]; 时,再把相关的断点打开,否则会有其他对象触发断点,而就不是我们想追踪的 ZXPerson 对象了。
4. 编译器的优化(LLVM优化)
这部分内容我只想简单的描述一下,不想做过多的解释,因为这个知识点我们平时并不需要特别关注,只要理解原理即可。
- 原理:
当我们编写完Objective-C程序完成编译之后,最终都会以汇编形式进行执行,那么在这个编译过程中,编译器(LLVM)会对我们的代码进行优化处理,具体他会根据程序来缩减、删除、简化等方式进行处理,例如:我们定义了一个变量NSString *str
但是并没有使用它,虽然这个变量是存在于我们的程序代码中的,但是最终编辑器会将这段代码进行删除,这个过程就是编译器的优化,大家只用理解这个概念即可。 - 在Xcode中控制优化等级:
在Build Settings
中搜索optimi
回到看到一个Optimization Level
选项后面就是可以调整优化级别例如: 图片.png 图片.png
这时我们会发现,当处于Release
时默认就是最快且最小
模式,而在Debug
模式下就是默认不优化的状态。 图片.png
5. alloc的主线流程
-
第一步:我们先来到了
图片.pngobjc_alloc
方法,通过名称我们大致可以猜到,通过[cls alloc]
方式alloc对象时,都应该先走到这里。 -
第二步:第二步:来的
图片.pngallAlloc
方法,这个方法有几个分支我们先不用管,先把分支打上断点,通过断点我们发现这里直接走到最后return
语句,这段话通过objc_msgSend方法
向cls
类的alloc
方法发送消息。 -
第三步:我们来到
图片.pngalloc
这个只是过渡方法直接无视继续往下。 -
第四步:来到
图片.pngobjc_rootAlloc
方法,还是过渡方法直接无视继续往下。 -
第五步:又来到了
图片.pngallAlloc
方法,这次进入了objc_rootAllocWithZone
方法。 - 第七步:又是一个过渡方法直接无视。 图片.png
- 第五步:进入
class_createInstanceFromZone
方法,我们先通过这个方法返回值来分析,通过查看我们发现返回的是一个叫obj
变量,在往上查找就看到了obj
变量的初始化代码,我们可以整体的分析出来大致的逻辑,首先通过instanceSize
来得到对象在内存所需的大小;然后对obj
对象重新分配内存空间,这个obj
对象可以理解成是一个空对象,它本身并没有说明含义,因为我们alloc
的是ZXPerson
类的对象,所以还需要将obj
与ZXPerson
类建立绑定关系,而来联系这层关系的就是我们熟悉的Isa
。后面hasCxxDtor
是将C++的相关功能也赋值给这个obj。 图片.png 图片.png
说了这么多我们一起来验证一下obj对象的变化,直接上图更直观。 图片.png 图片.png 图片.png 通过LLDB调试我们分别打印了obj在内存里面的变化。 - 最后返回obj 图片.png
- 最后附上一个流程图: 图片.png
6. 对象在内存中的结构
- 一个类的实例在创建之后不添加任何代码的情况下,在内存中占用的大小是8字节,为什么是8字节呢?因为实例对象在内存结构中存放在第一位的是Isa指针,而指针的大小就是占用8字节。我们可以通过增加断点进行验证;
图片.png
如上图,我们可以再左侧实例对象中观察到Isa的指针地址为
0x011d8001000080e9
,然后我们利用LLDB在右侧输入x zxp
(显示 zxp 指针的内存情况),等待打印出结果后我们就会看到,首个8字节的地址,因为iOS属于小端模式所以在读取内存时是从右往左读
,我们可以p 0x011d8001000080e9
打印一下看看是否会显示Isa的内容,结果出来之后并没有跟我预想的一样,原因是需要&上ISA_MASK
,为什么需要&上ISA_MASK
?ISA_MASK
值是什么?带着这两个问题我们一起来寻找答案。 - 我们从alloc流程中已经得知,与初始化Isa相关的事情都是在
_class_createInstanceFromZone()
函数中实现的,那么我直接来到改函数的initInstanceIsa()
方法,然后跟进查看一下; 图片.png 跟进之后发现了叫initIsa()
方法,继续前进。 图片.png 到这里我们看到了程序再给一个叫newisa
的对象赋值,而这个对象的类型是isa_t
,我们都知道Isa指向的是该对象的类信息,这里已经明显的有setClass()
的方法,我们只需看看是否有getClass
方法?该方法中是否有我们想找的东西。 图片.png 继续跟踪isa_t
,果然发现了getClass
方法,继续跟进查到了clsbits &= ISA_MASK
通过上面的注释,大致猜到是MASK是一个掩码,目的是为了屏蔽除了类指针与签名之外的一些东西。那么我们再看一下ISA_MASK
内容是什么? 图片.png 图片.png 通过搜索我们找到了。我这里因为是非ram64架构的,所以匹配到了这个0x0000000ffffffff8ULL
图片.png 最后我们来验证一下!果然打印的是Isa指针指向的类 图片.png - 刚才我们是在不添加任何代码的情况下,现在我们增加几个属性变量看一下内存的变化;然后我们这回用过
x/4gx zxp
方式对打印进行格式化(每隔4段以16进制的数据进行展示)结果如下: 图片.png 图片.png 图片.png 我们发现zxp
对象第一个位置还是Isa
,后面的数据分别存储了zxName
、zxAge
、zxSex
、zxHieght
,优化的部分不知道大家是否看出来了,zxAge
、zxSex
因为是int类型(占4字节)与char类型(占1字节)所以共用了8字节的空间,这就是内存对齐
(有关内存对齐的内容我会在下一篇中介绍)。下面我们分别来验证一下: 图片.png - 结构示意图: 图片.png
总结:
-
我们知道了如何通过三种方式来探索底层代码;
-
通过下载编译好的源码项目,使我们可以通过调试来进行探索。
-
LLVM是有优化策略的,可以在Xocde中可以手动修改。
-
alloc的主线流程
-
对象在内存中的结构
到此本篇内容以及结束!如果您喜欢的话可以赏个赞!
网友评论