<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lab.Lawliet</title><description>代码、思考与独立开发</description><link>https://blog.lab-lawliet.app/</link><language>zh-CN</language><item><title>不用加减乘除做加法</title><link>https://blog.lab-lawliet.app/posts/article-65-1b389644db6f910c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-65-1b389644db6f910c/</guid><description>写一个函数，求两个整数之和，要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。</description><pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;写一个函数，求两个整数之和，要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;p&gt;输入: a = 1, b = 1&lt;/p&gt;
&lt;p&gt;输出: 2
 
提示：&lt;/p&gt;
&lt;p&gt;a, b 均可能是负数或 0&lt;/p&gt;
&lt;p&gt;结果不会溢出 32 位整数&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
</content:encoded></item><item><title>组件化</title><link>https://blog.lab-lawliet.app/posts/ios-01-6e7f2278dbbc7ac0/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-01-6e7f2278dbbc7ac0/</guid><pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 CTMediator&lt;/h2&gt;
&lt;h2&gt;二、 BeeHive&lt;/h2&gt;
</content:encoded></item><item><title>iOS独立开发者注册公司到App备案上架</title><link>https://blog.lab-lawliet.app/posts/article-04-app-2022c90e70f837da/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-04-app-2022c90e70f837da/</guid><description>根据工业和信息化部发布的《工业和信息化部关于开展移动互联网应用程序备案工作的通知》要求，为进一步贯彻落实《中华人民共和国反电信网络诈骗法》《互联网信息服务管理办法》（国务院令第292号）等一系列法律法规要求，开发者需要积极配合相关部门做好移动 APP 备案工作，共促互联网行业健康可持续发展。</description><pubDate>Fri, 13 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 背景&lt;/h2&gt;
&lt;p&gt;根据工业和信息化部发布的《工业和信息化部关于开展移动互联网应用程序备案工作的通知》要求，为进一步贯彻落实《中华人民共和国反电信网络诈骗法》《互联网信息服务管理办法》（国务院令第292号）等一系列法律法规要求，开发者需要积极配合相关部门做好移动 APP 备案工作，共促互联网行业健康可持续发展。&lt;/p&gt;
&lt;p&gt;作为一名苦逼的 iOS 独立开发者，也要积极响应相关规定。经历了游戏版号、广告平台需要公司才能介入变现到现在的 APP 备案，感受到了行业的正规军化一个趋势。&lt;/p&gt;
&lt;p&gt;自己其实也想了很久注册一个公司来做独立开发，但是一直以来都是用爱发电，几乎每年开发账号的 $99 都是搭进去收不回来的。就一直没有做。&lt;/p&gt;
&lt;p&gt;去年底到现在情况好了很多，虽挣不到什么大钱，但开发账号和注册维持公司的成本开销现在看来并不大。&lt;/p&gt;
&lt;p&gt;从八月中旬开始注册公司到现在10月13日 APP 备案号下来，虽然遇到一些小问题，但整体还是挺顺利的。也是时候将自己从注册公司到完成APP备案的完整路径记录分享一下了。相信对于和我处境相同的独立开发者或者是未来的独立开发者会有些许帮助。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;个人身份备案不在讨论范围内，因为我没试过，有实践的同学也欢迎分享交流。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 个人注册公司&lt;/h2&gt;
&lt;p&gt;这步比较简单，问了几个朋友都是第三方代理注册公司并记账的，我也就一样了，这里就不打广告了，想具体了解可以加我微信。&lt;/p&gt;
&lt;p&gt;总之就是花钱解决。&lt;/p&gt;
&lt;h2&gt;三、 网站备案&lt;/h2&gt;
&lt;h3&gt;3.1 购买服务器&lt;/h3&gt;
&lt;p&gt;我是在阿里云买了&lt;code&gt;轻量应用服务器&lt;/code&gt;，放了一个静态网站上去作为官网。按需选择自己需要的配置就行。这时网站还没有域名可以访问。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;后面的流程也都在阿里云进行，其他服务商应该流程差不多&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.2 企业信息&lt;/h3&gt;
&lt;p&gt;有两种，个人、企业。为了所有流程身份的一致性，我选择了企业。&lt;strong&gt;要在拿到&lt;code&gt;营业执照&lt;/code&gt;和&lt;code&gt;所有印章&lt;/code&gt;后进行材料提交&lt;/strong&gt;，需要上传相关实物照片。&lt;/p&gt;
&lt;h3&gt;3.3 购买域名&lt;/h3&gt;
&lt;p&gt;购买域名的时候需要选择绑定身份，个人或者企业，这里就可以选择已经认证通过的企业信息了。&lt;/p&gt;
&lt;h3&gt;3.4 域名备案、解析&lt;/h3&gt;
&lt;p&gt;先不要配置解析，配置了没备案也解析不了。去做备案还得先解除解析。&lt;/p&gt;
&lt;p&gt;备案主体就选择上面已经认证过的企业信息，&lt;code&gt;IP&lt;/code&gt; 就填你的网站的 &lt;code&gt;IP&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;首先阿里云会对资料进行初审，通过后会有客服电话打过来，告诉你初审通过了，会问你一下身份信息，然后告诉你会有个短信验证。收到后按短信内容去工信部网站验证即可。&lt;/p&gt;
&lt;p&gt;短信验证完就到工信部审核了，这个时间会长点儿，我的5天就下来了。&lt;/p&gt;
&lt;p&gt;然后在网站主页添加备案号并且点击跳转工信部网站即可。&lt;/p&gt;
&lt;p&gt;再到后台配置一下域名解析。&lt;/p&gt;
&lt;h3&gt;3.5 企业邮箱（有坑）&lt;/h3&gt;
&lt;p&gt;开发账号转企业会用到，必须弄一个，记得和官网域名一致。&lt;/p&gt;
&lt;p&gt;注意有坑！这里体现出了阿里云的鸡贼：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/59cc9762ecf1a5cc.webp&quot; alt=&quot;企业邮箱&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入企业邮箱第一眼以为没有免费的！结果藏在这里：&lt;a href=&quot;https://developer.aliyun.com/article/1214235&quot;&gt;阿里云免费企业邮箱申请入口链接（2023新版申请流程）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;四、 苹果个人开发账号转公司账号&lt;/h2&gt;
&lt;h3&gt;4.1 邓白氏码&lt;/h3&gt;
&lt;p&gt;如果你按照上面的流程进行的话到这步，那么你的材料就是齐全的。&lt;/p&gt;
&lt;p&gt;怎么申请邓白氏，这里就不多言了，搜索一下就有了。&lt;/p&gt;
&lt;p&gt;我在企业注册成功，但是公司相关物料还没寄到的情况下就申请了邓白氏，然后告诉我一个时间节点在邓白氏网站完成资料上传。但是我没有，第一次申请就这样浪费了。&lt;/p&gt;
&lt;p&gt;这里还需要下载一份文档打印出来盖上公章，拍照上传。&lt;/p&gt;
&lt;p&gt;后面材料到了再提申请了一次就顺利通过了，拿到邓白氏码了。&lt;/p&gt;
&lt;h3&gt;4.2 个人转企业账号&lt;/h3&gt;
&lt;p&gt;到&lt;a href=&quot;https://developer.apple.com/account&quot;&gt;开发者官网&lt;/a&gt;，&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8870245483a50cf0.webp&quot; alt=&quot;DEV&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击这里开始相关流程。&lt;/p&gt;
&lt;p&gt;官网地址、官网域名的企业邮箱等资料提交后，一定注意邮件，会有一封邮件让你主动联系他们，然后会有电话打过来，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b0efcf3bfbad18d3.webp&quot; alt=&quot;email&quot; /&gt;&lt;/p&gt;
&lt;p&gt;电话里会问你一些问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;企业名称&lt;/li&gt;
&lt;li&gt;核对你的信息&lt;/li&gt;
&lt;li&gt;你在公司的职位
&lt;ul&gt;
&lt;li&gt;法人不是职位，说开发者就行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;公司另一个人的信息，以及邮箱
&lt;ul&gt;
&lt;li&gt;邮箱不限制是企业邮箱&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;就记得这么多，电话来的就挺快挺突然的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后就挺快的当天就好了，商店里开发者名字晚些也变成企业名字了。&lt;/p&gt;
&lt;h2&gt;五、 APP 备案&lt;/h2&gt;
&lt;p&gt;当然我也是在阿里云进行的，和域名备案一样都在 ICP 备案里。&lt;/p&gt;
&lt;p&gt;下面是需要的资料：&lt;/p&gt;
&lt;h3&gt;5.1 APP 信息&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/cbfe0ffc757bde67.webp&quot; alt=&quot;APP信息&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;云服务选择 &lt;code&gt;轻量应用服务器&lt;/code&gt;，就是我买的那个服务器
&lt;ul&gt;
&lt;li&gt;选择其他选项后面项目会不一样&lt;/li&gt;
&lt;li&gt;选择这个后只需要补充选择一下的 IP 地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;备注
&lt;ul&gt;
&lt;li&gt;填写 App Store 的链接
&lt;ul&gt;
&lt;li&gt;应用详情点击分享复制链接就有了&lt;/li&gt;
&lt;li&gt;或者长按从 App Store 下载的包，点击分享也有&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可以补充上 APP 是干嘛的
&lt;ul&gt;
&lt;li&gt;初审通过的电话里客服也有问我 APP 是干嘛的，然后我在资料里的备注里加上了相关说明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;APP 全部后台服务域名
&lt;ul&gt;
&lt;li&gt;我填了我的一个域名，&lt;strong&gt;虽然备案的 APP 并没有调用这个域名下的服务&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;其他项目都有很详细的说明，还有一些选填项目，就不再赘述&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 APP 平台信息&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d742728f8a54fa5b.webp&quot; alt=&quot;APP平台信息&quot; /&gt;&lt;/p&gt;
&lt;p&gt;正常情况按照&lt;a href=&quot;https://help.aliyun.com/zh/icp-filing/user-guide/fill-in-app-feature-information?spm=a2cmq.17629970.icp_beian.8.f0d079feMxXZxJ&quot;&gt;填写App特征信息&lt;/a&gt;就能获取所需要的信息。&lt;/p&gt;
&lt;p&gt;但是作为独立开发者，大概都是 Xcode 自动管理证书的吧。我也是，那么怎么获取发布证书的特征信息了啊！&lt;/p&gt;
&lt;h4&gt;获取 Xcode 自动管理的证书特征信息&lt;/h4&gt;
&lt;p&gt;来到开发者网站，进入证书列表。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/83d6b228ed1cd515.webp&quot; alt=&quot;开发者网站&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5219ac789518cbea.webp&quot; alt=&quot;证书列表&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;Distribution Managed&lt;/code&gt; 就是 &lt;code&gt;Xcode&lt;/code&gt; 管理的证书，但是我们无法下载查看。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1c3a0b09777e9c38.webp&quot; alt=&quot;Web 调试&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开 &lt;code&gt;Web 调试&lt;/code&gt;，找到网络请求记录&lt;/li&gt;
&lt;li&gt;找到 &lt;code&gt;https://developer.apple.com/services-account/v1/certificates&lt;/code&gt; 这个地址的请求&lt;/li&gt;
&lt;li&gt;返回的数据是个数组，找到对应的下标
&lt;ul&gt;
&lt;li&gt;你应该能看到一个 &lt;code&gt;&quot;certificateTypeId&quot;: &quot;EK2BED77X3&quot;&lt;/code&gt;，这个就是 &lt;code&gt;Distribution Managed&lt;/code&gt; 类型的证书了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;拷贝 &lt;code&gt;certificateContent&lt;/code&gt; 字段内部的字符串&lt;/li&gt;
&lt;li&gt;新建一个 &lt;code&gt;.txt&lt;/code&gt; 文件，存入上面的字符串&lt;/li&gt;
&lt;li&gt;修改文件后缀为 &lt;code&gt;.cer&lt;/code&gt; 保存&lt;/li&gt;
&lt;li&gt;然后在 &lt;code&gt;Windows&lt;/code&gt; 系统下右键查看文件的详细信息，就有&lt;code&gt;公钥&lt;/code&gt;和&lt;code&gt;指纹&lt;/code&gt;了
&lt;ul&gt;
&lt;li&gt;是的，你没看错，要换到 &lt;code&gt;Windows&lt;/code&gt; 😆，&lt;code&gt;Mac&lt;/code&gt; 下看不到这个&lt;code&gt;“证书”&lt;/code&gt;的特征信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/eea8e114b9649e37.webp&quot; alt=&quot;Windows&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.3 审核&lt;/h3&gt;
&lt;p&gt;还是阿里云会初审，然后阿里云初审通过来个电话核对信息，然后就工信部短信验证，最后就进工信部审核了等着吧。&lt;/p&gt;
&lt;h3&gt;5.4 App Store 更新&lt;/h3&gt;
&lt;p&gt;备案通过后就会有 APP 的备案号了，比如我的域名备案号是 &lt;code&gt;沪ICP备XXXX号&lt;/code&gt;，APP 的备案号就是 &lt;code&gt;沪ICP备XXXX号-XA&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;记得在 &lt;code&gt;Connect&lt;/code&gt; 里的 &lt;code&gt;APP 信息&lt;/code&gt;页补充上 &lt;code&gt;ICP 备案号&lt;/code&gt;，然后在 APP 内部（比如设置页）里添加 ICP 备案号的显示即可。&lt;/p&gt;
&lt;p&gt;我的新版本已经通过审核发布了。&lt;/p&gt;
&lt;p&gt;也祝大家备案顺利！&lt;/p&gt;
&lt;h2&gt;六、 一些问题&lt;/h2&gt;
&lt;h3&gt;6.1 个人可以备案么？&lt;/h3&gt;
&lt;p&gt;有朋友问了客服，说是可以备案，但是盈利得话就不行。具体会怎么落地到 &lt;code&gt;App Store&lt;/code&gt; 我没法实践就不太清楚。&lt;/p&gt;
&lt;p&gt;我猜可能类似之前游戏版号的做法。游戏有内购项目、或者下载收费的话，要在中国大陆发布就必须提供版号，Connect 后台上会提示的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;别想先不选中国大陆上线后再选上中国大陆，这种侮辱苹果智商的做法，虽然我自己就这么试过😂&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;6.2 可以批量备案么？&lt;/h3&gt;
&lt;p&gt;我在阿里云提交的，实测可以批量，可以避免多次备案，多次提交验证身份信息。不过好像只能一次三个，我加第四个的时候加不上了。&lt;/p&gt;
&lt;h3&gt;6.3 不联网 App 可以不备案么？&lt;/h3&gt;
&lt;p&gt;我看规则里写的可以不备案。但是实测 &lt;code&gt;App Store&lt;/code&gt; 提交一个完全本地的 App，没有备案号就没法提交审核。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://wanwang.aliyun.com/qualificationrec/bazszl?spm=5176.27804673.J_9865808500.8.5b8a7365ZkdqOE&quot;&gt;App备案快速入门知识&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.miit.gov.cn/zwgk/zcwj/wjfb/tz/art/2023/art_920db564162e4312916a01bed6540ad8.html?spm=a2c4g.11174386.n2.4.a8d64f50FEgQ9H&quot;&gt;工业和信息化部关于开展移动互联网应用程序备案工作的通知&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wap.miit.gov.cn/zwgk/zcjd/art/2023/art_39b4f1acc36745b98478e0ec3e07128d.html?spm=a2c4g.11174386.n2.5.a8d64f50FEgQ9H&quot;&gt;《工业和信息化部关于开展移动互联网应用程序备案工作的通知》解读&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wap.miit.gov.cn/zwgk/zcjd/art/2023/art_9f3a1277fdcb42d68e1eacd37787ae04.html?spm=a2c4g.11174386.n2.6.a8d64f50FEgQ9H&quot;&gt;一图读懂APP备案&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.aliyun.com/article/1214235&quot;&gt;阿里云免费企业邮箱申请入口链接（2023新版申请流程）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://help.aliyun.com/zh/icp-filing/user-guide/fill-in-app-feature-information?spm=a2cmq.17629970.icp_beian.8.f0d079feMxXZxJ&quot;&gt;填写App特征信息&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Swift编译流程</title><link>https://blog.lab-lawliet.app/posts/swift-01-swift-d60c3e598bba99cb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-01-swift-d60c3e598bba99cb/</guid><description>Swift Intermediate Language (SIL)</description><pubDate>Wed, 01 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;// 分析输出AST
swiftc main.swift -dump-parse

// 分析并且检查类型输出AST 
swiftc main.swift -dump-ast

// 生成中间体语言(SIL)，未优化 
swiftc main.swift -emit-silgen

// 生成中间体语言(SIL)，优化后的 
swiftc main.swift -emit-sil

// 生成LLVM中间体语言 (.ll文件) 
swiftc main.swift -emit-ir

// 生成LLVM中间体语言 (.bc文件) 
swiftc main.swift -emit-bc

// 生成汇编
swiftc main.swift -emit-assembly

// 编译生成可执行.out文件 
swiftc -o main.o main.swift
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/apple/swift/blob/main/docs/SIL.rst&quot;&gt;Swift Intermediate Language (SIL)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844904200493596680&quot;&gt;好文推荐：Swift编译器中间码SIL&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Swift方法调度</title><link>https://blog.lab-lawliet.app/posts/swift-02-swift-675b35eef9ed21ea/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-02-swift-675b35eef9ed21ea/</guid><description>生成 SIL 文件，找到：</description><pubDate>Wed, 01 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;纯 Swift 类&lt;/h1&gt;
&lt;h2&gt;普通函数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class RyukieSwiftClass {
    var age: String?
    
    func hello() {
        print(age ?? &quot;-&quot;)
    }
}

let ry = RyukieSwiftClass()
ry.age = &quot;Ryukie&quot;
ry.hello()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成 SIL 文件，找到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sil_vtable RyukieSwiftClass {
  #RyukieSwiftClass.age!getter: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; String? : @main.RyukieSwiftClass.age.getter : Swift.String?	// RyukieSwiftClass.age.getter
  #RyukieSwiftClass.age!setter: (RyukieSwiftClass) -&amp;gt; (String?) -&amp;gt; () : @main.RyukieSwiftClass.age.setter : Swift.String?	// RyukieSwiftClass.age.setter
  #RyukieSwiftClass.age!modify: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.age.modify : Swift.String?	// RyukieSwiftClass.age.modify
  #RyukieSwiftClass.hello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.hello() -&amp;gt; ()	// RyukieSwiftClass.hello()
  #RyukieSwiftClass.init!allocator: (RyukieSwiftClass.Type) -&amp;gt; () -&amp;gt; RyukieSwiftClass : @main.RyukieSwiftClass.__allocating_init() -&amp;gt; main.RyukieSwiftClass	// RyukieSwiftClass.__allocating_init()
  #RyukieSwiftClass.deinit!deallocator: @main.RyukieSwiftClass.__deallocating_deinit	// RyukieSwiftClass.__deallocating_deinit
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见：纯 Swift 类的函数，是存在一个 sil_vtable 类型的数据结构内的。&lt;/p&gt;
&lt;h2&gt;@objc、dynamic、@objc dynamic 关键字修饰的函数&lt;/h2&gt;
&lt;p&gt;对上面代码进行修改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class RyukieSwiftClass {
    var age: String?
    
    func hello() {
        print(&quot;hello: \(age ?? &quot;-&quot;)&quot;)
    }
    
    dynamic func dynamicHelo() {
        print(&quot;dynamic: \(age ?? &quot;-&quot;)&quot;)
    }
    
    @objc func atObjcHello() {
        print(&quot;@objc: \(age ?? &quot;-&quot;)&quot;)
    }
    
    @objc dynamic func atObjcDynamicHello() {
        print(&quot;@objc dynamic: \(age ?? &quot;-&quot;)&quot;)
    }
}

let ry = RyukieSwiftClass()
ry.age = &quot;Ryukie&quot;
ry.hello()
ry.dynamicHelo()
ry.atObjcHello()
ry.atObjcDynamicHello()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SIL:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sil_vtable RyukieSwiftClass {
  #RyukieSwiftClass.age!getter: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; String? : @main.RyukieSwiftClass.age.getter : Swift.String?	// RyukieSwiftClass.age.getter
  #RyukieSwiftClass.age!setter: (RyukieSwiftClass) -&amp;gt; (String?) -&amp;gt; () : @main.RyukieSwiftClass.age.setter : Swift.String?	// RyukieSwiftClass.age.setter
  #RyukieSwiftClass.age!modify: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.age.modify : Swift.String?	// RyukieSwiftClass.age.modify

// 普通函数
  #RyukieSwiftClass.hello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.hello() -&amp;gt; ()	// RyukieSwiftClass.hello()

// dynamic 函数
  #RyukieSwiftClass.dynamicHelo: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.dynamicHelo() -&amp;gt; ()	// RyukieSwiftClass.dynamicHelo()

// @objc 函数
  #RyukieSwiftClass.atObjcHello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.atObjcHello() -&amp;gt; ()	// RyukieSwiftClass.atObjcHello()

  #RyukieSwiftClass.init!allocator: (RyukieSwiftClass.Type) -&amp;gt; () -&amp;gt; RyukieSwiftClass : @main.RyukieSwiftClass.__allocating_init() -&amp;gt; main.RyukieSwiftClass	// RyukieSwiftClass.__allocating_init()
  #RyukieSwiftClass.deinit!deallocator: @main.RyukieSwiftClass.__deallocating_deinit	// RyukieSwiftClass.__deallocating_deinit
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 SIL 我们发现：&lt;code&gt;普通函数&lt;/code&gt;、&lt;code&gt;@objc 函数&lt;/code&gt; 和 &lt;code&gt;dynamic 函数&lt;/code&gt; 都是通过 &lt;code&gt;sil_vtable&lt;/code&gt; 存储的，而且内存空间连续。&lt;/p&gt;
&lt;p&gt;但是 &lt;code&gt;sil_vtable&lt;/code&gt; 内部少了 &lt;code&gt;@objc dynamic&lt;/code&gt; 修饰的函数。我们通过汇编断点看一下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/510e6eb479df8b92.webp&quot; alt=&quot;@objc dynamic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;图中我们发现 &lt;code&gt;@objc dynamic&lt;/code&gt; 修饰的函数，是通过 &lt;code&gt;objc_msgSend&lt;/code&gt; 进行调度的，所以并不在 &lt;code&gt;sil_vtable&lt;/code&gt; 内。&lt;/p&gt;
&lt;h2&gt;extension&lt;/h2&gt;
&lt;p&gt;我们新增一个 extension 并调用其中的函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extension RyukieSwiftClass {
    func extensionHello() {
        print(&quot;extension: \(age ?? &quot;-&quot;)&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编端点发现 &lt;code&gt;call   0x1000037e0&lt;/code&gt; 是直接通过地址调用的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;  0x1000029d9 &amp;lt;+73&amp;gt;: mov    r13, qword ptr [rip + 0x58a0] ; SourceCodeStudy.ry : SourceCodeStudy.RyukieSwiftClass
    0x1000029e0 &amp;lt;+80&amp;gt;: call   0x1000037e0               ; SourceCodeStudy.RyukieSwiftClass.extensionHello() -&amp;gt; () at main.swift:35
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且在 &lt;code&gt;sil_vtable&lt;/code&gt; 结构中没有发现 &lt;code&gt;extensionHello&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;所以：在 Swift 类的 extension 中的函数是 &lt;strong&gt;静态调用&lt;/strong&gt; 的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;子类&lt;/h2&gt;
&lt;p&gt;我们定义一个子类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class RyukieSwiftSubClass: RyukieSwiftClass {
    func subHello() {
        print(&quot;sub: \(age ?? &quot;-&quot;)&quot;)
    }
}


let subRy = RyukieSwiftSubClass()
subRy.age = &quot;18&quot;
subRy.hello()
subRy.subHello()
subRy.extensionHello()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SIL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sil_vtable RyukieSwiftSubClass {
// 父类的结构
  #RyukieSwiftClass.age!getter: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; String? : @main.RyukieSwiftClass.age.getter : Swift.String? [inherited]	// RyukieSwiftClass.age.getter
  #RyukieSwiftClass.age!setter: (RyukieSwiftClass) -&amp;gt; (String?) -&amp;gt; () : @main.RyukieSwiftClass.age.setter : Swift.String? [inherited]	// RyukieSwiftClass.age.setter
  #RyukieSwiftClass.age!modify: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.age.modify : Swift.String? [inherited]	// RyukieSwiftClass.age.modify
  #RyukieSwiftClass.hello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.hello() -&amp;gt; () [inherited]	// RyukieSwiftClass.hello()
  #RyukieSwiftClass.dynamicHelo: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.dynamicHelo() -&amp;gt; () [inherited]	// RyukieSwiftClass.dynamicHelo()
  #RyukieSwiftClass.atObjcHello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftClass.atObjcHello() -&amp;gt; () [inherited]	// RyukieSwiftClass.atObjcHello()

// 自己的结构
  #RyukieSwiftClass.init!allocator: (RyukieSwiftClass.Type) -&amp;gt; () -&amp;gt; RyukieSwiftClass : @main.RyukieSwiftSubClass.__allocating_init() -&amp;gt; main.RyukieSwiftSubClass [override]	// RyukieSwiftSubClass.__allocating_init()
  #RyukieSwiftSubClass.subHello: (RyukieSwiftSubClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftSubClass.subHello() -&amp;gt; ()	// RyukieSwiftSubClass.subHello()
  #RyukieSwiftSubClass.deinit!deallocator: @main.RyukieSwiftSubClass.__deallocating_deinit	// RyukieSwiftSubClass.__deallocating_deinit
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编端点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    0x100002546 &amp;lt;+182&amp;gt;: mov    rax, qword ptr [rip + 0x5e9b] ; SourceCodeStudy.subRy : SourceCodeStudy.RyukieSwiftSubClass
    0x10000254d &amp;lt;+189&amp;gt;: mov    r13, rax
    0x100002550 &amp;lt;+192&amp;gt;: mov    rax, qword ptr [rax]
    0x100002553 &amp;lt;+195&amp;gt;: call   qword ptr [rax + 0x70]
    0x100002556 &amp;lt;+198&amp;gt;: mov    r13, qword ptr [rip + 0x5e8b] ; SourceCodeStudy.subRy : SourceCodeStudy.RyukieSwiftSubClass
    0x10000255d &amp;lt;+205&amp;gt;: mov    rax, qword ptr [r13]
    0x100002561 &amp;lt;+209&amp;gt;: call   qword ptr [rax + 0x90]
-&amp;gt;  0x100002567 &amp;lt;+215&amp;gt;: mov    r13, qword ptr [rip + 0x5e7a] ; SourceCodeStudy.subRy : SourceCodeStudy.RyukieSwiftSubClass
    0x10000256e &amp;lt;+222&amp;gt;: call   0x100003370               ; SourceCodeStudy.RyukieSwiftClass.extensionHello() -&amp;gt; () at main.swift:35
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现子类的 &lt;code&gt;sil_vtable&lt;/code&gt; 结构中包含了父类的结构，自己专有的部分跟在父类的结构之后。&lt;/p&gt;
&lt;p&gt;子类的方法调度，没有特殊的方法变化。&lt;/p&gt;
&lt;h3&gt;override&lt;/h3&gt;
&lt;p&gt;如果重写父类方法呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;override func hello() {
    print(&quot;override hello: \(age ?? &quot;-&quot;)&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SIL 会有些许变化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sil_vtable RyukieSwiftSubClass {
// ...
  #RyukieSwiftClass.hello: (RyukieSwiftClass) -&amp;gt; () -&amp;gt; () : @main.RyukieSwiftSubClass.hello() -&amp;gt; () [override]	// RyukieSwiftSubClass.hello()
// ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;final&lt;/h2&gt;
&lt;p&gt;静态调度，不会存在于 vtable 中&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;private fileprivate 的会被自动优化为 final&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;sil_vtable&lt;/h1&gt;
&lt;p&gt;我们找到 &lt;code&gt;initClassVTable&lt;/code&gt; 源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// Using the information in the class context descriptor, fill in in the
/// immediate vtable entries for the class and install overrides of any
/// superclass vtable entries.
static void initClassVTable(ClassMetadata *self) {
  const auto *description = self-&amp;gt;getDescription();
  auto *classWords = reinterpret_cast&amp;lt;void **&amp;gt;(self);

  if (description-&amp;gt;hasVTable()) {
    // 遍历添加 method，数组结构
    auto *vtable = description-&amp;gt;getVTableDescriptor();
    auto vtableOffset = vtable-&amp;gt;getVTableOffset(description);
    for (unsigned i = 0, e = vtable-&amp;gt;VTableSize; i &amp;lt; e; ++i)
      classWords[vtableOffset + i] = description-&amp;gt;getMethod(i);
  }

  if (description-&amp;gt;hasOverrideTable()) {
    // 遍历处理 override 的 method
    auto *overrideTable = description-&amp;gt;getOverrideTable();
    auto overrideDescriptors = description-&amp;gt;getMethodOverrideDescriptors();

    for (unsigned i = 0, e = overrideTable-&amp;gt;NumEntries; i &amp;lt; e; ++i) {
      auto &amp;amp;descriptor = overrideDescriptors[i];

      // Get the base class and method.
      auto *baseClass = descriptor.Class.get();
      auto *baseMethod = descriptor.Method.get();

      // If the base method is null, it&apos;s an unavailable weak-linked
      // symbol.
      if (baseClass == nullptr || baseMethod == nullptr)
        continue;

      // Calculate the base method&apos;s vtable offset from the
      // base method descriptor. The offset will be relative
      // to the base class&apos;s vtable start offset.
      auto baseClassMethods = baseClass-&amp;gt;getMethodDescriptors().data();
      auto offset = baseMethod - baseClassMethods;

      // Install the method override in our vtable.
      auto baseVTable = baseClass-&amp;gt;getVTableDescriptor();
      classWords[baseVTable-&amp;gt;getVTableOffset(baseClass) + offset]
        = descriptor.Impl.get();
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过源码我们发现 &lt;code&gt;sil_vtable&lt;/code&gt; 内部是一个数组的结构，通过下标读写。&lt;/p&gt;
&lt;h1&gt;结构体&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;struct RyukieStruct {
    func hello() {
        print(&quot;hello&quot;)
    }
    
    dynamic func dynamicHelo() {
        print(&quot;dynamic&quot;)
    }
}

extension RyukieStruct {
    func extensionHello() {
        print(&quot;extension&quot;)
    }
}

let st = RyukieStruct()
st.hello()
st.dynamicHelo()
st.extensionHello()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;汇编断点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    0x100005ba9 &amp;lt;+9&amp;gt;:  call   0x100005bc0               ; SourceCodeStudy.RyukieStruct.hello() -&amp;gt; () at main.swift:15
    0x100005bae &amp;lt;+14&amp;gt;: call   0x100005c80               ; SourceCodeStudy.RyukieStruct.dynamicHelo() -&amp;gt; () at main.swift:19
-&amp;gt;  0x100005bb3 &amp;lt;+19&amp;gt;: call   0x100005d80               ; SourceCodeStudy.RyukieStruct.extensionHello() -&amp;gt; () at main.swift:25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现都是通过地址直接静态调用的。&lt;/p&gt;
</content:encoded></item><item><title>[译]编写高性能Swift代码-Writing High-Performance Swift Code(2022.8.25版)</title><link>https://blog.lab-lawliet.app/posts/swift-writing-high-performance-swift-code-14639de9259f958a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-writing-high-performance-swift-code-14639de9259f958a/</guid><description>文中的一些 Tips 能够帮助提高 Swift 程序的质量，并提高代码容错性和可读性。</description><pubDate>Wed, 22 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;编写高性能的 Swift 代码&lt;/h1&gt;
&lt;p&gt;文中的一些 Tips 能够帮助提高 Swift 程序的质量，并提高代码容错性和可读性。&lt;/p&gt;
&lt;h1&gt;一、 开启优化 - Enabling Optimizations&lt;/h1&gt;
&lt;p&gt;首先你应该做的就是开启优化。Swift 提供了三种优化等级：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-Onone&lt;/code&gt;: 标准的开发模式。编译器提供最少的优化并且提供所有 debug 信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O&lt;/code&gt;: 生产模式代码。编译器进行大量优化代码。调试信息会提供但是不完整。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Osize&lt;/code&gt;: 一个特殊的模式，编译器会优先考虑代码大小，而不是性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 Xcode 中，Project 的 Build Setting 中搜索 Optimization Level 就可以进行配置。&lt;/p&gt;
&lt;h1&gt;二、 整模块优化 - Whole Module Optimizations (WMO)&lt;/h1&gt;
&lt;p&gt;默认状态下 Swift 会独立编译每个文件。这使得 Xcode 可以快速的并行编译多个文件。但是，每个文件分开变异阻碍了编译器进行优化。Swift 也可以把整个应用程序作为一个文件进行编译，并作为一个编译单元进行优化。编译会花费更久的时间，但是 run 会更快。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.swift.org/blog/whole-module-optimizations/&quot;&gt;Whole-Module Optimization in Swift 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Xcode 8 默认开启&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;三、 减少动态派发 - Reducing Dynamic Dispatch&lt;/h1&gt;
&lt;p&gt;Swift 默认是和 Objective-C 一样非常&lt;code&gt;动态&lt;/code&gt;的语言。与 Objective-C 不同的是，Swift 给了开发者可以通过减少或者避免动态特性，来提升程序运行时性能的能力。&lt;/p&gt;
&lt;p&gt;下面由几个例子进行说明：&lt;/p&gt;
&lt;h2&gt;动态派发 - Dynamic Dispatch&lt;/h2&gt;
&lt;p&gt;类默认对 方法调用 和 属性访问 进行动态派发。例如下面的 &lt;code&gt;a.aProperty&lt;/code&gt;, &lt;code&gt;a.doSomething()&lt;/code&gt; and
&lt;code&gt;a.doSomethingElse()&lt;/code&gt; 动态派发的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  class A {
    var aProperty: [Int]
    func doSomething() { ... }
    dynamic doSomethingElse() { ... }
  }

  class B: A {
    override var aProperty {
      get { ... }
      set { ... }
    }

    override func doSomething() { ... }
  }

  func usingAnA(_ a: A) {
    a.doSomething()
    a.aProperty = ...
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Swift 里，动态派发默认是间接地通过一个 vtable[^1] 进行调用。如果在声明里加上了 &lt;code&gt;dynamic&lt;/code&gt; 关键字，Swift 会通过 Objective-C 的消息机制进行调用。两种方式相较于直接调用函数都更慢，因为阻碍了很多 编译器可以进行 优化[^2] 的地方，间接调用也多了很多开销。想要提升代码运行性能就需要限制动态特性。&lt;/p&gt;
&lt;h2&gt;建议：使用 &lt;code&gt;final&lt;/code&gt; 当你确定被声明的不需要被重写&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;final&lt;/code&gt; 关键字会限制 &lt;code&gt;class&lt;/code&gt;、&lt;code&gt;method&lt;/code&gt;、&lt;code&gt;property&lt;/code&gt; 不能被重写。这意味着编译器可以&lt;code&gt;直接调用&lt;/code&gt;函数，而不是&lt;code&gt;间接调用&lt;/code&gt;。下面的 &lt;code&gt;C.array1&lt;/code&gt; and &lt;code&gt;D.array1&lt;/code&gt; 将会被 直接访问[^3] 。相对的 &lt;code&gt;D.array2&lt;/code&gt; 将会被通过一个 &lt;code&gt;vtable&lt;/code&gt; 调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  final class C {
    // class &apos;C&apos; 中所有声明的都不能被重写
    // No declarations in class &apos;C&apos; can be overridden.
    var array1: [Int]
    func doSomething() { ... }
  }

  class D {
    // array1 不能被计算属性重写
    final var array1: [Int] // &apos;array1&apos; cannot be overridden by a computed property.
    // array2 能被计算属性重写
    var array2: [Int]      // &apos;array2&apos; *can* be overridden by a computed property.
  }

  func usingC(_ c: C) {
    // 访问 C.array1 不会经过动态派发
    c.array1[i] = ... // Can directly access C.array without going through dynamic dispatch.
    // 会直接调用 C.doSomething 不会经过虚函数表调用
    c.doSomething() = ... // Can directly call C.doSomething without going through virtual dispatch.
  }

  func usingD(_ d: D) {
    // 直接访问 D.array1 不经过动态派发
    d.array1[i] = ... // Can directly access D.array1 without going through dynamic dispatch.
    // 经过动态派发访问 D.array2
    d.array2[i] = ... // Will access D.array2 through dynamic dispatch.
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;建议：使用 &lt;code&gt;private&lt;/code&gt; 和 &lt;code&gt;fileprivate&lt;/code&gt; 声明不需要被文件外界访问的内容&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;private&lt;/code&gt; 和 &lt;code&gt;fileprivate&lt;/code&gt; 关键字能够将这些声明的访问限制在该文件内部。这会让编译器能查出所有其它潜在的重写声明。由此编译器能够自动推断 &lt;code&gt;final&lt;/code&gt; 关键字并删除&lt;code&gt;方法的间接调用&lt;/code&gt;和&lt;code&gt;空间访问&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如下：假设 &lt;code&gt;E&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt; 在该文件内没有重写任何声明，那么 &lt;code&gt;e.doSomething()&lt;/code&gt; 和 &lt;code&gt;f.myPrivateVar&lt;/code&gt; 就能够被直接访问：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  private class E {
    func doSomething() { ... }
  }

  class F {
    fileprivate var myPrivateVar: Int
  }

  func usingE(_ e: E) {
    /**
    该文件内没有子类存在
    编译器可以移除 doSomething 的虚表调用，并直接调用 E 的 doSomething 方法
    */
    e.doSomething() // There is no sub class in the file that declares this class.
                    // The compiler can remove virtual calls to doSomething()
                    // and directly call E&apos;s doSomething method.
  }

  func usingF(_ f: F) -&amp;gt; Int {
    return f.myPrivateVar
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;建议：如果启用了 &lt;code&gt;WMO&lt;/code&gt;，在模块内部对不需要外部访问的内容使用 &lt;code&gt;internal&lt;/code&gt; 关键字进行声明&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WMO&lt;/code&gt; 使得编译器将整个模块的代码作为一个整体一次性进行编译。使编译单个声明时能够拥有更广的视野从而进行优化。当一个声明为 &lt;code&gt;internal&lt;/code&gt; 就不会在模块外部被访问到，优化器可以根据是否存在潜在的声明重写，而自动推断出 &lt;code&gt;final&lt;/code&gt; 关键字。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: 由于现在 Swift 默认的访问级别已经是 &lt;code&gt;internal&lt;/code&gt;，通过开启 &lt;code&gt;WMO&lt;/code&gt; 就不用额外的操作（&lt;code&gt;WMO&lt;/code&gt; 也是默认开启的了）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;四、 高效的使用容器类型 - Using Container Types Efficiently&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Swift&lt;/code&gt; 标准库提供了重要功能：通用容器 &lt;code&gt;Array&lt;/code&gt; 和 &lt;code&gt;Dictionary&lt;/code&gt;。下面将介绍如何高效的使用这些类型。&lt;/p&gt;
&lt;h2&gt;建议：在数组中使用值类型&lt;/h2&gt;
&lt;p&gt;在 Swift 中，类型能够被分为两种：&lt;code&gt;值类型 (structs, enums, tuples)&lt;/code&gt;，&lt;code&gt;引用类型 (classes)&lt;/code&gt;。一个关键的差别是值类型不能被放入 &lt;code&gt;NSArray&lt;/code&gt;。因此当使用值类型时，优化器就不需要去处理对 &lt;code&gt;NSArray&lt;/code&gt; 的兼容。&lt;/p&gt;
&lt;p&gt;并且对比引用类型，值类型仅当&lt;code&gt;包含了引用类型&lt;/code&gt;的时候才需要&lt;code&gt;引用计数&lt;/code&gt;。通过使用不包含引用类型的值类型，可以避免在 &lt;code&gt;Array&lt;/code&gt; 内部的 &lt;code&gt;retain&lt;/code&gt; 和 &lt;code&gt;release&lt;/code&gt; 减少开销。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  // Don&apos;t use a class here.
  struct PhonebookEntry {
    var name: String
    var number: [Int]
  }

  var a: [PhonebookEntry]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要时刻注意平衡使用&lt;code&gt;大的值类型&lt;/code&gt;和&lt;code&gt;引用类型&lt;/code&gt;。有时拷贝移动一个&lt;code&gt;巨大的值类型&lt;/code&gt;可能消耗比管理一个&lt;code&gt;引用类型&lt;/code&gt;的 &lt;code&gt;retain/release&lt;/code&gt; 消耗还要大。&lt;/p&gt;
&lt;h2&gt;建议：不需要考虑 NSArray 桥接的时候可以使用 &lt;code&gt;ContiguousArray&lt;/code&gt; 来存储引用类型&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
  class C { ... }
  var a: ContiguousArray&amp;lt;C&amp;gt; = [C(...), C(...), ..., C(...)]
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844903626264035342&quot;&gt;Swift标准库源码阅读笔记 - Array和ContiguousArray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Array 需要考虑对 NSArray 的兼容，内部存在很多类型检查。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;建议：使用引用修改而不是对象重新赋值 - Use inplace mutation instead of object-reassignment&lt;/h2&gt;
&lt;p&gt;在 Swift 标准库中所有的容器类型都是使用了 &lt;code&gt;COW(copy-on-write)&lt;/code&gt;[^4] 机制来替代直接拷贝。在大多数情况下，编译器会引用容器而不是深拷贝。只有当容器的引用计数大于 1 并且容器被修改了，才会触发底层容器的拷贝。&lt;/p&gt;
&lt;p&gt;如下：当 &lt;code&gt;c&lt;/code&gt; 赋值给 &lt;code&gt;d&lt;/code&gt;，不会触发拷贝。但是当 &lt;code&gt;d&lt;/code&gt; 通过 &lt;code&gt;append(2)&lt;/code&gt; 修改的时候，&lt;code&gt;d&lt;/code&gt; 会先拷贝容器，然后 &lt;code&gt;2&lt;/code&gt; 会被添加进 &lt;code&gt;d&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  var c: [Int] = [ ... ]
  var d = c        // No copy will occur here.
  d.append(2)      // A copy *does* occur here.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果使用不当，COW 有时会造成没必要的拷贝。例如下面的例子通过在方法内部返回一个新数组，进行对象重新赋值来达到 &lt;code&gt;append_one&lt;/code&gt;。所有传入的参数都会被 &lt;code&gt;reatin&lt;/code&gt; 然后在函数结束后 &lt;code&gt;release&lt;/code&gt;。如果像下面这样实现一个 &lt;code&gt;append_one&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  func append_one(_ a: [Int]) -&amp;gt; [Int] {
    var a = a
    a.append(1)
    return a
  }

  var a = [1, 2, 3]
  a = append_one(a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;a&lt;/code&gt; 可能会被拷贝[^5]，尽管 &lt;code&gt;a&lt;/code&gt; 在 &lt;code&gt;append_one&lt;/code&gt; 结束后没有被使用。如果使用 &lt;code&gt;inout&lt;/code&gt; 就可以避免：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  func append_one_in_place(a: inout [Int]) {
    a.append(1)
  }

  var a = [1, 2, 3]
  append_one_in_place(&amp;amp;a)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;五、 避免检查溢出的计算 - Wrapping operations&lt;/h1&gt;
&lt;p&gt;Swift 在执行一般整型计算的时候会检查是否溢出。但是在已知不会溢出的场景下依旧这么做就显得非常不合适了，也不符合高性能编码的期望。&lt;/p&gt;
&lt;h2&gt;建议： 在已知不会溢出的情况下使用 &lt;code&gt;wrapping arithmetic&lt;/code&gt; 进行不检查的整型计算 &lt;code&gt;&amp;amp;+&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在高性能编码中，如果你确定不会溢出，就可以使用 &lt;code&gt;wrapping arithmetic&lt;/code&gt; 来避免检查。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  a: [Int]
  b: [Int]
  c: [Int]

  // Precondition: for all a[i], b[i]: a[i] + b[i] either does not overflow,
  // or the result of wrapping is desired.
  for i in 0 ... n {
    c[i] = a[i] &amp;amp;+ b[i]
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要指出的是，&lt;code&gt;&amp;amp;+&lt;/code&gt; &lt;code&gt;&amp;amp;-&lt;/code&gt; &lt;code&gt;&amp;amp;*&lt;/code&gt; 在溢出的时候结果会简单的处理。例如：&lt;code&gt;Int.max &amp;amp;+ 1&lt;/code&gt; 的结果是 &lt;code&gt;Int.min&lt;/code&gt;（不像C语言里，&lt;code&gt;INT_MAX + 1&lt;/code&gt; 是为无法定义的操作）。&lt;/p&gt;
&lt;h1&gt;六、 范型 - Generics&lt;/h1&gt;
&lt;p&gt;Swift 通过范型提供了非常强大的抽象能力。Swift 编译器通过 &lt;code&gt;MySwiftFunc&amp;lt;T&amp;gt;&lt;/code&gt; 生成可以为 &lt;code&gt;T&lt;/code&gt; 执行的代码块。生成的代码块包含一个函数指针列表和一个包含 &lt;code&gt;T&lt;/code&gt; 容器作为参数。&lt;code&gt;MySwiftFunc&amp;lt;Int&amp;gt;&lt;/code&gt; 和 &lt;code&gt;MySwiftFunc&amp;lt;String&amp;gt;&lt;/code&gt; 的不同表现，是通过传递不同的函数列表和容器大小来决定的。一个范型例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  class MySwiftFunc&amp;lt;T&amp;gt; { ... }

  // 生成为 Int 工作的代码
  MySwiftFunc&amp;lt;Int&amp;gt; X    // Will emit code that works with Int...
  // 生成为 String 工作的代码
  MySwiftFunc&amp;lt;String&amp;gt; Y // ... as well as String.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当开启了优化，Swift 编译器会检查每个调用调用，并检查其中使用的类型（比如非范型类型）。如果范型函数的定义对优化器是可见的并且类型是已知的，Swift 编译器将会生成一个针对这个特定类型版本的函数。这个过程叫做 &lt;code&gt;specialization（定制化）&lt;/code&gt;，可以避免一些范型相关的消耗。如下例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  class MyStack&amp;lt;T&amp;gt; {
    func push(_ element: T) { ... }
    func pop() -&amp;gt; T { ... }
  }

  func myAlgorithm&amp;lt;T&amp;gt;(_ a: [T], length: Int) { ... }

  // The compiler can specialize code of MyStack&amp;lt;Int&amp;gt;
  // 编译器会为范型 MyStack&amp;lt;Int&amp;gt; 生成特定代码
  var stackOfInts: MyStack&amp;lt;Int&amp;gt;

  // Use stack of ints.
  for i in ... {
    stack.push(...)
    stack.pop(...)
  }

  var arrayOfInts: [Int]
  // The compiler can emit a specialized version of &apos;myAlgorithm&apos; targeted for
  // [Int]&apos; types.
  // 编译器会生成针对 [Int] 版本的函数
  myAlgorithm(arrayOfInts, arrayOfInts.length)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;建议： 将范型声明放在需要使用它的模块内&lt;/h2&gt;
&lt;p&gt;优化器只能对声明在当前模块中的范型定义进行特化处理。如果 &lt;code&gt;WMO&lt;/code&gt; 没有开启，即只会当声明和调用在同一个文件的时候才会处理。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; 标准库是个特例。定义在标准库中的对所有模块可见并可以被特化处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;七、 Swift 中较大值类型的开销 - The cost of large Swift values&lt;/h1&gt;
&lt;p&gt;在 Swift 中，一些较大的值类型如果进行拷贝会比较耗时，并影响性能。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/swift/blog/?id=10&quot;&gt;More on value types&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下面使用值类型定义了一个 &lt;code&gt;Tree&lt;/code&gt;。其节点包含了其他同样遵守 P协议 的节点。计算机图形场景下经常由可以用值类型表示的不同的&lt;code&gt;实体entities&lt;/code&gt;和&lt;code&gt;变体transformations&lt;/code&gt;构成。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2015-408/&quot;&gt;面向协议编程 Protocol-Oriented-Programming&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  protocol P {}
  struct Node: P {
    var left, right: P?
  }

  struct Tree {
    var node: P?
    init() { ... }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当一个 tree 被拷贝，真个 tree 都要被拷贝。这里对于我们的 tree 来说是一个开销很大的操作，需要大量的执行 malloc/free 并校验引用计数。&lt;/p&gt;
&lt;p&gt;然而我们并不关心这些值是否被拷贝，只要它还在内存中可以被正常使用。&lt;/p&gt;
&lt;h2&gt;建议： 较大的值类型使用写时复制 COW - Advice: Use copy-on-write semantics for large values&lt;/h2&gt;
&lt;p&gt;写时复制能够解决值类型大量拷贝的问题。实现写时拷贝最简单的方式就是使用现成的 COW 数据结构，比如 Array。Swift 的 Array 是值类型。但是 Array 的内容不会每次都拷贝，因为使用来写时复制。&lt;/p&gt;
&lt;p&gt;我们使用 Array 改造 Tree 可以避免大量的拷贝操作。这提升了 Tree 数据结构的性能表现，并且将传递一个数组的时间复杂度由&lt;code&gt;O(n)&lt;/code&gt;降低到了&lt;code&gt;O(1)&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  struct Tree: P {
    var node: [P?]
    init() {
      node = [thing]
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用数组来实现 &lt;code&gt;COW&lt;/code&gt; 机制有两个明显的缺点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个问题就是数组中类似 &lt;code&gt;&quot;append&quot;&lt;/code&gt; 和 &lt;code&gt;&quot;count&quot;&lt;/code&gt; 的方法，它们在值封装中没有任何作用。这些方法让引用封装变得很不方便。我们可以通过创建一个隐藏未用到的 API 的封装结构来解决这个问题，并且优化器会移除它的开销，但是这样的封装并不能解决第二个问题。&lt;/li&gt;
&lt;li&gt;第二个问题就是数组内存在保证程序安全性和与 Objective-C 进行交互的代码， Swift 会检查索引访问是否在数组边界内，以及保存值时会判断数组存储时否需要扩展存储空间。这些操作运行时都会降低程序速度。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个替代方法就是实现一个 &lt;code&gt;copy-on-write&lt;/code&gt; 机制的数据结构来代替数组作为值封装。下面的例子就是介绍如何构建一个这样的数据结构：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: 这样的解决办法，对于嵌套结构并非最优，并且一个基于 COW 数据结构的 addressor 会更加高效。然而在这种情况下，抛开标准库执行 addressor 是行不通的。
&lt;a href=&quot;https://www.mikeash.com/pyblog/friday-qa-2015-04-17-lets-build-swiftarray.html&quot;&gt;More details in this blog post by Mike Ash&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;final class Ref&amp;lt;T&amp;gt; {
  var val: T
  init(_ v: T) { val = v }
}

struct Box&amp;lt;T&amp;gt; {
  var ref: Ref&amp;lt;T&amp;gt;
  init(_ x: T) { ref = Ref(x) }

  var value: T {
    get { return ref.val }
    set {
      if !isKnownUniquelyReferenced(&amp;amp;ref) {
        ref = Ref(newValue)
        return
      }
      ref.val = newValue
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Box 类型可以代替上个例子中的数组。&lt;/p&gt;
&lt;h1&gt;八、 不安全的代码 - Unsafe code&lt;/h1&gt;
&lt;p&gt;Swift 中类总是采用引用计数。 Swift 编译器会在每次对象被访问时插入增加引用计数的代码。例如，考虑一个通过使用类实现遍历链表的例子。遍历链表是通过从一个节点到下一个节点移动引用实现： &lt;code&gt;elem = elem.next&lt;/code&gt;。每次我们移动这个引用， Swift 将会增加 next 对象的引用计数，并且减少前一个对象的引用计数。这样的引用计数方法成本很高，但只要我们使用 Swift 的类就无法避免。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  final class Node {
    var next: Node?
    var data: Int
    ...
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;建议：使用非托管的引用来避免引用计数带来的开销&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;Unmanaged&amp;lt;T&amp;gt;._withUnsafeGuaranteedRef&lt;/code&gt; 不是公开的 API，并且今后将会被移除。&lt;code&gt;所以不要使用它&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在性能优先代码中，你可以选择使用未托管的引用。其中 &lt;code&gt;Unmanaged&amp;lt;T&amp;gt;&lt;/code&gt; 结构体就允许开发者关闭对于特殊引用的自动引用计数 (ARC) 功能。&lt;/p&gt;
&lt;p&gt;当你这样做了，你需要确保有另外的引用引用这个被 &lt;code&gt;Unmanaged&lt;/code&gt; 的实例，来保证在使用 &lt;code&gt;Unmanaged&lt;/code&gt; 期间实例不会释放。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  // The call to ``withExtendedLifetime(Head)`` makes sure that the lifetime of
  // Head is guaranteed to extend over the region of code that uses Unmanaged
  // references. Because there exists a reference to Head for the duration
  // of the scope and we don&apos;t modify the list of ``Node``s there also exist a
  // reference through the chain of ``Head.next``, ``Head.next.next``, ...
  // instances.

  withExtendedLifetime(Head) {

    // Create an Unmanaged reference.
    var Ref: Unmanaged&amp;lt;Node&amp;gt; = Unmanaged.passUnretained(Head)

    // Use the unmanaged reference in a call/variable access. The use of
    // _withUnsafeGuaranteedRef allows the compiler to remove the ultimate
    // retain/release across the call/access.

    while let Next = Ref._withUnsafeGuaranteedRef { $0.next } {
      ...
      Ref = Unmanaged.passUnretained(Next)
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/apple/swift/blob/main/stdlib/public/core/Unmanaged.swift&quot;&gt;Unmanaged.swift&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/unmanaged/&quot;&gt;Unmanaged&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;九、 协议 - Protocols&lt;/h1&gt;
&lt;h2&gt;建议：将智能由 class 实现的协议标记为 class-protocol&lt;/h2&gt;
&lt;p&gt;Swift 可以限定协议只能通过类实现。标记协议只能由类实现的一个优点就是，编译器可以基于只有类实现协议这一事实来优化程序。&lt;/p&gt;
&lt;p&gt;例如，如果 ARC 内存管理系统知道正在处理类对象，那么就能够简单的保留(增加对象的引用计数)它。如果编译器不知道这一事实，它就不得不假设结构体也可以实现协议，那么就需要准备保留或者释放不可忽视的结构体，这样做的代价很高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果限定只能由类实现某个协议，那么就需要标记类实现的协议为类协议，以便获得更好的运行性能。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  protocol Pingable: AnyObject { func ping() -&amp;gt; Int }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html&quot;&gt;Protocols&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;十、 left/var 被逃逸闭包捕获的性能消耗&lt;/h1&gt;
&lt;p&gt;可能很多人认为 let 和 var 的区别仅仅是语义层面的，起始其中也有性能方面的思考。一旦一个变量和一个闭包发生了绑定，就强制编译器生成一个逃逸闭包。例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  let f: () -&amp;gt; () = { ... } // Escaping closure
  // Contrasted with:
  ({ ... })() // Non Escaping closure
  x.map { ... } // Non Escaping closure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当一个 var 被逃逸闭包捕获，编译器必须在堆上开辟空间来存储这个 var 使得，创建者和闭包都能够读写这个 var。相对的当捕获一个 let。会直接在闭包的数据结构内添加一个该变量的 copy，而不用去堆上开辟空间。&lt;/p&gt;
&lt;h2&gt;建议：如果闭包不是真的需要逃逸，使用 &lt;code&gt;inout&lt;/code&gt; 参数传递 var&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;inout&lt;/code&gt; 会确保不会在堆上再去开辟空间，避免了没必要的 retain/release 操作。&lt;/p&gt;
&lt;h1&gt;补充说明 - Footnotes&lt;/h1&gt;
&lt;p&gt;[^1]: A virtual method table or &apos;vtable&apos; is a type specific table referenced by
instances that contains the addresses of the type&apos;s methods. Dynamic
dispatch proceeds by first looking up the table from the object and then
looking up the method in the table.&lt;/p&gt;
&lt;p&gt;[^2]: This is due to the compiler not knowing the exact function being called.&lt;/p&gt;
&lt;p&gt;[^3]: i.e. a direct load of a class&apos;s field or a direct call to a function.&lt;/p&gt;
&lt;p&gt;[^4]: An optimization technique in which a copy will be made if and only if
a modification happens to the original copy, otherwise a pointer will be given.&lt;/p&gt;
&lt;p&gt;[^5]: In certain cases the optimizer is able to via inlining and ARC
optimization remove the retain, release causing no copy to occur.&lt;/p&gt;
</content:encoded></item><item><title>dealloc</title><link>https://blog.lab-lawliet.app/posts/ios-06-dealloc-3c5b2b54607015eb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-06-dealloc-3c5b2b54607015eb/</guid><description>Dealloc 的实现机制是内容管理部分的重点，把这个知识点弄明白，对于全方位的理解内存管理的只是很有必要。</description><pubDate>Thu, 16 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Dealloc 的实现机制&lt;/h2&gt;
&lt;p&gt;Dealloc 的实现机制是内容管理部分的重点，把这个知识点弄明白，对于全方位的理解内存管理的只是很有必要。&lt;/p&gt;
&lt;h3&gt;1. Dealloc 调用流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;首先调用 &lt;code&gt;_objc_rootDealloc()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;接下来调用 &lt;code&gt;rootDealloc()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这时候会判断是否可以被释放，判断的依据主要有 5 个，判断是否有以上五种情况
&lt;ul&gt;
&lt;li&gt;NONPointer_ISA&lt;/li&gt;
&lt;li&gt;weakly_reference&lt;/li&gt;
&lt;li&gt;has_assoc&lt;/li&gt;
&lt;li&gt;has_cxx_dtor&lt;/li&gt;
&lt;li&gt;has_sidetable_rc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果有以上五中任意一种，将会调用 &lt;code&gt;object_dispose()&lt;/code&gt; 方法，做下一步的处理。&lt;/li&gt;
&lt;li&gt;如果没有之前五种情况的任意一种，则可以执行释放操作，&lt;code&gt;C&lt;/code&gt; 函数的 &lt;code&gt;free()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;执行完毕。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. object_dispose() 调用流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;直接调用 &lt;code&gt;objc_destructInstance()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;之后调用 &lt;code&gt;C&lt;/code&gt; 函数的 &lt;code&gt;free()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. objc_destructInstance() 调用流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先判断 &lt;code&gt;hasCxxDtor&lt;/code&gt;，如果有 &lt;code&gt;C++&lt;/code&gt; 的相关内容，要调用 &lt;code&gt;object_cxxDestruct()&lt;/code&gt; ，销毁 &lt;code&gt;C++&lt;/code&gt; 相关的内容。&lt;/li&gt;
&lt;li&gt;再判断 &lt;code&gt;hasAssocitatedObjects&lt;/code&gt;，如果有的话，要调用 &lt;code&gt;object_remove_associations()&lt;/code&gt;， 销毁关联对象的一系列操作。&lt;/li&gt;
&lt;li&gt;然后调用 &lt;code&gt;clearDeallocating()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;执行完毕。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. clearDeallocating() 调用流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;先执行 &lt;code&gt;sideTable_clearDellocating()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;再执行 &lt;code&gt;weak_clear_no_lock&lt;/code&gt;，在这一步骤中，会将指向该对象的弱引用指针置为 &lt;code&gt;nil&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;接下来执行 &lt;code&gt;table.refcnts.eraser()&lt;/code&gt;，从引用计数表中擦除该对象的引用计数。&lt;/li&gt;
&lt;li&gt;至此为止，&lt;code&gt;Dealloc&lt;/code&gt; 的执行流程结束。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>知识点梳理：HTML和浏览器</title><link>https://blog.lab-lawliet.app/posts/article-html-15340a08e80486cb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-html-15340a08e80486cb/</guid><description>共同点</description><pubDate>Wed, 01 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 浏览器&lt;/h2&gt;
&lt;h3&gt;1. Cookie、sessionStorage、localStorage&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;共同点&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;都是保存在浏览器端，并且是同源的&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;区别&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cookie 和 session 都可用来存储用户信息，cookie 存放于客户端，session 存放于服务器端， 因为 cookie 存放于客户端有可能被窃取，所以 cookie 一般用来存放不敏感的信息，比如 用户设置的网站主题，敏感的信息用 session 存储，比如用户的登陆信息，session 可以 存放于文件，数据库，内存中都可以，cookie 可以服务器端响应的时候设置，也可以客 户端通过 JS 设置 cookie 会在请求时在 http 首部发送给客户端，cookie 一般在客户端有 大小限制，一般为 4K，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下面从几个方向区分一下 cookie，localstorage，sessionstorage 的区别&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;生命周期:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cookie:可设置失效时间，否则默认为关闭浏览器后失效 Localstorage:除非被手动清除，否则永久保存&lt;/li&gt;
&lt;li&gt;Sessionstorage:仅在当前网页会话下有效，关闭页面或浏览器后就会被清除 2、存放数据:&lt;/li&gt;
&lt;li&gt;Cookie:4k 左右&lt;/li&gt;
&lt;li&gt;Localstorage 和 sessionstorage:可以保存 5M 的信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;http 请求:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cookie:每次都会携带在 http 头中，如果使用 cookie 保存过多数据会带来性能问题&lt;/li&gt;
&lt;li&gt;其他两个:仅在客户端即浏览器中保存，不参与和服务器的通信&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;易用性:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cookie:需要程序员自己封装，原生的 cookie 接口不友好&lt;/li&gt;
&lt;li&gt;其他两个:即可采用原生接口，亦可再次封装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;应用场景:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从安全性来说，因为每次 http 请求都回携带 cookie 信息，这样子浪费了带宽，所以 cookie 应该尽可能的少用
&lt;ul&gt;
&lt;li&gt;此外 cookie 还需要指定作用域，不可以跨域调用，限制很多&lt;/li&gt;
&lt;li&gt;但是 用户识别用户登陆来说，cookie 还是比 storage 好用，其他情况下可以用 storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;localstorage 可以用来在页面传递参数&lt;/li&gt;
&lt;li&gt;sessionstorage 可以用来保存一些临时的数据，防止用户刷新 页面后丢失了一些参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 说一下 web worker&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在 HTML 页面中，如果在执行脚本时，页面的状态是不可响应的，直到脚本执行完成后，页面才变成可响应。&lt;/p&gt;
&lt;p&gt;web worker 是运行在后台的 js，独立于其他脚本，不会影响页面的性能。并且通过 postMessage 将结果回传到主线程。这样在进行复杂操作的时候，就不会阻塞主线程了。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;概念&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Web Worker (工作线程) 是 HTML5 中提出的概念，分为两种类型，&lt;code&gt;专用线程（Dedicated Web Worker）&lt;/code&gt; 和&lt;code&gt;共享线程（Shared Web Worker）&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;专用线程&lt;/code&gt;仅能被创建它的脚本所使用（一个专用线程对应一个主线程），而&lt;code&gt;共享线程&lt;/code&gt;能够在不同的脚本中使用（一个共享线程对应多个主线程）。&lt;/p&gt;
&lt;p&gt;专用线程可以看做是默认情况的 Web Worker，其加上修饰词的目的是为了与共享线程进行区分。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;用途&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Web Worker 的意义在于可以将一些耗时的数据处理操作从主线程中剥离，使主线程更加专注于页面渲染和交互。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;懒加载&lt;/li&gt;
&lt;li&gt;文本分析&lt;/li&gt;
&lt;li&gt;流媒体数据处理&lt;/li&gt;
&lt;li&gt;canvas 图形绘制&lt;/li&gt;
&lt;li&gt;图像处理&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;需要注意的点&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有同源限制&lt;/li&gt;
&lt;li&gt;无法访问 DOM 节点&lt;/li&gt;
&lt;li&gt;运行在另一个上下文中，无法使用Window对象&lt;/li&gt;
&lt;li&gt;Web Worker 的运行不会影响主线程，但与主线程交互时仍受到主线程单线程的瓶颈制约。换言之，如果 Worker 线程频繁与主线程进行交互，主线程由于需要处理交互，仍有可能使页面发生阻塞&lt;/li&gt;
&lt;li&gt;共享线程可以被多个浏览上下文（Browsing context）调用，但所有这些浏览上下文必须同源（相同的协议，主机和端口号）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Doctype 作用?&lt;/h3&gt;
&lt;p&gt;Doctype 声明于文档最前面，告诉浏览器以何种方式来渲染页面，这里有两种模式，严格模式和混杂模式。&lt;/p&gt;
&lt;h4&gt;严格模式与混杂模式如何区分? 它们有何意义?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行&lt;/li&gt;
&lt;li&gt;混杂模式，向后兼容，模拟老式浏览器，防止浏览器无法兼容页面&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Cookie 如何防范 XSS 攻击&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;XSS(跨站脚本攻击)&lt;/code&gt;是指攻击者在返回的 HTML 中嵌入 javascript 脚本。&lt;/p&gt;
&lt;p&gt;为了减轻这些 攻击，需要在 HTTP 头部配上，set-cookie:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;httponly
&lt;ul&gt;
&lt;li&gt;这个属性可以防止 XSS,它会禁止 javascript 脚本来访问 cookie。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;secure
&lt;ul&gt;
&lt;li&gt;这个属性告诉浏览器仅在请求为 https 的时候发送 cookie。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;结果应该是这样的:Set-Cookie=&lt;code&gt;&amp;lt;cookie-value&amp;gt;&lt;/code&gt;.....&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;XSS 防御的总体思路是:对输入(和 URL 参数)进行过滤，对输出进行编码。也就是对提 交的所有内容进行过滤，对 url 中的参数进行过滤，过滤掉会导致脚本执行的相关内容; 然后对动态输出到页面的内容进行 html 编码，使脚本无法在浏览器中执行。虽然对输 入过滤可以被绕过，但是也还是会拦截很大一部分的 XSS 攻击。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5. cookie session 区别&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;cookie 数据存放在客户的浏览器上，session 数据放在服务器上。&lt;/li&gt;
&lt;li&gt;cookie 不是很安全，别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗
考虑到安全应当使用 session。&lt;/li&gt;
&lt;li&gt;session 会在一定时间内保存在服务器上。当访问增多，会比较占用你服务器的性能，考虑到减轻服务器性能方面，应当使用 COOKIE。&lt;/li&gt;
&lt;li&gt;单个 cookie 保存的数据不能超过 4K，很多浏览器都限制一个站点最多保存 20 个 cookie。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6. click在ios上有300ms延迟，原因及如何解决&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;原因&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;safari，为了能把PC端大屏幕的页面以较好的效果展示在小屏幕手机端上，采用了双击缩放（double tap to zoom）的方案。而这就是click在移动端ios会有300ms的缘由。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;解决方案一：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;禁止缩放，那么就没有了双击产生缩放的操作，那么就不需要等待300ms&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;user-scalable=no&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;解决方案二： FastClcik&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fast click 原理
&lt;ul&gt;
&lt;li&gt;监听touchend事件（touchstart touchend会先于click触发）&lt;/li&gt;
&lt;li&gt;使用自定义DOM事件 模拟一个click事件&lt;/li&gt;
&lt;li&gt;把默认的click事件（300ms之后触发）禁止掉&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;很久不维护了，坑多&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;7. 浏览器缓存&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000008956069&quot;&gt;HTTP强缓存和协商缓存&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储，当访问者再次访问同一页面时，浏览器就可以直接从本地磁盘加载文档。&lt;/p&gt;
&lt;p&gt;所以根据上面的特点，浏览器缓存有下面的优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少冗余的数据传输&lt;/li&gt;
&lt;li&gt;减少服务器负担&lt;/li&gt;
&lt;li&gt;加快客户端加载网页的速度&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;第一次请求时&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/22dc670db0c04f7c.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在第一次请求时，服务器会将页面最后修改时间通过Last-Modified标识由服务器发送给客户端，客户端记录修改时间；服务器还会生成一个Etag，并发送给客户端。&lt;/p&gt;
&lt;p&gt;浏览器后续再次进行请求时：
&lt;img src=&quot;/content-assets/02489d81357ba644.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;浏览器缓存主要分为强强缓存（也称本地缓存）和协商缓存（也称弱缓存）。根据上图，浏览器在第一次请求发生后，再次发送请求时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浏览器请求某一资源时，会先获取该资源缓存的header信息，
&lt;ul&gt;
&lt;li&gt;然后根据header中的Cache-Control和Expires来判断是否过期。&lt;/li&gt;
&lt;li&gt;若没过期则直接从缓存中获取资源信息，包括缓存的header的信息，所以此次请求不会与服务器进行通信。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;这里判断是否过期，则是强缓存相关。后面会讲Cache-Control和Expires相关。&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果显示已过期，浏览器会向服务器端发送请求
&lt;ul&gt;
&lt;li&gt;这个请求会携带第一次请求返回的有关缓存的header字段信息
&lt;ul&gt;
&lt;li&gt;比如客户端会通过If-None-Match头将先前服务器端发送过来的Etag发送给服务器，服务会对比这个客户端发过来的Etag是否与服务器的相同
&lt;ul&gt;
&lt;li&gt;若相同，就将If-None-Match的值设为false，
&lt;ul&gt;
&lt;li&gt;返回状态&lt;code&gt;304&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;客户端继续使用本地缓存，不解析服务器端发回来的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;若不相同就将If-None-Match的值设为true
&lt;ul&gt;
&lt;li&gt;返回状态为200&lt;/li&gt;
&lt;li&gt;客户端重新机械服务器端返回的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;客户端还会通过If-Modified-Since头将先前服务器端发过来的最后修改时间戳发送给服务器，服务器端通过这个时间戳判断客户端的页面是否是最新的，如果不是最新的，则返回最新的内容，如果是最新的，则返回&lt;code&gt;304&lt;/code&gt;，客户端继续使用本地缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;两种缓存&lt;/h4&gt;
&lt;p&gt;缓存分为两种:强缓存和协商缓存，根据响应的 header 内容来决定。&lt;/p&gt;
&lt;h4&gt;强缓存、协商缓存什么时候用哪个&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;强缓存是利用http头中的Expires和Cache-Control两个字段来控制的，用来表示资源的缓存时间。强缓存中，普通刷新会忽略它，但不会清除它，需要强制刷新。浏览器强制刷新，请求会带上Cache-Control:no-cache和Pragma:no-cache&lt;/li&gt;
&lt;li&gt;协商缓存就是由服务器来确定缓存资源是否可用，所以客户端与服务器端要通过某种标识来进行通信，从而让服务器判断请求资源是否可以缓存访问。
&lt;ul&gt;
&lt;li&gt;普通刷新会启用弱缓存，忽略强缓存。只有在地址栏或收藏夹输入网址、通过链接引用资源等情况下，浏览器才会启用强缓存，这也是为什么有时候我们更新一张图片、一个js文件，页面内容依然是旧的，但是直接浏览器访问那个图片或文件，看到的内容却是新的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;为什么要有Etag&lt;/h4&gt;
&lt;p&gt;你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新，为什么还需要Etag呢？HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一些文件也许会周期性的更改，但是他的内容并不改变(仅仅改变的修改时间)，这个时候我们并不希望客户端认为这个文件被修改了，而重新GET；&lt;/li&gt;
&lt;li&gt;某些文件修改非常频繁，比如在秒以下的时间内进行修改，(比方说1s内修改了N次)，If-Modified-Since能检查到的粒度是s级的，这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)；&lt;/li&gt;
&lt;li&gt;某些服务器不能精确的得到文件的最后修改时间。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Last-Modified与ETag是可以一起使用的，服务器会优先验证ETag，一致的情况下，才会继续比对Last-Modified，最后才决定是否返回304。&lt;/p&gt;
&lt;h4&gt;具体有哪些请求头是跟缓存相关的&lt;/h4&gt;
&lt;p&gt;强缓存相关字段有 expires，cache-control。如果 cache-control 与 expires 同时存在的话， cache-control 的优先级高于 expires。&lt;/p&gt;
&lt;p&gt;协商缓存相关字段有 Last-Modified/If-Modified-Since，Etag/If-None-Match&lt;/p&gt;
&lt;h3&gt;8. 输入一个URL,到这个页面呈现出来的过程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DNS 解析&lt;/li&gt;
&lt;li&gt;TCP 连接&lt;/li&gt;
&lt;li&gt;发送 HTTP 请求 服务器处理请求并返回 HTTP 报文 浏览器解析渲染页面&lt;/li&gt;
&lt;li&gt;连接结束&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/839c0921310cfce8.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;9. csrf 和 xss 的网络攻击及防范&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CSRF&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨站请求伪造
&lt;ul&gt;
&lt;li&gt;可以理解为攻击者盗用了用户的身份，以用户的名义发送了恶意请求，比如用户登录了一个网站后，立刻在另一个tab页面访问量攻击者用来制造攻击的网站，这个网站要求访问刚刚登陆的网站，并发送了一个恶意请求，这时候 CSRF 就产生了，比如这个制造攻击的网站使用一张图片，但是这种图片的链接却是可以修改 数据库的，这时候攻击者就可以以用户的名义操作这个数据库&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;防御方式的话
&lt;ul&gt;
&lt;li&gt;验证 HTTPReferer 字段&lt;/li&gt;
&lt;li&gt;在请求地址中添加 token 并 验证&lt;/li&gt;
&lt;li&gt;在 HTTP 头中自定义属性并验证。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;XSS&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨站脚本攻击
&lt;ul&gt;
&lt;li&gt;是说攻击者通过注入恶意的脚本，在用户浏览网页的时候进行攻 击，比如获取 cookie，或者其他用户身份信息，可以分为存储型和反射型，存储型是攻 击者输入一些数据并且存储到了数据库中，其他浏览者看到的时候进行攻击，反射型的 话不存储在数据库中，往往表现为将攻击代码放在 url 地址的请求参数中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;防御的话为 cookie 设置 httpOnly 属性，对用户的输入进行检查，进行特殊字符过滤。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10. cookie 有哪些编码方式?&lt;/h3&gt;
&lt;p&gt;encodeURI()&lt;/p&gt;
&lt;h3&gt;11. 说说 302，301，304 的状态码&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;301 Moved Permanently 永久移动。
&lt;ul&gt;
&lt;li&gt;请求的资源已被永久的移动到新 URI，返回信息会 包括新的 URI，浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;302 Found 临时移动。
&lt;ul&gt;
&lt;li&gt;与 301 类似。但资源只是临时被移动。客户端应继续使用原有 URI&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;304 Not Modified 未修改。
&lt;ul&gt;
&lt;li&gt;所请求的资源未修改，服务器返回此状态码时，不会返回 任何资源。客户端通常会缓存访问过的资源，通过提供一个头信息指出客户端希望只返 回在指定日期之后修改的资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 HTML&lt;/h2&gt;
&lt;h3&gt;1. 对 HTML 语义化标签的理解&lt;/h3&gt;
&lt;p&gt;HTML5 语义化标签是指正确的标签包含了正确的内容，结构良好，便于阅读，比如 nav 表示导航条，类似的还有 article、header、footer 等等标签。&lt;/p&gt;
&lt;h3&gt;2. iframe 是什么?有什么缺点?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;定义
&lt;ul&gt;
&lt;li&gt;iframe 元素会创建包含另一个文档的内联框架&lt;/li&gt;
&lt;li&gt;提示:可以将提示文字放在&lt;code&gt;&amp;lt;iframe&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/code&gt;之间，来提示某些不支持 iframe 的浏览器&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点:
&lt;ul&gt;
&lt;li&gt;会阻塞主页面的 onload 事件&lt;/li&gt;
&lt;li&gt;搜索引擎无法解读这种页面，不利于 SEO&lt;/li&gt;
&lt;li&gt;iframe 和主页面共享连接池，而浏览器对相同区域有限制所以会影响性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. addEventListener 参数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;event 指定事件名&lt;/li&gt;
&lt;li&gt;function 指定要事件触发时执行的函数&lt;/li&gt;
&lt;li&gt;useCapture 指定事件 是否在捕获或冒泡阶段执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. HTML5 新增的元素&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先 html5 为了更好的实践 web 语义化，增加了 header，footer，nav,aside,section 等语义 化标签&lt;/li&gt;
&lt;li&gt;在表单方面，为了增强表单，为 input 增加了 color，emial,data ,range 等类型，&lt;/li&gt;
&lt;li&gt;在存储方面，提供了 sessionStorage，localStorage,和离线存储，通过这些存储方式方便数 据在客户端的存储和获取&lt;/li&gt;
&lt;li&gt;在多媒体方面规定了音频和视频元素 audio 和 vedio&lt;/li&gt;
&lt;li&gt;另外还 有地理定位，canvas 画布，拖放，多线程编程的 web worker 和 websocket 协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 CSS&lt;/h2&gt;
&lt;h3&gt;1. 说一下 盒子模型&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/The_box_model&quot;&gt;MDN-强烈推荐好好读读&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;2. flex 弹性盒子&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox&quot;&gt;MDN-flex&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;3. 画条 0.5 px 的线&lt;/h3&gt;
&lt;p&gt;直接设置0.5px，在不同的浏览器会有不同的表现&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;transform 缩放&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.scale-half {
    height: 1px;
    transform: scaleY(0.5);
    transform-origin: 50% 100%; /*为了防止线模糊*/
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;meta viewport&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;这样子就能缩放到原来的0.5倍，如果是1px那么就会变成0.5px。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;initial-scale=0.5, maximum-scale=1.0, user-scalable=no&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：移动端适用，字体也会变小，原理是css的1px在手机上并不是物理上的1像素，可能是2像素，initial-scale的值改为0.5之后就只代表1像素&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;也可以直接用图片&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;4. 页面导入样式时，使用link和@import有什么区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;区别1：
&lt;ul&gt;
&lt;li&gt;link是XHTML标签，除了加载CSS外，还可以定义RSS等其他事务；&lt;/li&gt;
&lt;li&gt;@import属于CSS范畴，只能加载CSS。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;区别2：
&lt;ul&gt;
&lt;li&gt;link引用CSS时，在页面载入时同时加载；&lt;/li&gt;
&lt;li&gt;@import需要页面网页完全载入以后加载。所以会出现一开始没有css样式，闪烁一下出现样式后的页面(网速慢的情况下)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;区别3：
&lt;ul&gt;
&lt;li&gt;link是XHTML标签，无兼容问题；&lt;/li&gt;
&lt;li&gt;@import是在CSS2.1提出的，低版本的浏览器不支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;区别4：
&lt;ul&gt;
&lt;li&gt;link支持使用Javascript控制DOM去改变样式；&lt;/li&gt;
&lt;li&gt;而@import不支持。在html设计制作中，css有四种引入方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. css四种引入方式&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;方式一： 内联样式&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div style=&quot;display: none;background:red&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;方式二： 嵌入样式&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;style&amp;gt;
      .content {
         background: red;
       }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;方式三：链接样式&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;style.css&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;方式四：导入样式&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;导入方式指的是使用 CSS 规则引入外部 CSS 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
    @import url(style.css);
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者写在css样式中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@charset &quot;utf-8&quot;;
@import url(style.css);
*{ margin:0; padding:0;}
.notice-link a{ color:#999;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我们应尽量使用 &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; 标签导入外部 CSS 文件，避免或者少用使用其他三种方式。&lt;/p&gt;
&lt;h3&gt;6. transition 和 animation 的区别&lt;/h3&gt;
&lt;p&gt;animation属性类似于transition，他们都是随着时间改变元素的属性值，那么区别是什么？&lt;/p&gt;
&lt;p&gt;其主要区别在于：transition需要触发一个事件才会随着时间改变其CSS属性；animation在不需要触发任何事件的情况下，也可以显式的随时间变化来改变元素CSS属性，达到一种动画的效果。&lt;/p&gt;
&lt;p&gt;1）CSS的transition需要借助别的方式来触发，比如CSS的状态选择器（如:hover）或 借助JavaScript来触发；animation可以自动触发&lt;/p&gt;
&lt;p&gt;2）过渡只有一组（两个：开始-结束） 关键帧，动画可以设置多个。&lt;/p&gt;
&lt;h3&gt;7. BFC 块格式化上下文&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Block_formatting_context&quot;&gt;MDN-块格式化上下文&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;官方文档比较晦涩，建议搭配阅读&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/127187654&quot;&gt;一次弄懂css的BFC&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;8. 清除浮动&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844903504545316877#heading-4&quot;&gt;清除浮动的四种方式及其原理理解&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;9. 垂直居中&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/263417006&quot;&gt;CSS垂直居中的七个方法&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设定行高（line-height）
&lt;ul&gt;
&lt;li&gt;行内元素&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;添加伪元素（::before、::after）&lt;/li&gt;
&lt;li&gt;calc动态计算&lt;/li&gt;
&lt;li&gt;使用表格或假装表格&lt;/li&gt;
&lt;li&gt;transform&lt;/li&gt;
&lt;li&gt;绝对定位&lt;/li&gt;
&lt;li&gt;使用Flexbox&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10. css3动画与js动画的区别&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6997255364857036813&quot;&gt;参考&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;JS动画&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1)JavaScript在浏览器的主线程中运行，而主线程中还有其它需要运行的JavaScript脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞，从而造成丢帧的情况。&lt;/li&gt;
&lt;li&gt;(2)代码的复杂度高于CSS动画&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1)JavaScript动画控制能力很强, 可以在动画播放过程中对动画进行控制：开始、暂停、回放、终止、取消都是可以做到的。&lt;/li&gt;
&lt;li&gt;(2)动画效果比css3动画丰富,有些动画效果，比如曲线运动,冲击闪烁,视差滚动效果，只有JavaScript动画才能完成&lt;/li&gt;
&lt;li&gt;(3)CSS3有兼容性问题，而JS大多时候没有兼容性问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;CSS动画&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1)运行过程控制较弱,无法附加事件绑定回调函数。CSS动画只能暂停,不能在动画中寻找一个特定的时间点，不能在半路反转动画，不能变换时间尺度，不能在特定的位置添加回调函数或是绑定回放事件,无进度报告&lt;/li&gt;
&lt;li&gt;(2)代码冗长。想用 CSS 实现稍微复杂一点动画,最后CSS代码都会变得非常笨重。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(1)浏览器可以对动画进行优化。&lt;/li&gt;
&lt;li&gt;(2)代码相对简单,性能调优方向固定&lt;/li&gt;
&lt;li&gt;(3)对于帧速表现不好的低版本浏览器，CSS3可以做到自然降级，而JS则需要撰写额外代码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11. CSS动画流畅的原因与前提&lt;/h3&gt;
&lt;p&gt;渲染线程分为main thread(主线程)和compositor thread(合成器线程)。
如果CSS动画只是改变transform和opacity，这时整个CSS动画得以在compositor thread完成（而JS动画则会在main thread执行，然后触发compositor进行下一步操作）。&lt;/p&gt;
&lt;p&gt;在JS执行一些昂贵的任务时，main thread繁忙，CSS动画由于使用了compositor thread可以保持流畅。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CSS动画比JS流畅的前提：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JS在执行一些昂贵的任务&lt;/li&gt;
&lt;li&gt;同时CSS动画不触发layout或paint，在CSS动画或JS动画触发了paint或layout时，需要main thread进行Layer树的重计算，这时CSS动画或JS动画都会阻塞后续操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;只有如下属性的修改才符合“仅触发Composite，不触发layout或paint”：
&lt;ul&gt;
&lt;li&gt;backface-visibility&lt;/li&gt;
&lt;li&gt;opacity&lt;/li&gt;
&lt;li&gt;*erspective&lt;/li&gt;
&lt;li&gt;perspective-origin&lt;/li&gt;
&lt;li&gt;transfrom&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以只有用上了3D加速或修改opacity时，css3动画的优势才会体现出来。&lt;/p&gt;
&lt;h3&gt;12. css3动画与js动画怎么选&lt;/h3&gt;
&lt;p&gt;如果动画只是简单的状态切换，不需要中间过程控制，在这种情况下，css动画是优选方案。它可以让你将动画逻辑放在样式文件里面，而不会让你的页面充斥 Javascript 库。&lt;/p&gt;
&lt;p&gt;然而如果你在设计很复杂的富客户端界面或者在开发一个有着复杂UI状态的 APP。那么你应该使用js动画，这样你的动画可以保持高效，并且你的工作流也更可控。&lt;/p&gt;
&lt;p&gt;所以，在实现一些小的交互动效的时候，就多考虑考虑CSS动画。&lt;/p&gt;
&lt;p&gt;对于一些复杂控制的动画，使用javascript比较可靠。&lt;/p&gt;
&lt;h3&gt;13. 多行文本省略&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#post-title {
  display: -webkit-box;
  text-overflow: ellipsis;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14. 聊聊 visibility=hidden, opacity=0，display:none&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;opacity=0，该元素隐藏起来了，但不会改变页面布局，并且，如果该元素已经绑定一些 事件，如 click 事件，那么点击该区域，也能触发点击事件的&lt;/li&gt;
&lt;li&gt;visibility=hidden，该元素 隐藏起来了，但不会改变页面布局，但是不会触发该元素已经绑定的事件&lt;/li&gt;
&lt;li&gt;display=none， 把元素隐藏起来，并且会改变页面布局，可以理解成在页面中把该元素删除掉一样&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;15. 双边距重叠现象(外边距折叠)&lt;/h3&gt;
&lt;p&gt;多个相邻(兄弟或者父子关系)普通流的块元素垂直方向 marigin 会重叠&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;折叠的结果为:
&lt;ul&gt;
&lt;li&gt;两个相邻的外边距都是正数时，折叠结果是它们两者之间较大的值。&lt;/li&gt;
&lt;li&gt;两个相邻的外边距都是负数时，折叠结果是两者绝对值的较大值。&lt;/li&gt;
&lt;li&gt;两个外边距一正一负时，折叠结果是两者的相加的和。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;16. position 属性 比较&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;固定定位 fixed:
&lt;ul&gt;
&lt;li&gt;元素的位置相对于浏览器窗口是固定位置，即使窗口是滚动的它也不会移动。&lt;/li&gt;
&lt;li&gt;Fixed 定位使元素的位置与文档流无关，因此不占据空间。&lt;/li&gt;
&lt;li&gt;Fixed 定位的元素和其他元素重叠。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;相对定位 relative:
&lt;ul&gt;
&lt;li&gt;如果对一个元素进行相对定位，它将出现在它所在的位置上。&lt;/li&gt;
&lt;li&gt;然后，可以通过设置垂直 或水平位置，让这个元素“相对于”它的起点进行移动。&lt;/li&gt;
&lt;li&gt;在使用相对定位时，无论是 否进行移动，元素仍然占据原来的空间。&lt;/li&gt;
&lt;li&gt;因此，移动元素会导致它覆盖其它框。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;绝对定位 absolute:
&lt;ul&gt;
&lt;li&gt;绝对定位的元素的位置相对于最近的已定位父元素，如果元素没有已定位的父元素，那么它的位置相对于&lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;absolute 定位使元素的位置与文档流无关，因此不占据空间。&lt;/li&gt;
&lt;li&gt;absolute 定位的元素和其他元素重叠。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;粘性定位 sticky:
&lt;ul&gt;
&lt;li&gt;元素先按照普通文档流定位，然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。&lt;/li&gt;
&lt;li&gt;而后，元素定位表现为在跨越特定阈值前为相对定位，之后为固定定位。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;默认定位 Static:
&lt;ul&gt;
&lt;li&gt;默认值。没有定位，元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;inherit:
&lt;ul&gt;
&lt;li&gt;规定应该从父元素继承 position 属性的值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;17. css3 新特性&lt;/h3&gt;
&lt;p&gt;开放题。CSS3 边框如 border-radius，box-shadow 等;CSS3 背景如 background-size， background-origin 等;CSS3 2D，3D 转换如 transform 等;CSS3 动画如 animation 等。 参考 https://www.cnblogs.com/xkweb/p/5862612.html&lt;/p&gt;
&lt;h3&gt;18. CSS 选择器有哪些&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选择器&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;MDN&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;h1 { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Type_selectors&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通配选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Universal_selectors&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;类选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.box { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Class_selectors&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ID 选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#unique { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/ID_selectors&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;标签属性选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a[title] { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Attribute_selectors&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;伪类选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;p:first-child { }&lt;/code&gt; &lt;code&gt;button:hover {}&lt;/code&gt; 等&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-classes&quot;&gt;具体见：MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;伪元素选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;p::first-line { }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-elements&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;后代选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;article p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Descendant_combinator&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;子代选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;article &amp;gt; p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Child_combinator&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;相邻兄弟选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;h1 + p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Adjacent_sibling_combinator&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通用兄弟选择器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;h1 ~ p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/General_sibling_combinator&quot;&gt;MDN&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;19. CSS选择器优先级&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/CSS/Specificity#%E9%80%89%E6%8B%A9%E5%99%A8%E7%B1%BB%E5%9E%8B&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;四、前端优化&lt;/h2&gt;
&lt;h3&gt;1. 可优化内容&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;降低请求量
&lt;ul&gt;
&lt;li&gt;合并资源，减少 HTTP 请求数，minify / gzip 压缩，webP，lazyLoad。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;加快请求速度
&lt;ul&gt;
&lt;li&gt;预解析 DNS，减少域名数，并行加载，CDN 分发。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缓存
&lt;ul&gt;
&lt;li&gt;HTTP 协议缓存请求，离线缓存 manifest，离线数据缓存 localStorage。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;渲染
&lt;ul&gt;
&lt;li&gt;JS/CSS 优化，加载顺序，服务端渲染，pipeline。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 怎么看网站的性能如何&lt;/h3&gt;
&lt;p&gt;检测页面加载时间一般有两种方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一种是被动去测:
&lt;ul&gt;
&lt;li&gt;就是在被检测的页面置入脚本或 探针，当用户访问网页时，探针自动采集数据并传回数据库进行分析&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;另一种主动监测的方式
&lt;ul&gt;
&lt;li&gt;即主动的搭建分布式受控环境，模拟用户发起页面访问请求，主动采集性能数 据并分析，在检测的精准度上，专业的第三方工具效果更佳，比如说性能极客&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>知识点梳理：JavaSctipt</title><link>https://blog.lab-lawliet.app/posts/article-javascript-1c0e27c7a33675f0/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-javascript-1c0e27c7a33675f0/</guid><pubDate>Wed, 01 Feb 2023 00:00:00 GMT</pubDate><content:encoded/></item><item><title>一键搞定iOS16横竖屏切换</title><link>https://blog.lab-lawliet.app/posts/ios-12-ios16-dbf8ccbe32eedcf3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-12-ios16-dbf8ccbe32eedcf3/</guid><description>近期同事反馈视频横屏的功能失效了，在才发现 iOS16 下以前的方式无法使用了。于是完善了一些自己之前写的横竖屏切换的拓展。</description><pubDate>Thu, 13 Oct 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;近期同事反馈视频横屏的功能失效了，在才发现 &lt;code&gt;iOS16&lt;/code&gt; 下以前的方式无法使用了。于是完善了一些自己之前写的横竖屏切换的拓展。&lt;/p&gt;
&lt;h2&gt;一、 iOS16 之前&lt;/h2&gt;
&lt;h3&gt;1.1 切换横屏&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UIDevice.current.setValue(UIDeviceOrientation.landscapeLeft.rawValue, forKey: &quot;orientation&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 切换竖屏&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UIDevice.current.setValue(UIDeviceOrientation.portrait.rawValue, forKey: &quot;orientation&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 切换结果监听&lt;/h3&gt;
&lt;p&gt;通过系统通知 &lt;code&gt;UIDevice.orientationDidChangeNotification&lt;/code&gt; 监听提切换动作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@objc
private func orientationDidChange(noti: Notification) {
    guard
        let device = noti.object as? UIDevice
    else {
        return
    }
    if device.orientation.isLandscape {
        print(&quot;横屏&quot;)
    } else {
        print(&quot;竖屏&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 iOS16&lt;/h2&gt;
&lt;p&gt;更新了 &lt;code&gt;iOS16&lt;/code&gt; 之后，发现上面的都没效果了，就...很可爱。iOS16 之后需要通过 Scene 来进行横竖屏的切换控制。&lt;/p&gt;
&lt;p&gt;核心代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func enterFullScreen() {
    guard #available(iOS 16.0, *) else {
        UIDevice.current.setValue(UIDeviceOrientation.landscapeLeft.rawValue, forKey: &quot;orientation&quot;)
        return
    }
    switchMode(full: true)
}

func exitFullScreen() {
    guard #available(iOS 16.0, *) else {
        UIDevice.current.setValue(UIDeviceOrientation.portrait.rawValue, forKey: &quot;orientation&quot;)
        return
    }
    switchMode(full: false)
}

@available(iOS 16.0, *)
private func switchMode(full: Bool) {
    guard
        let scence = UIApplication.shared.connectedScenes.first as? UIWindowScene
    else {
        return
    }
    let orientation: UIInterfaceOrientationMask = full ? .landscape : .portrait
    let geometryPreferencesIOS = UIWindowScene.GeometryPreferences.iOS(interfaceOrientations: orientation)
    scence.requestGeometryUpdate(geometryPreferencesIOS) { error in
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、RyukieSwifty/FullScreen&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/26a158af56edfff5.gif&quot; alt=&quot;iOS16&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我对这些逻辑进行了封装可以简单的接入到项目之中。也提供了示例程序。&lt;a href=&quot;https://github.com/RyukieSama/Swifty#34-%E6%A8%AA%E7%AB%96%E5%B1%8F%E5%88%87%E6%8D%A2&quot;&gt;GitHub: RyukieSwifty/FullScreen&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pods&lt;/code&gt; 接入：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pod &apos;RyukieSwifty/FullScreen&apos;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下面以一个 &lt;code&gt;UIViewController&lt;/code&gt; 为例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import UIKit
import RyukieSwifty

class FullScreenViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    // MARK: - Life
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        swifty.exitFullScreen()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        swifty.needFullScreen()
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if size.width &amp;gt; size.height {
            switchButton.isSelected = true
        } else {
            switchButton.isSelected = false
        }
    }
    // MARK: - Function-Public
    
    // MARK: - Function-Private
    
    // MARK: UI
    private func setupUI() {
        view.backgroundColor = .white
        
        switchButton
            .added(to: view)
            .layout { make in
                make?.center.equalToSuperview()
            }
    }
    
    // MARK: Buiness
    
    // MARK: Action
    @objc
    private func switchAction() {
        switchButton.isSelected ? swifty.exitFullScreen() : swifty.enterFullScreen()
    }
    
    // MARK: Request
    
    // MARK: - VarLet-Public
    
    // MARK: - VarLet-Private
    private lazy var switchButton: UIButton = {
        return UIButton()
            .config {
                $0.setTitleColor(.black, for: .normal)
                $0.setTitle(&quot;切换为横屏&quot;, for: .normal)
                $0.setTitle(&quot;切换为竖屏&quot;, for: .selected)
                $0.addTarget(self, action: #selector(switchAction), for: .touchUpInside)
            }
    }()
}

extension AppDelegate {
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -&amp;gt; UIInterfaceOrientationMask {
        return application.swifty.supportedInterfaceOrientations
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>重建二叉树</title><link>https://blog.lab-lawliet.app/posts/article-07-d25346b7bb363c56/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-07-d25346b7bb363c56/</guid><description>输入某二叉树的前序遍历和中序遍历的结果，请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。</description><pubDate>Sun, 08 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入某二叉树的前序遍历和中序遍历的结果，请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。&lt;/p&gt;
&lt;p&gt;例如，给出&lt;/p&gt;
&lt;p&gt;前序遍历 preorder = [3,9,20,15,7]&lt;/p&gt;
&lt;p&gt;中序遍历 inorder = [9,3,15,20,7]&lt;/p&gt;
&lt;p&gt;返回如下的二叉树：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
/ \
9  20
    /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 节点个数 &amp;lt;= 5000&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func buildTree(_ preorder: [Int], _ inorder: [Int]) -&amp;gt; TreeNode? {
    guard preorder.isEmpty == false, inorder.isEmpty == false else {
        return nil
    }
    
    let root = TreeNode(preorder[0], nil, nil)
    
    /**
     对中序的进行遍历，寻找根结点在其中的位置
     从而划分左右中序遍历结果
     */
    for (idx, node) in inorder.enumerated() {
        if node == preorder[0] { // 找到根结点
            root.left = buildTree(
                Array(preorder[1..&amp;lt;idx+1]),// 根结点在中序中的下标可以算出其 左子树的元素数量
                Array(inorder[0..&amp;lt;idx]))
            root.right = buildTree(
                Array(preorder[idx+1..&amp;lt;preorder.endIndex]),// 根结点在中序中的下标可以算出其 右子树的元素数量
                Array(inorder[idx+1..&amp;lt;inorder.endIndex]))
        }
    }
    
    return root
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>替换空格</title><link>https://blog.lab-lawliet.app/posts/article-05-2e867cfd5b3da4c9/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-05-2e867cfd5b3da4c9/</guid><description>请实现一个函数，把字符串 s 中的每个空格替换成&quot;%20&quot;。</description><pubDate>Thu, 28 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;请实现一个函数，把字符串 s 中的每个空格替换成&quot;%20&quot;。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：s = &quot;We are happy.&quot;&lt;/p&gt;
&lt;p&gt;输出：&quot;We%20are%20happy.&quot;&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= s 的长度 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func replaceSpace(_ s: String) -&amp;gt; String {    
    var temp = &quot;&quot;
    for c in s {
        if String(c) == &quot; &quot; {
            temp += &quot;%20&quot;
        }
        else {
            temp += String(c)
        }
    }
    return temp
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>从尾到头打印链表</title><link>https://blog.lab-lawliet.app/posts/article-06-94afaf0513d962c5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-06-94afaf0513d962c5/</guid><description>输入一个链表的头节点，从尾到头反过来返回每个节点的值（用数组返回）。</description><pubDate>Thu, 28 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个链表的头节点，从尾到头反过来返回每个节点的值（用数组返回）。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：head = [1,3,2]&lt;/p&gt;
&lt;p&gt;输出：[2,3,1]&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;2.1 辅助空间&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func reversePrint(_ head: ListNode?) -&amp;gt; [Int] {
    var res: [Int] = [], node = head
    
    while let n = node {
        res.insert(n.val, at: 0)
        node = node?.next
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func reversePrint(_ head: ListNode?) -&amp;gt; [Int] {
    guard let node = head else {
        return []
    }
    return reversePrint(head?.next) + [node.val]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>找出数组中重复的数字</title><link>https://blog.lab-lawliet.app/posts/article-03-7a4b6f2cb565a602/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-03-7a4b6f2cb565a602/</guid><description>找出数组中重复的数字。</description><pubDate>Wed, 27 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;找出数组中重复的数字。&lt;/p&gt;
&lt;p&gt;在一个长度为 n 的数组 nums 里的所有数字都在 0～n-1 的范围内。数组中某些数字是重复的，但不知道有几个数字重复了，也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;p&gt;[2, 3, 1, 0, 2, 5, 3]&lt;/p&gt;
&lt;p&gt;输出：2 或 3&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;2 &amp;lt;= n &amp;lt;= 100000&lt;/p&gt;
&lt;p&gt;通过次数188,858提交次数280,286&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;2.1 二分法&lt;/h3&gt;
&lt;p&gt;二分法 时间 O(log2N) 空间 O(N)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func findRepeatNumber(_ nums: [Int]) -&amp;gt; Int {
    var dic: [Int : Int] = [:]
    var left = 0, right = nums.count - 1
    
    while left &amp;lt;= right {
        if let _ = dic[nums[left]] {
            return nums[left]
        }
        else {
            dic[nums[left]] = 1
        }
        
        if let _ = dic[nums[right]] {
            return nums[right]
        }
        else {
            dic[nums[right]] = 1
        }
        
        left += 1
        right -= 1
    }
    
    return 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 一个萝卜一个坑&lt;/h3&gt;
&lt;p&gt;由题目限制 &lt;code&gt;n 个元素，取值范围 0～n-1&lt;/code&gt; 可知：每个元素的值对应的下标处的元素如果存在相同元素那么就说明重复了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func findRepeatNumber(in nums: [Int]) -&amp;gt; Int {
    var temp = nums, index = 0
    
    while index &amp;lt; temp.count {
        let currentValue = temp[index]
        
        if currentValue == index {
            index += 1 // 注意前移的条件，不是直接遍历
            continue
        }
        
        if currentValue == temp[currentValue] {
            return currentValue
        }
        
        temp.swapAt(index, currentValue)
    }
    
    return -1
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二维数组中的查找</title><link>https://blog.lab-lawliet.app/posts/article-04-a43253a82b9b585f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-04-a43253a82b9b585f/</guid><description>在一个 n * m 的二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个高效的函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。</description><pubDate>Wed, 27 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;在一个 n * m 的二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个高效的函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;p&gt;现有矩阵 matrix 如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
[1,   4,  7, 11, 15],
[2,   5,  8, 12, 19],
[3,   6,  9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;给定 target = 5，返回 true。&lt;/p&gt;
&lt;p&gt;给定 target = 20，返回 false。&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= n &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= m &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 240 题相同：https://leetcode-cn.com/problems/search-a-2d-matrix-ii/&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;2.1 从右上角开始&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func findNumberIn2DArrayA(_ matrix: [[Int]], _ target: Int) -&amp;gt; Bool {
    guard matrix.isEmpty == false, let first = matrix.first, first.isEmpty == false else {
        return false
    }
    var x = first.count - 1, y = 0
    
    while y &amp;lt; matrix.count {
        let line = matrix[y]
        let lineFinal = line[line.count - 1]
        
        if lineFinal &amp;gt;= target {
            while x &amp;gt;= 0 {
                if line[x] != target {
                    x -= 1
                }
                else {
                    return true
                }
            }
        }
        
        y += 1
        x = first.count - 1
    }
    
    return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 从右上角开始优化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func findNumberIn2DArray(_ matrix: [[Int]], _ target: Int) -&amp;gt; Bool {
    guard matrix.isEmpty == false, let first = matrix.first, first.isEmpty == false else {
        return false
    }
    var x = first.count - 1, y = 0
    
    while y &amp;lt; matrix.count, x &amp;gt;= 0 {
        let value = matrix[y][x]
        if value &amp;gt; target { x -= 1 }
        else if value &amp;lt; target { y += 1 }
        else { return true }
    }
    
    return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 从左下角&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func findNumberIn2DArray(_ matrix: [[Int]], _ target: Int) -&amp;gt; Bool {
    guard matrix.isEmpty == false, let first = matrix.first, first.isEmpty == false else {
        return false
    }
    var x = 0, y = matrix.count - 1
    
    while x &amp;lt; first.count, y &amp;gt;= 0 {
        let value = matrix[y][x]
        if value &amp;gt; target { y -= 1 }
        else if value &amp;lt; target { x += 1 }
        else { return true }
    }
    
    return false
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>68-II.二叉树的最近公共祖先</title><link>https://blog.lab-lawliet.app/posts/article-68-ii-cfa6976ba1824338/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-68-ii-cfa6976ba1824338/</guid><description>给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。</description><pubDate>Tue, 26 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。&lt;/p&gt;
&lt;p&gt;百度百科中最近公共祖先的定义为：“对于有根树 T 的两个结点 p、q，最近公共祖先表示为一个结点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”&lt;/p&gt;
&lt;p&gt;例如，给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1&lt;/p&gt;
&lt;p&gt;输出: 3&lt;/p&gt;
&lt;p&gt;解释: 节点 5 和节点 1 的最近公共祖先是节点 3。&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4&lt;/p&gt;
&lt;p&gt;输出: 5&lt;/p&gt;
&lt;p&gt;解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。&lt;/p&gt;
&lt;p&gt;说明:&lt;/p&gt;
&lt;p&gt;所有节点的值都是唯一的。&lt;/p&gt;
&lt;p&gt;p、q 为不同节点且均存在于给定的二叉树中。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func lowestCommonAncestor(root: TreeNode?, pNode: TreeNode, qNode: TreeNode) -&amp;gt; TreeNode? {
    guard let root = root, root.val != pNode.val, root.val != qNode.val else {
        return root
    }
    
    let left = lowestCommonAncestor(root: root.left, pNode: pNode, qNode: qNode)
    let right = lowestCommonAncestor(root: root.right, pNode: pNode, qNode: qNode)
        
    if left == nil, right == nil { return nil }
    if left == nil { return right }
    if right == nil { return left }
    return root
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>把字符串转换成整数</title><link>https://blog.lab-lawliet.app/posts/article-67-8cccd53d9435a576/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-67-8cccd53d9435a576/</guid><description>写一个函数 StrToInt，实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。</description><pubDate>Wed, 20 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;写一个函数 StrToInt，实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。&lt;/p&gt;
&lt;p&gt;首先，该函数会根据需要丢弃无用的开头空格字符，直到寻找到第一个非空格的字符为止。&lt;/p&gt;
&lt;p&gt;当我们寻找到的第一个非空字符为正或者负号时，则将该符号与之后面尽可能多的连续数字组合起来，作为该整数的正负号；假如第一个非空字符是数字，则直接将其与之后连续的数字字符组合起来，形成整数。&lt;/p&gt;
&lt;p&gt;该字符串除了有效的整数部分之后也可能会存在多余的字符，这些字符可以被忽略，它们对于函数不应该造成影响。&lt;/p&gt;
&lt;p&gt;注意：假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时，则你的函数不需要进行转换。&lt;/p&gt;
&lt;p&gt;在任何情况下，若函数不能进行有效的转换时，请返回 0。&lt;/p&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;p&gt;假设我们的环境只能存储 32 位大小的有符号整数，那么其数值范围为 [−2^31,  2^31 − 1]。如果数值超过这个范围，请返回  INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: &quot;42&quot;&lt;/p&gt;
&lt;p&gt;输出: 42&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: &quot;   -42&quot;&lt;/p&gt;
&lt;p&gt;输出: -42&lt;/p&gt;
&lt;p&gt;解释: 第一个非空白字符为 &apos;-&apos;, 它是一个负号。我们尽可能将负号与后面所有连续出现的数字组合起来，最后得到 -42 。&lt;/p&gt;
&lt;p&gt;示例 3:&lt;/p&gt;
&lt;p&gt;输入: &quot;4193 with words&quot;&lt;/p&gt;
&lt;p&gt;输出: 4193&lt;/p&gt;
&lt;p&gt;解释: 转换截止于数字 &apos;3&apos; ，因为它的下一个字符不为数字。&lt;/p&gt;
&lt;p&gt;示例 4:&lt;/p&gt;
&lt;p&gt;输入: &quot;words and 987&quot;&lt;/p&gt;
&lt;p&gt;输出: 0&lt;/p&gt;
&lt;p&gt;解释: 第一个非空字符是 &apos;w&apos;, 但它不是数字或正、负号。 因此无法执行有效的转换。&lt;/p&gt;
&lt;p&gt;示例 5:&lt;/p&gt;
&lt;p&gt;输入: &quot;-91283472332&quot;&lt;/p&gt;
&lt;p&gt;输出: -2147483648&lt;/p&gt;
&lt;p&gt;解释: 数字 &quot;-91283472332&quot; 超过 32 位有符号整数范围。因此返回 INT_MIN (−231) 。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;找规律&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let numbers: [Character] = [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;0&quot;]
let symbles: [Character] = [&quot;-&quot;, &quot;+&quot;]
let space: Character = &quot; &quot;

func strToInt(_ str: String) -&amp;gt; Int {
    let chrs = Array(str)
    var res: String = &quot;&quot;
    var lastChr: Character?
    
    for idx in 0..&amp;lt;chrs.count {
        let c = chrs[idx]
        
        guard
            symbles.contains(c) || numbers.contains(c) || space == c
        else {
            return number(of: res)
        }
        
        if let last = lastChr {
            // 空格之后只能跟 空格 符号 数字
            if last == space {
                lastChr = c
                if symbles.contains(c) || numbers.contains(c) {
                    res.append(c)
                }
            }
            // 符号后只能跟数字
            else if symbles.contains(last), numbers.contains(c) {
                lastChr = c
                res.append(c)
            }
            // 数字后只能跟数字
            else if numbers.contains(last), numbers.contains(c) {
                lastChr = c
                res.append(c)
            }
            else {
                return number(of: res)
            }
        }
        else {
            if numbers.contains(c) {
                res.append(c)
            }
            else if symbles.contains(c) {
                res.append(c)
            }
            lastChr = c
        }
    }
    
    return number(of: res)
}

func number(of str: String) -&amp;gt; Int {
    guard str.isEmpty == false, let last = str.last, symbles.contains(last) == false else {
        return 0
    }
    let maxValue = NSDecimalNumber(decimal: pow(2, 31)).intValue - 1
    let minValue = -NSDecimalNumber(decimal: pow(2, 31)).intValue
    
    if let res = Int64(str) {
        if res &amp;gt; Int64(maxValue) { return maxValue }
        if res &amp;lt; Int64(minValue) { return minValue }
        return Int(res)
    }
    return str.contains(&quot;-&quot;) ? minValue : maxValue
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>68-I.二叉搜索树的最近公共祖先</title><link>https://blog.lab-lawliet.app/posts/article-68-i-76c558928ed08ec3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-68-i-76c558928ed08ec3/</guid><description>给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。</description><pubDate>Wed, 20 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。&lt;/p&gt;
&lt;p&gt;百度百科中最近公共祖先的定义为：“对于有根树 T 的两个结点 p、q，最近公共祖先表示为一个结点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”&lt;/p&gt;
&lt;p&gt;例如，给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8&lt;/p&gt;
&lt;p&gt;输出: 6&lt;/p&gt;
&lt;p&gt;解释: 节点 2 和节点 8 的最近公共祖先是 6。&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4&lt;/p&gt;
&lt;p&gt;输出: 2&lt;/p&gt;
&lt;p&gt;解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
 
说明:&lt;/p&gt;
&lt;p&gt;所有节点的值都是唯一的。&lt;/p&gt;
&lt;p&gt;p、q 为不同节点且均存在于给定的二叉搜索树中。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;官方没有Swift用例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func lowestCommonAncestor(root: TreeNode, pNode: TreeNode, qNode: TreeNode) -&amp;gt; TreeNode {
    let minNode = pNode.val &amp;gt; qNode.val ? qNode : pNode
    let maxNode = pNode.val &amp;lt; qNode.val ? qNode : pNode
    guard
        !(minNode.val &amp;lt; root.val &amp;amp;&amp;amp; maxNode.val &amp;gt; root.val),// 左右分布，直接return根结点
        (minNode.val != root.val &amp;amp;&amp;amp; maxNode.val != root.val),
        let oNode = maxNode.val &amp;gt; root.val ? root.right : root.left
    else {
        // 利用二叉搜索树的特性排除一些情况，分别在两侧的话公共先祖就是跟节点
        return root
    }
    
    return lowestCommonAncestor(root: oNode, pNode: pNode, qNode: qNode)
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>构建乘积数组</title><link>https://blog.lab-lawliet.app/posts/article-66-930f04fc02fadfc5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-66-930f04fc02fadfc5/</guid><description>给定一个数组 A[0,1,…,n-1]，请构建一个数组 B[0,1,…,n-1]，其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。</description><pubDate>Mon, 18 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一个数组 A[0,1,…,n-1]，请构建一个数组 B[0,1,…,n-1]，其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。&lt;/p&gt;
&lt;p&gt;不能使用除法。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;p&gt;输入: [1,2,3,4,5]&lt;/p&gt;
&lt;p&gt;输出: [120,60,40,30,24]&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;所有元素乘积之和不会溢出 32 位整数&lt;/p&gt;
&lt;p&gt;a.length &amp;lt;= 100000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;动态规划&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func constructArr(_ a: [Int]) -&amp;gt; [Int] {
    guard a.isEmpty == false else {
        return []
    }
    var res: [Int] = []
    /// 某位之前的乘积
    var dpL: [Int : Int] = [0 : 1]
    /// 某位之后的乘积
    var dpR: [Int : Int] = [(a.count - 1) : 1]
    
    for idx in 1..&amp;lt;a.count {
        let lastIdx = idx - 1
        if let last = dpL[lastIdx] {
            dpL[idx] = last * a[lastIdx]
        }
        
        let backwordIdx = a.count - idx - 1
        if let last = dpR[backwordIdx + 1] {
            dpR[backwordIdx] = last * a[backwordIdx + 1]
        }
        
    }

    for idx in 0..&amp;lt;a.count {
        if let l = dpL[idx], let r = dpR[idx] {
            res.append(l * r)
        }
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>股票的最大利润</title><link>https://blog.lab-lawliet.app/posts/article-63-511ff4e4c90f7d14/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-63-511ff4e4c90f7d14/</guid><description>假设把某股票的价格按照时间先后顺序存储在数组中，请问买卖该股票一次可能获得的最大利润是多少？</description><pubDate>Sat, 16 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;假设把某股票的价格按照时间先后顺序存储在数组中，请问买卖该股票一次可能获得的最大利润是多少？&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: [7,1,5,3,6,4]&lt;/p&gt;
&lt;p&gt;输出: 5&lt;/p&gt;
&lt;p&gt;解释: 在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: [7,6,4,3,1]&lt;/p&gt;
&lt;p&gt;输出: 0&lt;/p&gt;
&lt;p&gt;解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 数组长度 &amp;lt;= 10^5&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func maxProfit(_ prices: [Int]) -&amp;gt; Int {
    guard prices.isEmpty == false else {
        return 0
    }
    var p = 0, res = 0, bottom = prices[0]
    
    while p &amp;lt; prices.count {
        let item = prices[p]
        if item &amp;lt;= bottom {
            bottom = item
        }
        else if item - bottom &amp;gt; res {
            res = item - bottom
        }
        p += 1
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>求1+2+…+n</title><link>https://blog.lab-lawliet.app/posts/article-64-1-2-n-5d7174a270c60b1d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-64-1-2-n-5d7174a270c60b1d/</guid><description>求 1+2+...+n ，要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句（A?B:C）。</description><pubDate>Sat, 16 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;求 1+2+...+n ，要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句（A?B:C）。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入: n = 3&lt;/p&gt;
&lt;p&gt;输出: 6&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入: n = 9&lt;/p&gt;
&lt;p&gt;输出: 45&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= n &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/qiu-12n-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;分析&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为有限制条件，我们就不能直接使用公式：Sn = n * (n + 1) / 2 或 Smn=(n+m)(n-m+1)/2&lt;/li&gt;
&lt;li&gt;可以使用递归代替循环&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func sumNums(_ n: Int) -&amp;gt; Int {
    if n == 0 { return 0 }
    return n + sumNums(n - 1)
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>58-I.翻转单词顺序</title><link>https://blog.lab-lawliet.app/posts/article-58-i-7ed97fa94d801f49/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-58-i-7ed97fa94d801f49/</guid><description>输入一个英文句子，翻转句子中单词的顺序，但单词内字符的顺序不变。为简单起见，标点符号和普通字母一样处理。例如输入字符串&quot;I am a student. &quot;，则输出&quot;student. a am I&quot;。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个英文句子，翻转句子中单词的顺序，但单词内字符的顺序不变。为简单起见，标点符号和普通字母一样处理。例如输入字符串&quot;I am a student. &quot;，则输出&quot;student. a am I&quot;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入: &quot;the sky is blue&quot;&lt;/p&gt;
&lt;p&gt;输出: &quot;blue is sky the&quot;&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入: &quot;  hello world!  &quot;&lt;/p&gt;
&lt;p&gt;输出: &quot;world! hello&quot;&lt;/p&gt;
&lt;p&gt;解释: 输入字符串可以在前面或者后面包含多余的空格，但是反转后的字符不能包括。&lt;/p&gt;
&lt;p&gt;示例 3：&lt;/p&gt;
&lt;p&gt;输入: &quot;a good   example&quot;&lt;/p&gt;
&lt;p&gt;输出: &quot;example good a&quot;&lt;/p&gt;
&lt;p&gt;解释: 如果两个单词间有多余的空格，将反转后单词间的空格减少到只含一个。&lt;/p&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;p&gt;无空格字符构成一个单词。&lt;/p&gt;
&lt;p&gt;输入字符串可以在前面或者后面包含多余的空格，但是反转后的字符不能包括。&lt;/p&gt;
&lt;p&gt;如果两个单词间有多余的空格，将反转后单词间的空格减少到只含一个。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func reverseWords(_ s: String) -&amp;gt; String {
    var res: String = &quot;&quot;
    var temp: String = &quot;&quot;
    
    for i in s {
        if i == &quot; &quot; {
            if temp.isEmpty == false {
                res = (res.isEmpty == false) ? &quot;\(temp) \(res)&quot; : temp
                temp = &quot;&quot;
            }
        }
        else {
            temp = &quot;\(temp)\(i)&quot;
        }
    }
    
    if temp.isEmpty == false {
        res = (res.isEmpty == false) ? &quot;\(temp) \(res)&quot; : temp
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>58-II.左旋转字符串</title><link>https://blog.lab-lawliet.app/posts/article-58-ii-5b85c825713bfb52/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-58-ii-5b85c825713bfb52/</guid><description>字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如，输入字符串&quot;abcdefg&quot;和数字2，该函数将返回左旋转两位得到的结果&quot;cdefgab&quot;。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如，输入字符串&quot;abcdefg&quot;和数字2，该函数将返回左旋转两位得到的结果&quot;cdefgab&quot;。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入: s = &quot;abcdefg&quot;, k = 2&lt;/p&gt;
&lt;p&gt;输出: &quot;cdefgab&quot;&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入: s = &quot;lrloseumgh&quot;, k = 6&lt;/p&gt;
&lt;p&gt;输出: &quot;umghlrlose&quot;&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= k &amp;lt; s.length &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func reverseLeftWords(_ s: String, _ n: Int) -&amp;gt; String {
    let idx = s.index(s.startIndex, offsetBy: n)
    let pre = String(s[..&amp;lt;idx])
    let aft = String(s[idx...])
    return &quot;\(aft)\(pre)&quot;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>59-I.滑动窗口的最大值</title><link>https://blog.lab-lawliet.app/posts/article-59-i-0377db8be2b87d5a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-59-i-0377db8be2b87d5a/</guid><description>给定一个数组 nums 和滑动窗口的大小 k，请找出所有滑动窗口里的最大值。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一个数组 nums 和滑动窗口的大小 k，请找出所有滑动窗口里的最大值。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;p&gt;输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3&lt;/p&gt;
&lt;p&gt;输出: [3,3,5,5,6,7]&lt;/p&gt;
&lt;p&gt;解释:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;滑动窗口的位置                最大值
[1  3  -1] -3  5  3  6  7       3
1 [3  -1  -3] 5  3  6  7       3
1  3 [-1  -3  5] 3  6  7       5
1  3  -1 [-3  5  3] 6  7       5
1  3  -1  -3 [5  3  6] 7       6
1  3  -1  -3  5 [3  6  7]      7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;你可以假设 k 总是有效的，在输入数组不为空的情况下，1 ≤ k ≤ 输入数组的大小。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法 动态规划&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func maxSlidingWindow(_ nums: [Int], _ k: Int) -&amp;gt; [Int] {
    var res: [Int] = []
    guard nums.isEmpty == false else {
        return res
    }
    var left = 0, right = k - 1
    /// 存放每个段落的最大值下标，Key为右边界
    var dp: [Int : Int] = [:]
    
    while right &amp;lt; nums.count {
        let newItem = nums[right]
        if let lastmaxIdx = dp[right - 1], lastmaxIdx &amp;gt;= left {// 上一个最大值在当前范围内
            let lastMax = nums[lastmaxIdx]
            if lastMax &amp;gt; newItem {
                res.append(lastMax)
                dp[right] = lastmaxIdx
            }
            else {
                res.append(newItem)
                dp[right] = right
            }
        }
        else {
            var curMaxIdx = right
            var curMax = nums[curMaxIdx]
            for i in left..&amp;lt;right {
                if nums[i] &amp;gt; curMax {
                    curMaxIdx = i
                    curMax = nums[curMaxIdx]
                }
            }
            res.append(curMax)
            dp[right] = curMaxIdx
        }
        
        right += 1
        left += 1
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>59-II.队列的最大值</title><link>https://blog.lab-lawliet.app/posts/article-59-ii-129f4ceed4605283/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-59-ii-129f4ceed4605283/</guid><description>请定义一个队列并实现函数 max_value 得到队列里的最大值，要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;请定义一个队列并实现函数 max_value 得到队列里的最大值，要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。&lt;/p&gt;
&lt;p&gt;若队列为空，pop_front 和 max_value 需要返回 -1&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入:&lt;/p&gt;
&lt;p&gt;[&quot;MaxQueue&quot;,&quot;push_back&quot;,&quot;push_back&quot;,&quot;max_value&quot;,&quot;pop_front&quot;,&quot;max_value&quot;]&lt;/p&gt;
&lt;p&gt;[[],[1],[2],[],[],[]]&lt;/p&gt;
&lt;p&gt;输出: [null,null,null,2,1,2]&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入:&lt;/p&gt;
&lt;p&gt;[&quot;MaxQueue&quot;,&quot;pop_front&quot;,&quot;max_value&quot;]&lt;/p&gt;
&lt;p&gt;[[],[],[]]&lt;/p&gt;
&lt;p&gt;输出: [null,-1,-1]
 
限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= push_back,pop_front,max_value的总操作数 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= value &amp;lt;= 10^5&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;实现一个优先队列么&lt;/p&gt;
</content:encoded></item><item><title>n个骰子的点数</title><link>https://blog.lab-lawliet.app/posts/article-60-n-f59e63e425ea4b64/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-60-n-f59e63e425ea4b64/</guid><description>把n个骰子扔在地上，所有骰子朝上一面的点数之和为s。输入n，打印出s的所有可能的值出现的概率。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;把n个骰子扔在地上，所有骰子朝上一面的点数之和为s。输入n，打印出s的所有可能的值出现的概率。&lt;/p&gt;
&lt;p&gt;你需要用一个浮点数数组返回答案，其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: 1&lt;/p&gt;
&lt;p&gt;输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: 2&lt;/p&gt;
&lt;p&gt;输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= n &amp;lt;= 11&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数组内元素和为 1&lt;/li&gt;
&lt;li&gt;要求每个和有多少种组合方式&lt;/li&gt;
&lt;li&gt;每个组合方式数/总组合数就是概率了&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func dicesProbability(_ n: Int) -&amp;gt; [Double] {
    let sRangeMin = n
    let sRangeMax = n * 6
    var total = 0
    var temp: [Int] = []
    var res: [Double] = []
    
    for s in sRangeMin...sRangeMax {
        let c = resultCount(of: s, by: n)
        total += c
        temp.append(c)
    }
    
    temp.forEach {
        res.append(Double($0) / Double(total))
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;设 f(n,x) 为 n 个骰子和为 x 的组合数&lt;/li&gt;
&lt;li&gt;f(n,x) = f(n-1, x-1) + f(n-1, x-2) + f(n-1, x-3) + f(n-1, x-4) + f(n-1, x-5) + f(n-1, x-6)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var dp: [String : Int] = [:]

/// 求 n 个 1-6 的数字和为 num 的组合数
/// - Parameters:
///   - num: -
///   - n: -
/// - Returns: 组合数
func resultCount(of num: Int, by n: Int) -&amp;gt; Int {
    guard num &amp;gt;= n, num &amp;lt;= n*6 else { // 注意控制边界条件
        return 0
    }
    
    guard n &amp;gt; 1 else {
        return 1
    }
    
    let key = &quot;\(n) - \(num)&quot;
    if let r = dp[key] {
        return r
    }
    
    var res: Int = 0
    for i in 1...6 {
        res += resultCount(of: num - i, by: n - 1)
    }
    
    dp[key] = res
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>扑克牌中的顺子</title><link>https://blog.lab-lawliet.app/posts/article-61-705b6d49d6d8e7bd/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-61-705b6d49d6d8e7bd/</guid><description>从若干副扑克牌中随机抽 5 张牌，判断是不是一个顺子，即这5张牌是不是连续的。2～10为数字本身，A为1，J为11，Q为12，K为13，而大、小王为 0 ，可以看成任意数字。A 不能视为 14。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;从若干副扑克牌中随机抽 5 张牌，判断是不是一个顺子，即这5张牌是不是连续的。2～10为数字本身，A为1，J为11，Q为12，K为13，而大、小王为 0 ，可以看成任意数字。A 不能视为 14。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: [1,2,3,4,5]&lt;/p&gt;
&lt;p&gt;输出: True&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: [0,0,1,2,5]&lt;/p&gt;
&lt;p&gt;输出: True&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;数组长度为 5&lt;/p&gt;
&lt;p&gt;数组的数取值为 [0, 13] .&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func isStraight(_ nums: [Int]) -&amp;gt; Bool {
    var zeroCount = 0
    var last: Int?
    
    for i in nums.sorted() {
        if i == 0 {
            zeroCount += 1
        }
        else if let l = last {
            let deta = i - l
            if deta &amp;gt; 0 {
                zeroCount -= (deta - 1)
                last = i
            }
            else {
                return false
            }
        }
        else {
            last = i
        }
        
        if zeroCount &amp;lt; 0 {
            return false
        }
    }
    
    return true
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>圆圈中最后剩下的数字</title><link>https://blog.lab-lawliet.app/posts/article-62-e2531f9ad2cfd30a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-62-e2531f9ad2cfd30a/</guid><description>0,1,···,n-1这n个数字排成一个圆圈，从数字0开始，每次从这个圆圈里删除第m个数字（删除后从下一个数字开始计数）。求出这个圆圈里剩下的最后一个数字。</description><pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;0,1,···,n-1这n个数字排成一个圆圈，从数字0开始，每次从这个圆圈里删除第m个数字（删除后从下一个数字开始计数）。求出这个圆圈里剩下的最后一个数字。&lt;/p&gt;
&lt;p&gt;例如，0、1、2、3、4这5个数字组成一个圆圈，从数字0开始每次删除第3个数字，则删除的前4个数字依次是2、0、4、1，因此最后剩下的数字是3。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入: n = 5, m = 3&lt;/p&gt;
&lt;p&gt;输出: 3&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入: n = 10, m = 17&lt;/p&gt;
&lt;p&gt;输出: 2
 
限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= n &amp;lt;= 10^5
1 &amp;lt;= m &amp;lt;= 10^6&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;超时&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func lastRemaining(_ n: Int, _ m: Int) -&amp;gt; Int {
    var right = m - 1
    var nums: [Int] = Array(0..&amp;lt;n)
    
    while nums.count &amp;gt; 1 {
        right = (right &amp;lt; nums.count) ? right : (right % nums.count)
        
        nums.remove(at: right)
        
        right += (m - 1)
    }
    
    return nums[0]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数学解法&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/&quot;&gt;能力有限。理解不能&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>和为s的两个数字</title><link>https://blog.lab-lawliet.app/posts/article-57-s-a4d1aa4910dabfb4/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-57-s-a4d1aa4910dabfb4/</guid><description>输入一个递增排序的数组和一个数字s，在数组中查找两个数，使得它们的和正好是s。如果有多对数字的和等于s，则输出任意一对即可。</description><pubDate>Thu, 31 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个递增排序的数组和一个数字s，在数组中查找两个数，使得它们的和正好是s。如果有多对数字的和等于s，则输出任意一对即可。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：nums = [2,7,11,15], target = 9&lt;/p&gt;
&lt;p&gt;输出：[2,7] 或者 [7,2]&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：nums = [10,26,30,31,47,60], target = 40&lt;/p&gt;
&lt;p&gt;输出：[10,30] 或者 [30,10]&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= nums.length &amp;lt;= 10^5&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= nums[i] &amp;lt;= 10^6&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;2.1 Map&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func twoSum(_ nums: [Int], _ target: Int) -&amp;gt; [Int] {
    var temp: [Int : Int] = [:]
    for num in nums {
        guard target &amp;gt;= num else { break }
        if let other = temp[num] {
            return [num, other]
        }
        temp[target - num] = num
    }
    return []
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 双指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func twoSum(_ nums: [Int], _ target: Int) -&amp;gt; [Int] {
    var l = 0, r = nums.count - 1
    
    while l &amp;lt; r {
        let a = nums[l], b = nums[r]
        if a + b &amp;gt; target {
            r -= 1
        }
        else if a + b &amp;lt; target {
            l += 1
        }
        else {
            return [a, b]
        }
    }
    
    return []
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>56-II.数组中数字出现的次数II</title><link>https://blog.lab-lawliet.app/posts/article-56-ii-ii-8e04335d13d81263/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-56-ii-ii-8e04335d13d81263/</guid><description>在一个数组 nums 中除一个数字只出现一次之外，其他数字都出现了三次。请找出那个只出现一次的数字。</description><pubDate>Tue, 29 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;在一个数组 nums 中除一个数字只出现一次之外，其他数字都出现了三次。请找出那个只出现一次的数字。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：nums = [3,4,3,3]&lt;/p&gt;
&lt;p&gt;输出：4&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：nums = [9,1,7,9,7,9,7]&lt;/p&gt;
&lt;p&gt;输出：1&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= nums.length &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= nums[i] &amp;lt; 2^31&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建一个临时队列，遍历 nums
&lt;ul&gt;
&lt;li&gt;数字 item 存在，下标为 lastIdx
&lt;ul&gt;
&lt;li&gt;插入 item 到队列末端&lt;/li&gt;
&lt;li&gt;移除 lastIdx 下标元素&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不存在
&lt;ul&gt;
&lt;li&gt;插入到队列头部&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;遍历完成，队列头部元素即为结果&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func singleNumber(_ nums: [Int]) -&amp;gt; Int {
    var queue: [Int] = []
    
    for item in nums {
        if let lastIdx = queue.firstIndex(of: item) {
            queue.remove(at: lastIdx)
            queue.append(item)
        }
        else {
            queue.insert(item, at: 0)
        }
    }
    
    return queue[0]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>56-I.数组中数字出现的次数</title><link>https://blog.lab-lawliet.app/posts/article-56-i-2833fa5c8254f957/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-56-i-2833fa5c8254f957/</guid><description>一个整型数组 nums 里除两个数字之外，其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)，空间复杂度是O(1)。</description><pubDate>Thu, 24 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;一个整型数组 nums 里除两个数字之外，其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)，空间复杂度是O(1)。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：nums = [4,1,4,6]&lt;/p&gt;
&lt;p&gt;输出：[1,6] 或 [6,1]&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：nums = [1,2,10,4,1,4,3,3]&lt;/p&gt;
&lt;p&gt;输出：[2,10] 或 [10,2]&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;2 &amp;lt;= nums.length &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;h3&gt;2.1 HashMap&lt;/h3&gt;
&lt;p&gt;HashMap 解法很容易想到，但是不符合空间复杂度的要求。&lt;/p&gt;
&lt;h3&gt;2.2 位运算&lt;/h3&gt;
&lt;p&gt;如果只有一个不同的话全员异或就能得出结果，本题我们如果能够将数组拆分为两个是不是就能得出结果了？&lt;/p&gt;
&lt;p&gt;[1,2,10,4,1,4,3,3] 对应的二进制如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0001
0010
1010
0100
0001
0100
0011
0011
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;设结果为 x y 两个元素&lt;/li&gt;
&lt;li&gt;全员异或结果 n = x ^ y = 1000&lt;/li&gt;
&lt;li&gt;那么怎么分成两个数组呢？要拆分需要一个基准，即一个用来区分两个数组元素不同的点&lt;/li&gt;
&lt;li&gt;我们回到 n ，我们可以知道必有两个不同的，所以结果 n 必定至少有一位为 1，那么我们可以分为两组：
&lt;ul&gt;
&lt;li&gt;这一位为 1 的元素&lt;/li&gt;
&lt;li&gt;这一位为 0 的元素&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为了找到这一位1，我们定义一个临时变量 t = 1，逐步左移 &amp;amp; n 找到为1的情况。&lt;/li&gt;
&lt;li&gt;接下来便利元素，通过 &amp;amp; t 是否大于 1 来区分元素，分别累积异或 x 或者 y&lt;/li&gt;
&lt;li&gt;最终的 x，y 就是我们要找的两个元素了&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;时间复杂度 O(N) 或者说是 2N，控件复杂度 O(1)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;func singleNumbers(_ nums: [Int]) -&amp;gt; [Int] {
    var x = 0, y = 0, n = 0, t = 1
    nums.forEach { n ^= $0 }
    while (t &amp;amp; n) == 0 { t &amp;lt;&amp;lt;= 1 }
    nums.forEach {
        (($0 &amp;amp; t) &amp;gt;= 1) ? (x ^= $0) : (y ^= $0)
    }
    return [x, y]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>55-I.二叉树的深度</title><link>https://blog.lab-lawliet.app/posts/article-55-i-7a56e225899c2faf/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-55-i-7a56e225899c2faf/</guid><description>输入一棵二叉树的根节点，求该树的深度。从根节点到叶节点依次经过的节点（含根、叶节点）形成树的一条路径，最长路径的长度为树的深度。</description><pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一棵二叉树的根节点，求该树的深度。从根节点到叶节点依次经过的节点（含根、叶节点）形成树的一条路径，最长路径的长度为树的深度。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;给定二叉树 [3,9,20,null,null,15,7]，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回它的最大深度 3 。&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;h3&gt;2.1 递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func maxDepth(_ root: TreeNode?) -&amp;gt; Int {
    guard let tree = root else { return 0 }
    if tree.left == nil, tree.right == nil { return 1 }
    return max(maxDepth(tree.left), maxDepth(tree.right)) + 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 非递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func maxDepth(_ root: TreeNode?) -&amp;gt; Int {
    var deep = 0
    guard let root = root else { return deep }
    var queue: [TreeNode] = [root]
    var currentLevelCount = 1
    var nextLevelCount = 0
    
    while queue.isEmpty == false {
        let current = queue.removeFirst()
        currentLevelCount -= 1
        
        if let left = current.left {
            queue.append(left)
            nextLevelCount += 1
        }
        
        if let right = current.right {
            queue.append(right)
            nextLevelCount += 1
        }
        
        if currentLevelCount == 0 {
            deep += 1
            currentLevelCount = nextLevelCount
            nextLevelCount = 0
        }
    }
    
    return deep
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>55-II.平衡二叉树</title><link>https://blog.lab-lawliet.app/posts/article-55-ii-1b5706753e034677/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-55-ii-1b5706753e034677/</guid><description>输入一棵二叉树的根节点，判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1，那么它就是一棵平衡二叉树。 示例 1:</description><pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一棵二叉树的根节点，判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1，那么它就是一棵平衡二叉树。
 
示例 1:&lt;/p&gt;
&lt;p&gt;给定二叉树 [3,9,20,null,null,15,7]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回 true 。&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;给定二叉树 [1,2,2,3,3,null,null,4,4]&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        1
       / \
      2   2
     / \
    3   3
   / \
  4   4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回 false 。&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 树的结点个数 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;p&gt;深度有用到上一题的思路。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isBalanced(_ root: TreeNode?) -&amp;gt; Bool {
    let deepL = maxDepth(root?.left)
    let deepR = maxDepth(root?.right)
    if (deepL == 0 &amp;amp;&amp;amp; deepR == 0) {
        return true
    }
    if (abs(deepL - deepR) &amp;gt; 1) {
        return false
    }
    return (isBalanced(root?.left) &amp;amp;&amp;amp; isBalanced(root?.right))
}

func maxDepth(_ root: TreeNode?) -&amp;gt; Int {
    var deep = 0
    guard let root = root else { return deep }
    var queue: [TreeNode] = [root]
    var currentLevelCount = 1
    var nextLevelCount = 0
    
    while queue.isEmpty == false {
        let current = queue.removeFirst()
        currentLevelCount -= 1
        
        if let left = current.left {
            queue.append(left)
            nextLevelCount += 1
        }
        
        if let right = current.right {
            queue.append(right)
            nextLevelCount += 1
        }
        
        if currentLevelCount == 0 {
            deep += 1
            currentLevelCount = nextLevelCount
            nextLevelCount = 0
        }
    }
    
    return deep
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>第一个只出现一次的字符</title><link>https://blog.lab-lawliet.app/posts/article-50-78202f29084deb6c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-50-78202f29084deb6c/</guid><description>在字符串 s 中找出第一个只出现一次的字符。如果没有，返回一个单空格。 s 只包含小写字母。</description><pubDate>Tue, 22 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;在字符串 s 中找出第一个只出现一次的字符。如果没有，返回一个单空格。 s 只包含小写字母。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入：s = &quot;abaccdeff&quot;&lt;/p&gt;
&lt;p&gt;输出：&apos;b&apos;&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入：s = &quot;&quot;&lt;/p&gt;
&lt;p&gt;输出：&apos; &apos;&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= s 的长度 &amp;lt;= 50000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func firstUniqChar(_ s: String) -&amp;gt; Character {
    let strArr = Array(s)
    var dp: [Character : Int] = [:]
    var indexs: [Character] = []
    
    strArr.forEach {
        if dp[$0] != nil {
            if let idx = indexs.firstIndex(of: $0) {
                indexs.remove(at: idx)                
            }
        }
        else {
            dp[$0] = 1
            indexs.append($0)
        }
    }
    
    return indexs.first ?? &quot; &quot;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>53-I.在排序数组中查找数字</title><link>https://blog.lab-lawliet.app/posts/article-53-i-1552317129852057/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-53-i-1552317129852057/</guid><description>统计一个数字在排序数组中出现的次数。</description><pubDate>Tue, 22 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;统计一个数字在排序数组中出现的次数。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: nums = [5,7,7,8,8,10], target = 8&lt;/p&gt;
&lt;p&gt;输出: 2&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: nums = [5,7,7,8,8,10], target = 6&lt;/p&gt;
&lt;p&gt;输出: 0
 
提示：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= nums.length &amp;lt;= 105&lt;/p&gt;
&lt;p&gt;-109 &amp;lt;= nums[i] &amp;lt;= 109&lt;/p&gt;
&lt;p&gt;nums 是一个非递减数组&lt;/p&gt;
&lt;p&gt;-109 &amp;lt;= target &amp;lt;= 109&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func search(_ nums: [Int], _ target: Int) -&amp;gt; Int {
    guard
        let first = nums.firstIndex(of: target),
        let last = nums.lastIndex(of: target)
    else {
        return 0
    }
    return last - first + 1
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>53-II.0～n-1中缺失的数字</title><link>https://blog.lab-lawliet.app/posts/article-53-ii-0-n-1-2da7af241ec4149f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-53-ii-0-n-1-2da7af241ec4149f/</guid><description>一个长度为n-1的递增排序数组中的所有数字都是唯一的，并且每个数字都在范围0～n-1之内。在范围0～n-1内的n个数字中有且只有一个数字不在该数组中，请找出这个数字。</description><pubDate>Tue, 22 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;一个长度为n-1的递增排序数组中的所有数字都是唯一的，并且每个数字都在范围0～n-1之内。在范围0～n-1内的n个数字中有且只有一个数字不在该数组中，请找出这个数字。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: [0,1,3]&lt;/p&gt;
&lt;p&gt;输出: 2&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: [0,1,2,3,4,5,6,7,9]&lt;/p&gt;
&lt;p&gt;输出: 8&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= 数组长度 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;h3&gt;2.1 下标匹配&lt;/h3&gt;
&lt;p&gt;本题为有序的数据，因此可以通过下标匹配来寻找缺失的数字&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func missingNumber(_ nums: [Int]) -&amp;gt; Int {
    var tmp = 0
    
    for i in nums {
        if tmp != i {
            return tmp
        }
        else {
            tmp += 1
        }
    }
    
    return tmp
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 二分法&lt;/h3&gt;
&lt;p&gt;排序数组中的搜索问题，首先可以想到 &lt;code&gt;二分法&lt;/code&gt; 解决，时间复杂度降低为了 O(logN)。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/92b6b0a27c0220c0.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func missingNumber(_ nums: [Int]) -&amp;gt; Int {
    var per = 0, aft = nums.count - 1
    while per &amp;lt;= aft {
        // 求中间下标
        let m = (per + aft) / 2

        if nums[m] == m {
            per = m + 1
        }
        else {
            aft = m - 1
        }
    }
    return per
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二叉搜索树的第k大节点</title><link>https://blog.lab-lawliet.app/posts/article-54-k-b28e4931e236ee65/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-54-k-b28e4931e236ee65/</guid><description>给定一棵二叉搜索树，请找出其中第 k 大的节点的值。</description><pubDate>Tue, 22 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一棵二叉搜索树，请找出其中第 k 大的节点的值。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: root = [3,1,4,null,2], k = 1&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3
/ \
1   4
\
   2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出: 4&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: root = [5,3,6,2,4,null,null,1], k = 3&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 5
/ \
3   6
/ \
2   4
/
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出: 4
 
限制：&lt;/p&gt;
&lt;p&gt;1 ≤ k ≤ 二叉搜索树元素个数&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;p&gt;根据二叉搜索树的特性，我们通过中序遍历得到有序的数组就可以轻松获得结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func kthLargest(_ root: TreeNode?, _ k: Int) -&amp;gt; Int {
    var inorderArr: [Int] = []
    
    func inorder(_ node: TreeNode?) {
        guard let n = node else {
            return
        }
        
        inorder(n.right)
        inorderArr.append(n.val)
        inorder(n.left)
    }
    
    inorder(root)
    
    return inorderArr[k - 1]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>两个链表的第一个公共节点</title><link>https://blog.lab-lawliet.app/posts/article-52-0e20381c9bd1f202/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-52-0e20381c9bd1f202/</guid><description>输入两个链表，找出它们的第一个公共节点。</description><pubDate>Mon, 21 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入两个链表，找出它们的第一个公共节点。&lt;/p&gt;
&lt;p&gt;如下面的两个链表：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在节点 c1 开始相交。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.leetcode.com/uploads/2018/12/13/160_example_1.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3&lt;/p&gt;
&lt;p&gt;输出：Reference of the node with value = 8&lt;/p&gt;
&lt;p&gt;输入解释：相交节点的值为 8 （注意，如果两个列表相交则不能为 0）。从各自的表头开始算起，链表 A 为 [4,1,8,4,5]，链表 B 为 [5,0,1,8,4,5]。在 A 中，相交节点前有 2 个节点；在 B 中，相交节点前有 3 个节点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.leetcode.com/uploads/2018/12/13/160_example_2.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1&lt;/p&gt;
&lt;p&gt;输出：Reference of the node with value = 2&lt;/p&gt;
&lt;p&gt;输入解释：相交节点的值为 2 （注意，如果两个列表相交则不能为 0）。从各自的表头开始算起，链表 A 为 [0,9,1,2,4]，链表 B 为 [3,2,4]。在 A 中，相交节点前有 3 个节点；在 B 中，相交节点前有 1 个节点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 3：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://assets.leetcode.com/uploads/2018/12/13/160_example_3.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2&lt;/p&gt;
&lt;p&gt;输出：null&lt;/p&gt;
&lt;p&gt;输入解释：从各自的表头开始算起，链表 A 为 [2,6,4]，链表 B 为 [1,5]。由于这两个链表不相交，所以 intersectVal 必须为 0，而 skipA 和 skipB 可以是任意值。&lt;/p&gt;
&lt;p&gt;解释：这两个链表不相交，因此返回 null。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果两个链表没有交点，返回 null.&lt;/p&gt;
&lt;p&gt;在返回结果后，两个链表仍须保持原有的结构。&lt;/p&gt;
&lt;p&gt;可假定整个链表结构中没有循环。&lt;/p&gt;
&lt;p&gt;程序尽量满足 O(n) 时间复杂度，且仅用 O(1) 内存。&lt;/p&gt;
&lt;p&gt;本题与主站 160 题相同：https://leetcode-cn.com/problems/intersection-of-two-linked-lists/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;p&gt;示例1，中看到公共节点位8，而不是1。可能有些同学会感到很疑惑。但其实这里两个节点的比较并不是数值的比较，而是内存地址的比较。同样值是1，但却是两个不同内存地址的两个节点。知道这个前提，这个题目就很简单两。&lt;/p&gt;
&lt;h2&gt;三、题解&lt;/h2&gt;
&lt;p&gt;本体没有官方 Swift 用例。这里我们自己构造数据结构&lt;/p&gt;
&lt;h3&gt;3.1 ListNode&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ListNode: Equatable {
    public static func == (lhs: ListNode, rhs: ListNode) -&amp;gt; Bool {
        Unmanaged.passUnretained(lhs).toOpaque() == Unmanaged.passUnretained(rhs).toOpaque()
    }
    
    public var val: Int
    public var next: ListNode?
    public init(_ val: Int) {
        self.val = val
        self.next = nil
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 题解&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func getIntersectionNode(headA: ListNode, headB: ListNode) -&amp;gt; ListNode? {
    var temp: [ListNode] = []
    var headA: ListNode? = headA
    var headB: ListNode? = headB
    
    while let a = headA {
        temp.append(a)
        headA = a.next
    }
    
    while let b = headB {
        if temp.contains(where: { $0 == b }) {
            return b
        }
        headB = b.next
    }
    
    return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 用例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let n0 = ListNode(0)
let n11 = ListNode(1)
let n12 = ListNode(1)
let n4 = ListNode(4)
let n42 = ListNode(4)
let n51 = ListNode(5)
let n52 = ListNode(5)
let n8 = ListNode(8)

// [4,1,8,4,5]
n4.next = n11
n11.next = n8
n8.next = n42
n42.next = n52

// [5,0,1,8,4,5]
n51.next = n0
n0.next = n12
n12.next = n8

let res = getIntersectionNode(headA: n4, headB: n51)
print(res?.val)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>最长不含重复字符的子字符串</title><link>https://blog.lab-lawliet.app/posts/article-48-8a838f7cbb7fd400/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-48-8a838f7cbb7fd400/</guid><description>请从字符串中找出一个最长的不包含重复字符的子字符串，计算该最长子字符串的长度。</description><pubDate>Tue, 15 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;请从字符串中找出一个最长的不包含重复字符的子字符串，计算该最长子字符串的长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入: &quot;abcabcbb&quot;&lt;/p&gt;
&lt;p&gt;输出: 3&lt;/p&gt;
&lt;p&gt;解释: 因为无重复字符的最长子串是 &quot;abc&quot;，所以其长度为 3。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 2:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入: &quot;bbbbb&quot;&lt;/p&gt;
&lt;p&gt;输出: 1&lt;/p&gt;
&lt;p&gt;解释: 因为无重复字符的最长子串是 &quot;b&quot;，所以其长度为 1。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 3:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入: &quot;pwwkew&quot;&lt;/p&gt;
&lt;p&gt;输出: 3&lt;/p&gt;
&lt;p&gt;解释: 因为无重复字符的最长子串是 &quot;wke&quot;，所以其长度为 3。
     请注意，你的答案必须是 子串 的长度，&quot;pwke&quot; 是一个子序列，不是子串。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;s.length &amp;lt;= 40000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func lengthOfLongestSubstring(_ s: String) -&amp;gt; Int {
    guard s.isEmpty == false else {
        return 0
    }
    let stringArr: [Character] = Array(s)
    var maxCount: Int = 0
    /// 最后一次元素出现的下标
    var map: [Character : Int] = [ : ]
    /// 一个重复段内的不重复子串长度
    var temp: Int = 0
    
    for idx in 0..&amp;lt;stringArr.count {
        if let last = map[stringArr[idx]] {
            temp = (temp &amp;lt; (idx - last)) ? (temp + 1) : (idx - last)
        }
        else {
            temp += 1
        }
        map[stringArr[idx]] = idx
        maxCount = max(maxCount, temp)
    }
    
    return maxCount
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以简化为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func lengthOfLongestSubstring(_ s: String) -&amp;gt; Int {
    ...

    for idx in 0..&amp;lt;stringArr.count {
        let last = map[stringArr[idx]] ?? -1
        temp = (temp &amp;lt; (idx - last)) ? (temp + 1) : (idx - last)
        map[stringArr[idx]] = idx
        maxCount = max(maxCount, temp)
    }
    
    ...
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>数字序列中某一位的数字</title><link>https://blog.lab-lawliet.app/posts/article-44-c872689f1ec6be35/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-44-c872689f1ec6be35/</guid><description>数字以0123456789101112131415…的格式序列化到一个字符序列中。</description><pubDate>Thu, 10 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;数字以0123456789101112131415…的格式序列化到一个字符序列中。&lt;/p&gt;
&lt;p&gt;在这个序列中，第5位（从下标0开始计数）是5，第13位是1，第19位是4，等等。&lt;/p&gt;
&lt;p&gt;请写一个函数，求任意第n位对应的数字。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：n = 3&lt;/p&gt;
&lt;p&gt;输出：3&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：n = 11&lt;/p&gt;
&lt;p&gt;输出：0&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= n &amp;lt; 2^31&lt;/p&gt;
&lt;p&gt;注意：本题与主站 400 题相同：https://leetcode-cn.com/problems/nth-digit/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;p&gt;本题我们先要看看为了找到下标为 &lt;code&gt;n&lt;/code&gt; 的数字 &lt;code&gt;x&lt;/code&gt; ，我们需要什么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在那个数中 &lt;code&gt;num&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;其在 &lt;code&gt;num&lt;/code&gt; 中的下标&lt;/li&gt;
&lt;li&gt;即可求得 &lt;code&gt;x&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>把数组排成最小的数</title><link>https://blog.lab-lawliet.app/posts/article-45-b01b692f5f9ce215/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-45-b01b692f5f9ce215/</guid><description>输入一个非负整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。</description><pubDate>Thu, 10 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个非负整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: [10,2]&lt;/p&gt;
&lt;p&gt;输出: &quot;102&quot;&lt;/p&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;p&gt;输入: [3,30,34,5,9]&lt;/p&gt;
&lt;p&gt;输出: &quot;3033459&quot;&lt;/p&gt;
&lt;p&gt;提示:&lt;/p&gt;
&lt;p&gt;0 &amp;lt; nums.length &amp;lt;= 100&lt;/p&gt;
&lt;p&gt;说明:&lt;/p&gt;
&lt;p&gt;输出结果可能非常大，所以你需要返回一个字符串而不是整数&lt;/p&gt;
&lt;p&gt;拼接起来的数字可能会有前导 0，最后结果不需要去掉前导 0&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 优先队列&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func minNumber(_ nums: [Int]) -&amp;gt; String {
    var queue = SwiftPriorityQueue&amp;lt;Int&amp;gt;.init(hasHigherPriority: {
        return hasHigher(x: $0, compare: $1)
    }, isEqual: {
        $0 == $1
    })
    
    var result: String = &quot;&quot;
    
    nums.forEach {
        queue.enqueue($0)
    }
        
    while let value = queue.dequeue() {
        result.append(&quot;\(value)&quot;)
    }
        
    return result
}

func hasHigher(x: Int, compare y: Int) -&amp;gt; Bool {
    let aArr = [Character](String(x))
    let bArr = [Character](String(y))
    
    let resultA = Int(String(aArr + bArr)) ?? 0
    let resultB = Int(String(bArr + aArr)) ?? 0
    
    return resultA &amp;lt;= resultB
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6035a3c33e9673ba.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、 自带的排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func minNumber(_ nums: [Int]) -&amp;gt; String {
    let queue = nums.sorted { return hasHigher(x: $0, compare: $1) }
    var result: String = &quot;&quot;
    queue.forEach {
        result.append(&quot;\($0)&quot;)
    }
    return result
}

func hasHigher(x: Int, compare y: Int) -&amp;gt; Bool {
    let aArr = [Character](String(x))
    let bArr = [Character](String(y))
    
    let resultA = Int(String(aArr + bArr)) ?? 0
    let resultB = Int(String(bArr + aArr)) ?? 0
    
    return resultA &amp;lt;= resultB
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8f6e2d8525317016.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>把数字翻译成字符串</title><link>https://blog.lab-lawliet.app/posts/article-46-76e1d2aa45989651/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-46-76e1d2aa45989651/</guid><description>给定一个数字，我们按照如下规则把它翻译为字符串：0 翻译成 “a” ，1 翻译成 “b”，……，11 翻译成 “l”，……，25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数，用来计算一个数字有多少种不同的翻译方法。</description><pubDate>Thu, 10 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给定一个数字，我们按照如下规则把它翻译为字符串：0 翻译成 “a” ，1 翻译成 “b”，……，11 翻译成 “l”，……，25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数，用来计算一个数字有多少种不同的翻译方法。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: 12258&lt;/p&gt;
&lt;p&gt;输出: 5&lt;/p&gt;
&lt;p&gt;解释: 12258有5种不同的翻译，分别是&quot;bccfi&quot;, &quot;bwfi&quot;, &quot;bczi&quot;, &quot;mcfi&quot;和&quot;mzi&quot;&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= num &amp;lt; 231&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
</content:encoded></item><item><title>礼物的最大价值</title><link>https://blog.lab-lawliet.app/posts/article-47-866f8601de04989b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-47-866f8601de04989b/</guid><description>在一个 m*n 的棋盘的每一格都放有一个礼物，每个礼物都有一定的价值（价值大于 0）。你可以从棋盘的左上角开始拿格子里的礼物，并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值，请计算你最多能拿到多少价值的礼物？ 示例 1:</description><pubDate>Thu, 10 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;在一个 m*n 的棋盘的每一格都放有一个礼物，每个礼物都有一定的价值（价值大于 0）。你可以从棋盘的左上角开始拿格子里的礼物，并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值，请计算你最多能拿到多少价值的礼物？
 
示例 1:&lt;/p&gt;
&lt;p&gt;输入:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出: 12&lt;/p&gt;
&lt;p&gt;解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;0 &amp;lt; grid.length &amp;lt;= 200&lt;/p&gt;
&lt;p&gt;0 &amp;lt; grid[0].length &amp;lt;= 200&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 图解&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2132d0e3ab0dca81.webp&quot; alt=&quot;图解&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、 解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func maxValue(_ grid: [[Int]]) -&amp;gt; Int {
    let w = grid[0].count
    let h = grid.count
    var dp: [[Int]] = Array(repeating: Array(repeating: 0, count: w), count: h)
    
    for y in 0..&amp;lt;h {
        for x in 0..&amp;lt;w {
            let value = grid[y][x]
            if x == 0, y == 0 { dp[y][x] = value }
            else if y == 0, x &amp;gt; 0 { dp[y][x] = dp[y][x-1] + value }
            else if y &amp;gt; 0, x == 0 { dp[y][x] = dp[y-1][x] + value }
            else { dp[y][x] = max(dp[y][x-1], dp[y-1][x]) + value }
        }
    }
    
    return dp[h-1][w-1]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>1～n整数中1出现的次数</title><link>https://blog.lab-lawliet.app/posts/article-43-1-n-1-c16166a17ef2ecf9/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-43-1-n-1-c16166a17ef2ecf9/</guid><description>输入一个整数 n ，求1～n这n个整数的十进制表示中1出现的次数。</description><pubDate>Tue, 22 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个整数 n ，求1～n这n个整数的十进制表示中1出现的次数。&lt;/p&gt;
&lt;p&gt;例如，输入12，1～12这些整数中包含1 的数字有1、10、11和12，1一共出现了5次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入：n = 12&lt;/p&gt;
&lt;p&gt;输出：5&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：n = 13&lt;/p&gt;
&lt;p&gt;输出：6&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= n &amp;lt; 2^31&lt;/p&gt;
&lt;p&gt;注意：本题与主站 233 题相同：https://leetcode-cn.com/problems/number-of-digit-one/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;h3&gt;2.1 暴力&lt;/h3&gt;
&lt;p&gt;当然遍历全部数字，进行累加理论上是可行的。但是如果真写出这样的代码就别想通过面试了🤷‍♂️。&lt;/p&gt;
&lt;h3&gt;2.2 按位分析&lt;/h3&gt;
&lt;p&gt;我们需要知道一个前提，1出现的次数和就是1在每一位上出现的次数的和。那么我们如何计算出每一位上出现1的次数呢？&lt;/p&gt;
&lt;p&gt;下面我们以&lt;code&gt;310562&lt;/code&gt;为例进行一下分析。&lt;/p&gt;
&lt;h4&gt;百位&lt;/h4&gt;
&lt;p&gt;我们想知道在这位出现1的次数，我们定义当前位的数字为 &lt;code&gt;num = 5&lt;/code&gt; ， 其左边的所有数字为 &lt;code&gt;pre = 310&lt;/code&gt; ， 右边的数字为 &lt;code&gt;aft = 62&lt;/code&gt; ，当前位为 &lt;code&gt;base = 100&lt;/code&gt; 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左组合数量：
&lt;ul&gt;
&lt;li&gt;0 ～ 310&lt;/li&gt;
&lt;li&gt;pre + 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;右组合数量：
&lt;ul&gt;
&lt;li&gt;0 ～ 99&lt;/li&gt;
&lt;li&gt;base
&lt;ul&gt;
&lt;li&gt;因为 1 &amp;lt; 5 ，百位为1时位数足够，可以取到 99&lt;/li&gt;
&lt;li&gt;其实就等于当前的&lt;code&gt;位&lt;/code&gt;，即100&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次数 = (pre + 1) * base&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7e5bb4f658bfcae4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;千位&lt;/h4&gt;
&lt;p&gt;pre = 31, num = 0, base = 1000, aft = 562&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c4b90c4735c85fc4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前位为0，当为1时必须需要前面退位，如上图变化
&lt;ul&gt;
&lt;li&gt;前范围变为 0 ～ 30&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;左组合数量
&lt;ul&gt;
&lt;li&gt;0 ～ 30&lt;/li&gt;
&lt;li&gt;pre&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;右组合数量：
&lt;ul&gt;
&lt;li&gt;0 ～ 999&lt;/li&gt;
&lt;li&gt;base&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次数 = (pre * base)&lt;/p&gt;
&lt;h4&gt;万位&lt;/h4&gt;
&lt;p&gt;pre = 3, num = 1, base = 10000, aft = 0562&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e84cb42a1392641.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 pre 取 0 ～ 2 时：
&lt;ul&gt;
&lt;li&gt;左组合数量
&lt;ul&gt;
&lt;li&gt;0 ～ 2&lt;/li&gt;
&lt;li&gt;pre&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;右组合数量：
&lt;ul&gt;
&lt;li&gt;0 ～ 999&lt;/li&gt;
&lt;li&gt;base&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当 pre 取 3 时：
&lt;ul&gt;
&lt;li&gt;左组合数量
&lt;ul&gt;
&lt;li&gt;3&lt;/li&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;右组合数量：
&lt;ul&gt;
&lt;li&gt;0 ～ 562&lt;/li&gt;
&lt;li&gt;(aft + 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;次数 = (pre * base) * (aft + 1)&lt;/p&gt;
&lt;h4&gt;左数 &amp;amp; 右数&lt;/h4&gt;
&lt;p&gt;上面将所有三种特殊情况的次数计算方式都推导出来了。剩下推出 pre aft num 的公式即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据规律推导出：
&lt;ul&gt;
&lt;li&gt;pre = n / base / 10&lt;/li&gt;
&lt;li&gt;aft = n % base&lt;/li&gt;
&lt;li&gt;num = n / base % 10&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们就可以根据上面的公式完成遍历的逻辑了。&lt;/p&gt;
&lt;h2&gt;三、 编码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func countDigitOne(_ n: Int) -&amp;gt; Int {
    var base = 1
    var sum = 0

    while base &amp;lt;= n {
        let pre = n / base / 10
        let aft = n % base
        let num = n / base % 10
                
        if num == 1 {
            sum += ((pre * base) + (aft + 1))
        }
        else if num == 0 {
            sum += (pre * base)
        }
        else {
            sum += ((pre + 1) * base)
        }
                
        base *= 10
    }
    
    return sum
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d4fb638a4873f94a.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>连续子数组的最大和</title><link>https://blog.lab-lawliet.app/posts/article-42-81eccb81d888cd22/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-42-81eccb81d888cd22/</guid><description>输入一个整型数组，数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。</description><pubDate>Mon, 21 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入一个整型数组，数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。&lt;/p&gt;
&lt;p&gt;要求时间复杂度为O(n)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例1:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入: nums = [-2,1,-3,4,-1,2,1,-5,4]&lt;/p&gt;
&lt;p&gt;输出: 6&lt;/p&gt;
&lt;p&gt;解释: 连续子数组 [4,-1,2,1] 的和最大，为 6。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= arr.length &amp;lt;= 10^5&lt;/p&gt;
&lt;p&gt;-100 &amp;lt;= arr[i] &amp;lt;= 100&lt;/p&gt;
&lt;p&gt;注意：本题与主站 53 题相同：https://leetcode-cn.com/problems/maximum-subarray/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 解法一：找规律图解&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d4785e6d043a7fe3.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图中，纵坐标表示的是累加和，绿色代表的是我们需要求的最大子数组和。我们发现下面的规律：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们需要找的是整体呈上升趋势的分段
&lt;ul&gt;
&lt;li&gt;即：区段内的累加和&lt;strong&gt;大于0&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果区段内的累加和&lt;strong&gt;小于0&lt;/strong&gt;那么当前区段是呈下降趋势的
&lt;ul&gt;
&lt;li&gt;这段我们可以丢弃，即将累加和重置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分析出这样的规律后我们就可以编码了&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func maxSubArray(_ nums: [Int]) -&amp;gt; Int {
    var currentSum = nums[0]
    var maxSum = nums[0]
    
    for idx in 1..&amp;lt;nums.count {
        if currentSum &amp;lt; 0 { // 前面的是下降趋势，丢弃之前的
            currentSum = nums[idx]
        }
        else {
            currentSum += nums[idx]
        }
        maxSum = max(maxSum, currentSum)
    }
    return maxSum
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e6c4dd4d5abbe6ae.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、解法二：动态规划&lt;/h2&gt;
&lt;p&gt;如果用函数 f(i) 代表第i个数子结尾的子数组最大和，那么有下面的关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;i == 0 或者 f(i-1) &amp;lt;= 0
&lt;ul&gt;
&lt;li&gt;f(i) = nums[i]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;else
&lt;ul&gt;
&lt;li&gt;f(i) = f(i-1) + nums[i]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;转化成代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func maxSubArray(_ nums: [Int]) -&amp;gt; Int {
    var dp: [Int] = Array(repeating: 0, count: nums.count)

    for i in 0..&amp;lt;nums.count {
        if i == 0 || (i &amp;gt; 0 &amp;amp;&amp;amp; dp[i-1] &amp;lt;= 0) {
            dp[i] = nums[i]
        }
        else {
            dp[i] = dp[i - 1] + nums[i]
        }
    }

    return dp.max() ?? nums[0]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c3a63054aebb2fa9.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;本题考查对时间复杂度的理解。如果给出 O(n2) O(n3) 的算法，是很难通过面试的。&lt;/li&gt;
&lt;li&gt;考察队动态规划的理解，如果熟练掌握动态规划算法，那么就可以轻松的找到解题方案。&lt;/li&gt;
&lt;li&gt;如果没有想到使用动态规划，通过分析计算过程寻找规律同样可以找到解法。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>iOS截屏防护方案</title><link>https://blog.lab-lawliet.app/posts/ios-04-ios-2acb36f8c00f746e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-04-ios-2acb36f8c00f746e/</guid><description>想必很多同学都有遇到过或者想过怎么能防止系统截图，但通过大量的搜索并没有找到具体的实现方案。</description><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;想必很多同学都有遇到过或者想过怎么能防止系统截图，但通过大量的搜索并没有找到具体的实现方案。&lt;/p&gt;
&lt;h2&gt;一、 常见方案&lt;/h2&gt;
&lt;h3&gt;1.1 系统通知&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;UIApplication.userDidTakeScreenshotNotification&lt;/code&gt; 这个系统通知是在完成截屏动作后，系统给到 &lt;code&gt;App&lt;/code&gt; 的，在收到这个通知后做处理，并不能达到防护的效果。&lt;/p&gt;
&lt;p&gt;微信的付款吗就是这样的实现，在截屏后提供一个警示内容。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/01b023db80cd3eb1.webp&quot; alt=&quot;微信&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.2 小红书&lt;/h3&gt;
&lt;p&gt;在使用小红书的截屏的时候顶部状态栏会添加一个小红书的水印如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0014c3184d57c807.webp&quot; alt=&quot;小红书&quot; /&gt;&lt;/p&gt;
&lt;p&gt;它是怎么实现的呢？我有两个猜测：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一种：水印一直在，刘海后面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为非刘海屏没有这个水印，所以估计是这样，但是通过切换到任务管理器状态并没有看到有那个水印。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ec591364e9f84057.webp&quot; alt=&quot;小红书&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二种：通过什么黑科技加上的？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不是第一种，难道真的是又什么黑科技？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;继续验证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我觉得肯定是第一种，觉得任务切换的状态下是不是那个水印被系统隐藏掉。想道还有 QuickTime 可以投屏来验证。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0f471e493320265a.webp&quot; alt=&quot;小红书&quot; /&gt;&lt;/p&gt;
&lt;p&gt;破案，果然是藏在刘海后面的，没有继续深入的价值了🤷‍♂️。&lt;/p&gt;
&lt;h3&gt;1.3 ScreenShieldKit&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/95f3d0a563a2d518.gif&quot; alt=&quot;ScreenShieldKit&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在寻找解决方案的时候发现了这样一个SDK &lt;a href=&quot;https://screenshieldkit.com&quot;&gt;ScreenShieldKit&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Contact us for more information and to receive a free evaluation SDK!&lt;/p&gt;
&lt;p&gt;这句话一看就是收钱的了，抱着能白嫖绝不三连的宗旨，先略过暂不深入研究。（开玩笑，如果你觉得我的方案不错，本文求赞评转+Star！）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.4 爱奇艺&lt;/h3&gt;
&lt;p&gt;爱奇艺使用的是截图后直接读取用户相册删除图片的方式进行的，不给权限，截图就不会被删除。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/missing-image.svg&quot; alt=&quot;爱奇艺&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.5 UITextField&lt;/h3&gt;
&lt;p&gt;当我们将一个 &lt;code&gt;UITextField&lt;/code&gt; 的 &lt;code&gt;isSecureTextEntry&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt; 的时候，会隐去输入的文案用 &lt;code&gt;圆点&lt;/code&gt; 替代。并且在进行录屏或者截屏的时候都会被系统隐去。&lt;/p&gt;
&lt;p&gt;下面用我的个人项目 &lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;梦见账本&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如下图我正在用 &lt;code&gt;QuickTime&lt;/code&gt; 进行录屏：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1d8927c740547cf1.webp&quot; alt=&quot;UITextField&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么我们是否可以使用这种特性呢？&lt;/p&gt;
&lt;h2&gt;二、 RyukieSwifty/ScreenShield&lt;/h2&gt;
&lt;p&gt;基于 &lt;code&gt;UITextField&lt;/code&gt; 的效果我实现了一个 &lt;code&gt;ScreenShieldView&lt;/code&gt; 可以很方便的进行使用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/Swifty&quot;&gt;GitHub: RyukieSwifty/ScreenShield&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;如果觉得不错的话，欢迎留个⭐️哦&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2.1 使用方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cocoapods 导入&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pod &apos;RyukieSwifty/ScreenShield&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e17fd814ebe14a64.gif&quot; alt=&quot;RyukieSwifty/ScreenShield&quot; /&gt;&lt;/p&gt;
&lt;p&gt;例如对整个控制器的 &lt;code&gt;View&lt;/code&gt; 进行截屏防护：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import UIKit
import RyukieSwifty

class TransactionAddViewController: UIViewController {
    // MARK: - Life
    override func loadView() {
        view = ScreenShieldView.create()
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 如何实现的&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4d378ac4d954974c.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;按照向里面添加子视图的方式验证具体的原因，最后发现这个效果是由一个私有类实现的 &lt;code&gt;_UITextLayoutCanvasView&lt;/code&gt; 。他本质上也是一个 &lt;code&gt;UIView&lt;/code&gt; ，所以理论上只要我们能够创建出来一个它，那么就可以将想要保护的内容添加进去。&lt;/p&gt;
&lt;p&gt;由于它是私有类，无法直接创建，并且如果直接通过字符串区创建也担心有审核风险，于是我通过下面的方式来创建。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private func makeSecureView() -&amp;gt; UIView? {
    let field = UITextField()
    field.isSecureTextEntry = true
    let fv = field.subviews.first
    fv?.subviews.forEach { $0.removeFromSuperview() }
    fv?.isUserInteractionEnabled = true
    return fv
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;当前手头没有更多的系统版本设备可以测试，已经验证的 &lt;code&gt;13.7 ～ 15.3.1&lt;/code&gt; 是没问题的。&lt;/p&gt;
&lt;p&gt;不排除会存在某些版本上 &lt;code&gt;UITextField&lt;/code&gt; 的结构发生了调整导致失效的问题，如果有，欢迎在 &lt;a href=&quot;https://github.com/RyukieSama/Swifty&quot;&gt;GitHub&lt;/a&gt; 提 &lt;code&gt;issue&lt;/code&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;不知道各位有没有其他的实现方式，有的话欢迎一起交流。&lt;/p&gt;
&lt;p&gt;如果本文有帮到你，欢迎赞评转+Star！&lt;/p&gt;
</content:encoded></item><item><title>提高编译速度</title><link>https://blog.lab-lawliet.app/posts/ios-05-b9b750c482beabc5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-05-b9b750c482beabc5/</guid><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-提高编译速度&lt;/h1&gt;
&lt;h2&gt;forward declaration&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;@class ClassName&lt;/code&gt;而不是&lt;code&gt;#import ClassName.h&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;能大大提高编译速度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;组件库打包优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;对不经常变更的库做成&lt;code&gt;Framework&lt;/code&gt;或者&lt;code&gt;.a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这部分代码就不需要重新编译了&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常用头文件放到预编译文件里&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;XCode的pch文件是预编译文件，这里的内容在执行XCode build之前就已经被预编译，并且引入到每一个.m文件里了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Debug模式下不生成dsym文件&lt;/h2&gt;
&lt;h2&gt;开启编译当前架构版本&lt;/h2&gt;
&lt;h2&gt;Debug模式下关闭编译优化&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdn.net/20161206231413542?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSGVsbG9fSHdj/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://blog.csdn.net/Hello_Hwc/article/details/53557308&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>图片格式-WebP</title><link>https://blog.lab-lawliet.app/posts/ios-06-webp-b9ab75b3e2c3bdcc/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-06-webp-b9ab75b3e2c3bdcc/</guid><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;Google出品，Android原生支持，iOS需要单独适配如SD和Kinfisher&lt;/li&gt;
&lt;li&gt;基于VP8编码&lt;/li&gt;
&lt;li&gt;比JPEG小40+&lt;/li&gt;
&lt;li&gt;有损 无损都有&lt;/li&gt;
&lt;li&gt;PNG转
&lt;ul&gt;
&lt;li&gt;PNG 转 WebP 的压缩率要高于 PNG 原图压缩率，同样支持有损与无损压缩&lt;/li&gt;
&lt;li&gt;转换后的 WebP 体积大幅减少，图片质量也得到保障（同时肉眼几乎无法看出差异）&lt;/li&gt;
&lt;li&gt;转换后的 WebP 支持 Alpha 透明和 24-bit 颜色数，不存在 PNG8 色彩不够丰富和在浏览器中可能会出现毛边的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JPEG转效果更佳&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>App启动速度优化</title><link>https://blog.lab-lawliet.app/posts/ios-07-app-97312a97f90e807e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-07-app-97312a97f90e807e/</guid><description>Image</description><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一：启动时间&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;启动时间 = main()之前的时间 + main()之后的时间&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.1 如何衡量时间&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;通过添加环境变量可以打印出APP的启动时间分析
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Edit scheme&lt;/code&gt; -&amp;gt; &lt;code&gt;Run&lt;/code&gt; -&amp;gt; &lt;code&gt;Arguments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DYLD_PRINT_STATISTICS&lt;/code&gt; 设置为 &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果需要更详细的信息，那就将 &lt;code&gt;DYLD_PRINT_STATISTICS_DETAILS&lt;/code&gt; 设置为 &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 作为开发者我们可以介入的地方有哪些？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5f4f6ee6a6497ca4.webp&quot; alt=&quot;Image&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二: main()之前&lt;/h2&gt;
&lt;h3&gt;2.1 dyld&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;iOS&lt;/code&gt;的动态链接器，可以用来装载&lt;code&gt;Mach-O&lt;/code&gt;文件（可执行文件、动态库等）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启动APP时，&lt;code&gt;dyld&lt;/code&gt;所做的事情有
&lt;ul&gt;
&lt;li&gt;装载APP的可执行文件，同时会递归加载所有依赖的动态库&lt;/li&gt;
&lt;li&gt;当&lt;code&gt;dyld&lt;/code&gt;把可执行文件、动态库都装载完毕后，会通知&lt;code&gt;Runtime&lt;/code&gt;进行下一步的处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 runtime&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;启动APP时，&lt;code&gt;runtime&lt;/code&gt;所做的事情有
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;map_images&lt;/code&gt;进行可执行文件(&lt;code&gt;.o&lt;/code&gt;)内容的解析和处理&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;load_images&lt;/code&gt;中调用&lt;code&gt;call_load_methods&lt;/code&gt;，调用所有&lt;code&gt;Class&lt;/code&gt;和&lt;code&gt;Category&lt;/code&gt;的&lt;code&gt;+load&lt;/code&gt;方法&lt;/li&gt;
&lt;li&gt;进行各种&lt;code&gt;objc&lt;/code&gt;结构的初始化（注册&lt;code&gt;Objc类&lt;/code&gt; 、&lt;code&gt;初始化类对象&lt;/code&gt;等等）&lt;/li&gt;
&lt;li&gt;调用&lt;code&gt;C++静态初始化器&lt;/code&gt;和&lt;code&gt;__attribute__((constructor))&lt;/code&gt;修饰的函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到此为止，可执行文件和动态库中所有的符号(&lt;code&gt;Class&lt;/code&gt;，&lt;code&gt;Protocol&lt;/code&gt;，&lt;code&gt;Selector&lt;/code&gt;，&lt;code&gt;IMP&lt;/code&gt;，…)都已经按格式成功加载到内存中，被&lt;code&gt;runtime&lt;/code&gt;所管理&lt;/p&gt;
&lt;h3&gt;2.3 可优化点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;减少不必要的&lt;code&gt;framework&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;framework&lt;/code&gt;应当设为&lt;code&gt;optional&lt;/code&gt;和&lt;code&gt;required&lt;/code&gt;，如果该&lt;code&gt;framework&lt;/code&gt;在当前App支持的所有iOS系统版本都存在，那么就设为&lt;code&gt;required&lt;/code&gt;，否则就设为&lt;code&gt;optional&lt;/code&gt;，因为&lt;code&gt;optional&lt;/code&gt;会有些额外的检查&lt;/li&gt;
&lt;li&gt;合并或者删减一些OC类，关于清理项目中没用到的类，使用工具AppCode代码检查功能，查到当前项目中没有用到的类如&lt;/li&gt;
&lt;li&gt;删除废弃方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+load&lt;/code&gt;里少做事情，用&lt;code&gt;+initialize&lt;/code&gt;方法和&lt;code&gt;dispatch_once&lt;/code&gt;取代所有的&lt;code&gt;__attribute__((constructor))&lt;/code&gt;、&lt;code&gt;C++静态构造器&lt;/code&gt;、&lt;code&gt;ObjC&lt;/code&gt;的&lt;code&gt;+load&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;尽量不用C++虚函数(创建虚函数有开销)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Swift&lt;/code&gt;尽量使用&lt;code&gt;Struct&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: main()之后&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;主要是App本身相关的一些初始化服务&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;不适用xib/sb,使用代码加载首页视图&lt;/li&gt;
&lt;li&gt;NSUserDefaults实际上是在Library文件夹下会生产一个plist文件，如果文件太大的话一次能读取到内存中可能很耗时，这个影响需要评估，如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)&lt;/li&gt;
&lt;li&gt;每次用NSLog方式打印会隐式的创建一个Calendar，因此需要删减启动时各业务方打的log，或者仅仅针对内测版输出log&lt;/li&gt;
&lt;li&gt;梳理应用启动时发送的所有网络请求，是否可以统一在异步线程请求&lt;/li&gt;
&lt;li&gt;首页相关控制器的&lt;code&gt;viewDidLoad&lt;/code&gt;/&lt;code&gt;viewWillAppear&lt;/code&gt;里少做操作或者延迟做&lt;/li&gt;
&lt;li&gt;少做/不做 I/O 操作,或者异步做&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://techblog.toutiao.com/2017/01/17/iosspeed/&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>IDL自动化埋点</title><link>https://blog.lab-lawliet.app/posts/ios-08-idl-119b778b71e19fd6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-08-idl-119b778b71e19fd6/</guid><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;IDL自动化生成埋点模型&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;使用定义好的&lt;code&gt;IDL&lt;/code&gt;语言规范生成&lt;code&gt;JSON&lt;/code&gt;上传至指定仓库&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Jenkins&lt;/code&gt;脚本转化生成代码，上传至指定仓库&lt;/li&gt;
&lt;li&gt;项目同步更新相关仓库，在需要埋点的地方埋点时使用相关&lt;code&gt;事件模型&lt;/code&gt;即可&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>渲染原理及优化</title><link>https://blog.lab-lawliet.app/posts/ios-09-e03fdebcf01101b6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-09-e03fdebcf01101b6/</guid><description>界面优化-界面渲染流程</description><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;将图形渲染到屏幕是&lt;code&gt;CPU&lt;/code&gt;和&lt;code&gt;GPU&lt;/code&gt;协同完成的一个过程.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、 职责与分工&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CPU&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算视图Frame&lt;/li&gt;
&lt;li&gt;图片解码&lt;/li&gt;
&lt;li&gt;将需要绘制的纹理图片通过数据总线传递给&lt;code&gt;GPU&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;GPU&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;纹理混合&lt;/li&gt;
&lt;li&gt;顶点变换与计算&lt;/li&gt;
&lt;li&gt;像素点的填充计算&lt;/li&gt;
&lt;li&gt;渲染到缓冲区&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;时钟信号&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂直同步信号&lt;code&gt;V-Sync&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;水平同步信号&lt;code&gt;H-Sync&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;iOS&lt;/code&gt;设备双缓冲机制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显示系统通常会引入两个帧缓冲区&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c94a0f03331f672e.webp&quot; alt=&quot;界面优化-界面渲染流程&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 双缓冲机制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;有前帧缓存、后帧缓存，即GPU会预先渲染好一帧放入一个缓冲区内（前帧缓存），让视频控制器读取&lt;/li&gt;
&lt;li&gt;当下一帧渲染好后，GPU会直接把视频控制器的指针指向第二个缓冲器（后帧缓存）&lt;/li&gt;
&lt;li&gt;当你视频控制器已经读完一帧，准备读下一帧的时候
&lt;ul&gt;
&lt;li&gt;GPU会等待显示器的VSync信号发出后，前帧缓存和后帧缓存会瞬间切换&lt;/li&gt;
&lt;li&gt;后帧缓存会变成新的前帧缓存，同时旧的前帧缓存会变成新的后帧缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c4f9ac58118972aa.webp&quot; alt=&quot;界面优化-双缓存原理&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 卡顿产生的原因&lt;/h3&gt;
&lt;p&gt;如果在一个&lt;code&gt;VSync&lt;/code&gt;时间内&lt;code&gt;CPU&lt;/code&gt;或&lt;code&gt;GPU&lt;/code&gt;没有将处理完成的数据提交到帧缓冲区，那一帧就会被丢弃&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;按照&lt;code&gt;60fps&lt;/code&gt;刷新率，每隔16ms就会又一次&lt;code&gt;VSync&lt;/code&gt;信号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b73a8d0210c7f8ab.webp&quot; alt=&quot;VSync与丢帧&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 卡顿优化-CPU&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;尽量用轻量级的对象，比如用不到事件处理的地方，可以考虑使用CALayer取代UIView&lt;/li&gt;
&lt;li&gt;不要频繁地调用UIView的相关属性，比如frame、bounds、transform等属性，尽量减少不必要的修改&lt;/li&gt;
&lt;li&gt;尽量提前计算好布局，在有需要时一次性调整对应的属性，不要多次修改属性&lt;/li&gt;
&lt;li&gt;Autolayout会比直接设置frame消耗更多的CPU资源&lt;/li&gt;
&lt;li&gt;图片的size最好刚好跟UIImageView的size保持一致&lt;/li&gt;
&lt;li&gt;控制一下线程的最大并发数量&lt;/li&gt;
&lt;li&gt;尽量把耗时的操作放到子线程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 卡顿优化-GPU&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;尽量避免短时间内大量图片的显示，尽可能将多张图片合成一张进行显示&lt;/li&gt;
&lt;li&gt;尽量减少视图数量和层次&lt;/li&gt;
&lt;li&gt;减少透明的视图（alpha&amp;lt;1），不透明的就设置opaque为YES&lt;/li&gt;
&lt;li&gt;尽量避免出现离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 卡顿检测&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;YYFPSLabel
&lt;ul&gt;
&lt;li&gt;基于 &lt;code&gt;DisplayLink&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;每次++，少了就掉帧了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Runloop
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;主线程&lt;/code&gt; 的 &lt;code&gt;Runloop&lt;/code&gt; 中创建一个 &lt;code&gt;Observer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;监听事务&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;子线程&lt;/code&gt; 开启计时&lt;/li&gt;
&lt;li&gt;两次事务的间隔超过一定的时间就标识有事务比较耗时&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;微信的 &lt;code&gt;Matrix&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;也是基于 &lt;code&gt;Runloop&lt;/code&gt; 的，更全面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;滴滴 Doraemon
&lt;ul&gt;
&lt;li&gt;主线程发不断发信号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 图片加载的过程&lt;/h2&gt;
&lt;h3&gt;3.1 imageNamed&lt;/h3&gt;
&lt;p&gt;使用后会有缓存，适合重复使用的场景&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When searching the asset catalog, this method prefers an asset containing a symbol image over an asset with the same name containing a bitmap image. Because symbol images are supported only in iOS 13 and later, you may include both types of assets in the same asset catalog. The system automatically falls back to the bitmap image on earlier versions of iOS. You cannot use this method to load system symbol images; use the init(systemName:) method instead.&lt;/p&gt;
&lt;p&gt;This method checks the system caches for an image object with the specified name and returns the variant of that image that is best suited for the main screen. If a matching image object is not already in the cache, this method creates the image from an available asset catalog or loads it from disk. The system may purge cached image data at any time to free up memory. Purging occurs only for images that are in the cache but are not currently being used.&lt;/p&gt;
&lt;p&gt;In iOS 9 and later, this method is thread safe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.2 imageWithContentsOfFile&lt;/h3&gt;
&lt;p&gt;不会创建缓存，适合于低频使用的资源图&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This method does not cache the image object.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.3 具体过程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;将&lt;code&gt;UIImage&lt;/code&gt;赋值给&lt;code&gt;UIImageView&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;一个隐式的&lt;code&gt;CATransaction&lt;/code&gt;捕捉到了&lt;code&gt;UIImageView&lt;/code&gt;图层树的变化&lt;/li&gt;
&lt;li&gt;主线程下个&lt;code&gt;RunLoop&lt;/code&gt;到来时，&lt;code&gt;CoreAnimation&lt;/code&gt;提交了这个&lt;code&gt;Transaction&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;这个过程可能会对图片进行&lt;code&gt;Copy&lt;/code&gt;操作，而受图片&lt;code&gt;是否字节对齐&lt;/code&gt;等因素的影响，这个&lt;code&gt;Copy&lt;/code&gt;操作可能包含一下部分或全部操作：&lt;/li&gt;
&lt;li&gt;分配内存缓冲区，用于&lt;code&gt;管理文件IO&lt;/code&gt;和&lt;code&gt;解压缩操作&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将图片数据从磁盘读取到内存&lt;/li&gt;
&lt;li&gt;将图片数据解码成未压缩的位图格式，这是个非常耗时的&lt;code&gt;CPU&lt;/code&gt;操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CPU&lt;/code&gt;计算好图片&lt;code&gt;Frame&lt;/code&gt;，对图片&lt;code&gt;解压&lt;/code&gt;后，交给&lt;code&gt;GPU&lt;/code&gt;进行&lt;code&gt;渲染&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CoreAnimation&lt;/code&gt;的&lt;code&gt;CALayer&lt;/code&gt;用位图数据渲染&lt;code&gt;UIImageView&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;渲染流程
&lt;ul&gt;
&lt;li&gt;GPU获取图片坐标&lt;/li&gt;
&lt;li&gt;将坐标交给&lt;a href=&quot;/posts/opengl-01-0217a0e7e9718a0b/&quot;&gt;顶点着色器&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;顶点计算&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将图片光栅化
&lt;ul&gt;
&lt;li&gt;获取图片对应屏幕上的像素点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/opengl-01-0217a0e7e9718a0b/&quot;&gt;片元着色器&lt;/a&gt;计算
&lt;ul&gt;
&lt;li&gt;计算每个像素点最终显示的颜色值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;从帧缓存区中渲染到屏幕上&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;四、 为什么需要解压缩&lt;/h2&gt;
&lt;h3&gt;4.1 位图 JPG PNG&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位图就是一个像素数组，包含了每个像素点的信息。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JPG&lt;/code&gt;和&lt;code&gt;PNG&lt;/code&gt;都是压缩的位图
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JPG&lt;/code&gt;有损压缩&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PNG&lt;/code&gt;无损压缩，支持&lt;code&gt;alpha&lt;/code&gt;通道&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 获取原始图片数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UIImage *image = [UIImage imageNamed:@&quot;text.png&quot;];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、 界面优化&lt;/h2&gt;
&lt;h3&gt;5.1 预排版&lt;/h3&gt;
&lt;p&gt;提前进行相关计算，例如在 &lt;code&gt;Cell&lt;/code&gt; 复用之前就通过数据模型对相关 &lt;code&gt;Layout&lt;/code&gt; 信息进行计算。否则每次复用都进行计算就很浪费性能了。&lt;/p&gt;
&lt;h3&gt;5.2 预解码&lt;/h3&gt;
&lt;p&gt;系统默认会在主线程对图片进行解压，但该操作是个非常耗时的操作。所以现在就出现了对图片进行异步解压的方案。核心方法是&lt;code&gt;CGBitmapContextCreate&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;SDWebImage&lt;/code&gt;可以在&lt;code&gt;sd_decompressedImageWithImage&lt;/code&gt;处断点调试过程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面是我在我的个人项目 &lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;梦见账本&lt;/a&gt; 中的账户封面设置中用做的优化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public struct SwiftyImageLoader {
    public static let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
    
    public static func imageRefContainsAlpha(imageRef: CGImage) -&amp;gt; Bool {
        switch imageRef.alphaInfo {
        case .none, .noneSkipFirst, .noneSkipLast:
            return false
        default:
            return true
        }
    }
}

extension UIImageView {
    func asyncSet(newImage: UIImage?) {
        guard let image = newImage else {
            self.image = nil
            return
        }
        
        DispatchQueue.global().async { [weak self] in
            guard let imageRef = image.cgImage else {
                self?.setImageInMainQueue(image: image)
                return
            }

    //        let bytesPerPixel: Int = 4
            let bitsPerComponent: Int = 8
            
            // device color space
            let colorspaceRef = SwiftyImageLoader.colorSpace
            let hasAlpha = SwiftyImageLoader.imageRefContainsAlpha(imageRef: imageRef)
            
            // iOS display alpha info (BRGA8888/BGRX8888)
            // 位移枚举可以这样写
            let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: hasAlpha
                                                            ? CGImageAlphaInfo.premultipliedFirst.rawValue
                                                            : CGImageAlphaInfo.noneSkipFirst.rawValue)
                .union(.byteOrder32Little)
            
            let width = imageRef.width
            let height = imageRef.height
            
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
            
            guard
                let context: CGContext = CGContext.init(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: 0, space: colorspaceRef, bitmapInfo: bitmapInfo.rawValue)
            else {
                self?.setImageInMainQueue(image: image)
                return
            }
            
            // Draw the image into the context and retrieve the new bitmap image without alpha
    //        CGContextDrawImage
            context.draw(imageRef, in: CGRect(origin: .zero, size: CGSize(width: width, height: height)))
            
            guard
                let imageRefWithoutAlpha = context.makeImage()
            else {
                self?.setImageInMainQueue(image: image)
                return
            }
            let imageWithoutAlpha = UIImage(cgImage: imageRefWithoutAlpha, scale: image.scale, orientation: image.imageOrientation)
            self?.setImageInMainQueue(image: imageWithoutAlpha)
            //  CGContextRelease(context) Swift 会自动去管理了，不用手动去释放了
        }
    }
    
    private func setImageInMainQueue(image: UIImage) {
        DispatchQueue.main.async { [weak self] in
            self?.image = image
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 按需加载&lt;/h3&gt;
&lt;p&gt;一般我们卡顿主要是因为列表有大量的图片，我们可以选择在滚动的时候采用展示展位图，滚动停止的时候再展示相关图片的方式来提高性能。&lt;/p&gt;
&lt;p&gt;当然这样对于用户的浏览体验来说会有一些影响。&lt;/p&gt;
&lt;h3&gt;5.4 异步渲染&lt;/h3&gt;
&lt;h4&gt;UIView 和 CALayer 的关系&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d462557c02cab998.webp&quot; alt=&quot;UIView&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Graver&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://tech.meituan.com/2018/12/20/waimai-graver.html&quot;&gt;美团开源Graver框架：用“雕刻”诠释iOS端UI界面的高效渲染&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6844904082583322637#heading-6&quot;&gt;从 Graver 源码再来看异步渲染&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Graver 好像是借鉴了 YYAsyncLayer 有争议，后来取消开源了。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动</title><link>https://blog.lab-lawliet.app/posts/ios-10-metrics-diagnostics-8a5f94c9c9307299/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-10-metrics-diagnostics-8a5f94c9c9307299/</guid><description>之前参与了Apple「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动，之前只简单的总结了一下，现在整理一下。感觉有些东西还是挺实用的。</description><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;之前参与了Apple「利用 Metrics 和 Diagnostics 提高性能」网络研讨活动，之前只简单的总结了一下，现在整理一下。感觉有些东西还是挺实用的。&lt;/p&gt;
&lt;h2&gt;一：内存指标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;平均挂起内存和峰值内存&lt;/li&gt;
&lt;li&gt;内存管理会影响启动时间（冷启动热启动）&lt;/li&gt;
&lt;li&gt;通过这些指标了解内存的使用，可以：
&lt;ul&gt;
&lt;li&gt;发现难以重现的内存泄露问题&lt;/li&gt;
&lt;li&gt;减小挂起平均内存
&lt;ul&gt;
&lt;li&gt;减少挂起内存占用有助于减少应用在后台被释放的几率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二：MetricKit&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;搜集电池和性能指标和诊断的框架&lt;/li&gt;
&lt;li&gt;应用内接入后就可以通过API获取丰富的性能指标数据&lt;/li&gt;
&lt;li&gt;如果有必要可以将数据上报到自己的平台上，也可以作为开发过程中西能调优的工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三：Xcode Metrics Organizer&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;Xcode&lt;/code&gt;中&lt;code&gt;Window&lt;/code&gt;下拉菜单选择&lt;code&gt;Organizer&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/643b9a4b7aefb6ba.webp&quot; alt=&quot;MetricsXcode&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f9239bb2876e80d8.webp&quot; alt=&quot;OrganizerCrash&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里是可以选择版本的包含：AppStore（Release） 和 TestFlight（Beta）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reports
&lt;ul&gt;
&lt;li&gt;这里的数据也是系统自动收集的，前提是用户需要允许给Apple共享数据&lt;/li&gt;
&lt;li&gt;Crashes
&lt;ul&gt;
&lt;li&gt;实测是可以看到具体崩溃信息的，无须上传符号表
&lt;ul&gt;
&lt;li&gt;自己的小项目没有太多的崩溃可以看，后面找时间看下公司的项目的信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Disk Writes
&lt;ul&gt;
&lt;li&gt;没看到数据，可能是有异常才会上报吧&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Energy
&lt;ul&gt;
&lt;li&gt;没看到数据，可能是有异常才会上报吧&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Metrics
&lt;ul&gt;
&lt;li&gt;无须应用内集成&lt;code&gt;MetricsKit&lt;/code&gt;就可以看到相关数据&lt;/li&gt;
&lt;li&gt;前提是用户量达到一定的水平
&lt;ul&gt;
&lt;li&gt;具体是多少并没有提到，反正我自己的小App是没有的&lt;strong&gt;TAT&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;需要用户在设置系统的时候统一将数据共享给Apple，否则是没有该用户的数据的&lt;/li&gt;
&lt;li&gt;所有数据都是苹果系统内自动收集上报的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四：App Store Connect API&lt;/h2&gt;
&lt;p&gt;这是苹果提供的一套&lt;code&gt;REST API&lt;/code&gt;接口，可以直接通过请求获取&lt;code&gt;Metrics&lt;/code&gt;数据。&lt;/p&gt;
&lt;p&gt;但是需要和上面的满足一样的前提：达到一定用户量&lt;/p&gt;
&lt;h2&gt;五：参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;附本次主题参考资料：（会后Apple邮件提供的）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/news/?id=q8jd4xub&quot;&gt;mprove performance and battery life in your app&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/417/&quot;&gt;Improving Battery Life and Performance&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/419/&quot;&gt;Optimizing Storage in Your App&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/metrickit&quot;&gt;MetricKit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/xcode/improving_your_app_s_performance&quot;&gt;Improving Your App&apos;s Performance&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>离屏渲染</title><link>https://blog.lab-lawliet.app/posts/ios-11-7ea2b738ab06c737/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-11-7ea2b738ab06c737/</guid><description>App -&gt; OffscreenBuffer -&gt; FrameBuffer</description><pubDate>Fri, 18 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS离屏渲染&lt;/h1&gt;
&lt;h2&gt;一: 什么是离屏渲染&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果要爱屏幕上显示内容,我们需要一块至少和屏幕数据量一样大的&lt;code&gt;FrameBuffer&lt;/code&gt;作为像素数据的缓存区域,也是GPU储存渲染结果的地方.
如果有时面临一些限制,无法把渲染结果直接写入&lt;code&gt;FrameBuffer&lt;/code&gt;而是存在别的区域之后再写入&lt;code&gt;FrameBuffer&lt;/code&gt;这个过程就是&lt;code&gt;离屏渲染&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;App -&amp;gt; OffscreenBuffer -&amp;gt; FrameBuffer&lt;/p&gt;
&lt;h2&gt;二: CPU离屏渲染&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;UIView中实现了&lt;code&gt;drawRect&lt;/code&gt;就算内部没有实际代码,系统也会为这个view申请一片内存区域等待&lt;code&gt;CoreGraphics&lt;/code&gt;可能的绘画操作&lt;/li&gt;
&lt;li&gt;很多人对于开一块&lt;code&gt;CGContext&lt;/code&gt;来画图的操作也成为离屏渲染,因为数据是存入了CGContext而不是FrameBuffer&lt;/li&gt;
&lt;li&gt;进一步讲其实所有CPU进行的光栅化操作(文字渲染,图片解码)都无法直接绘制到有GPU管理的&lt;code&gt;FrameBuffer&lt;/code&gt;中,只能暂时放在另一块内存中&lt;/li&gt;
&lt;li&gt;但是苹果工程师说CPU渲染并非真正意义的离屏渲染
&lt;ul&gt;
&lt;li&gt;https://lobste.rs/s/ckm4uw/performance_minded_take_on_ios_design#c_itdkfh&lt;/li&gt;
&lt;li&gt;实现DrawRect打开Xcode调试的“Color offscreen rendered yellow”开关,该区域不会变黄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: GPU渲染&lt;/h2&gt;
&lt;h3&gt;画家算法&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;主要渲染操作都是由&lt;code&gt;CoreAnimation&lt;/code&gt;和&lt;code&gt;Render Server&lt;/code&gt;模块通过调用显卡驱动提供的接口OpenGL/Metal来执行的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;对于每一层Layer,&lt;code&gt;Render Server&lt;/code&gt;会按照深度排序依次输出到FrameBuffer&lt;/li&gt;
&lt;li&gt;后一层覆盖前一层,达到最终效果&lt;/li&gt;
&lt;li&gt;此过程不可逆
&lt;ul&gt;
&lt;li&gt;遮住的部分数据永久丢失无法修改&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;想要突破这个限制
&lt;ul&gt;
&lt;li&gt;在FrameBuffer外开辟一块空间&lt;/li&gt;
&lt;li&gt;把待处理的Layer画上去&lt;/li&gt;
&lt;li&gt;进行修改后再写回FrameBuffer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GPU离屏渲染&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;上面开辟空间渲染,真个过程就算离屏渲染&lt;/li&gt;
&lt;li&gt;对于每层Layer如果能通过单次遍历即可完成渲染的话效率最高
&lt;ul&gt;
&lt;li&gt;否则只能开辟离屏Buffer来完成一些复杂操作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: 常见离屏渲染场景&lt;/h2&gt;
&lt;h3&gt;4.1 cornerRadius+clipsToBounds&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果只是设置cornerRadius,而不剪切内容,或者只裁掉矩形区域以外的内容,并不会触发离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 shadow&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;阴影的形状未必是矩形&lt;/li&gt;
&lt;li&gt;只能在Layer以上的内容全部渲染完成后才能知道形状是什么
&lt;ul&gt;
&lt;li&gt;这样只能离屏渲染,将上层内容渲染完成,知道了shadow的形状,将shadow添加到FrameBuffer&lt;/li&gt;
&lt;li&gt;再把内容回执上去&lt;/li&gt;
&lt;li&gt;如果事先告诉了CoreAnimation(通过shadowPath)阴影的几何形状的话就不需要依赖Layer了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3 group opacity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;两个带透明度的图层叠加&lt;/li&gt;
&lt;li&gt;叠加部分无法通过一次遍历即完成&lt;/li&gt;
&lt;li&gt;会触发离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.4 mask&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;作用在Layer所有子Layer之上的而且可能有透明度&lt;/li&gt;
&lt;li&gt;离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.5 UIBlurEffect&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;无法一次遍历完成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.6 allowsEdgeAntialiasing 抗锯齿&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;无法一次遍历完成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.7 UIButton &amp;amp; UIImageView&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UIButton&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;默认自带背景图层&lt;/li&gt;
&lt;li&gt;切圆角会有离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// clipsToBounds = YES 有
- (UIButton *)bgImageButton {
    if (!_imageButton) {
        _imageButton = [[UIButton alloc] init];
        [_imageButton setBackgroundImage:[UIImage imageNamed:@&quot;bg_image&quot;] forState:UIControlStateNormal];
        _imageButton.layer.cornerRadius = 15;
        _imageButton.contentMode = UIViewContentModeScaleAspectFill;
        _imageButton.clipsToBounds = YES;
    }
    return _imageButton;
}

// clipsToBounds = YES 有
- (UIButton *)imageButton {
    if (!_bgImageButton) {
        _bgImageButton = [[UIButton alloc] init];
        [_bgImageButton setImage:[UIImage imageNamed:@&quot;bg_image&quot;] forState:UIControlStateNormal];
        _bgImageButton.layer.cornerRadius = 15;
        _bgImageButton.contentMode = UIViewContentModeScaleAspectFill;
        _bgImageButton.clipsToBounds = YES;
    }
    return _bgImageButton;
}

- (UIButton *)colorButton {
    if (!_colorButton) {
        _colorButton = [[UIButton alloc] init];
        [_colorButton setBackgroundColor:[UIColor greenColor]];
        _colorButton.layer.cornerRadius = 15;
        _colorButton.clipsToBounds = YES;
        // 不加 title 就没有
        [_colorButton setTitle:@&quot;Ryukie&quot; forState:UIControlStateNormal];
    }
    return _colorButton;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UIImageView&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;默认无背景图层&lt;/li&gt;
&lt;li&gt;默认情况下直接图片切圆角不会触发离屏渲染&lt;/li&gt;
&lt;li&gt;如果内部加了子视图会离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 没有
- (UIImageView *)imageView {
    if (!_imageView) {
        _imageView = [[UIImageView alloc] init];
        _imageView.image = [UIImage imageNamed:@&quot;bg_image&quot;];
        _imageView.layer.cornerRadius = 15;
        _imageView.clipsToBounds = YES;
    }
    return _imageView;
}

// 有
- (UIImageView *)imageView {
    if (!_imageView) {
        _imageView = [[UIImageView alloc] init];
        _imageView.image = [UIImage imageNamed:@&quot;bg_image&quot;];
        _imageView.layer.cornerRadius = 15;
        _imageView.clipsToBounds = YES;
        
        UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
        redView.backgroundColor = [UIColor redColor];
        [_imageView addSubview:redView];
    }
    return _imageView;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五: GPU离屏渲染性能影响&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GPU操作是高度流水线化的.正常情况一直在向FrameBuffer中输出&lt;/li&gt;
&lt;li&gt;当需要开启另一块内存进行其他操作,要暂时停止向FrameBuffer中输出,进行相关操作&lt;/li&gt;
&lt;li&gt;完成后再向FrameBuffer中输出内容&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;TableView/CollectionView中滚动每一帧变化都会触发重绘
一旦发生离屏渲染,上面的切换操作将会发生很多次(60/s)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;六: 利用离屏渲染&lt;/h2&gt;
&lt;h3&gt;6.1 只离屏渲染一次&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CALayer的shouldRasterize属性
&lt;ul&gt;
&lt;li&gt;可以把Layer及其子Layer,还有圆角等内容保存在内存中&lt;/li&gt;
&lt;li&gt;下一帧可以服用,而不会继续触发离屏渲染&lt;/li&gt;
&lt;li&gt;注意
&lt;ul&gt;
&lt;li&gt;如果本来就不复杂,也没有圆角等,打开反而会触发一次不必要的离屏渲染&lt;/li&gt;
&lt;li&gt;缓存有限,不会超过屏幕总像素的2.5倍&lt;/li&gt;
&lt;li&gt;超过100ms没用就被释放&lt;/li&gt;
&lt;li&gt;静态内容,如果有resize或动画的话就失效了,需要重新离屏渲染
&lt;ul&gt;
&lt;li&gt;针对这种情况，Xcode提供了“Color Hits Green and Misses Red”的选项，帮助我们查看缓存的使用是否符合预期&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;除了用来优化离屏渲染还可以用于展示十分复杂的Layer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;七: 什么时候需要CPU渲染&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;渲染新能调优核心在于
&lt;ul&gt;
&lt;li&gt;平衡CPU&amp;amp;GPU的负载&lt;/li&gt;
&lt;li&gt;让他们各自做自己擅长的事情&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;文字(CoreText使用CoreGraphics渲染)图片(ImageIO)的渲染
&lt;ul&gt;
&lt;li&gt;GPU不擅长这些事情&lt;/li&gt;
&lt;li&gt;需要CPU处理完再交给GPU
&lt;ul&gt;
&lt;li&gt;例如:&lt;/li&gt;
&lt;li&gt;一个圆角实现
&lt;ul&gt;
&lt;li&gt;用CoreGraphics给图片加圆角-CPU完成&lt;/li&gt;
&lt;li&gt;这样就不用给容器设置cornerRadius避免可能发生的离屏渲染&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;注意点:
&lt;ul&gt;
&lt;li&gt;渲染不是CPU强项,调用CoreGraphics会消耗一定的计算时间,因此CPU渲染放在后台线程去完成,然后回到主线程将结果返回给CoreAnimation.但是多线程间数据同步会增加一定的复杂度
&lt;ul&gt;
&lt;li&gt;也是AsyncDisplayKit/Texture的主要思想&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU渲染速度慢,适合不复杂的元素
&lt;ul&gt;
&lt;li&gt;没有硬件加速的视频解码...酸爽啊&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;渲染结果bitmap数据量较大(一般为解码后的UIImage),需要及时释放&lt;/li&gt;
&lt;li&gt;选择用CPU渲染的话就没必要触发GPU的离屏渲染,否则会有两块相同内存&lt;/li&gt;
&lt;li&gt;善用工具进行检测&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;八: 优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用AsyncDisplayKit/Texture框架&lt;/li&gt;
&lt;li&gt;图片圆角
&lt;ul&gt;
&lt;li&gt;采用不适用容器裁剪的方式,用上面提到过的预处理图片的方式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;视屏圆角
&lt;ul&gt;
&lt;li&gt;创建四个弧形Layer盖住四个角&lt;/li&gt;
&lt;li&gt;图片也可以&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;View的圆形边框
&lt;ul&gt;
&lt;li&gt;如果没有backgroundColor可以使用cornerRadius来做&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;阴影
&lt;ul&gt;
&lt;li&gt;使用shadowPath&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;特殊形状View
&lt;ul&gt;
&lt;li&gt;使用layer mask并打开shouldRasterize来对渲染结果进行缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;模糊
&lt;ul&gt;
&lt;li&gt;不采用系统提供的UIVisualEffect，而是另外实现模糊效果（CIGaussianBlur），并手动管理渲染结果&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;九: 总结&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;CPU渲染虽然也是“离屏”，但是通常提到的离屏渲染是发生在GPU
如果一个layer无法在一次遍历就完成绘制，那么就不得不触发离屏渲染
离屏渲染的开销主要在与frame buffer与离屏buffer之间的上下文切换。如果无法避免，也可以通过有效利用shouldRasterize，减少触发的次数
CPU和GPU是相互扶持的关系。CPU渲染效率不高，但是较为通用灵活；GPU擅长并行计算，但也有捉襟见肘之时，此时CPU可以适当给与帮助&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://medium.com/@jasonyuh/关于离屏渲染的深入研究-e776f56b3e60
https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>优先队列</title><link>https://blog.lab-lawliet.app/posts/article-08-6706e452c5688f15/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-08-6706e452c5688f15/</guid><description>如果有这样一个数据流 1，2，3，4，5，6，7，8 我们需要按照一定的优先级找到满足条件（最大或者最小）的元素，并且每次取出最高优先级的元素后，内部元素依旧能够按照既定优先级保证最高优先级的元素在顶端。</description><pubDate>Wed, 16 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 什么是优先队列&lt;/h2&gt;
&lt;p&gt;如果有这样一个数据流 &lt;code&gt;1，2，3，4，5，6，7，8&lt;/code&gt; 我们需要按照一定的优先级找到满足条件（最大或者最小）的元素，并且每次取出最高优先级的元素后，内部元素依旧能够按照既定优先级保证最高优先级的元素在顶端。&lt;/p&gt;
&lt;h2&gt;二、 最大堆 &amp;amp; 最小堆&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;堆&lt;/code&gt; 分为 &lt;code&gt;最大堆&lt;/code&gt; 和 &lt;code&gt;最小堆&lt;/code&gt; ，其实就是 &lt;code&gt;完全二叉树&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;最大堆
&lt;ul&gt;
&lt;li&gt;要求节点的元素都要大于等于其孩子&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;最小堆
&lt;ul&gt;
&lt;li&gt;要求节点元素都小于等于其左右孩子&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;两者对左右孩子的大小关系不做任何要求&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;为什么用 &lt;code&gt;二叉树&lt;/code&gt; 来实现呢？&lt;/h3&gt;
&lt;p&gt;如果使用数组复杂度会更高&lt;/p&gt;
&lt;h2&gt;三、 插入元素&lt;/h2&gt;
&lt;p&gt;如下数据流，我们按照顺序插入数据，并且每次返回最大的元素，我们就可以使用优先队列（最大堆）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤1&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 1 和 5 ，如图二叉树结构&lt;/li&gt;
&lt;li&gt;叶子节点 5 &amp;gt; 1 ，不符合优先级规则，需要交换位置&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[5, 1]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3ea009b8380fa897.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 6
&lt;ul&gt;
&lt;li&gt;每次插入都插入到当前最深层从左往右的位置，或者创建下一层&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6 大于父节点 5，需要调整位置&lt;/li&gt;
&lt;li&gt;交换 5 6&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[6, 1, 5]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6ff2588434ad6298.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤3&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 2
&lt;ul&gt;
&lt;li&gt;这一层满了，下一层最左边&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2 大于父节点 1，需要调整&lt;/li&gt;
&lt;li&gt;交换 2 1&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[6, 2, 5, 1]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bf89f321761a9b79.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤4&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 7&lt;/li&gt;
&lt;li&gt;7 大于父节点 2，需要调整&lt;/li&gt;
&lt;li&gt;交换 7 2
&lt;ul&gt;
&lt;li&gt;7 的父节点变为 6，还需要调整&lt;/li&gt;
&lt;li&gt;交换 7 6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[7, 6, 5, 1, 2]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/579dcafaf428089b.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤5&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 4，父节点为 5，无需调整&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[7, 6, 5, 1, 2, 4]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/83181e7751c69b0d.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤6&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 3，父节点为 5，无需调整&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[7, 6, 5, 1, 2, 4, 3]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3cd412b58e8d8b33.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤7&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入 8
&lt;ul&gt;
&lt;li&gt;下一层&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;大于父节点 1，交换&lt;/li&gt;
&lt;li&gt;大于父节点 6，交换&lt;/li&gt;
&lt;li&gt;大于父节点 7，交换&lt;/li&gt;
&lt;li&gt;等价数组为
&lt;ul&gt;
&lt;li&gt;[8, 7, 5, 6, 2, 4, 3, 1]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c84ba86e6c0339a7.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;插入代码实现&lt;/h3&gt;
&lt;h4&gt;基础数据结构&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public struct SwiftPriorityQueue&amp;lt;Element&amp;gt; {
    private let _hasHigherPriority: (Element, Element) -&amp;gt; Bool
    private let _isEqual: (Element, Element) -&amp;gt; Bool

    private var _elements = [Element]()

    public init(hasHigherPriority: @escaping (Element, Element) -&amp;gt; Bool, isEqual: @escaping (Element, Element) -&amp;gt; Bool) {
        _hasHigherPriority = hasHigherPriority
        _isEqual = isEqual
    }

    public func peek() -&amp;gt; Element? {
        return _elements.first
    }
    
    public func array() -&amp;gt; [Element] {
        return _elements
    }

    public var isEmpty: Bool {
        return _elements.count == 0
    }
    
    public var count: Int {
        return _elements.count
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;插入逻辑&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 插入
public mutating func enqueue(_ element: Element) {
    _elements.append(element)
    bubbleToHigherPriority(_elements.count - 1)
}

// 爬升
private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
    precondition(initialUnbalancedIndex &amp;gt;= 0)
    precondition(initialUnbalancedIndex &amp;lt; _elements.count)

    var unbalancedIndex = initialUnbalancedIndex

    while unbalancedIndex &amp;gt; 0 {
        let parentIndex = (unbalancedIndex - 1) / 2
        guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }
        _elements.swapAt(unbalancedIndex, parentIndex)
        unbalancedIndex = parentIndex
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、 移除元素&lt;/h2&gt;
&lt;p&gt;我们还以上面完成的 &lt;code&gt;优先队列&lt;/code&gt; 数据结构为例，继续对移除元素的操作进行研究。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里我们计划移除 5 元素（当前下表为A）&lt;/li&gt;
&lt;li&gt;现将其和尾部元素交换&lt;/li&gt;
&lt;li&gt;移除尾部元素&lt;/li&gt;
&lt;li&gt;检查 A 处元素是否需要爬升
&lt;ul&gt;
&lt;li&gt;是
&lt;ul&gt;
&lt;li&gt;将其上浮
&lt;ul&gt;
&lt;li&gt;此处 A 元素为 1，小于父节点 8，无需处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;检查 A 处元素是否需要下沉
&lt;ul&gt;
&lt;li&gt;是
&lt;ul&gt;
&lt;li&gt;将其下降
&lt;ul&gt;
&lt;li&gt;此处 A 元素为 1，小于子节点，进行下沉处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/43534e3ad72816ec.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;移除与下沉逻辑实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;private mutating func removeAt(_ index: Int) {
    // 是否是最后一个元素
    let removingLast = index == _elements.count - 1
    if !removingLast {
        _elements.swapAt(index, _elements.count - 1)
    }

    _ = _elements.popLast()

    if !removingLast {
        // 检查并保持优先队列的元素
        bubbleToHigherPriority(index)
        bubbleToLowerPriority(index)
    }
}

private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
    precondition(initialUnbalancedIndex &amp;gt;= 0)
    precondition(initialUnbalancedIndex &amp;lt; _elements.count)

    var unbalancedIndex = initialUnbalancedIndex
    while true {
        // 根据二叉树的结构，当前节点左叶子节点的下标为 unbalancedIndex * 2 + 1
        let leftChildIndex = unbalancedIndex * 2 + 1
        // 根据二叉树的结构，当前节点右叶子节点的的下标为 unbalancedIndex * 2 + 2
        let rightChildIndex = unbalancedIndex * 2 + 2

        var highestPriorityIndex = unbalancedIndex

        // 左叶子更高
        if leftChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
            highestPriorityIndex = leftChildIndex
        }

        // 右叶子更高
        if rightChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
            highestPriorityIndex = rightChildIndex
        }

        guard highestPriorityIndex != unbalancedIndex else { 
            break // 不用调整优先级
        }

        // 交换元素
        _elements.swapAt(highestPriorityIndex, unbalancedIndex)

        // 保存更高优先级的下标，继续进行对比
        unbalancedIndex = highestPriorityIndex
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Swift 完整实现&lt;/h2&gt;
&lt;p&gt;下面的优先队列实现是参考 RxSwift 中的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public struct SwiftPriorityQueue&amp;lt;Element&amp;gt; {
    private let _hasHigherPriority: (Element, Element) -&amp;gt; Bool
    private let _isEqual: (Element, Element) -&amp;gt; Bool

    private var _elements = [Element]()

    public init(hasHigherPriority: @escaping (Element, Element) -&amp;gt; Bool, isEqual: @escaping (Element, Element) -&amp;gt; Bool) {
        _hasHigherPriority = hasHigherPriority
        _isEqual = isEqual
    }

    public mutating func enqueue(_ element: Element) {
        _elements.append(element)
        bubbleToHigherPriority(_elements.count - 1)
    }

    public func peek() -&amp;gt; Element? {
        return _elements.first
    }
    
    public func array() -&amp;gt; [Element] {
        return _elements
    }

    public var isEmpty: Bool {
        return _elements.count == 0
    }
    
    public var count: Int {
        return _elements.count
    }

    public mutating func dequeue() -&amp;gt; Element? {
        guard let front = peek() else {
            return nil
        }

        removeAt(0)

        return front
    }

    public mutating func remove(_ element: Element) {
        for i in 0 ..&amp;lt; _elements.count {
            if _isEqual(_elements[i], element) {
                removeAt(i)
                return
            }
        }
    }

    private mutating func removeAt(_ index: Int) {
        let removingLast = index == _elements.count - 1
        if !removingLast {
            _elements.swapAt(index, _elements.count - 1)
        }

        _ = _elements.popLast()

        if !removingLast {
            bubbleToHigherPriority(index)
            bubbleToLowerPriority(index)
        }
    }

    private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex

        while unbalancedIndex &amp;gt; 0 {
            let parentIndex = (unbalancedIndex - 1) / 2
            guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }
            _elements.swapAt(unbalancedIndex, parentIndex)
            unbalancedIndex = parentIndex
        }
    }

    private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex
        while true {
            let leftChildIndex = unbalancedIndex * 2 + 1
            let rightChildIndex = unbalancedIndex * 2 + 2

            var highestPriorityIndex = unbalancedIndex

            if leftChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = leftChildIndex
            }

            if rightChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = rightChildIndex
            }

            guard highestPriorityIndex != unbalancedIndex else { break }
            _elements.swapAt(highestPriorityIndex, unbalancedIndex)

            unbalancedIndex = highestPriorityIndex
        }
    }
}

extension SwiftPriorityQueue : CustomDebugStringConvertible {
    public var debugDescription: String {
        return _elements.debugDescription
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>数据流中的中位数</title><link>https://blog.lab-lawliet.app/posts/article-41-13e0f3230ec884cd/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-41-13e0f3230ec884cd/</guid><description>如何得到一个数据流中的中位数？如果从数据流中读出奇数个数值，那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值，那么中位数就是所有数值排序之后中间两个数的平均值。</description><pubDate>Mon, 14 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;如何得到一个数据流中的中位数？如果从数据流中读出奇数个数值，那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值，那么中位数就是所有数值排序之后中间两个数的平均值。&lt;/p&gt;
&lt;p&gt;例如，&lt;/p&gt;
&lt;p&gt;[2,3,4] 的中位数是 3&lt;/p&gt;
&lt;p&gt;[2,3] 的中位数是 (2 + 3) / 2 = 2.5&lt;/p&gt;
&lt;p&gt;设计一个支持以下两种操作的数据结构：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;void addNum(int num)&lt;/code&gt; - 从数据流中添加一个整数到数据结构中。
&lt;code&gt;double findMedian()&lt;/code&gt; - 返回目前所有元素的中位数。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;p&gt;[&quot;MedianFinder&quot;,&quot;addNum&quot;,&quot;addNum&quot;,&quot;findMedian&quot;,&quot;addNum&quot;,&quot;findMedian&quot;]&lt;/p&gt;
&lt;p&gt;[[],[1],[2],[],[3],[]]&lt;/p&gt;
&lt;p&gt;输出：[null,null,null,1.50000,null,2.00000]&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;p&gt;[&quot;MedianFinder&quot;,&quot;addNum&quot;,&quot;findMedian&quot;,&quot;addNum&quot;,&quot;findMedian&quot;]&lt;/p&gt;
&lt;p&gt;[[],[2],[],[3],[]]&lt;/p&gt;
&lt;p&gt;输出：[null,null,2.00000,null,2.50000]&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;最多会对 &lt;code&gt;addNum&lt;/code&gt; 、 &lt;code&gt;findMedian&lt;/code&gt; 进行 &lt;code&gt;50000&lt;/code&gt; 次调用。&lt;/p&gt;
&lt;p&gt;注意：本题与主站 295 题相同：https://leetcode-cn.com/problems/find-median-from-data-stream/&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;大小堆&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class MedianFinder {
    
    var minStack = SwiftPriorityQueue&amp;lt;Int&amp;gt;(hasHigherPriority: { $0 &amp;gt; $1 }, isEqual: { $0 == $1 })
    var maxStack = SwiftPriorityQueue&amp;lt;Int&amp;gt;(hasHigherPriority: { $0 &amp;lt; $1 }, isEqual: { $0 == $1 })
    
    /** initialize your data structure here. */
    init() {

    }
    
    func addNum(_ num: Int) {
        if maxStack.count &amp;gt; minStack.count {
            minStack.enqueue(num)
        }
        else if minStack.count &amp;gt; maxStack.count {
            maxStack.enqueue(num)
        }
        else {
            if let minMax = minStack.array().first, minMax &amp;gt; num {
                minStack.enqueue(num)
            }
            else if let maxMin = maxStack.array().first, maxMin &amp;lt; num {
                maxStack.enqueue(num)
            }
            else {
                maxStack.enqueue(num)
            }
        }
        
        if let minMax = minStack.array().first, let maxMin = maxStack.array().first, minMax &amp;gt; maxMin {
            minStack.dequeue()
            minStack.enqueue(maxMin)
            
            maxStack.dequeue()
            maxStack.enqueue(minMax)
        }
        
//        print(&quot;大堆：\(maxStack)&quot;)
//        print(&quot;小堆：\(minStack)&quot;)
//        print(&quot;======&quot;)
    }

    func findMedian() -&amp;gt; Double {
        if minStack.count == maxStack.count, let a = minStack.peek(), let b = maxStack.peek() {
            return Double((a + b)) / 2.0
        }
        else if minStack.count &amp;gt; maxStack.count {
            return Double(minStack.peek() ?? 0)
        }
        else if maxStack.count &amp;gt; minStack.count {
            return Double(maxStack.peek() ?? 0)
        }
        return 0
    }
}

let obj = MedianFinder()
obj.addNum(-1)
obj.addNum(-2)
obj.addNum(-3)
obj.addNum(-4)
obj.addNum(-5)
obj.addNum(6)

let ret_2: Double = obj.findMedian()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7a507f7b976dc143.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Swift - 优先队列&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import Foundation

public struct SwiftPriorityQueue&amp;lt;Element&amp;gt; {
    private let _hasHigherPriority: (Element, Element) -&amp;gt; Bool
    private let _isEqual: (Element, Element) -&amp;gt; Bool

    private var _elements = [Element]()

    public init(hasHigherPriority: @escaping (Element, Element) -&amp;gt; Bool, isEqual: @escaping (Element, Element) -&amp;gt; Bool) {
        _hasHigherPriority = hasHigherPriority
        _isEqual = isEqual
    }

    public mutating func enqueue(_ element: Element) {
        _elements.append(element)
        bubbleToHigherPriority(_elements.count - 1)
    }

    public func peek() -&amp;gt; Element? {
        return _elements.first
    }
    
    public func array() -&amp;gt; [Element] {
        return _elements
    }

    public var isEmpty: Bool {
        return _elements.count == 0
    }
    
    public var count: Int {
        return _elements.count
    }

    public mutating func dequeue() -&amp;gt; Element? {
        guard let front = peek() else {
            return nil
        }

        removeAt(0)

        return front
    }

    public mutating func remove(_ element: Element) {
        for i in 0 ..&amp;lt; _elements.count {
            if _isEqual(_elements[i], element) {
                removeAt(i)
                return
            }
        }
    }

    private mutating func removeAt(_ index: Int) {
        let removingLast = index == _elements.count - 1
        if !removingLast {
            _elements.swapAt(index, _elements.count - 1)
        }

        _ = _elements.popLast()

        if !removingLast {
            bubbleToHigherPriority(index)
            bubbleToLowerPriority(index)
        }
    }

    private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex

        while unbalancedIndex &amp;gt; 0 {
            let parentIndex = (unbalancedIndex - 1) / 2
            guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }
            _elements.swapAt(unbalancedIndex, parentIndex)
            unbalancedIndex = parentIndex
        }
    }

    private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex
        while true {
            let leftChildIndex = unbalancedIndex * 2 + 1
            let rightChildIndex = unbalancedIndex * 2 + 2

            var highestPriorityIndex = unbalancedIndex

            if leftChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = leftChildIndex
            }

            if rightChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = rightChildIndex
            }

            guard highestPriorityIndex != unbalancedIndex else { break }
            _elements.swapAt(highestPriorityIndex, unbalancedIndex)

            unbalancedIndex = highestPriorityIndex
        }
    }
}

extension SwiftPriorityQueue : CustomDebugStringConvertible {
    public var debugDescription: String {
        return _elements.debugDescription
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>深度优先&amp;广度优先</title><link>https://blog.lab-lawliet.app/posts/article-05-119e960e63610792/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-05-119e960e63610792/</guid><pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate><content:encoded/></item><item><title>冒泡排序</title><link>https://blog.lab-lawliet.app/posts/article-06-26134f6bbe53c1b8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-06-26134f6bbe53c1b8/</guid><description>当发现在某一趟排序中没有发生交换，则说明排序已经完成，所以可以在此趟排序后结束排序，在每趟排序前设置flag，当其未发生变化时，终止算法</description><pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 常规冒泡&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func bubbleSortA(nums: [Int]) -&amp;gt; [Int] {
    var nums = nums
    let count = nums.count
    
    for i in 0..&amp;lt;count {
        print(&quot;&amp;lt;&quot;)
        for j in (i + 1)..&amp;lt;count {
            if nums[i] &amp;gt; nums[j] {
                nums.swapAt(i, j)
            }
            print(nums)
        }
        print(&quot;&amp;gt;\n&quot;)
    }
    
    return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 外层优化&lt;/h2&gt;
&lt;p&gt;当发现在某一趟排序中没有发生交换，则说明排序已经完成，所以可以在此趟排序后结束排序，在每趟排序前设置flag，当其未发生变化时，终止算法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func bubbleSortB(nums: [Int]) -&amp;gt; [Int] {
    print(&quot;bubbleSortB&quot;)
    
    var nums = nums
    let count = nums.count
    
    for i in 0..&amp;lt;count {
        var swapFlag = true
        print(&quot;&amp;lt;&quot;)
        for j in (i + 1)..&amp;lt;count {
            if nums[i] &amp;gt; nums[j] {
                nums.swapAt(i, j)
                swapFlag = false
            }
            print(nums)
        }
        print(&quot;&amp;gt;\n&quot;)
        if swapFlag {
            break
        }
    }
    
    return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 内层优化&lt;/h2&gt;
&lt;p&gt;在每趟排序中，右面的许多元素已经是有序的结果了，可算法还是进行后面数值的排序，因此进行第二次优化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func bubbleSortC(nums: [Int]) -&amp;gt; [Int] {
    print(&quot;bubbleSortC&quot;)
    
    var nums = nums
    var boundary = nums.count - 1
    var lastSwapIdx = 0
    
    for _ in 0..&amp;lt;nums.count {
        var swapFlag = true
        print(&quot;&amp;lt;&quot;)
        for j in 0..&amp;lt;boundary {
            if nums[j] &amp;gt; nums[j+1] {
                nums.swapAt(j, j+1)
                swapFlag = false
                lastSwapIdx = j
            }
            print(nums)
        }
        print(&quot;&amp;gt;\n&quot;)
        boundary = lastSwapIdx
        if swapFlag {
            break
        }
    }
    
    return nums
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、 对比&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let numbers: [Int] = [3, 5, 1, 11, 2, 4, 12]

bubbleSortA(nums: numbers)
bubbleSortB(nums: numbers)
bubbleSortC(nums: numbers)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;
[3, 5, 1, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
&amp;gt;

&amp;lt;
[1, 3, 5, 11, 2, 4, 12]
[1, 3, 5, 11, 2, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
&amp;gt;

&amp;lt;
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 5, 11, 4, 12]
[1, 2, 3, 4, 11, 5, 12]
[1, 2, 3, 4, 11, 5, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 4, 5, 11, 12]
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;

&amp;lt;
&amp;gt;

bubbleSortB
&amp;lt;
[3, 5, 1, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
[1, 5, 3, 11, 2, 4, 12]
&amp;gt;

&amp;lt;
[1, 3, 5, 11, 2, 4, 12]
[1, 3, 5, 11, 2, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 5, 11, 3, 4, 12]
&amp;gt;

&amp;lt;
[1, 2, 5, 11, 3, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
[1, 2, 3, 11, 5, 4, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 5, 11, 4, 12]
[1, 2, 3, 4, 11, 5, 12]
[1, 2, 3, 4, 11, 5, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 4, 5, 11, 12]
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;

bubbleSortC
&amp;lt;
[3, 5, 1, 11, 2, 4, 12]
[3, 1, 5, 11, 2, 4, 12]
[3, 1, 5, 11, 2, 4, 12]
[3, 1, 5, 2, 11, 4, 12]
[3, 1, 5, 2, 4, 11, 12]
[3, 1, 5, 2, 4, 11, 12]
&amp;gt;

&amp;lt;
[1, 3, 5, 2, 4, 11, 12]
[1, 3, 5, 2, 4, 11, 12]
[1, 3, 2, 5, 4, 11, 12]
[1, 3, 2, 4, 5, 11, 12]
&amp;gt;

&amp;lt;
[1, 3, 2, 4, 5, 11, 12]
[1, 2, 3, 4, 5, 11, 12]
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;

&amp;lt;
[1, 2, 3, 4, 5, 11, 12]
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>摩尔投票</title><link>https://blog.lab-lawliet.app/posts/article-07-fab537d3123735cd/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-07-fab537d3123735cd/</guid><description>摩尔投票 是一种用来解决 绝对众数 问题的算法。</description><pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;什么是摩尔投票&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;摩尔投票&lt;/code&gt; 是一种用来解决 &lt;code&gt;绝对众数&lt;/code&gt; 问题的算法。&lt;/p&gt;
&lt;p&gt;在一个集合中，如果一个元素的出现次数比其他所有元素的出现次数之和还多，那么就称它为这个集合的 &lt;code&gt;绝对众数&lt;/code&gt; 。等价地说， &lt;code&gt;绝对众数&lt;/code&gt; 的出现次数大于总元素数的一半。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;摩尔投票&lt;/code&gt; 的过程非常简单，让我们把找到绝对众数的过程想象成一次选举。我们维护一个 &lt;code&gt;m&lt;/code&gt; ，表示当前的候选人，然后维护一个 &lt;code&gt;cnt&lt;/code&gt; 。对于每一张新的选票，如果它投给了当前的候选人，就把 &lt;code&gt;cnt&lt;/code&gt; 加1，否则就把 &lt;code&gt;cnt&lt;/code&gt; 减1（也许你可以想象成，B的一个狂热支持者去把A的一个支持者揍了一顿，然后两个人都没法投票）。特别地，计票时如果 &lt;code&gt;cnt == 0&lt;/code&gt; ，我们可以认为目前谁都没有优势，所以新的选票投给谁，谁就成为新的候选人。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果我们要求的是 &lt;code&gt;众数&lt;/code&gt; ，这样的做法并不能给出正确答案，但如果要求的是 &lt;code&gt;绝对众数&lt;/code&gt; （且绝对众数确实存在），那么 &lt;code&gt;m&lt;/code&gt; 一定是正确的。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/posts/article-39-c2fb104353ba64b6/&quot;&gt;39.数组中出现次数超过一半的数字&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/387744743&quot;&gt;算法学习笔记(78): 摩尔投票&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>数组中出现次数超过一半的数字</title><link>https://blog.lab-lawliet.app/posts/article-39-c2fb104353ba64b6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-39-c2fb104353ba64b6/</guid><description>数组中有一个数字出现的次数超过数组长度的一半，请找出这个数字。</description><pubDate>Wed, 09 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;数组中有一个数字出现的次数超过数组长度的一半，请找出这个数字。&lt;/p&gt;
&lt;p&gt;你可以假设数组是非空的，并且给定的数组总是存在多数元素。&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;p&gt;输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;1 &amp;lt;= 数组长度 &amp;lt;= 50000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 169 题相同：https://leetcode-cn.com/problems/majority-element/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 题解&lt;/h2&gt;
&lt;h3&gt;2.1 哈希表统计法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func majorityElementHash(_ nums: [Int]) -&amp;gt; Int {
    var temp: [Int : Int] = [:]
    
    for item in nums {
        if let count = temp[item] {
            if count == (nums.count / 2) {
                return item
            }
            temp[item] = count + 1
        }
        else {
            temp[item] = 1
        }
    }
    
    return nums[0]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/af675356bfc779f3.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 数组排序法&lt;/h3&gt;
&lt;p&gt;将数组 nums 排序，数组中点的元素 一定为众数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func majorityElementArray(_ nums: [Int]) -&amp;gt; Int {
    nums.sorted()[nums.count / 2]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3b03cd9682218501.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 摩尔投票法&lt;/h3&gt;
&lt;p&gt;核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N) O(1) 为本题的最佳解法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func majorityElementMo(_ nums: [Int]) -&amp;gt; Int {
    var score = 0
    var x = 0
    for n in nums {
        if score == 0 {
            x = n
        }
        score += (x == n ? 1 : -1)
    }
    return x
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fe5ab110ae4e3cdd.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/387744743&quot;&gt;算法学习笔记(78): 摩尔投票&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>TabView使用</title><link>https://blog.lab-lawliet.app/posts/swiftui-07-tabview-281cf2bb217c3823/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-07-tabview-281cf2bb217c3823/</guid><description>https://www.hackingwithswift.com/books/ios-swiftui/creating-tabs-with-tabview-and-tabitem</description><pubDate>Mon, 24 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;https://www.hackingwithswift.com/books/ios-swiftui/creating-tabs-with-tabview-and-tabitem&lt;/p&gt;
&lt;h2&gt;切圆角&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.cornerRadius(14)
.clipShape(Capsule())
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;导航栏形式&lt;/h2&gt;
&lt;p&gt;默认大标题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.navigationBarTitleDisplayMode( xx )

/**
/// Inherit the display mode from the previous navigation item.
case automatic

/// Display the title within the standard bounds of the navigation bar.
case inline

/// Display a large title within an expanded navigation bar.
case large
*/

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Search</title><link>https://blog.lab-lawliet.app/posts/swiftui-09-search-4c94569be9577fdf/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-09-search-4c94569be9577fdf/</guid><description>Searchable modifier in SwiftUI: A UISearchController and UISearchBar equivalent</description><pubDate>Mon, 24 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://sarunw.com/posts/searchable-in-swiftui/#placement&quot;&gt;Searchable modifier in SwiftUI: A UISearchController and UISearchBar equivalent&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.appcoda.com.tw/swiftui-searchable/&quot;&gt;利用 Searchable 修飾符　輕鬆在 SwiftUI List 視圖中實作搜尋欄&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-a-search-bar-to-filter-your-data&quot;&gt;How to add a search bar to filter your data&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>SwifUI中使用WKWebView</title><link>https://blog.lab-lawliet.app/posts/swiftui-10-swifui-wkwebview-e22a7f5b196b637b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-10-swifui-wkwebview-e22a7f5b196b637b/</guid><description>Create a web browser with WebKit and SwiftUI</description><pubDate>Mon, 24 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;
import WebKit
import SwiftUI

struct WebView: UIViewRepresentable {
    typealias UIViewType = WKWebView

    let webView: WKWebView
    
    func makeUIView(context: Context) -&amp;gt; WKWebView {
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) { }
}

class WebViewNavigationDelegate: NSObject, WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -&amp;gt; Void) {
        // TODO
        decisionHandler(.allow)
    }
    
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -&amp;gt; Void) {
        // TODO
        decisionHandler(.allow)
    }
}

class WebViewModel: ObservableObject {
    let webView: WKWebView
    
    private let navigationDelegate: WebViewNavigationDelegate
    
    init() {
        let configuration = WKWebViewConfiguration()
        configuration.websiteDataStore = .nonPersistent()
        webView = WKWebView(frame: .zero, configuration: configuration)
        navigationDelegate = WebViewNavigationDelegate()
        
        webView.navigationDelegate = navigationDelegate
        setupBindings()
    }
    
    @Published var urlString: String = &quot;&quot;
    @Published var canGoBack: Bool = false
    @Published var canGoForward: Bool = false
    @Published var isLoading: Bool = false
    
    private func setupBindings() {
        webView.publisher(for: \.canGoBack)
            .assign(to: &amp;amp;$canGoBack)
        
        webView.publisher(for: \.canGoForward)
            .assign(to: &amp;amp;$canGoForward)
        
        webView.publisher(for: \.isLoading)
            .assign(to: &amp;amp;$isLoading)
        
    }
    
    func loadUrl() {
        guard let url = URL(string: urlString) else {
            return
        }
        
        webView.load(URLRequest(url: url))
    }
    
    func loadHTML(html: String) {
        webView.loadHTMLString(html, baseURL: nil)
    }
    
    func goForward() {
        webView.goForward()
    }
    
    func goBack() {
        webView.goBack()
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://benoitpasquier.com/create-webview-in-swiftui/&quot;&gt;Create a web browser with WebKit and SwiftUI&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>自动释放池与Runloop</title><link>https://blog.lab-lawliet.app/posts/ios-05-runloop-80344f7b9e77616d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-05-runloop-80344f7b9e77616d/</guid><description>为了探究 autoreleasepool 的原理，我们需要定位到源码。我们通过汇编断点试试</description><pubDate>Sun, 19 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 寻找线索&lt;/h2&gt;
&lt;p&gt;为了探究 autoreleasepool 的原理，我们需要定位到源码。我们通过汇编断点试试&lt;/p&gt;
&lt;p&gt;我们在 main 函数中添加断点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a7882d2f8875e5aa.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0b0b0464bb32c812.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们发现 &lt;code&gt;@autoreleasepool&lt;/code&gt; 关键字实际调用的是： &lt;code&gt;objc_autoreleasePoolPush&lt;/code&gt;。同时还发现一个与之匹配的 &lt;code&gt;objc_autoreleasePoolPop&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在源码中我们可以发现最终调用的是 &lt;code&gt;AutoreleasePoolPage&lt;/code&gt; 的方法&lt;/p&gt;
&lt;h2&gt;二、 AutoreleasePoolPage&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
   Autorelease pool implementation

   A thread&apos;s autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 
**********************************************************************/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一个线程的自动释放池是一个栈结构，存放的指针。每个指针要么是一个待释放的对象，要么是一个自动释放边界 &lt;code&gt;POOL_BOUNDARY&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;一个 &lt;code&gt;pool token&lt;/code&gt; 是一个指向该自动释放池的 &lt;code&gt;POOL_BOUNDARY&lt;/code&gt; 的指针。&lt;/p&gt;
&lt;p&gt;栈被绑定到一个双向链表。&lt;code&gt;Pages&lt;/code&gt; 会按需进行增删。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POOL_BOUNDARY&lt;/code&gt; 边界的作用是防止访问到不该访问的内存区域
&lt;ul&gt;
&lt;li&gt;只在第一页中存在哨兵对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这里使用双向链表的作用是， &lt;code&gt;Page&lt;/code&gt; 能够互相找到
&lt;ul&gt;
&lt;li&gt;每页的内存地址并不是连续的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分页结构图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/377c2c471faa6a76.webp&quot; alt=&quot;自动释放池-双向链表分页&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每页的容量，源码中有 &lt;code&gt;1 &amp;lt;&amp;lt; 12&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Push 流程&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b9da8446508a2f2c.webp&quot; alt=&quot;自动释放池-入栈逻辑&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Pop 流程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;先入后出
&lt;ul&gt;
&lt;li&gt;从栈顶开始通过内存平移进行处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;一个 &lt;code&gt;Page&lt;/code&gt; 空了就将这个 &lt;code&gt;Page&lt;/code&gt; 释放
&lt;ul&gt;
&lt;li&gt;并将上一个 &lt;code&gt;Page&lt;/code&gt; ， 即父节点 设为 &lt;code&gt;Hot&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 一个注意点&lt;/h2&gt;
&lt;p&gt;通过 &lt;code&gt;alloc&lt;/code&gt; &lt;code&gt;new&lt;/code&gt; &lt;code&gt;copy&lt;/code&gt; &lt;code&gt;mutablecopy&lt;/code&gt; &lt;code&gt;other&lt;/code&gt; 前缀的方法创建的对象不会加入自动释放池。&lt;/p&gt;
&lt;h2&gt;四、 Runloop&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/85a3f54f64e9a551.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.1 什么是RunLoop&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使程序一直运行并接受用户输入&lt;/li&gt;
&lt;li&gt;决定程序在何时应该处理那些&lt;code&gt;事件&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;消息队列&lt;/li&gt;
&lt;li&gt;节省&lt;code&gt;CPU&lt;/code&gt;时间&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 RunLoop 与 线程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;线程&lt;/code&gt; 和 &lt;code&gt;RunLoop&lt;/code&gt; 之间是一一对应的(可嵌套)，其关系是保存在一个全局的 &lt;code&gt;Dictionary&lt;/code&gt; 里&lt;/li&gt;
&lt;li&gt;线程结束时销毁, 或手动退出&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.3 结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set&amp;lt;Source/Observer/Timer&amp;gt;
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ee2a0c3e93d16871.webp&quot; alt=&quot;Runloop-结构&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Source&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;RunLoop&lt;/code&gt; 的数据源抽象类&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RunLoop运行必须要在加入NSTimer或Source0、Sourc1、Observer输入后运行否则会直接退出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Source0: 处理App内部事件, App自行管理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含了一个&lt;code&gt;函数指针&lt;/code&gt;，它并不能主动触发事件&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;CFRunLoopSourceSignal(source)&lt;/code&gt;，将这个 Source 标记为待处理&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;CFRunLoopWakeUp(runloop)&lt;/code&gt; 来唤醒 RunLoop，让其处理这个事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Source1: 由&lt;code&gt;RunLoop&lt;/code&gt;内核管理, &lt;code&gt;MachPort&lt;/code&gt;驱动(进程间通讯)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含了一个 &lt;code&gt;mach_port&lt;/code&gt; 和一个 &lt;code&gt;函数指针&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;被用于通过内核和其他线程相互发送消息&lt;/li&gt;
&lt;li&gt;如解锁/摇晃等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Timer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含了时长 和 &lt;code&gt;函数指针&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Observer&lt;/h4&gt;
&lt;p&gt;向外部报告 &lt;code&gt;RunLoop&lt;/code&gt; 状态的变化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL &amp;lt;&amp;lt; 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL &amp;lt;&amp;lt; 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL &amp;lt;&amp;lt; 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL &amp;lt;&amp;lt; 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL &amp;lt;&amp;lt; 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL &amp;lt;&amp;lt; 7), // 即将退出Loop
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Observer 与 AutoreleasePool(UIKit)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Observer&lt;/code&gt; 在&lt;code&gt;RunLoop&lt;/code&gt;两次&lt;code&gt;Sleep&lt;/code&gt;间 &lt;code&gt;Pop&lt;/code&gt;旧的并释放对象 &lt;code&gt;Push&lt;/code&gt;新的&lt;/p&gt;
&lt;h4&gt;Mode&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @&quot;kCFRunLoopDefaultMode&quot;
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在同一时间必须且只能在一个&lt;code&gt;Mode&lt;/code&gt;下运行&lt;/li&gt;
&lt;li&gt;切换&lt;code&gt;Mode&lt;/code&gt;必须退出&lt;code&gt;RunLoop&lt;/code&gt;,重启&lt;/li&gt;
&lt;li&gt;保证页面滑动流畅的关键&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;NSDefaultRunLoopMode // 默认状态 空闲状态
UITrackingRunLoopMode // 滑动
//私有Mode
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;当创建一个 &lt;code&gt;Timer&lt;/code&gt; 并加到 &lt;code&gt;DefaultMode&lt;/code&gt; 时，&lt;code&gt;Timer&lt;/code&gt; 会得到重复回调，但此时滑动一个&lt;code&gt;TableView&lt;/code&gt; 时，&lt;code&gt;RunLoop&lt;/code&gt; 会将 &lt;code&gt;mode&lt;/code&gt; 切换为 &lt;code&gt;TrackingRunLoopMode&lt;/code&gt;，这时 &lt;code&gt;Timer&lt;/code&gt; 就不会被回调，并且也不会影响到滑动操作。这两个 &lt;code&gt;Mode&lt;/code&gt; 都已经被标记为&lt;code&gt;Common&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;特殊&lt;code&gt;Mode&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSRunLoopCommonModes
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;有时你需要一个 &lt;code&gt;Timer&lt;/code&gt;，在两个 &lt;code&gt;Mode&lt;/code&gt; 中都能得到回调，一种办法就是将这个 &lt;code&gt;Timer&lt;/code&gt; 分别加入这两个 &lt;code&gt;Mode&lt;/code&gt;。还有一种方式，就是将 &lt;code&gt;Timer&lt;/code&gt; 加入到顶层的 &lt;code&gt;RunLoop&lt;/code&gt; 的 &lt;code&gt;commonModeItems&lt;/code&gt; 中。&lt;code&gt;commonModeItems&lt;/code&gt; 被 &lt;code&gt;RunLoop&lt;/code&gt; 自动更新到所有具有 &lt;code&gt;Common&lt;/code&gt; 的 &lt;code&gt;Mode&lt;/code&gt; 里去。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.4 Runloop 原理&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7924b426596efd1f.webp&quot; alt=&quot;Runloop原理&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、 AutoreleasePool 什么时候创建？什么时候销毁？&lt;/h2&gt;
&lt;p&gt;App启动后，系统在主线程RunLoop 里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一个 Observer 监视的事件 是 Entry(即将进入Loop)
&lt;ul&gt;
&lt;li&gt;其回调内会调用 _objc_autoreleasePoolPush() &lt;code&gt;创建自动释放池&lt;/code&gt;。其优先级最高，保证&lt;code&gt;创建&lt;/code&gt;释放池发生在其他所有回调之前。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第二个 Observer 监视了两个事件
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_BeforeWaiting(准备进入休眠)&lt;/code&gt; 时 _ 调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() &lt;code&gt;释放旧的池&lt;/code&gt; 并 &lt;code&gt;创建新池&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_Exit(即将退出Loop)&lt;/code&gt; 时 _ 调用 &lt;code&gt;_objc_autoreleasePoolPop()&lt;/code&gt; 来&lt;code&gt;释放&lt;/code&gt;自动释放池。&lt;/li&gt;
&lt;li&gt;这个 Observer 优先级最低，保证其&lt;code&gt;释放&lt;/code&gt;池子发生在其他所有回调之后。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在我们知道了&lt;code&gt;AutoreleasePool&lt;/code&gt;是在&lt;code&gt;RunLoop即将进入RunLoop&lt;/code&gt;和&lt;code&gt;准备进入休眠&lt;/code&gt;这两种状态的时候被&lt;code&gt;创建&lt;/code&gt;和&lt;code&gt;销毁&lt;/code&gt;的。&lt;/p&gt;
&lt;p&gt;所以AutoreleasePool的释放有如下两种情况。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一是Autorelease对象是在当前的runloop迭代结束时释放的，而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。&lt;/li&gt;
&lt;li&gt;二是手动调用AutoreleasePool的释放方法（drain方法）来销毁AutoreleasePool&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1&quot;&gt;Threading Programming Guide - Runloop&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Timer优化</title><link>https://blog.lab-lawliet.app/posts/ios-04-timer-f4d5b62304170564/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-04-timer-f4d5b62304170564/</guid><description>NSTimer 是 iOS 开发中老生常谈的知识点了，今天再来唠叨一番。</description><pubDate>Mon, 13 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;NSTimer 是 iOS 开发中老生常谈的知识点了，今天再来唠叨一番。&lt;/p&gt;
&lt;h2&gt;一、 NSTimer 常规使用&lt;/h2&gt;
&lt;p&gt;通过下面简单的 Demo 看看现象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;NSTimerViewController.h&quot;

@interface NSTimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger count;
@end

@implementation NSTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self selfTargetTimer];
}

#pragma mark - 不同 NSTimer 使用方式
/// 常规使用 self 作为 Target 的 NSTimer
- (void)selfTargetTimer {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

#pragma mark - Action
- (void)countAction {
    self.count += 1;
    NSLog(@&quot;%@ - %ld&quot;,self, self.count);
}

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@&quot;NSTimerViewController dealloc&quot;);
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们反复 Push 到这个 ViewController ，出现了典型的内存泄露。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c711f8e9ce41fc6e.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过内存图我们能够发现，根本原因是 &lt;code&gt;RunLoop&lt;/code&gt; -&amp;gt; &lt;code&gt;Timer&lt;/code&gt; &amp;lt;-&amp;gt; &lt;code&gt;Self&lt;/code&gt; 这样的一个持有关系。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f87703373d4590e3.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而在该API的文档中也是有说明的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1651bf894bac4129.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为了解决内存泄露，我们需要打破这个循环引用。&lt;/p&gt;
&lt;h3&gt;1.1 weakSelf&lt;/h3&gt;
&lt;p&gt;我们用 weakself 替代 Self 是否有用呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(countAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/945a758e491870ab.gif&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;还是一样，这里是 &lt;code&gt;RunLoop&lt;/code&gt; -&amp;gt; &lt;code&gt;Timer&lt;/code&gt; &amp;lt;-&amp;gt; &lt;code&gt;weakSelf&lt;/code&gt; -&amp;gt; &lt;code&gt;Self&lt;/code&gt; 依旧没有打破。&lt;/p&gt;
&lt;h3&gt;1.2 block&lt;/h3&gt;
&lt;p&gt;还有这样一个 API&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval 
                                    repeats:(BOOL)repeats 
                                      block:(void (^)(NSTimer *timer))block;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们试试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__weak typeof(self)weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf countAction];
    }];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e3aef3a9691b33fb.gif&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;API&lt;/code&gt; 就可以很好的避免循环引用了。这里的关系是 &lt;code&gt;RunLoop&lt;/code&gt; -&amp;gt; &lt;code&gt;Timer&lt;/code&gt; -&amp;gt; &lt;code&gt;block&lt;/code&gt; 。这里只需要注意不要让 &lt;code&gt;block&lt;/code&gt; 产生内存泄露就ok了。&lt;/p&gt;
&lt;h2&gt;二、 自定义 Timer&lt;/h2&gt;
&lt;h3&gt;2.1 加一层间接&lt;/h3&gt;
&lt;p&gt;通过封装，解决 &lt;code&gt;Timer&lt;/code&gt; 和 &lt;code&gt;Self&lt;/code&gt; 的强引用关系： &lt;code&gt;RunLoop&lt;/code&gt; -&amp;gt; &lt;code&gt;Timer&lt;/code&gt; -&amp;gt; &lt;code&gt;??&lt;/code&gt; ~~ &lt;code&gt;Self&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;objc/message.h&amp;gt;

@interface RYTimer ()

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation RYTimer

- (instancetype)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    if (self == [super init]) {
        self.target = aTarget;
        self.aSelector = aSelector;
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireIMP, type);
            
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

void fireIMP(RYTimer *timer){
    if (timer.target) {
        void (*ry_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
        ry_msgSend((__bridge void *)(timer.target), timer.aSelector,timer.timer);
    }else{
        [timer invalidate];
    }
}

- (void)invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc{
    NSLog(@&quot;%s&quot;,__func__);
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/77dae82cc42b29bf.gif&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 消息转发机制&lt;/h3&gt;
&lt;p&gt;我们同样可以利用消息转发机制来对 Timer 进行优化，这里的思想和上面一样，都是使用了一层间接，但是具体实现方式不同。这里使用了 NSProxy 结合消息转发实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RYProxy : NSProxy

+ (instancetype)proxyWithTransformObject:(id)object;

@end


@interface RYProxy ()

@property (nonatomic, weak) id object;

@end

@implementation RYProxy

+ (instancetype)proxyWithTransformObject:(id)object {
    RYProxy *proxy = [RYProxy alloc];
    proxy.object = object;
    return proxy;
}

-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
    if (self.object) {
    }
    return [self.object methodSignatureForSelector:sel];

}

- (void)forwardInvocation:(NSInvocation *)invocation{
    if (self.object) {
        [invocation invokeWithTarget:self.object];
    }
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然 &lt;code&gt;Target&lt;/code&gt; 并不是 &lt;code&gt;Self&lt;/code&gt; 但经过消息转发，最终处理消息的还是消息的还是 &lt;code&gt;Self&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYProxy *timerProxy = [RYProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:timerProxy selector:@selector(countAction) userInfo:nil repeats:YES];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7232c2875758ab5d.gif&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>SideTable和weak底层实现</title><link>https://blog.lab-lawliet.app/posts/ios-03-sidetable-weak-607ecf10ac6be981/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-03-sidetable-weak-607ecf10ac6be981/</guid><description>我们先看一个小题目，看看下面的输出回事什么样的？</description><pubDate>Fri, 10 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我们先看一个小题目，看看下面的输出回事什么样的？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSObject *objc = [[NSObject alloc] init];

NSLog(@&quot;%zd - %@&quot;,(long)CFGetRetainCount((__bridge CFTypeRef)(objc)), objc);

__weak typeof(id) weakObjc = objc;

NSLog(@&quot;%zd - %@&quot;,(long)CFGetRetainCount((__bridge CFTypeRef)(objc)), objc);

NSLog(@&quot;%zd - %@&quot;,(long)CFGetRetainCount((__bridge CFTypeRef)(weakObjc)), weakObjc);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1、1、2。是不是很迷惑？别急我们慢慢来&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;前面在对内存管理进行研究的时候接触到了 &lt;code&gt;SideTalbe&lt;/code&gt; ，今天我们也依然会和他见面，它究竟是什么呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void 
objc_object::sidetable_lock()
{
    SideTable&amp;amp; table = SideTables()[this];
    table.lock();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SideTable&lt;/code&gt; 可以看出， &lt;code&gt;SideTable&lt;/code&gt; 是有多张的。它是一个 &lt;code&gt;HashMap&lt;/code&gt; 。&lt;/p&gt;
&lt;h2&gt;一、 SideTable 的结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct SideTable {
    spinlock_t slock;
    // 引用计数 Map
    RefcountMap refcnts;
    // 若引用 Map
    weak_table_t weak_table;

    SideTable() {
        memset(&amp;amp;weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal(&quot;Do not delete SideTable.&quot;);
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单了解了 SideTable 后我们继续回到题目。&lt;/p&gt;
&lt;h2&gt;二、 weak 做了什么&lt;/h2&gt;
&lt;h3&gt;2.1 调试&lt;/h3&gt;
&lt;p&gt;在这里打上汇编断点&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/80d7a3f4bcd54667.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;走到了 objc_initWeak&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c7508399c339f339.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;objc_initWeak&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/603a0c7abb516965.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;storeWeak&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/374c9a5854b0d3d3.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;weak_register_no_lock&lt;/h4&gt;
&lt;p&gt;注册一个 对象 - weak指针 对。如果没有的话，创建一个 weak 的 entry 对象。&lt;/p&gt;
&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;weak_table : SideTable 地址&lt;/li&gt;
&lt;li&gt;referent : 被弱引用的对象&lt;/li&gt;
&lt;li&gt;referrer : 弱引用指针地址&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1dc99ea1558efb28.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;append_referrer&lt;/h4&gt;
&lt;p&gt;把 referrer 加进 entry 的 inline_referrers&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d2ffcb5bc136e46f.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里又见到了熟悉的 3/4 扩容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;weak_entry_t&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct weak_entry_t {
    DisguisedPtr&amp;lt;objc_object&amp;gt; referent;
    union {
        struct {
            // 一个对象可以有多个 referent
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };

    ...

    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i &amp;lt; WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 回到题目继续分析&lt;/h2&gt;
&lt;p&gt;我们在这里下个断点，并打开汇编断点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5190b7673c6fd07c.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;来到了 &lt;code&gt;objc_loadWeakRetained&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3c8474b00c736d06.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;objc_loadWeakRetained&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;内部继续跟进来到了 &lt;code&gt;rootTryRetain&lt;/code&gt; ，这里内部引用计数会加&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9b58ae760b8f4edc.webp&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weak&lt;/code&gt; 原理
&lt;ul&gt;
&lt;li&gt;得到 &lt;code&gt;SideTable&lt;/code&gt; 的弱引用表&lt;/li&gt;
&lt;li&gt;得到一个 &lt;code&gt;weak_entry_t&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;把  &lt;code&gt;referent&lt;/code&gt; 加入到 &lt;code&gt;entry&lt;/code&gt; 的数组 &lt;code&gt;inline_referrers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;weak_table&lt;/code&gt; 需要扩容
&lt;ul&gt;
&lt;li&gt;3/4 扩容，类似与方法列表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;new_entry&lt;/code&gt; 加入到 &lt;code&gt;weak_table&lt;/code&gt; 中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当我们使用一个 &lt;code&gt;weakObjc&lt;/code&gt; 的时候，会调用 &lt;code&gt;rootTryRetain&lt;/code&gt; ，所以引用计数会 +1。也就是这里我们看到输出为 2 的原因所在。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>从一个面试题看TaggedPointer</title><link>https://blog.lab-lawliet.app/posts/ios-01-taggedpointer-c3953db7b1bfa0c7/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-01-taggedpointer-c3953db7b1bfa0c7/</guid><description>之前我们在WWDC20-runtime优化中有初步接触到了 TaggedPointer 。本文就讲结合一个面试题加深对它的理解。</description><pubDate>Thu, 09 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;之前我们在&lt;a href=&quot;/posts/ios-06-wwdc20-runtime-0f3a73b9c44b6d75/&quot;&gt;WWDC20-runtime优化&lt;/a&gt;中有初步接触到了 &lt;code&gt;TaggedPointer&lt;/code&gt; 。本文就讲结合一个面试题加深对它的理解。&lt;/p&gt;
&lt;h2&gt;一、 一个面试题&lt;/h2&gt;
&lt;p&gt;分析一下下面的代码会有什么问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;ViewController.h&quot;

@interface ViewController ()

@property (nonatomic, strong) dispatch_queue_t  queue;
@property (nonatomic, strong) NSString *nameStr;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self taggedPointerDemo];

}

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create(&quot;Ryukie&quot;, DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i&amp;lt;100000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@&quot;Ryukie&quot;];
             NSLog(@&quot;%@&quot;,self.nameStr);
        });
    }
}

- (void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event{
    for (int i = 0; i&amp;lt;100000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@&quot;RyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukieRyukie&quot;];
            NSLog(@&quot;%@&quot;,self.nameStr);
        });
    }
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 分析&lt;/h3&gt;
&lt;p&gt;乍一看本题考验的是多线程读写，很难想到和 &lt;code&gt;TaggedPointer&lt;/code&gt; 有关。本题两处中的不同点是字符串的长度而已。而根据我们之前对 &lt;code&gt;TaggedPointer&lt;/code&gt; 的了解，其中的 &lt;code&gt;Payload&lt;/code&gt; 会用来存储数据。但是 &lt;code&gt;Payload&lt;/code&gt; 的长度是有限的，过长的数据就无法完整保存进去。&lt;/p&gt;
&lt;p&gt;所以，这里分析两处的指针类型是不同的。&lt;/p&gt;
&lt;h3&gt;1.2 断点调试&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;较短的字符串&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e5e9191754c1283c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里发现指针类型为 &lt;code&gt;NSTaggedPointerString&lt;/code&gt; 。明显是一个 &lt;code&gt;TaggedPointer&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;较长的字符串&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c947dabf24b04775.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里的指针类型为 &lt;code&gt;__NSCFString&lt;/code&gt; 是一个普通的指针。&lt;/p&gt;
&lt;h3&gt;1.3 问题&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c3f4fe7226f57972.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在第二段代码运行中出现了野指针的崩溃。&lt;/p&gt;
&lt;h3&gt;1.4 思考&lt;/h3&gt;
&lt;p&gt;根据奔溃的现象，你可能会想到。 &lt;code&gt;TaggedPointer&lt;/code&gt; 的 &lt;code&gt;Retain&lt;/code&gt; 和 &lt;code&gt;Release&lt;/code&gt; 难道和普通的有不同么？&lt;/p&gt;
&lt;h2&gt;二、 源码分析&lt;/h2&gt;
&lt;p&gt;在 OC 源码中找到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (id)retain {
    return _objc_rootRetain(self);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进一步找到具体实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return (id)this;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 &lt;code&gt;Retain&lt;/code&gt; 的源码，我们可以看出，在判断是 &lt;code&gt;TaggedPointer&lt;/code&gt; 之后就直接 &lt;code&gt;return&lt;/code&gt; 了，没有进行任何操作。&lt;/p&gt;
&lt;p&gt;那么 Release 呢？我们探索一下源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    if (slowpath(isTaggedPointer())) return false;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现这里和 &lt;code&gt;Retain&lt;/code&gt; 是一样的处理。&lt;/p&gt;
&lt;h2&gt;三、 问题分析&lt;/h2&gt;
&lt;p&gt;初步了解了 Retain 和 Release 对与 &lt;code&gt;TaggedPointer&lt;/code&gt; 之后，我们就很容易理解上面的面试题所发生的现象了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaggedPointer&lt;/code&gt; 的较短的字符串没有进行 Retain Release 所以多线程异步读写过程中没有出现野指针错误&lt;/li&gt;
&lt;li&gt;非 &lt;code&gt;TaggedPointer&lt;/code&gt; 的较长的字符串，由于进行了多线程异步读写操作，不断的 Retain Release ，所以可能出现野指针错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaggedPointer&lt;/code&gt; 是用于特定类型的小对象的，并不是那些类型就都是 &lt;code&gt;TaggedPointer&lt;/code&gt; 。前提是 &lt;code&gt;Payload&lt;/code&gt; 要能够装得下。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Retain&lt;/code&gt; &lt;code&gt;Release&lt;/code&gt; 对 &lt;code&gt;TaggedPointer&lt;/code&gt; 的特殊处理，也提高了 &lt;code&gt;TaggedPointer&lt;/code&gt; 的效率，同时在多线程场景下的安全性也比较高。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Retain&amp;Release</title><link>https://blog.lab-lawliet.app/posts/ios-02-retain-release-c54ecc4db95613a8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-02-retain-release-c54ecc4db95613a8/</guid><description>到现在我们接触到了三种指针： TaggedPointer 、 Nonpointer isa 和 纯isa 。</description><pubDate>Thu, 09 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;到现在我们接触到了三种指针： &lt;code&gt;TaggedPointer&lt;/code&gt; 、 &lt;code&gt;Nonpointer isa&lt;/code&gt; 和 &lt;code&gt;纯isa&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;经过&lt;a href=&quot;/posts/ios-01-taggedpointer-c3953db7b1bfa0c7/&quot;&gt;上文&lt;/a&gt;对 &lt;code&gt;TaggedPointer&lt;/code&gt; 的探索，我们接触到了 &lt;code&gt;Reatin&lt;/code&gt; 和 &lt;code&gt;Release&lt;/code&gt; ，发现他们对 &lt;code&gt;TaggedPointer&lt;/code&gt; 有特殊处理的。本文将更加深入的对 &lt;code&gt;Reatin&lt;/code&gt; 和 &lt;code&gt;Release&lt;/code&gt; 进行研究。&lt;/p&gt;
&lt;h2&gt;一、 Retain&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant)
{
    // 不处理 TaggedPointer 直接 return
    if (slowpath(isTaggedPointer())) return (id)this;

    ...

    do {
        transcribeToSideTable = false;
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 非 nonpointer 通过 sidetable 处理
            ClearExclusive(&amp;amp;isa.bits);
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain(sideTableLocked);
        }
        
        ...

        uintptr_t carry; // 标志 extra_rc 容量是否还能够承受
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &amp;amp;carry);  // extra_rc++

        if (slowpath(carry)) {
            // 超出 extra_rc 容量
            // newisa.extra_rc++ overflowed
            if (variant != RRVariant::Full) {
                ClearExclusive(&amp;amp;isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // 保留一般的引用计数，并将另一半拷贝到 side table
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain &amp;amp;&amp;amp; !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&amp;amp;isa.bits, &amp;amp;oldisa.bits, newisa.bits)));

    if (variant == RRVariant::Full) {
        if (slowpath(transcribeToSideTable)) {
            // Copy the other half of the retain counts to the side table.
            sidetable_addExtraRC_nolock(RC_HALF);
        }

        if (slowpath(!tryRetain &amp;amp;&amp;amp; sideTableLocked)) sidetable_unlock();
    } else {
        ASSERT(!transcribeToSideTable);
        ASSERT(!sideTableLocked);
    }

    return (id)this;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaggedPointer&lt;/code&gt; 不处理，直接 return&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Nonpointer isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BITFIELD&lt;/code&gt; 和 &lt;code&gt;SideTable&lt;/code&gt; 结合使用
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;BITFIELD&lt;/code&gt; 的 &lt;code&gt;extra_rc&lt;/code&gt; 容量足够的时候持续 ++&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;BITFIELD&lt;/code&gt; 的 &lt;code&gt;extra_rc&lt;/code&gt; 容量不够的时候
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extra_rc&lt;/code&gt; 减半，拷贝一半到 &lt;code&gt;SideTable&lt;/code&gt; 进行管理&lt;/li&gt;
&lt;li&gt;同时标志位 &lt;code&gt;has_sidetable_rc&lt;/code&gt; 改变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;纯isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;SideTable&lt;/code&gt; 管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 Release&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;objc_object::rootRelease(bool performDealloc, objc_object::RRVariant variant)
{
    // 不处理 TaggedPointer 直接 return
    if (slowpath(isTaggedPointer())) return false;

    ...

retry:
    do {
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {
            // 非 nonpointer 通过 sidetable 处理
            ClearExclusive(&amp;amp;isa.bits);
            return sidetable_release(sideTableLocked, performDealloc);
        }
        ...
        // don&apos;t check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &amp;amp;carry);  // extra_rc--
        if (slowpath(carry)) {
            // don&apos;t ClearExclusive()
            // extra_rc 见底 跳转执行 underflow
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&amp;amp;isa.bits, &amp;amp;oldisa.bits, newisa.bits)));

    if (slowpath(newisa.isDeallocating()))
        goto deallocate;

    ...
    return false;

 underflow:
    // extra_rc 见底后，从 SideTable 拿回引用计数，或者销毁
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;

    if (slowpath(newisa.has_sidetable_rc)) {
        // 如果有使用 SideTable
        if (variant != RRVariant::Full) {
            ClearExclusive(&amp;amp;isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.
        
        if (!sideTableLocked) {
            ClearExclusive(&amp;amp;isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -&amp;gt; raw pointer transition.
            oldisa = LoadExclusive(&amp;amp;isa.bits);
            goto retry;
        }

        // 从 SideTable 移除引用计数
        // Try to remove some retain counts from the side table.        
        auto borrow = sidetable_subExtraRC_nolock(RC_HALF);

        bool emptySideTable = borrow.remaining == 0; // we&apos;ll clear the side table if no refcounts remain there

        if (borrow.borrowed &amp;gt; 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            bool didTransitionToDeallocating = false;
            // 修改 extra_rc 及标志位
            newisa.extra_rc = borrow.borrowed - 1;  // redo the original decrement too
            newisa.has_sidetable_rc = !emptySideTable;
            ...
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

deallocate:
    // Really deallocate.
    // 销毁
    ...

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TaggedPointer&lt;/code&gt; 不处理，直接 return&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Nonpointer isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BITFIELD&lt;/code&gt; 和 &lt;code&gt;SideTable&lt;/code&gt; 结合使用
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;BITFIELD&lt;/code&gt; 的 &lt;code&gt;extra_rc&lt;/code&gt; 容量未见底的时候持续 --&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;BITFIELD&lt;/code&gt; 的 &lt;code&gt;extra_rc&lt;/code&gt; 容量见底的时候
&lt;ul&gt;
&lt;li&gt;检查标志位 &lt;code&gt;has_sidetable_rc&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;使用了 &lt;code&gt;SideTable&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;SideTable&lt;/code&gt; 中的引用计数转移回 &lt;code&gt;BITFIELD&lt;/code&gt; 的 &lt;code&gt;extra_rc&lt;/code&gt; 进行处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;未使用 &lt;code&gt;SideTable&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;需要销毁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;纯isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;通过 &lt;code&gt;SideTable&lt;/code&gt; 管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 总结&lt;/h2&gt;
&lt;p&gt;通过对 Retain 和 Release 源码的探索，发现使用 &lt;code&gt;TaggedPointer&lt;/code&gt; 和 &lt;code&gt;Nonpointer isa&lt;/code&gt; 在内存管理的效率上会更高。一旦 &lt;code&gt;SideTable&lt;/code&gt;  介入，流程就回变的复杂还要上锁解锁，影响效率。虽然一般开发过程中很难遇到这种情况，但是理解了这些后，对一些问题一些现象就能更清楚的看到本质。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/posts/ios-03-isa-60c1a1b7a018e93e/&quot;&gt;对象本质探究与isa&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>iOS原生嵌入FlutterModule</title><link>https://blog.lab-lawliet.app/posts/flutter-25-ios-fluttermodule-7e8f1dc950b45c91/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-25-ios-fluttermodule-7e8f1dc950b45c91/</guid><description>为了将 Flutter 嵌入原生项目，我们需要创建一个 Flutter Module</description><pubDate>Wed, 08 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Flutter Module&lt;/h2&gt;
&lt;p&gt;为了将 Flutter 嵌入原生项目，我们需要创建一个 Flutter Module&lt;/p&gt;
&lt;h3&gt;方式一：通过AS创建 Module&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/cc3a8caffd575b48.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;方式二：通过命令行创建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;flutter create -t module 你的module名字比如my_module命名要符合规范
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/767ec6d5044961a0.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;配置Podfile&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;flutter_application_path = &apos;flutter Module 路径&apos;
load File.join(flutter_application_path, &apos;.ios&apos;, &apos;Flutter&apos;, &apos;podhelper.rb&apos;)

target &apos;FlutterDemo&apos; do
  # Comment the next line if you don&apos;t want to use dynamic frameworks
  use_frameworks!

end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置完成后 pod install&lt;/p&gt;
</content:encoded></item><item><title>Key</title><link>https://blog.lab-lawliet.app/posts/flutter-23-key-19082b15eef3ef31/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-23-key-19082b15eef3ef31/</guid><description>我们创建这样一个示例：</description><pubDate>Thu, 02 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Key 的作用&lt;/h2&gt;
&lt;p&gt;我们创建这样一个示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &apos;dart:math&apos;;

import &apos;package:flutter/material.dart&apos;;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &apos;Flutter Demo&apos;,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  _HomePageState createState() =&amp;gt; _HomePageState();
}

class _HomePageState extends State&amp;lt;HomePage&amp;gt; {
  //key的作用就非常大了！！
  List&amp;lt;Widget&amp;gt; items = [
    ColorItem(&apos;第1个&apos;),
    ColorItem(&apos;第2个&apos;),
    ColorItem(&apos;第3个&apos;),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&apos;Key的作用&apos;),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: items,
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() {
            items.removeAt(0);
          });
        },
      ),
    );
  }
}

class ColorItem extends StatefulWidget {
  final String title;

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() =&amp;gt; _ColorItemState();
}

class _ColorItemState extends State&amp;lt;ColorItem&amp;gt; {
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      child: Text(widget.title),
      color: color,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b252b70b517ec144.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;运行发现了上面的现象，作为有 &lt;code&gt;iOS&lt;/code&gt; 开发经验的我们，很容易联想到这应该是和&lt;code&gt;复用&lt;/code&gt;有关的现象。&lt;/p&gt;
&lt;h3&gt;复用问题&lt;/h3&gt;
&lt;p&gt;这里我们需要理解， &lt;code&gt;ColorItem&lt;/code&gt; 作为一个 &lt;code&gt;StatefulWidget&lt;/code&gt; 它是由两部分组成的： &lt;code&gt;Widget&lt;/code&gt; 和 &lt;code&gt;State&lt;/code&gt; 。这里就是 &lt;code&gt;Widget&lt;/code&gt; 被移除后， &lt;code&gt;State&lt;/code&gt; 还在内存中。所以出现了复用异常的问题。&lt;/p&gt;
&lt;p&gt;那么将数据放在 &lt;code&gt;Widget&lt;/code&gt; 中按照这里分析的理论来说应该就没有这个现象了吧？我们试试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ColorItem extends StatefulWidget {
  final String title;
  // 移到 Widget 中
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);

  ColorItem(this.title, {Key? key}) : super(key: key);

  @override
  _ColorItemState createState() =&amp;gt; _ColorItemState();
}

class _ColorItemState extends State&amp;lt;ColorItem&amp;gt; {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      child: Text(widget.title),
      color: widget.color,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f0a7a3f5e75898b2.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;验证成功！那么具体原因是什么呢？&lt;/p&gt;
&lt;h3&gt;增量渲染与 canUpdate&lt;/h3&gt;
&lt;p&gt;我们来到 &lt;code&gt;Widget&lt;/code&gt; 的实现中， &lt;code&gt;canUpdate&lt;/code&gt; 方法决定了一个 &lt;code&gt;Widget&lt;/code&gt; 的 &lt;code&gt;Element&lt;/code&gt; 是否会被更新，而 &lt;code&gt;Element&lt;/code&gt; 的更新，又直接关系到了&lt;code&gt;增量渲染&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;同时满足两个条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新旧 &lt;code&gt;Widget&lt;/code&gt; 的类型相同&lt;/li&gt;
&lt;li&gt;新旧 &lt;code&gt;Widget&lt;/code&gt; 的 &lt;code&gt;Key&lt;/code&gt; 相同
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;Key&lt;/code&gt; 是空的，只通过类型判断，就算他们的子 &lt;code&gt;Widget&lt;/code&gt; 完全不同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里就很好的解释了一开始发生的异常现象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// Whether the `newWidget` can be used to update an [Element] that currently
/// has the `oldWidget` as its configuration.
///
/// An element that uses a given widget as its configuration can be updated to
/// use another widget as its configuration if, and only if, the two widgets
/// have [runtimeType] and [key] properties that are [operator==].
///
/// If the widgets have no key (their key is null), then they are considered a
/// match if they have the same type, even if their children are completely
/// different.
static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        &amp;amp;&amp;amp; oldWidget.key == newWidget.key;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;图解&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Widget&lt;/code&gt; 和 &lt;code&gt;Element&lt;/code&gt; 是一一对应的，而 &lt;code&gt;State&lt;/code&gt; 是在 &lt;code&gt;Element&lt;/code&gt; 中的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3a6b6aa1bd49fdcc.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移除一个 &lt;code&gt;Widget&lt;/code&gt; 开始检查&lt;/li&gt;
&lt;li&gt;第一个 &lt;code&gt;Element&lt;/code&gt; 检查到第二个 &lt;code&gt;Widget&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;调用 &lt;code&gt;canUpdate&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;类型相同&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Key&lt;/code&gt; 为空&lt;/li&gt;
&lt;li&gt;return &lt;code&gt;ture&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;于是就用 &lt;code&gt;第一个Element&lt;/code&gt; 更新 &lt;code&gt;第二个Widget&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;依次类推，就发生了前面的异常现象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3ebd55aa531e407a.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;既然这里提到了 &lt;code&gt;Key&lt;/code&gt; ，那么加上 &lt;code&gt;Key&lt;/code&gt; 是否就可以上面这个问题呢？&lt;/p&gt;
&lt;h3&gt;Key 的使用&lt;/h3&gt;
&lt;p&gt;这里为每个 &lt;code&gt;ColorItem&lt;/code&gt; 加上 &lt;code&gt;Key&lt;/code&gt; ， 并将 &lt;code&gt;Color&lt;/code&gt; 属性放回 &lt;code&gt;State&lt;/code&gt; 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...

class _HomePageState extends State&amp;lt;HomePage&amp;gt; {
  //key的作用就非常大了！！
  List&amp;lt;Widget&amp;gt; items = [
    ColorItem(
      &apos;第1个&apos;,
      key: ValueKey(1),
    ),
    ColorItem(
      &apos;第2个&apos;,
      key: ValueKey(2),
    ),
    ColorItem(
      &apos;第3个&apos;,
      key: ValueKey(3),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    ...
  }
}

class ColorItem extends StatefulWidget {
  final String title;
  ...
}

class _ColorItemState extends State&amp;lt;ColorItem&amp;gt; {
  final color = Color.fromRGBO(
      Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
  ...
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/70df4a838ae7d6aa.gif&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有效！&lt;/p&gt;
&lt;h2&gt;Key&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Key&lt;/code&gt; 本身是一个抽象类，有一个工厂方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
@immutable
abstract class Key {
  /// Construct a [ValueKey&amp;lt;String&amp;gt;] with the given [String].
  ///
  /// This is the simplest way to create keys.
  const factory Key(String value) = ValueKey&amp;lt;String&amp;gt;;

  /// Default constructor, used by subclasses.
  ///
  /// Useful so that subclasses can call us, because the [new Key] factory
  /// constructor shadows the implicit constructor.
  @protected
  const Key.empty();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;它有两个子类
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LocalKey&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;同一个 &lt;code&gt;父Element&lt;/code&gt; 内唯一的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GlobalKey&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;整个 &lt;code&gt;App&lt;/code&gt; 内唯一&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;刚才用的 &lt;code&gt;ValueKey&lt;/code&gt; 是 &lt;code&gt;LocalKey&lt;/code&gt; 的子类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;LocalKey&lt;/h2&gt;
&lt;p&gt;区别哪个 &lt;code&gt;Element&lt;/code&gt; 要保留，哪个 &lt;code&gt;Element&lt;/code&gt; 要删除。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
abstract class LocalKey extends Key {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const LocalKey() : super.empty();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ValueKey&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;以值作为参数（数字、字符串等）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ObjectKey&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;以对象作为参数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UniqueKey&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;创建唯一标识&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GlobalKey&lt;/h2&gt;
&lt;p&gt;在整个 &lt;code&gt;App&lt;/code&gt; 中 &lt;code&gt;唯一的Key&lt;/code&gt;。 &lt;code&gt;GlobalKey&lt;/code&gt; 可以唯一标识一个元素， 比如访问一个 &lt;code&gt;BuildContext&lt;/code&gt; 或者一个 &lt;code&gt;Widget。对于&lt;/code&gt; &lt;code&gt;StatefulWidget&lt;/code&gt; 而言， &lt;code&gt;GlobalKey&lt;/code&gt; 也可以访问 &lt;code&gt;State&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;当有 &lt;code&gt;GlobalKey&lt;/code&gt; 的 &lt;code&gt;Widget&lt;/code&gt; 被移动到 &lt;code&gt;Widget树&lt;/code&gt; 中新的位置的话，会重新渲染他们的子树。&lt;/p&gt;
&lt;p&gt;为了渲染子树，一个 &lt;code&gt;Widget&lt;/code&gt; 必须在同一个&lt;code&gt;动画帧&lt;/code&gt;内完成在树中，从旧位置移动到新位置。&lt;/p&gt;
&lt;p&gt;重新渲染一个使用 &lt;code&gt;GlobalKey&lt;/code&gt; 的 &lt;code&gt;Element&lt;/code&gt; 是很消耗性能的，因为会触发调用所有相关的 &lt;code&gt;State&lt;/code&gt; 的 &lt;code&gt;deactivate&lt;/code&gt; 方法，然后让所有依赖 &lt;code&gt;InheritedWidget&lt;/code&gt; 的 &lt;code&gt;Widget&lt;/code&gt; 重建。&lt;/p&gt;
&lt;p&gt;所以如果你不需要达到上面的效果，那么建议使用其他的 &lt;code&gt;Key&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;注意点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;两个在同一个树中的 &lt;code&gt;Widget&lt;/code&gt; 不能同时有相同的 &lt;code&gt;GlobalKey&lt;/code&gt; 。尝试这样做的话在运行时会触发异常。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GlobalKeys&lt;/code&gt; 不应该再每次 &lt;code&gt;build&lt;/code&gt; 的时候被重新创建。他们应该是长期被一个 State 所持有的。
&lt;ul&gt;
&lt;li&gt;例如：
&lt;ul&gt;
&lt;li&gt;每次 &lt;code&gt;build&lt;/code&gt; 的时候都创建一个新的 &lt;code&gt;GlobalKey&lt;/code&gt; 会丢弃和 &lt;code&gt;旧Key&lt;/code&gt; 相关的子树，并为&lt;code&gt;新Key&lt;/code&gt;创建一个新树。除了会损害性能，这种操作还有可能会对子树造成未知的影响。&lt;/li&gt;
&lt;li&gt;例如子树中的 &lt;code&gt;GestureDetector&lt;/code&gt; 将无法继续追踪正在进行中的手势，因为它会在每次 &lt;code&gt;build&lt;/code&gt; 的时候被重新创建&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;比较好的做法是：
&lt;ul&gt;
&lt;li&gt;让一个 &lt;code&gt;State&lt;/code&gt; 持有这个 &lt;code&gt;GlobalKey&lt;/code&gt; ，并且在 &lt;code&gt;Build&lt;/code&gt; 方法外初始化它&lt;/li&gt;
&lt;li&gt;比如在 &lt;code&gt;State.initState&lt;/code&gt; 中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GlobalKey简单示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import &apos;dart:math&apos;;

import &apos;package:flutter/material.dart&apos;;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: &apos;Flutter Demo&apos;,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GlobalKeyDemo(),
    );
  }
}

class GlobalKeyDemo extends StatelessWidget {

  final GlobalKey&amp;lt;_GKeyItemState&amp;gt; _gKey = GlobalKey();

  GlobalKeyDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&apos;Key的作用&apos;),
      ),
      body: Center(
        child: GKeyItem(key: _gKey,),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          _gKey.currentState?.setState(() {
            _gKey.currentState?.count += 1;
          });
        },
      ),
    );
  }
}


class GKeyItem extends StatefulWidget {
  const GKeyItem({Key? key}) : super(key: key);

  @override
  _GKeyItemState createState() =&amp;gt; _GKeyItemState();
}

class _GKeyItemState extends State&amp;lt;GKeyItem&amp;gt; {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(&apos;$count&apos;),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>调用原生相册</title><link>https://blog.lab-lawliet.app/posts/flutter-24-a823fdac2b803663/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-24-a823fdac2b803663/</guid><description>Flutter 与原生交互的通道是 MethodChannel。下面我们通过调用 iOS 原生相册选择来体验一下。</description><pubDate>Thu, 02 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;MethodChannel&lt;/h2&gt;
&lt;p&gt;Flutter 与原生交互的通道是 MethodChannel。下面我们通过调用 iOS 原生相册选择来体验一下。&lt;/p&gt;
&lt;h2&gt;示例代码&lt;/h2&gt;
&lt;h3&gt;定义一个Channel&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;MethodChannel _channel = const MethodChannel(&apos;mine_header/channel&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;给原生发送指令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;_channel.invokeMapMethod(&apos;sel_avatar&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原生处理指令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import UIKit
import Flutter

/// 这里将指令用 枚举包装，便于使用
enum MineChannelMethod: String {
    case AvatarPick = &quot;sel_avatar&quot;
}

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -&amp;gt; Bool {
        GeneratedPluginRegistrant.register(with: self)
        
        if let flutterVC = window?.rootViewController as? FlutterViewController, 
        let messenger = flutterVC as? FlutterBinaryMessenger 
        {
            // 实例化 channel
            let mineChannel = FlutterMethodChannel.init(name: &quot;mine_header/channel&quot;, binaryMessenger: messenger)
            mineChannel.setMethodCallHandler { call, result in
                guard 
                    call.method.isEmpty == false, 
                    let method = MineChannelMethod(rawValue: call.method) 
                else {
                    return
                }
                
                switch method {
                case .AvatarPick:
                    let picker = UIImagePickerController()
                    picker.modalPresentationStyle = .overFullScreen
                    picker.delegate = self
                    flutterVC.present(picker, animated: true, completion: nil)
                }
                
            }
        }
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

extension AppDelegate: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        picker.dismiss(animated: true, completion: nil)
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;检验效果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a4de0b9cf0e39bb6.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;数据传递&lt;/h3&gt;
&lt;p&gt;通过检查选择完成后的回调我们发现 info 中包含的数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): public.image, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerImageURL): file:///private/var/mobile/Containers/Data/Application/8A9A613F-32F9-4E45-8CB3-BB882CC9F590/tmp/3BC0596E-8B97-4E64-B260-269668F9CA73.png, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerOriginalImage): &amp;lt;UIImage:0x281538f30 anonymous {1125, 2436} renderingMode=automatic&amp;gt;, __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): assets-library://asset/asset.PNG?id=65B7A1B5-597F-4B97-B8E3-9CA019A9A526&amp;amp;ext=PNG]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们需要将图片路径回传给 Flutter 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if let imgPath = info[UIImagePickerController.InfoKey.imageURL] as? URL {
    print(imgPath.absoluteString)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;file:///private/var/mobile/Containers/Data/Application/EE26C1FB-3551-4454-8BAA-B62CADD2B311/tmp/C2302E64-C587-4975-9A24-BD43B34FA0F1.png&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if let imgPath = (info[UIImagePickerController.InfoKey.imageURL] as? URL)?.absoluteString, imgPath.count &amp;gt; 8 {
    let path = String(imgPath.suffix(from: String.Index.init(utf16Offset: 7, in: imgPath)))
    print(path)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;截取有效字段： &lt;code&gt;/private/var/mobile/Containers/Data/Application/C7341168-0BCB-4712-8092-CB2661798888/tmp/828FF919-E315-4401-B76D-C774F5F2EF25.png&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据回传&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if let imgPath = (info[UIImagePickerController.InfoKey.imageURL] as? URL)?.absoluteString, imgPath.count &amp;gt; 8 {
    let path = String(imgPath.suffix(from: String.Index.init(utf16Offset: 7, in: imgPath)))
    channel?.invokeMethod(&quot;rec_imagePath&quot;, arguments: path)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;数据接收&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final String _selAvatar = &apos;sel_avatar&apos;;
final String _reciveImagePath = &apos;rec_imagePath&apos;;
final MethodChannel _channel = const MethodChannel(&apos;mine_header/channel&apos;);

@override
void initState() {
    super.initState();
    _channel.setMethodCallHandler( (call) async {
      if (call.method == _reciveImagePath) {
        print(&apos;${call.arguments}&apos;);
        setState(() {
          _avatarPath = call.arguments.toString();
        });
      }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到： &lt;code&gt;flutter: /private/var/mobile/Containers/Data/Application/336E79D6-B33A-4ABC-A380-4501E1550E1D/tmp/D50D8A94-3625-4CA7-8D2B-2C6AD0CCCC67.png&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;数据展示&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/34d549cd12902126.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MeHeaderView extends StatelessWidget {
  Function()? avatarTap;
  File? avatarFile;

  MeHeaderView({this.avatarTap, this.avatarFile});

  @override
  Widget build(BuildContext context) {
    return Container(
            ...
                decoration: BoxDecoration(
                    image: DecorationImage(
                    fit: BoxFit.fitWidth,
                    image: (avatarFile == null
                        ? const AssetImage(&apos;images/header_avatar.GIF&apos;)
                        : FileImage(avatarFile!)) as ImageProvider,
                    ),
                ),
            ),
            ...
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Widget的生命周期</title><link>https://blog.lab-lawliet.app/posts/flutter-21-widget-c44bd9c1f27d1bb3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-21-widget-c44bd9c1f27d1bb3/</guid><pubDate>Mon, 29 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;StatelessWidget&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;构造方法&lt;/li&gt;
&lt;li&gt;Build方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;StatefulWidget&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Widget 的构造方法&lt;/li&gt;
&lt;li&gt;Widget 的 createState 方法&lt;/li&gt;
&lt;li&gt;State 的构造方法&lt;/li&gt;
&lt;li&gt;State 的 initState 方法&lt;/li&gt;
&lt;li&gt;State 的 didChangeDependencies 方法
&lt;ul&gt;
&lt;li&gt;依赖的 InheritedWidget （共享数据） 发生变化后 didChangeDependencies 才会调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;State 的 build 方法
&lt;ul&gt;
&lt;li&gt;当调用 setState ，会重新调用 Build 进行渲染
&lt;ul&gt;
&lt;li&gt;setState 内部主要是利用 _element 调用 markNeedsBuild&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Widget 销毁的时候，调用 State 的 dispose 方法&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Widget树&amp;Render树&amp;Element树</title><link>https://blog.lab-lawliet.app/posts/flutter-22-widget-render-element-84b88ff019c9a178/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-22-widget-render-element-84b88ff019c9a178/</guid><description>不是所有的 Widget 都会被独立渲染。只有继承 RenderObjectWidget 的才会创建 RenderObject 对象。</description><pubDate>Mon, 29 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Widget 的渲染原理&lt;/h2&gt;
&lt;p&gt;不是所有的 &lt;code&gt;Widget&lt;/code&gt; 都会被独立渲染。只有继承 &lt;code&gt;RenderObjectWidget&lt;/code&gt; 的才会创建 &lt;code&gt;RenderObject&lt;/code&gt; 对象。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;Flutter&lt;/code&gt; 中，有三个树非常重要， &lt;code&gt;Widget树&lt;/code&gt; 、 &lt;code&gt;Render树&lt;/code&gt; 、 &lt;code&gt;Element树&lt;/code&gt;。&lt;code&gt;Flutter引擎&lt;/code&gt;是针对 &lt;code&gt;Render树&lt;/code&gt; 进行渲染。&lt;/p&gt;
&lt;h2&gt;Widget树 、 Render树 、 Element树&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;每一个 &lt;code&gt;Widget&lt;/code&gt; 都会创建一个 &lt;code&gt;Elememt&lt;/code&gt; 对象&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隐式调用 &lt;code&gt;createElement&lt;/code&gt; 方法。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Element&lt;/code&gt; 加入 &lt;code&gt;Element树&lt;/code&gt; 中&lt;/li&gt;
&lt;li&gt;它会创建三种 &lt;code&gt;Element&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RenderElement&lt;/code&gt; 主要是创建 &lt;code&gt;RenderObject&lt;/code&gt; 对象&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;继承 &lt;code&gt;RenderObjectWidget&lt;/code&gt; 的 &lt;code&gt;Widget&lt;/code&gt; 会创建 &lt;code&gt;RenderElement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Flutter&lt;/code&gt; 会调用 &lt;code&gt;mount&lt;/code&gt; 方法，调用 &lt;code&gt;createRanderObject&lt;/code&gt; 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;StatefulElement&lt;/code&gt; 继承 &lt;code&gt;ComponentElement&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;StatefulWidget&lt;/code&gt; 会创建 &lt;code&gt;StatefulElement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;createState&lt;/code&gt; 创建 &lt;code&gt;State&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;Widget&lt;/code&gt; 赋值给 &lt;code&gt;state&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;state&lt;/code&gt; 的 &lt;code&gt;build&lt;/code&gt; 方法，并将自己 &lt;code&gt;Element&lt;/code&gt; 传出去
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt; 里面的 &lt;code&gt;context&lt;/code&gt; 就是 &lt;code&gt;element&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;StatelessElement&lt;/code&gt; 继承 &lt;code&gt;ComponentElement&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;StatelessWidget&lt;/code&gt; 会创建 &lt;code&gt;StatelessElement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;主要就是调用 &lt;code&gt;build&lt;/code&gt; 方法，并将自己 &lt;code&gt;Element&lt;/code&gt; 传出去&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Future&amp;Microtask</title><link>https://blog.lab-lawliet.app/posts/flutter-19-future-microtask-58b5998bc5fc625b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-19-future-microtask-58b5998bc5fc625b/</guid><description>下面多个 Future 的执行顺序会是什么样的呢？</description><pubDate>Sat, 27 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;多个 Future&lt;/h2&gt;
&lt;p&gt;下面多个 &lt;code&gt;Future&lt;/code&gt; 的执行顺序会是什么样的呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void main() {
  Future( () {
    sleep(const Duration(seconds: 1));
    return &quot;${DateTime.now()}: 1&quot;;
  })
  .then((value){
    print(value);
  });

  Future( () {
    sleep(const Duration(seconds: 1));
    return &quot;${DateTime.now()}: 2&quot;;
  })
      .then((value){
    print(value);
  });

  Future( () {
    sleep(const Duration(seconds: 1));
    return &quot;${DateTime.now()}: 3&quot;;
  })
      .then((value){
    print(value);
  });

  Future( () {
    sleep(const Duration(seconds: 1));
    return &quot;${DateTime.now()}: 4&quot;;
  })
      .then((value){
    print(value);
  });
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：
多个 &lt;code&gt;Future&lt;/code&gt; 是按照代码顺序，依次添加到任务队列的，会依次被执行。所以会按顺序输出。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-27 15:34:53.829132: 1
flutter: 2021-11-27 15:34:54.855321: 2
flutter: 2021-11-27 15:34:55.863015: 3
flutter: 2021-11-27 15:34:56.870869: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;依赖连续调用&lt;/h2&gt;
&lt;p&gt;如果有几个异步任务是有一定的顺序或者结果依赖的，那么可以通过 &lt;code&gt;then&lt;/code&gt; 的链式调用进行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  Future(() {
    sleep(const Duration(seconds: 1));
    number += 1;
    print(&quot;${DateTime.now()}: $number&quot;);
    return number;
  }).then((value) {
    sleep(const Duration(seconds: 1));
    value += 1;
    print(&quot;${DateTime.now()}: $value&quot;);
    return value;
  }).then((value) {
    sleep(const Duration(seconds: 1));
    value += 1;
    print(&quot;${DateTime.now()}: $value&quot;);
    return value;
  }).then((value) {
    sleep(const Duration(seconds: 1));
    value += 1;
    print(&quot;${DateTime.now()}: $value&quot;);
    return value;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出的结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-27 15:43:29.512735: 2
flutter: 2021-11-27 15:43:30.537787: 3
flutter: 2021-11-27 15:43:31.544347: 4
flutter: 2021-11-27 15:43:32.549391: 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;多个无依赖的任务结束后处理&lt;/h2&gt;
&lt;p&gt;开发中，我们还会遇到一些多个异步任务都执行完再去做一些事情的场景。 &lt;code&gt;Future&lt;/code&gt; 也有提供这种歌场景的功能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void main() {
  Future.wait([
    Future(() {
      sleep(Duration(seconds: 1));
      print(&apos;${DateTime.now()}: 1&apos;);
      return &quot;任务1&quot;;
    }),
    Future(() {
      sleep(Duration(seconds: 1));
      print(&apos;${DateTime.now()}: 2&apos;);
      return &quot;任务2&quot;;
    }),
    Future(() {
      sleep(Duration(seconds: 1));
      print(&apos;${DateTime.now()}: 3&apos;);
    }),
    Future(() {
      sleep(Duration(seconds: 1));
      print(&apos;${DateTime.now()}: 4&apos;);
    }),
    Future(() {
      sleep(Duration(seconds: 1));
      print(&apos;${DateTime.now()}: 5&apos;);
      return &quot;任务5&quot;;
    }),
  ]).then((value) {
    print(value);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-27 15:50:47.972739: 1
flutter: 2021-11-27 15:50:49.000895: 2
flutter: 2021-11-27 15:50:50.008548: 3
flutter: 2021-11-27 15:50:51.015465: 4
flutter: 2021-11-27 15:50:52.023024: 5
flutter: [任务1, 任务2, null, null, 任务5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里和使用单个 &lt;code&gt;Future&lt;/code&gt; 一样，也可以通过 &lt;code&gt;then&lt;/code&gt; 来处理结果。通过输出内容我们可以发现，这里的 &lt;code&gt;then&lt;/code&gt; 里的 &lt;code&gt;value&lt;/code&gt; 其实是一个数组，并且如果数组内部的顺序是和 &lt;code&gt;Future&lt;/code&gt; 数组的顺序保持一致的。&lt;/p&gt;
&lt;h2&gt;scheduleMicrotask&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/dart-async/scheduleMicrotask.html&quot;&gt;scheduleMicrotask&lt;/a&gt; 也是异步编程常用的一个 &lt;code&gt;API&lt;/code&gt;，它的优先级比 &lt;code&gt;Future&lt;/code&gt; 会高，同一队列中，会优先将 &lt;code&gt;微任务&lt;/code&gt; 处理完成再去处理 &lt;code&gt;Future&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;题目&lt;/h3&gt;
&lt;p&gt;这里通过一个题目考验一下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void main() {
  Future((){
    print(&apos;1&apos;);
  }).then((value){
    print(&apos;2&apos;);
    scheduleMicrotask((){
      print(&apos;3&apos;);
    });
  }).then((value){
    print(&apos;4&apos;);
  });

  scheduleMicrotask((){
    print(&apos;5&apos;);
  });

  Future((){
    print(&apos;6&apos;);
  });

  scheduleMicrotask((){
    print(&apos;7&apos;);
  });

  print(&apos;8&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;思考一下这里的输出会是什么样的顺序呢？不要运行，欢迎在评论中告诉我哦。&lt;/p&gt;
</content:encoded></item><item><title>Dart异步编程:Isolates和事件循环</title><link>https://blog.lab-lawliet.app/posts/flutter-20-dart-isolates-bd07c849fb450f19/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-20-dart-isolates-bd07c849fb450f19/</guid><description>Dart 是单线程语言，它为 Futures Steams 后台作业，以及编写现代化、异步、响应式的 Flutter 应用程序所需的其他内容提供支持。</description><pubDate>Sat, 27 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Dart 是单线程语言，它为 Futures Steams 后台作业，以及编写现代化、异步、响应式的 Flutter 应用程序所需的其他内容提供支持。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/117f15d70121e26a.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Isolate&lt;/h2&gt;
&lt;p&gt;Isolate 是所有 Dart 代码运行的地方。它就像机器中的一个小空间，它拥有自己的&lt;code&gt;内存块&lt;/code&gt;和运行&lt;code&gt;事件循环&lt;/code&gt;的&lt;code&gt;单一线程&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在其他语言中，比如 C++ 你可以让多个线程共享相同的内存。并运行他们想要的代码。但是在 Dart 中，每个线程都有自己的 Isolate 和它自己的内存，它只处理事件。&lt;/p&gt;
&lt;p&gt;许多 Dart 应用程序，在单一 Isolate 中运行所有代码。但是如果需要，你可以使用多个 Isolate。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7831566c94ba3ffc.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果你要运行的计算量太过庞大，如果它在主要的 Isolate 中运行的话，会导致应用程序扔掉当前的栈帧（Drop Frame）&lt;/p&gt;
&lt;h3&gt;子Isolate&lt;/h3&gt;
&lt;p&gt;你可以使用 Isolate.spawn 或者 Flutter 的计算函数，两者都会创建一个单独的 Isolate 来进行运算。让你主要的 Isolate 可以不受影响。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a012835e469b1baf.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;新的 Isolate 将会获得独立的事件循环和自己的内存，即使原本的 Isolate 是这个新 Isolate 的母体，原本的 Isolate 也不可以访问它。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/55ee82f1218198ca.gif&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这就是 Isolate 名称的来源。这些小空间之间彼此隔离。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Isolate 间通讯&lt;/h3&gt;
&lt;p&gt;Isolate 间合作的唯一方式，是来回传递消息。&lt;/p&gt;
&lt;p&gt;一个 Isolate 会给另一个 Isolate 发送消息，接收消息的 Isolate 会使用他的事件循环处理消息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e804b639317f15d.gif&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Isolate 为什么这样设计&lt;/h3&gt;
&lt;p&gt;缺乏共享内存，可能听起来有些严格。 特别如果你是使用 Java C++ 这样的语言的话。但它对 Dart 编译器有一些关键性的好处。&lt;/p&gt;
&lt;p&gt;比如:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Isolate 的内存分配和垃圾回收不需要&lt;code&gt;锁&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;因为他只有单一线程，因此如果它不忙，你就知道内存没有发生变化。这对 Flutter 应用程序非常有用。它有时需要快速构建和拆解很多 Widget。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;事件循环&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/08cb8b7d3e1dca53.gif&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;想象一下，应用程序的生命，在时间轴中延伸。期间会有很多外部信息的输入，而我们并不能预知这些信息什么时候会输入。这时我们就需要一个永不阻塞的线程来处理所有事件，它运行一个事件循环。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ea637eda86157ce7.gif&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;他从事件队列中，取出最旧的事件，处理它，返回，再取出下一个事件处理，依次循环，直到队列清空。&lt;/p&gt;
&lt;p&gt;当事件清空，事件循环就会进入等待，直到有新的事件加入队列。&lt;/p&gt;
&lt;p&gt;所有高级API都可用于异步编程。他们全部建立在这个简单的循环上。&lt;/p&gt;
&lt;h2&gt;Isolate 的使用&lt;/h2&gt;
&lt;h3&gt;真多线程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void main() {
  print(&apos;${DateTime.now()}: 1&apos;);

  Isolate.spawn(isolateFunc, 15);

  sleep(Duration(seconds: 3));
  print(&apos;${DateTime.now()}: Sleep end&apos;);
}

void isolateFunc(int value) {
  sleep(Duration(seconds: 1));
  print(&apos;${DateTime.now()}: isolateFunc end $value&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过日志我们发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-28 10:12:09.313570: 1
flutter: 2021-11-28 10:12:10.422793: isolateFunc end 15
flutter: 2021-11-28 10:12:12.349747: Sleep end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sleep 的3秒并没有影响到 Isolate 调用，这里真正实现了多线程。&lt;/p&gt;
&lt;h3&gt;资源竞争&lt;/h3&gt;
&lt;p&gt;在进行 iOS 多线程开发的时候，我们会遇到资源竞争的问题。我们通常通过加锁来保证逻辑的正常。那么在使用 Isolate 的时候会不会也遇到这样的情况呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()}: 1&apos;);

  Isolate.spawn(isolateFunc, 1);
  Isolate.spawn(isolateFunc, 2);
  Isolate.spawn(isolateFunc, 3);
  Isolate.spawn(isolateFunc, 4);
  Isolate.spawn(isolateFunc, 5);
  Isolate.spawn(isolateFunc, 6);
  Isolate.spawn(isolateFunc, 7);
  Isolate.spawn(isolateFunc, 8);
  Isolate.spawn(isolateFunc, 9);
  Isolate.spawn(isolateFunc, 10);

  sleep(Duration(seconds: 5));
  print(&apos;${DateTime.now()}: Sleep end，number = $number&apos;);
}

void isolateFunc(int value) {
  number += 1;
  print(&apos;${DateTime.now()}: 第$value个调用， number = $number&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照 iOS 开发经验理解，上面的输出应该是乱序的，而且 number 的读写也是会比较乱的。且 sleep 5秒后输出的 number 值会 +10。&lt;/p&gt;
&lt;p&gt;我们看下输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-28 10:24:18.275046: 1
flutter: 2021-11-28 10:24:18.506326: 第1个调用， number = 2
flutter: 2021-11-28 10:24:18.543709: 第3个调用， number = 2
flutter: 2021-11-28 10:24:18.572635: 第4个调用， number = 2
flutter: 2021-11-28 10:24:18.589551: 第5个调用， number = 2
flutter: 2021-11-28 10:24:18.613625: 第7个调用， number = 2
flutter: 2021-11-28 10:24:18.613557: 第8个调用， number = 2
flutter: 2021-11-28 10:24:18.618459: 第2个调用， number = 2
flutter: 2021-11-28 10:24:18.645893: 第6个调用， number = 2
flutter: 2021-11-28 10:24:18.669942: 第10个调用， number = 2
flutter: 2021-11-28 10:24:18.674398: 第9个调用， number = 2
flutter: 2021-11-28 10:24:23.296321: Sleep end，number = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;确实是乱序执行的，符合多线程的特征&lt;/li&gt;
&lt;li&gt;number 值全部都只 +1 了，都是2
&lt;ul&gt;
&lt;li&gt;这里就关系到前文有提到的 Isolate 的特性之一：
&lt;ul&gt;
&lt;li&gt;含有独立的内存空间&lt;/li&gt;
&lt;li&gt;这里每个 Isolate 内的 number 都是独立的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;线程间通讯&lt;/h3&gt;
&lt;p&gt;这里的通讯需要通过端口 Port 来进行，并且完成后需要手动释放。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() async {
  print(&apos;${DateTime.now()}: 1&apos;);

  // 创建端口
  ReceivePort port = ReceivePort();
  // Isolate
  Isolate iso = await Isolate.spawn(isolateFunc, port.sendPort);
  // 监听数据变化
  port.listen((message) {
    number = message;
    print(&apos;${DateTime.now()}: 监听到 number = $number&apos;);
    // 关闭端口
    port.close();
    // 销毁 Isolate
    iso.kill();
  });
}

void isolateFunc(SendPort send) {
  sleep(Duration(seconds: 3));
  send.send(15);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-28 10:38:59.982321: 1
flutter: 2021-11-28 10:39:03.114209: 监听到 number = 15
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;compute&lt;/h2&gt;
&lt;p&gt;compute 是对 Isolate 的封装可以达到一样的效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 0;

void main() async {
  print(&apos;${DateTime.now()}: 1&apos;);

  compute(comFunc, 1);
  compute(comFunc, 2);
  compute(comFunc, 3);
  compute(comFunc, 4);
  compute(comFunc, 5);
  compute(comFunc, 6);
  compute(comFunc, 7);
  compute(comFunc, 8);
  compute(comFunc, 9);
  compute(comFunc, 10);
}

void comFunc(int value) {
  number = value;
  print(&apos;${DateTime.now()}: value = $value number = $number&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-28 10:54:03.331916: value = 6 number = 6
flutter: 2021-11-28 10:54:03.337162: value = 5 number = 5
flutter: 2021-11-28 10:54:03.382666: value = 4 number = 4
flutter: 2021-11-28 10:54:03.404757: value = 1 number = 1
flutter: 2021-11-28 10:54:03.406688: value = 7 number = 7
flutter: 2021-11-28 10:54:03.424210: value = 3 number = 3
flutter: 2021-11-28 10:54:03.433246: value = 8 number = 8
flutter: 2021-11-28 10:54:03.447492: value = 2 number = 2
flutter: 2021-11-28 10:54:03.494157: value = 9 number = 9
flutter: 2021-11-28 10:54:03.495839: value = 10 number = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;进程间通讯&lt;/h3&gt;
&lt;p&gt;可以通过返回值的形式进行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 0;

void main() async {
  number = await compute(comFunc, 1);

  print(number);
}

int comFunc(int value) {
  sleep(Duration(seconds: 2));
  return number + value;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a&lt;/p&gt;
&lt;p&gt;https://www.youtube.com/watch?v=vl_AaCgudcY&amp;amp;t=53s&lt;/p&gt;
</content:encoded></item><item><title>异步编程之Future</title><link>https://blog.lab-lawliet.app/posts/flutter-18-future-fe46046540af32e0/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-18-future-fe46046540af32e0/</guid><description>下面一段代码的执行顺序是很明显的</description><pubDate>Fri, 26 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;代码执行顺序&lt;/h2&gt;
&lt;p&gt;下面一段代码的执行顺序是很明显的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() {
  for(int i = 0; i &amp;lt; 1000000000; i++) {
    number += 1;
  }
  print(&apos;${DateTime.now()}  $number&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺序输出没有什么问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 19:53:59.727819 start: 1
flutter: 2021-11-26 19:54:02.463404  1000000001
flutter: 2021-11-26 19:54:02.463815 end: 1000000001
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;async&lt;/h2&gt;
&lt;p&gt;加上 &lt;code&gt;async&lt;/code&gt; 呢？是不是就变成异步的了？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() async {
  for(int i = 0; i &amp;lt; 1000000000; i++) {
    number += 1;
  }
  print(&apos;${DateTime.now()}  $number&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并没有什么变化，依然按顺序同步执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 19:57:19.945126 start: 1
flutter: 2021-11-26 19:57:22.676219  1000000001
flutter: 2021-11-26 19:57:22.678135 end: 1000000001
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;在上文&lt;a href=&quot;/posts/flutter-16-6cfa9e3d381da23f/&quot;&gt;16.第三方库导入与网络数据异步请求与展示&lt;/a&gt;中我们的有用到 Future 进行异步网络请求。这里我们模拟网络请求，将耗时操作进行包装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() {
  Future( () {
    for(int i = 0; i &amp;lt; 1000000000; i++) {
      number += 1;
    }
    print(&apos;${DateTime.now()}  $number&apos;);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过打印我们发现这里的确是异步执行的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 20:01:22.301321 start: 1
flutter: 2021-11-26 20:01:22.306180 end: 1
flutter: 2021-11-26 20:01:25.041822  1000000001
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Future是什么&lt;/h3&gt;
&lt;p&gt;通过查看源码我们能够发现。它是一个工厂构造函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  factory Future(FutureOr&amp;lt;T&amp;gt; computation()) {
    _Future&amp;lt;T&amp;gt; result = new _Future&amp;lt;T&amp;gt;();
    Timer.run(() {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    });
    return result;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;async - await&lt;/h3&gt;
&lt;p&gt;思考一下，下面代码的执行顺序会是什么样呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nt number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() async {
  await Future( () {
    for(int i = 0; i &amp;lt; 1000000000; i++) {
      number += 1;
    }
    print(&apos;${DateTime.now()}  $number&apos;);
  });
  print(&apos;doSomeThing结束&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里进行一下简单的分析：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;doSomeThing&lt;/code&gt; 内
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Future&lt;/code&gt; 是异步执行的&lt;/li&gt;
&lt;li&gt;但 &lt;code&gt;Future&lt;/code&gt; 加了 &lt;code&gt;await&lt;/code&gt;，那么在当前作用域中，后面的代码就会被阻塞。
&lt;ul&gt;
&lt;li&gt;即 &lt;code&gt;print(&apos;doSomeThing结束&apos;);&lt;/code&gt; 这段代码会等待 &lt;code&gt;Future&lt;/code&gt; 内的任务完成才执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;main 函数内
&lt;ul&gt;
&lt;li&gt;三行代码执行是按顺序执行的，不会受 &lt;code&gt;doSomeThing&lt;/code&gt; 内部的 await 的影响&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;按照预期输出应为：
&lt;ul&gt;
&lt;li&gt;start&lt;/li&gt;
&lt;li&gt;end&lt;/li&gt;
&lt;li&gt;&lt;code&gt;print(&apos;${DateTime.now()}  $number&apos;);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;doSomeThing结束&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行验证✅&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 20:09:46.105049 start: 1
flutter: 2021-11-26 20:09:46.114976 end: 1
flutter: 2021-11-26 20:09:48.872681  1000000001
flutter: doSomeThing结束
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;async 和 await 是成对出现的。你也可以试试只写 await，看看是什么效果。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;then 与任务的返回值&lt;/h3&gt;
&lt;p&gt;对 &lt;code&gt;Future&lt;/code&gt; 有所了解的话，知道通过 &lt;code&gt;then&lt;/code&gt; 我们可以在任务完成的时候进行一些操作。下面对代码嫂做修改：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() async {
  await Future( () {
    for(int i = 0; i &amp;lt; 1000000000; i++) {
      number += 1;
    }
    print(&apos;${DateTime.now()}  $number&apos;);
  }).then( (value) {
    print(&apos;then: ${DateTime.now()}  $value&apos;);
  });
  print(&apos;doSomeThing结束&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 20:20:39.641708 start: 1
flutter: 2021-11-26 20:20:39.649521 end: 1
flutter: 2021-11-26 20:20:42.388725  1000000001
flutter: then: 2021-11-26 20:20:42.390922  null
flutter: doSomeThing结束
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 &lt;code&gt;then&lt;/code&gt; 回调里的 &lt;code&gt;value&lt;/code&gt; 是空的，该怎样为 &lt;code&gt;value&lt;/code&gt; 赋值呢？很简单，直接在 &lt;code&gt;Future&lt;/code&gt; 的任务中 &lt;code&gt;return&lt;/code&gt; 就可以了&lt;/p&gt;
&lt;p&gt;这里再次修改代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int number = 1;

void main() {
  print(&apos;${DateTime.now()} start: $number&apos;);

  doSomeThing();

  print(&apos;${DateTime.now()} end: $number&apos;);
}

doSomeThing() async {
  await Future( () {
    for(int i = 0; i &amp;lt; 1000000000; i++) {
      number += 1;
    }
    return number;
  }).then( (value) {
    print(&apos;then: ${DateTime.now()}  $value&apos;);
  });
  print(&apos;doSomeThing结束&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出验证✅&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: 2021-11-26 20:23:45.843172 start: 1
flutter: 2021-11-26 20:23:45.850300 end: 1
flutter: then: 2021-11-26 20:23:48.584267  1000000001
flutter: doSomeThing结束
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;符合预期。&lt;/p&gt;
</content:encoded></item><item><title>请求数据保留</title><link>https://blog.lab-lawliet.app/posts/flutter-17-c5f15d02a20b78e5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-17-c5f15d02a20b78e5/</guid><description>前文中可以发现一个问题，每次切换页面，都会重新请求数据。而实际开发中，我们会对数据进行保留，在需要的时候再进行数据更新。</description><pubDate>Thu, 25 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;前文中可以发现一个问题，每次切换页面，都会重新请求数据。而实际开发中，我们会对数据进行保留，在需要的时候再进行数据更新。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/abc5752184331032.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Mixin&lt;/h2&gt;
&lt;h3&gt;调整继承关系&lt;/h3&gt;
&lt;p&gt;多继承 &lt;code&gt;AutomaticKeepAliveClientMixin&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MessagePage extends StatefulWidget {
  @override
  _MessagePageState createState() =&amp;gt; _MessagePageState();
}

class _MessagePageState extends State&amp;lt;MessagePage&amp;gt; with AutomaticKeepAliveClientMixin&amp;lt;MessagePage&amp;gt; {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;重写 wantKeepAlive&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class _MessagePageState extends State&amp;lt;MessagePage&amp;gt; with AutomaticKeepAliveClientMixin&amp;lt;MessagePage&amp;gt; {
    ...

    @override
    bool get wantKeepAlive =&amp;gt; true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;调整 build 方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(...);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;调整 MyHomePage （标签Tab页）&lt;/h3&gt;
&lt;p&gt;原先我们的切换Tab的逻辑是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _MyHomePageDataSource extends State&amp;lt;MyHomePage&amp;gt; {
  int _selectedTab = 0;
  final List&amp;lt;Widget&amp;gt; _pages = [
      MessagePage(), 
      ContactsPage(), 
      MommentPage(), 
      MinePage()
      ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_selectedTab],
      ...
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并非所有的子页面的 &lt;code&gt;widget&lt;/code&gt; 都在标签容器的 &lt;code&gt;widget&lt;/code&gt; 树中，导致每次切换都会去重新生成渲染。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们需要做如下调整：
&lt;ul&gt;
&lt;li&gt;构建 &lt;code&gt;PageController&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调整 &lt;code&gt;Body&lt;/code&gt; 为 &lt;code&gt;PageView&lt;/code&gt;， 并绑定 &lt;code&gt;PageController&lt;/code&gt; 和 &lt;code&gt;pages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;PageController&lt;/code&gt; 切换子页面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class _MyHomePageDataSource extends State&amp;lt;MyHomePage&amp;gt; {
  int _selectedTab = 0;
  final List&amp;lt;Widget&amp;gt; _pages = [
    MessagePage(),
    ContactsPage(),
    MommentPage(),
    MinePage()
  ];
  // 一、 构建 PageController
  final PageController _pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        // 二、 调整 Body 为 PageView， 并绑定 PageController 和 pages
      body: PageView(
        controller: _pageController,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        selectedItemColor: Colors.green,
        unselectedItemColor: Colors.grey,
        currentIndex: _selectedTab,
        onTap: (idx) {
          setState(() {
            _selectedTab = idx;
            // 三、 通过 PageController 切换子页面
            _pageController.jumpToPage(_selectedTab);
          });
        },
        items: const [...],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;检查效果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/172093cd82f2fe01.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;PageView&lt;/h2&gt;
&lt;h3&gt;滑动切换子页面&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e99dac04857e9cf.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在使用总突然发现可以通过滑动切换 PageView 的子页面。但是底部的标签选中没有变化，下面我们来处理一下。&lt;/p&gt;
&lt;h3&gt;同步标签切换&lt;/h3&gt;
&lt;p&gt;通过 onPageChanged 来监听页面的切换，同步修改选中标签。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body: PageView(
        onPageChanged: (idx) {
          setState(() {
            _selectedTab = idx;
          });
        },
        controller: _pageController,
        children: _pages,
      ),
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;关闭滑动切换&lt;/h3&gt;
&lt;p&gt;当然，不需要滑动的话可以通过 physics 来进行关闭。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body: PageView(
    physics: const NeverScrollableScrollPhysics(),
    controller: _pageController,
    children: _pages,
),
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>悬浮菜单列表</title><link>https://blog.lab-lawliet.app/posts/flutter-14-c7257e833404a9ac/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-14-c7257e833404a9ac/</guid><description>1</description><pubDate>Tue, 23 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/content-assets/37ff4da0b240066b.gif&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MessagePage extends StatefulWidget {
  @override
  _MessagePageState createState() =&amp;gt; _MessagePageState();
}

class _MessagePageState extends State&amp;lt;MessagePage&amp;gt; {
  PopupMenuItem _buildPopItem(String title, IconData icon) {
    return PopupMenuItem(
      child: Row(
        children: [
          Icon(
            icon,
            color: Colors.white,
          ),
          SizedBox(width: 8,),
          Text(
            title,
            style: const TextStyle(color: Colors.grey),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(&apos;微信&apos;),
        actions: [
          PopupMenuButton(
            color: Colors.black,
            // 设置垂直偏移量
            offset: const Offset(0, 60),
            itemBuilder: (context) {
              return [
                _buildPopItem(&apos;发起群聊&apos;, Icons.chat_bubble),
                _buildPopItem(&apos;添加朋友&apos;, Icons.person_add),
                _buildPopItem(&apos;扫一扫&apos;, Icons.qr_code_scanner),
                _buildPopItem(&apos;收付款&apos;, Icons.check_box),
              ];
            },
          ),
        ],
      ),
      body: Container(
        color: Colors.yellow,
        child: const Image(image: AssetImage(&apos;images/公众号.png&apos;)),
      ),
    );
  }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Mock数据小技巧</title><link>https://blog.lab-lawliet.app/posts/flutter-15-mock-44fc380411daa443/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-15-mock-44fc380411daa443/</guid><description>http://rap2.taobao.org</description><pubDate>Tue, 23 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Mock 接口&lt;/h2&gt;
&lt;p&gt;http://rap2.taobao.org&lt;/p&gt;
&lt;h2&gt;Mock 用户&lt;/h2&gt;
&lt;p&gt;https://randomuser.me/documentation&lt;/p&gt;
&lt;h2&gt;Mock 生成规则&lt;/h2&gt;
&lt;p&gt;http://mockjs.com/examples.html#String&lt;/p&gt;
</content:encoded></item><item><title>第三方库导入与网络数据异步请求与展示</title><link>https://blog.lab-lawliet.app/posts/flutter-16-6cfa9e3d381da23f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-16-6cfa9e3d381da23f/</guid><description>在 iOS 开发中 我们经常使用 CocoaPods 等工具进行包管理。那么在 flutter 开发中又该如何进行呢？</description><pubDate>Tue, 23 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 iOS 开发中 我们经常使用 CocoaPods 等工具进行包管理。那么在 flutter 开发中又该如何进行呢？&lt;/p&gt;
&lt;h2&gt;一、 http&lt;/h2&gt;
&lt;p&gt;这是官方提供来一个网络库：&lt;a href=&quot;https://pub.dev/packages/http&quot;&gt;http&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;点击复制&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b1d0e6a137e6d9b4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.1 修改 pubspec.yaml 配置文件&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b39e20cd0d490d4a.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.2 更新依赖库&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8169e915aa3879dd.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.3 在使用的地方导入&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import &apos;package:http/http.dart&apos; as http;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 异步函数&lt;/h2&gt;
&lt;h3&gt;2.1 简单汇总Service&lt;/h3&gt;
&lt;p&gt;简单汇总一下Service，便于后续使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &apos;package:flutter/material.dart&apos;;

class ServiceURL {
  /// 会话列表
  static Uri conversationList = Uri.parse(&apos;xxxxxx&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 发送请求&lt;/h3&gt;
&lt;p&gt;这里简单选择在 initState 中进行网络请求，调用移步方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  void initState() {
    super.initState();
    _loadConversation();
  }

  /// 加载会话列表
  void _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    print(response.body);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里可以发现，和我们在 &lt;code&gt;iOS&lt;/code&gt; 开发中的网络请求的调用形式有很大差别。&lt;code&gt;iOS&lt;/code&gt;开发中我们都是通过回调的形式接收请求结果的。这里使用 &lt;code&gt;await&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;三、 数据模型转换&lt;/h2&gt;
&lt;p&gt;接收到网络数据后我们自然需要进行数据模型的转换。&lt;/p&gt;
&lt;h3&gt;3.1 定义数据模型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import &apos;package:flutter/material.dart&apos;;

class ConversationData {
  final String nick;
  final String avatar;
  final String lastestMessage;

  ConversationData({
    required this.nick,
    required this.avatar,
    required this.lastestMessage,
  });

  factory ConversationData.formMap(Map map) {
    return ConversationData(
      nick: map[&apos;nick&apos;],
      avatar: map[&apos;avatar&apos;],
      lastestMessage: map[&apos;lastest_message&apos;],
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 Json 转 Map&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
  List _conversations = [];
  bool _isLoading = false;

  /// 加载会话列表
  void _loadConversation() async {
    _isLoading = true;
    var response = await http.get(ServiceURL.conversationList);
    _isLoading = false;
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      final result = bodyMap[&apos;lists&apos;].map((item) {
        return ConversationData.formMap(item);
      }).toList();

      setState(() {
        _conversations = result;
      });
    } else {
      throw Exception(&apos;请求失败：${response.statusCode}&apos;);
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 构建 Cell&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Text
&lt;ul&gt;
&lt;li&gt;样式设置&lt;/li&gt;
&lt;li&gt;缩略方式设置&lt;/li&gt;
&lt;li&gt;设置最大宽度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Container
&lt;ul&gt;
&lt;li&gt;设置图片圆角&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class ConversationCell extends StatelessWidget {
  ConversationCell({required this.conversation});

  final ConversationData conversation;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      color: Colors.white,
      child: Stack(
        children: [
          Align(
            alignment: Alignment.center,
            child: Container(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const SizedBox(
                    width: 12,
                  ),
                  // 圆角
                  Container(
                    width: 48,
                    height: 48,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(4),
                      image: DecorationImage(
                          // 网络图片
                        image: NetworkImage(conversation.avatar),
                      ),
                    ),
                  ),
                  const SizedBox(
                    width: 12,
                  ),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const SizedBox(height: 12),
                      Text(
                        conversation.nick,
                        maxLines: 1,
                        style: const TextStyle(
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                      Container(
                          // 限制宽度
                        width: UIConfig.screenWidth(context) * 0.7,
                        child: Text(
                          conversation.lastestMessage,
                          maxLines: 1,
                          // 缩略样式
                          overflow: TextOverflow.ellipsis,
                          style: const TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                          ),
                        ),
                      )
                    ],
                  ),
                ],
              ),
            ),
          ),
          // 分割线
          Align(
            alignment: Alignment.bottomCenter,
            child: Container(
              margin: const EdgeInsets.only(left: 70),
              height: 0.5,
              color: Colors.grey,
            ),
          ),
        ],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 构建 ListView&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  Widget _itemForRow(BuildContext context, int index) {
      return ConversationCell(conversation: _conversations[index]);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      body: Container(
        color: Colors.yellow,
        child: _isLoading
            ? Center(
                child: Text(&apos;Loading&apos;),
              )
            : ListView.builder(
                itemBuilder: _itemForRow,
                itemCount: _conversations.length,
              ),
      ),
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 效果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fe819f586459c46d.gif&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 Future &amp;amp; FutureBuilder&lt;/h2&gt;
&lt;p&gt;除了按照上面这种我们在 iOS 开发中一样常规的思路进行请求和展示外，再介绍另一种可以处理这种场景。&lt;/p&gt;
&lt;h3&gt;4.1 请求方法修改&lt;/h3&gt;
&lt;p&gt;之前的加载方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  /// 加载会话列表
  void _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      
      _conversations = bodyMap[&apos;lists&apos;]
                            .map((item) {
                                return ConversationData.formMap(item);
                            })
                            .toList();

    } else {
      throw Exception(&apos;请求失败：${response.statusCode}&apos;);
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  /// 加载会话列表
  Future&amp;lt;List&amp;gt; _loadConversation() async {
    var response = await http.get(ServiceURL.conversationList);
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      return bodyMap[&apos;lists&apos;].map((item) {
        return ConversationData.formMap(item);
      }).toList();
    } else {
      throw Exception(&apos;请求失败：${response.statusCode}&apos;);
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 ListView 修改&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;AsyncSnapshot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;包含有绑定的 future 回调的数据，也包含异步任务的状态。我们可以用来判断是否展示加载页面。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      body: Container(
        color: Colors.yellow,
        child: FutureBuilder(
          // 传入 Future
          future: _loadConversation(),
          builder: (context, AsyncSnapshot snap){
            // 判断请求状态
            if (snap.connectionState != ConnectionState.done) {
              return const Center(
                child: Text(&apos;Loading&apos;),
              );
            }
            // 根据结果处理数据展示
            return ListView(
              children: snap.data.map&amp;lt;Widget&amp;gt;((item){
                return ConversationCell(conversation: item);
              }).toList(),
            );
          },
        ),
      ),
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 看看效果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/abc5752184331032.gif&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、 结合上面两种方式&lt;/h2&gt;
&lt;p&gt;结合上面两种方式，这里使用 Future 调整加载数据的函数。&lt;/p&gt;
&lt;h3&gt;5.1 请求函数修改&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  /// 加载会话列表
  Future&amp;lt;List&amp;gt; _loadConversation() async {
    _isLoading = true;
    var response = await http.get(ServiceURL.conversationList);
    _isLoading = false;
    if (response.statusCode == 200) {
      final bodyMap = json.decode(response.body);
      final result = bodyMap[&apos;lists&apos;].map((item) {
        return ConversationData.formMap(item);
      }).toList();

      return result;
    } else {
      throw Exception(&apos;请求失败：${response.statusCode}&apos;);
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 回调处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      // value 就是 Future 里 return 的结果
      setState(() {
        _conversations = value;
      });
    });
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 异常处理&lt;/h3&gt;
&lt;p&gt;通过 Future 我们也可以捕捉异常。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      setState(() {
        _conversations = value;
      });
    }).catchError((error) {
      print(error);
    });
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.4 设置超时&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  @override
  void initState() {
    super.initState();
    _loadConversation().then((value) {
      setState(() {
        _conversations = value;
      });
    }).catchError((error) {
      print(error);
    }).whenComplete(() {
      print(&apos;Done&apos;);
    }).timeout(Duration(seconds: 10));
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、 保留数据&lt;/h2&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;第一种方式更加接近我们 iOS 开发中的思路&lt;/li&gt;
&lt;li&gt;第二种方式 Future &amp;amp; FutureBuilder 更加充分利用 Future 特性&lt;/li&gt;
&lt;li&gt;而结合二者的方式则对我这个iOS刚入门Flutter的新手而言，更加熟悉同时也有一些新的体验&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/FlutterStudy/blob/master/fake_wechat/lib/widgets/message/message_page.dart&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>ListView滑动到指定位置</title><link>https://blog.lab-lawliet.app/posts/flutter-13-listview-fa2bd875bbb5c310/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-13-listview-fa2bd875bbb5c310/</guid><description>在进行列表视图的开发场景中，通过索引快速滑动到指定 section 是常见的功能。那么在 flutter 中我们该如何实现这种功能呢？ 本文就将以联系人页面为例进行讲解。</description><pubDate>Mon, 22 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在进行列表视图的开发场景中，通过索引快速滑动到指定 section 是常见的功能。那么在 flutter 中我们该如何实现这种功能呢？ 本文就将以联系人页面为例进行讲解。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f2a82996fab00f02.gif&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;分析ListView&lt;/h2&gt;
&lt;p&gt;在 iOS 开发中我们常使用 &lt;code&gt;open func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool)&lt;/code&gt; 这类方法来实现 CollectionView 和 TableView 下的滚动到指定位置的效果。&lt;/p&gt;
&lt;p&gt;但 flutter 的 ListView 并不含有这种分组的概念，也没有提供这种便利的方法，但是有基础API可以为我们提供一些思路。&lt;/p&gt;
&lt;h3&gt;ScrollController&lt;/h3&gt;
&lt;p&gt;我们可以在为 ListView 绑定一个 ScrollController ，它的作用可以理解为我们进行 iOS 开发中的 相关代理，但是他的作用会更富一些。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ba2a03dd56ca87de.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为来滚动到指定位置，这里提供了两个 API，更具体的说明可以点进头文件查看注释，这里就不再赘述。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;animateTo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有动画&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  Future&amp;lt;void&amp;gt; animateTo(
    double offset, {
    required Duration duration,
    required Curve curve,
  }) async {
    assert(_positions.isNotEmpty, &apos;ScrollController not attached to any scroll views.&apos;);
    await Future.wait&amp;lt;void&amp;gt;(&amp;lt;Future&amp;lt;void&amp;gt;&amp;gt;[
      for (int i = 0; i &amp;lt; _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),
    ]);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;jumpTo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;无动画&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  void jumpTo(double value) {
    assert(_positions.isNotEmpty, &apos;ScrollController not attached to any scroll views.&apos;);
    for (final ScrollPosition position in List&amp;lt;ScrollPosition&amp;gt;.from(_positions))
      position.jumpTo(value);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里为了实现通讯录滑动到组头部的效果，选择使用 &lt;code&gt;jumpTo&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;提前计算好每组的偏移量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class _ContactsPageState extends State&amp;lt;ContactsPage&amp;gt; {
  final List&amp;lt;Friends&amp;gt; _contacts = [];// 联系人数组
  final List&amp;lt;Friends&amp;gt; _systems = []; // 顶部系统入口数组
  final List&amp;lt;String&amp;gt; _indexs = []; // 索引
  ScrollController _scroller = ScrollController();
  Map&amp;lt;int, double&amp;gt; _indexOffsets = {}; // 偏移量Map
  
  @override
  void initState() {
    super.initState();

    double offsetY = 0;

    _contacts
      ..addAll(friendsData)
      ..addAll(friendsData)
      ..sort((a, b) {
        if (a.indexLetter == null || b.indexLetter == null) {
          return 0;
        }
        return a.indexLetter!.compareTo(b.indexLetter!);
      });

    _systems
        .addAll(friendsHeaderData);

    offsetY = ContactCell.baseCellHeight * friendsHeaderData.length.toDouble();// 第一个联系人组的顶部偏移量为顶部系统入口的总高度

    var index = 0;
    
    _contacts.forEach((element) {
      if (element.indexLetter != null &amp;amp;&amp;amp; _indexs.contains(element.indexLetter) == false) {
        _indexs.add(element.indexLetter!);

        _indexOffsets.addAll({ index : offsetY });

        index += 1;
        // 基础Cell高度，加上顶部分组头部高度
        offsetY += ContactCell.baseCellHeight + ContactCell.indexHeight;
      }
      else {
        // 基础Cell高度
        offsetY += ContactCell.baseCellHeight;
      }
    });
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;IndexBar的事件处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;IndexBar(
    dataSource: _indexs,
    callBack: (index, title) {
        if (_indexOffsets[index] != null) {
        _scroller.jumpTo(_indexOffsets[index]!);
        }
    },
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;项目完整代码&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/FlutterStudy/tree/master/fake_wechat&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>索引条：手势及clamp函数</title><link>https://blog.lab-lawliet.app/posts/flutter-12-clamp-0a84640eebed222b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-12-clamp-0a84640eebed222b/</guid><description>在进行类似联系人页面的开发过程中，我们经常会遇到侧边栏索引条。在iOS中我们只需要简单设置即可使用系统提供的控件。</description><pubDate>Sun, 21 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在进行类似联系人页面的开发过程中，我们经常会遇到侧边栏索引条。在iOS中我们只需要简单设置即可使用系统提供的控件。&lt;/p&gt;
&lt;p&gt;这里我们使用 Flutter 自定义一个索引条，来熟悉一些常用的知识点。&lt;/p&gt;
&lt;h2&gt;一、 一个索引条包含什么？&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据源
&lt;ul&gt;
&lt;li&gt;索引数组&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;点击手势&lt;/li&gt;
&lt;li&gt;滑动手势&lt;/li&gt;
&lt;li&gt;事件回调&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 开始布局&lt;/h2&gt;
&lt;p&gt;索引条展示的数据是可变的，所以这里我们使用&lt;code&gt;有状态的Widget&lt;/code&gt;进行布局。这里以 &lt;code&gt;Container&lt;/code&gt; + &lt;code&gt;Column&lt;/code&gt; 进行基础封装，看看效果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &apos;package:flutter/material.dart&apos;;

class IndexBar extends StatefulWidget {
  IndexBar({
    required this.dataSource,
  });

  final List&amp;lt;String&amp;gt; dataSource;
  final double _indexItemHeight = 22;

  @override
  _IndexBarState createState() =&amp;gt; _IndexBarState();
}

class _IndexBarState extends State&amp;lt;IndexBar&amp;gt; {
  List&amp;lt;Widget&amp;gt; _indexsWidgets = [];

  @override
  void initState() {
    super.initState();

    for (int i = 0; i &amp;lt; widget.dataSource.length; i++) {
      _indexsWidgets.add(Container(
        height: widget._indexItemHeight,
        child: Text(
          widget.dataSource[i],
          style: const TextStyle(color: Colors.grey),
        ),
      ));
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: (details) {
        RenderBox box = context.findRenderObject() as RenderBox;
        Offset point = box.globalToLocal(details.globalPosition);
        double y = point.dy;
        // 在当前 Widget 内的 Offset
        print(y);
      },
      child: Container(
        width: 22,
        color: const Color.fromRGBO(1, 1, 1, 0.2),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: _indexsWidgets,
        ),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基础使用，传入索引数组：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
      body: Stack(
        children: [
          Container(...),
          Align(
            alignment: Alignment.centerRight,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IndexBar(dataSource: _indexs)
              ],
            ),
          ),
        ],
      )
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8125f2a95a927061.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们完成了基础布局，和数据源的传入，下面我们继续完善。&lt;/p&gt;
&lt;h2&gt;三、 点击事件&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2d26d873269ada98.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们需要定位到具体点击了哪个 index 所以 onTap 不能够满足，这里通过注释我们可以发现 &lt;code&gt;onTapUp&lt;/code&gt; 回调带有更多信息。我们使用这个进行点击事件的处理&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;typedef GestureTapUpCallback = void Function(TapUpDetails details);&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/72e1d96787f9f08f.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在我们能够获取点击坐标了，接下来进行计算获得具体 index。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/79a45b87a4c99e35.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: L
flutter: N
flutter: E
flutter: B
flutter: J
flutter: L
flutter: J
flutter: K
flutter: N
flutter: J
flutter: F
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、 滑动事件&lt;/h2&gt;
&lt;p&gt;和点击事件类似，这里我们添加一下滑动选择事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;onVerticalDragUpdate: (details) {
  RenderBox box = context.findRenderObject() as RenderBox;
  Offset point = box.globalToLocal(details.globalPosition);
  print(&apos;onVerticalDragUpdate${widget.dataSource[_currentIndex(point)]}&apos;);
},
onTapUp: (details) {
  RenderBox box = context.findRenderObject() as RenderBox;
  Offset point = box.globalToLocal(details.globalPosition);
  print(&apos;onTapUp${widget.dataSource[_currentIndex(point)]}&apos;);
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试有效：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flutter: onVerticalDragUpdateF
flutter: onVerticalDragUpdateF
flutter: onVerticalDragUpdateH
flutter: onVerticalDragUpdateH
flutter: onVerticalDragUpdateH
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、 事件回调&lt;/h2&gt;
&lt;p&gt;内部的事件处理完成了，我们来来添加一下外部的回调：&lt;/p&gt;
&lt;h3&gt;定义回调类型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;typedef IndexBarSelectCallBack = void Function(int index,  String title);

class IndexBar extends StatefulWidget {
  IndexBar({
    required this.dataSource,
    required this.callBack
  });

  final List&amp;lt;String&amp;gt; dataSource;
  final double _indexItemHeight = 22;
  final IndexBarSelectCallBack callBack;

  @override
  _IndexBarState createState() =&amp;gt; _IndexBarState();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;调整事件处理及回调&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;onVerticalDragUpdate: (details) {
  RenderBox box = context.findRenderObject() as RenderBox;
  Offset point = box.globalToLocal(details.globalPosition);
  int index = _currentIndex(point);
  String title = widget.dataSource[index];
  widget.callBack(index, title);
},
onTapUp: (details) {
  RenderBox box = context.findRenderObject() as RenderBox;
  Offset point = box.globalToLocal(details.globalPosition);
  int index = _currentIndex(point);
  String title = widget.dataSource[index];
  widget.callBack(index, title);
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;外部传入回调&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/39522f076ce633e1.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;六、 Clamp 函数&lt;/h2&gt;
&lt;p&gt;在滑动偏移过大超出范围的时候会发生越界，出现下面的警告&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RangeError (index): Invalid value: Not in inclusive range 0..10: 14&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里安利一个很好用的函数 clamp 来对方法 &lt;code&gt;_currentIndex(Offset point)&lt;/code&gt; 进行一下优化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int _currentIndex(Offset point) {
  return (point.dy ~/ widget._indexItemHeight).clamp(0, widget.dataSource.length - 1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就对返回的值的范围作出了限制。&lt;/p&gt;
&lt;h2&gt;七、 最终代码&lt;/h2&gt;
&lt;p&gt;https://github.com/RyukieSama/FlutterStudy/blob/master/fake_wechat/lib/widgets/contacts/index_bar.dart&lt;/p&gt;
</content:encoded></item><item><title>有状态Widget初始化重写&amp;链式调用&amp;排序</title><link>https://blog.lab-lawliet.app/posts/flutter-11-widget-6798a86aefa72e53/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-11-widget-6798a86aefa72e53/</guid><description>前文我们通过联系人页面聊到了断言的使用，本文将继续结合该场景了机更多 Flutter 开发小技巧。</description><pubDate>Sat, 20 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;前文我们通过联系人页面聊到了断言的使用，本文将继续结合该场景了机更多 &lt;code&gt;Flutter&lt;/code&gt; 开发小技巧。&lt;/p&gt;
&lt;h2&gt;初始化方法重写&lt;/h2&gt;
&lt;p&gt;对于一个 StatefulWidget 我们如果需要在其初始化的时候进行一些操作的话，可以通过重写 &lt;code&gt;initState()&lt;/code&gt; 来达到效果。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _ContactsPageState extends State&amp;lt;ContactsPage&amp;gt; {

  @override
  void initState() {
    super.initState();

    // 添加数据
    _contacts
        .addAll(friendsData);

    _systems
        .addAll(friendsHeaderData);
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;链式调用&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;iOS&lt;/code&gt; 开发中，比如使用 &lt;code&gt;SnapKit&lt;/code&gt; 进行UI控件约束布局的时候，我们经常会用到这样的&lt;code&gt;链式调用&lt;/code&gt;，非常爽。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make.leading.trailing.top.equalToSuperview()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实在 Flutter 开发中我们也可以使用类似的写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@override
  void initState() {
    super.initState();

    // 链式调用 多次添加元素
    _contacts
      ..addAll(friendsData)
      ..addAll(friendsData);

    _systems.addAll(friendsHeaderData);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;排序&lt;/h2&gt;
&lt;p&gt;在上面可以看到 &lt;code&gt;..addAll(friendsData)&lt;/code&gt; 这样连续进行了调用。那么是否可以做的更多呢？&lt;/p&gt;
&lt;p&gt;通常联系人列表我们会进行按字母排序，这里通过链式调用进行尝试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@override
  void initState() {
    super.initState();
    _contacts
      ..addAll(friendsData)
      ..addAll(friendsData)
      ..sort((a, b) {
        if (a.indexLetter == null || b.indexLetter == null) {
          return 0;
        }
        return a.indexLetter!.compareTo(b.indexLetter!);
      });
    _systems.addAll(friendsHeaderData);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;和 Swift sort 的不同&lt;/h3&gt;
&lt;p&gt;这里你可能会发现，这里的 &lt;code&gt;sort&lt;/code&gt; 函数和 &lt;code&gt;Swift&lt;/code&gt; 的略有不同，内部回调的返回值不是 &lt;code&gt;bool&lt;/code&gt; 而是 &lt;code&gt;int&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;结合函数头文件注释我们可以了解更多：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  /// Sorts this list according to the order specified by the [compare] function.
  ///
  /// The [compare] function must act as a [Comparator].
  /// ```dart
  /// var numbers = [&apos;two&apos;, &apos;three&apos;, &apos;four&apos;];
  /// // Sort from shortest to longest.
  /// numbers.sort((a, b) =&amp;gt; a.length.compareTo(b.length));
  /// print(numbers);  // [two, four, three]
  /// ```
  /// The default [List] implementations use [Comparable.compare] if
  /// [compare] is omitted.
  /// ```dart
  /// List&amp;lt;int&amp;gt; nums = [13, 2, -11];
  /// nums.sort();
  /// print(nums);  // [-11, 2, 13]
  /// ```
  /// In that case, the elements of the list must be [Comparable] to
  /// each other.
  ///
  /// A [Comparator] may compare objects as equal (return zero), even if they
  /// are distinct objects.
  /// The sort function is not guaranteed to be stable, so distinct objects
  /// that compare as equal may occur in any order in the result:
  /// ```dart
  /// var numbers = [&apos;one&apos;, &apos;two&apos;, &apos;three&apos;, &apos;four&apos;];
  /// numbers.sort((a, b) =&amp;gt; a.length.compareTo(b.length));
  /// print(numbers);  // [one, two, four, three] OR [two, one, four, three]
  /// ```
  void sort([int compare(E a, E b)?]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;简略写法&lt;/h3&gt;
&lt;p&gt;这里的排序函数可以在条件比较简单的情况下可以简写，提高编码效率&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;未简写&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sort((a, b) {
  if (a.indexLetter == null || b.indexLetter == null) {
    return 0;
  }
  return a.indexLetter!.compareTo(b.indexLetter!);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;简写&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sort( (a, b) =&amp;gt; a.indexLetter.compareTo(b.indexLetter) );
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;头文件注释中有这样简写说明，平时多阅读注释，可以帮助我们写出更有语言特色风格的代码哦～&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Cell点击跳转</title><link>https://blog.lab-lawliet.app/posts/flutter-08-cell-620ff881eab5538c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-08-cell-620ff881eab5538c/</guid><description>在 iOS 开发中，TableView 的 cell 的点击事件是通过代理处理的。那么在 Flutter 中，我们又该怎么处理呢？</description><pubDate>Thu, 18 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 iOS 开发中，TableView 的 cell 的点击事件是通过代理处理的。那么在 Flutter 中，我们又该怎么处理呢？&lt;/p&gt;
&lt;p&gt;首先我们看看点击事件一般是怎样处理的。&lt;/p&gt;
&lt;h2&gt;点击事件&lt;/h2&gt;
&lt;p&gt;这里我们可以使用 &lt;code&gt;GestureDetector&lt;/code&gt; widget 来包装需要处理点击事件的 widget 。&lt;/p&gt;
&lt;p&gt;事件回调 onTap 本质是一个无参数的函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// A tap with a primary button has occurred.
///
/// This triggers when the tap gesture wins. If the tap gesture did not win,
/// [onTapCancel] is called instead.
///
/// See also:
///
///  * [kPrimaryButton], the button this callback responds to.
///  * [onTapUp], which is called at the same time but includes details
///    regarding the pointer position.
final GestureTapCallback? onTap;

/// Signature for when a tap has occurred.
///
/// See also:
///
///  * [GestureDetector.onTap], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
typedef GestureTapCallback = void Function();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样我们就可以通过为 Cell 添加一个回调类型的成员，来传入事件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MomentHomeCell extends StatelessWidget {
  final String title;
  final String? subTitle;
  final String imageName;
  final String? subImageName;
  final GestureTapCallback? didSelected;

  MomentHomeCell(
      {required this.title,
      this.subTitle,
      required this.imageName,
      this.subImageName,
      this.didSelected});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: didSelected,
      child: ...,
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用的地方就可以直接传入事件了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MomentHomeCell(
              title: &apos;朋友圈&apos;,
              imageName: &apos;images/朋友圈.png&apos;,
              didSelected: () {
                print(&apos;111&apos;);
              },
            ),
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;页面跳转 Navigator&lt;/h2&gt;
&lt;p&gt;在 iOS 中我们都是通过 NavigationController 来进行页面跳转的。在 Flutter 中就要用到 Navigator 这个 widget 来进行处理了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _MommentPageState extends State&amp;lt;MommentPage&amp;gt; {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        ...
      ),
      body: Container(
        color: Colors.brown,
        child: ListView(
          children: &amp;lt;Widget&amp;gt;[
            MomentHomeCell(
              title: &apos;朋友圈&apos;,
              imageName: &apos;images/朋友圈.png&apos;,
              didSelected: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                      builder: (BuildContext context) =&amp;gt; MomentDetailPage(title: &apos;朋友圈&apos;,),
                  )
                );
              },
            ),
            ...
          ],
        ),
      ),
    );
  }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>代码规范</title><link>https://blog.lab-lawliet.app/posts/flutter-09-dac123affc2ac82f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-09-dac123affc2ac82f/</guid><description>1</description><pubDate>Thu, 18 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;常见警告&lt;/h2&gt;
&lt;h3&gt;Name non-constant identifiers using lowerCamelCase.&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e655989d4da95bb4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如上图，警告。这里不是 const 不能使用大写开头的驼峰。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DO&lt;/strong&gt; name non-constant identifiers using lowerCamelCase.&lt;/p&gt;
&lt;p&gt;Class members, top-level definitions, variables, parameters, named parameters and named constructors should capitalize the first letter of each word except the first word, and use no separators.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GOOD:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var item;

HttpRequest httpRequest;

align(clearItems) {
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;https://dart-lang.github.io/linter/lints/non_constant_identifier_names.html&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>通过联系人Cell看断言</title><link>https://blog.lab-lawliet.app/posts/flutter-10-cell-9638015837de0623/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-10-cell-9638015837de0623/</guid><description>我们在构建如微信联系人页类似的 Cell 时，主要有两种。一种是加载网络图片，用来现实我们的联系人；一种是本地图片，用来显示一些系统入口，如公众号等。那么如何优雅的完成这样对两种形式数据展示的 Cell 呢？借助这个场景，我们来了解一下 断言 。</description><pubDate>Thu, 18 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;我们在构建如微信联系人页类似的 Cell 时，主要有两种。一种是加载网络图片，用来现实我们的联系人；一种是本地图片，用来显示一些系统入口，如公众号等。那么如何优雅的完成这样对两种形式数据展示的 Cell 呢？借助这个场景，我们来了解一下 &lt;code&gt;断言&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9791de589ba53721.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;不使用断言&lt;/h2&gt;
&lt;p&gt;这样的代码，并没有什么明显问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通联系人传入 avatarUrl 网络图片&lt;/li&gt;
&lt;li&gt;系统入口传入 assetName 资源名称&lt;/li&gt;
&lt;li&gt;avatarUrl 和 assetName 都是可选字段，即均可为空&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样的隐患是无法保证至少有一个是有值的。下面我们使用断言进行优化，如果两者都为空就会抛出异常。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _ContactCell extends StatelessWidget {
  const _ContactCell({
    required this.name,
    this.avatarUrl,
    this.assetName,
  });

  final String name;
  final String? avatarUrl;
  final String? assetName;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      color: Colors.white,
      child: Stack(
        children: [
          Row(
            children: [
              Row(
                children: [
                  const SizedBox(
                    width: 12,
                  ),
                  avatarUrl != null
                      ? Container(
                          width: 48,
                          height: 48,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(4),
                            image: DecorationImage(
                              image: NetworkImage(avatarUrl!),
                            ),
                          ),
                        )
                      : Container(),
                  assetName != null
                      ? Container(
                          width: 48,
                          height: 48,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(4),
                            image: DecorationImage(
                              image: AssetImage(assetName!),
                            ),
                          ),
                        )
                      : Container(),
                  const SizedBox(
                    width: 12,
                  ),
                  Text(
                    name,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ],
              ),
            ],
          ),
          Positioned(
            left: 50,
            bottom: 0,
            child: Container(
              height: 1,
              color: Colors.grey,
            ),
          ),
        ],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用断言进行优化&lt;/h2&gt;
&lt;p&gt;这里在构造方法加了断言： &lt;code&gt;assert((avatarUrl != null || assetName != null), &apos;至少一张图片&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果两者都为空，那么就会抛出如图异常：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/188a38050066cd3f.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _ContactCell extends StatelessWidget {
  const _ContactCell({
    required this.name,
    this.avatarUrl,
    this.assetName,
  }) : assert((avatarUrl != null || assetName != null), &apos;至少一张图片&apos;);

  final String name;
  final String? avatarUrl;
  final String? assetName;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      color: Colors.white,
      child: Stack(
        children: [
          Row(
            children: [
              Row(
                children: [
                  const SizedBox(
                    width: 12,
                  ),
                  avatarUrl != null
                      ? Container(
                          width: 48,
                          height: 48,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(4),
                            image: DecorationImage(
                              image: NetworkImage(avatarUrl!),
                            ),
                          ),
                        )
                      : Container(),
                  assetName != null
                      ? Container(
                          width: 48,
                          height: 48,
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(4),
                            image: DecorationImage(
                              image: AssetImage(assetName!),
                            ),
                          ),
                        )
                      : Container(),
                  const SizedBox(
                    width: 12,
                  ),
                  Text(
                    name,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ],
              ),
            ],
          ),
          Positioned(
            left: 50,
            bottom: 0,
            child: Container(
              height: 1,
              color: Colors.grey,
            ),
          ),
        ],
      ),
    );
  }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Positioned与Container嵌套无法充满容器</title><link>https://blog.lab-lawliet.app/posts/flutter-07-positioned-container-cf40339ce401af53/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-07-positioned-container-cf40339ce401af53/</guid><description>是这样的，写好了Cell，想给Cell添加分割线，我自己在iOS开发中一般都是直接加在Cell里面的。这里我也准备这样实现。</description><pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题&lt;/h2&gt;
&lt;p&gt;是这样的，写好了Cell，想给Cell添加分割线，我自己在iOS开发中一般都是直接加在Cell里面的。这里我也准备这样实现。&lt;/p&gt;
&lt;p&gt;前面有使用过 Position ，思考通过 Stack + Position + Container 理论上可以实现。于是有了下面的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      height: 44,
      child: Stack(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Container(
                padding: const EdgeInsets.all(10),
                color: Colors.green,
                child: Row(
                  children: [
                    Image(image: AssetImage(imageName)),
                    const SizedBox(
                      width: 8,
                    ),
                    Text(title),
                  ],
                ),
              ),
              Container(
                  color: Colors.yellow,
                  child: Row(
                    children: [
                      subTitle != null ? Text(subTitle!) : Container(),
                      subImageName != null
                          ? Image.asset(subImageName!)
                          : Container(),
                      Image.asset(
                        &apos;images/icon_right.png&apos;,
                        width: 15,
                      )
                    ],
                  ))
            ],
          ),
          Positioned(
            left: 48,
            bottom: 0,
            child: Container(
              height: 0.5,
              color: Colors.red,
            ),
          ),
        ],
      )
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里希望得到的效果是，左边留 48 空隙，右边贴边的分割线。但是时机效果并没有出现：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5c7b963cc9384568.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Align&lt;/h2&gt;
&lt;p&gt;这里改用 Align 就可以实现效果了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Align(
    alignment: Alignment.bottomCenter,
    child: Container(
        margin: EdgeInsets.only(left: 48),
        height: 0.5,
        color: Colors.grey,
    ),
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9d821c69d48a8451.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://cloud.tencent.com/developer/article/1402558&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>openURL</title><link>https://blog.lab-lawliet.app/posts/swiftui-08-openurl-60ed10c7e4cbfbd5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-08-openurl-60ed10c7e4cbfbd5/</guid><description>在 UIKit 中我们通常使用下面方式来打开 外部链接，那么在 SwiftUI 中我们该怎样使用呢？</description><pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;UIKit&lt;/code&gt; 中我们通常使用下面方式来打开 &lt;code&gt;外部链接&lt;/code&gt;，那么在 &lt;code&gt;SwiftUI&lt;/code&gt; 中我们该怎样使用呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UIApplication.shared.open(url, options: [ : ], completionHandler: nil)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一、 注入环境变量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@Environment(\.openURL) var openURL

... 

ZStack {
    Color.blue
    Text(&quot;App Store&quot;)
        .fontWeight(.medium)
        .foregroundColor(.white)
        .onTapGesture {
            openURL.callAsFunction(url) { accepted in }
        }
}

...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 Link&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Link(destination: url, label: {
    ZStack {
        Color.blue
        Text(&quot;App Store&quot;)
            .fontWeight(.medium)
            .foregroundColor(.white)
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/environmentvalues&quot;&gt;EnvironmentValues&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>BottomNavigationBar</title><link>https://blog.lab-lawliet.app/posts/flutter-04-bottomnavigationbar-8a2f53e1dc05cbab/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-04-bottomnavigationbar-8a2f53e1dc05cbab/</guid><description>开发一个应用，界面的开始大部分情况下都是一个底部标签控制器。而在Flutter中就是 BottomNavigationBar 。</description><pubDate>Sun, 07 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;开发一个应用，界面的开始大部分情况下都是一个&lt;code&gt;底部标签控制器&lt;/code&gt;。而在Flutter中就是 &lt;code&gt;BottomNavigationBar&lt;/code&gt; 。&lt;/p&gt;
&lt;h2&gt;一、 创建 BottomNavigationBar&lt;/h2&gt;
&lt;p&gt;这里我想创建一个类似微信的底部标签，Widget内我会这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.chat),
            label: &apos;微信&apos;,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.contact_mail),
            label: &apos;通讯录&apos;,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.nature_people),
            label: &apos;发现&apos;,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            label: &apos;我&apos;,
          ),
        ],
      ),
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;显示问题&lt;/h3&gt;
&lt;p&gt;看看效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b441ab499b87598c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;怎么什么都没有？仔细看下其实都是白色的，我们设置一下颜色看看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        selectedItemColor: Colors.green,
        unselectedItemColor: Colors.grey,
        ...
      ),
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c342dba70155ba95.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是选中的标签的样式会比较突出，怎么改成我们熟悉的样式呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.green,
        unselectedItemColor: Colors.grey,
        ...
      ),
    );
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/098e71328ee2290b.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 选中标签&lt;/h2&gt;
&lt;p&gt;这里明显是需要 StatefulWidget 的。通过 &lt;code&gt;BottomNavigationBar: onTap&lt;/code&gt; 修改选中的标签&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State&amp;lt;StatefulWidget&amp;gt; createState() {
    return _MyHomePageDataSource();
  }
}

class _MyHomePageDataSource extends State&amp;lt;MyHomePage&amp;gt; {
  int _selectedTab = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        ...
        currentIndex: _selectedTab,
        onTap: (idx) {
          setState(() {
            _selectedTab = idx;
          });
        },
        ...
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 页面切换&lt;/h2&gt;
&lt;p&gt;选中了标签我们也需要切换到对应的页面才行，而具体显示哪个页面是由 Scaffold 的 body 控制的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _MyHomePageDataSource extends State&amp;lt;MyHomePage&amp;gt; {
  int _selectedTab = 0;
  final List&amp;lt;Widget&amp;gt; _pages = [MessagePage(), ContactsPage(), MommentPage(), MinePage()];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_selectedTab],// 切换页面
      bottomNavigationBar: BottomNavigationBar(
        ...
        currentIndex: _selectedTab,
        onTap: (idx) {
          setState(() {
            _selectedTab = idx; // 切换选中标签
          });
        },
        ...
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;p&amp;gt;原文视频文件暂缺。&amp;lt;/p&amp;gt;&amp;lt;/video&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>MaterialApp</title><link>https://blog.lab-lawliet.app/posts/flutter-05-materialapp-6ef758007da53465/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-05-materialapp-6ef758007da53465/</guid><pubDate>Sun, 07 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;MaterialApp 一些小Tips&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.orange,// 导航背景
        splashColor: const Color.fromRGBO(1, 1, 1, 0), // 去掉点击波纹
      ),
      title: &quot;FakeWechat&quot;, // Android 任务中心标题
      home: MyHomePage(),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>android资源配置</title><link>https://blog.lab-lawliet.app/posts/flutter-06-android-8b25c1540263af52/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-06-android-8b25c1540263af52/</guid><description>android - app - src - main - AndroidManifest.xml - android:label</description><pubDate>Sun, 07 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 App名称&lt;/h2&gt;
&lt;p&gt;android - app - src - main - AndroidManifest.xml - android:label&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3a61b1d6c3532f50.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 图片资源&lt;/h2&gt;
&lt;p&gt;与 iOS 不同，安卓应用的图片资源不是按 1x 2x 3x 来区分的。&lt;/p&gt;
&lt;p&gt;android - app - src - main - res&lt;/p&gt;
&lt;p&gt;哦们可以看到以下目录：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/18c3f4516dbfd3d8.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mipmap-mdpi
&lt;ul&gt;
&lt;li&gt;相当于 1x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mipmap-hdpi
&lt;ul&gt;
&lt;li&gt;相当于 1.5x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mipmap-xhdpi
&lt;ul&gt;
&lt;li&gt;相当于 2x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mipmap-xxhdpi
&lt;ul&gt;
&lt;li&gt;相当于 3x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mipmap-xxxhdpi
&lt;ul&gt;
&lt;li&gt;相当于 4x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f9c6dc2a45442c65.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8827407f6b4f1d4a.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意： 命名不能驼峰，用下划线&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 启动页&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5cf4e1393adc3228.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 其他图片&lt;/h2&gt;
&lt;p&gt;修改 pubspec.yaml 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# To add assets to your application, add an assets section, like this:
# assets:
#   - images/a_dot_burr.jpeg
#   - images/a_dot_ham.jpeg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：这里格式需要严格对齐&lt;/p&gt;
&lt;p&gt;错误：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/abf258a2b2954059.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;正确：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f502c2c57d8df3c5.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里写入文件夹路径或者文件路径都可以&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;五、 使用图片&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Image(image: AssetImage(&apos;路径/xxxxxx.png&apos;))
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>状态管理</title><link>https://blog.lab-lawliet.app/posts/flutter-03-c873c0b30f9520d4/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-03-c873c0b30f9520d4/</guid><description>Flutter 的渲染模式是 增量渲染 ，当一部分有变化的时候就将原先的替换掉。所以页面元素没有必要拥有状态。但是这样对于时机开发并不友好，尤其对于数据驱动的复杂的页面。</description><pubDate>Sat, 06 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Flutter 的渲染模式是 增量渲染 ，当一部分有变化的时候就将原先的替换掉。所以页面元素没有必要拥有状态。但是这样对于时机开发并不友好，尤其对于数据驱动的复杂的页面。&lt;/p&gt;
&lt;p&gt;这样就出现了 &lt;code&gt;有状态的Widget&lt;/code&gt; ：数据逻辑与渲染逻辑拆分开来，数据保留，页面增量渲染。&lt;/p&gt;
&lt;h2&gt;StatefulWidget&lt;/h2&gt;
&lt;p&gt;在我们需要可变数据驱动的Widget时，需要两个重要的部分：由数据驱动渲染。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/31445d5262fcd53e.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import &apos;package:flutter/material.dart&apos;;

// 渲染逻辑
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State&amp;lt;StatefulWidget&amp;gt; createState() {
    return _MyStateDataSource();
  }
}

// 数据逻辑
class _MyStateDataSource extends State&amp;lt;MyStatefulWidget&amp;gt; {
  int _clickCount = 0;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: FloatingActionButton(
        child: Text(&quot;$_clickCount&quot;),
        onPressed: () {
          setState(() {// 触发状态改变，重新渲染
            _clickCount += 1;
          });
        },
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;增量渲染&lt;/h2&gt;
&lt;p&gt;你可能会想，改动一个小数据整个Widget都重新渲染，效率是不是很低啊？&lt;/p&gt;
&lt;p&gt;不用担心这里采用的是增量渲染，通过树状的结构实现只修改使用到对应数据的叶子Widget。&lt;/p&gt;
</content:encoded></item><item><title>初见Flutter</title><link>https://blog.lab-lawliet.app/posts/flutter-01-flutter-5f70aea5cc2f7495/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-01-flutter-5f70aea5cc2f7495/</guid><description>option + enter 即可快捷导入</description><pubDate>Wed, 03 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 Flutter的特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;不依赖原生UI
&lt;ul&gt;
&lt;li&gt;拥有独立的渲染引擎&lt;/li&gt;
&lt;li&gt;包体积会变大&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;界面更新逻辑和原生不一样（&lt;code&gt;增量渲染&lt;/code&gt;）
&lt;ul&gt;
&lt;li&gt;重新创建一个新的 &lt;code&gt;Widget&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么 &lt;code&gt;Flutter&lt;/code&gt; 中大量 &lt;code&gt;final&lt;/code&gt; 修饰的属性， &lt;code&gt;const&lt;/code&gt; 修饰的构造方法（&lt;code&gt;常量对象&lt;/code&gt;）
&lt;ul&gt;
&lt;li&gt;因为 &lt;code&gt;Flutter&lt;/code&gt; 的渲染逻辑，是&lt;code&gt;增量渲染&lt;/code&gt;。 &lt;code&gt;Widget&lt;/code&gt; 结构是&lt;code&gt;树状结构&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;想改变屏幕内容就直接改变 &lt;code&gt;Widget&lt;/code&gt; 对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;常量对象&lt;/code&gt; 的创建效率更高&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 万物皆 Widget&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Flutter的Widget分两类
&lt;ul&gt;
&lt;li&gt;有状态： StatefulWidget&lt;/li&gt;
&lt;li&gt;无状态： StatelessWidget&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 MaterialApp（App素材&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;home属性（主页面）
&lt;ul&gt;
&lt;li&gt;需要一个Widget&lt;/li&gt;
&lt;li&gt;Scaffold小部件
&lt;ul&gt;
&lt;li&gt;带有导航栏（appBar）的小部件&lt;/li&gt;
&lt;li&gt;导航栏可以设置文字、颜色。而且可以自定义Widget&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;body属性。
&lt;ul&gt;
&lt;li&gt;显示在导航栏下面的区域。传递一个Widget给他&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;debugShowCheckedModeBanner属性。
&lt;ul&gt;
&lt;li&gt;是否显示Debug标记（便于我们在调试版本中做操作）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;小Tips&lt;/h2&gt;
&lt;h3&gt;1. 未导入相关文件&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;option&lt;/code&gt; + &lt;code&gt;enter&lt;/code&gt; 即可快捷导入&lt;/p&gt;
&lt;h3&gt;2. 强退AS缓存的问题&lt;/h3&gt;
&lt;p&gt;清理下面的文件即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/66def4faad2fea75.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Layout</title><link>https://blog.lab-lawliet.app/posts/flutter-02-layout-6a715501b5c03c0c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/flutter-02-layout-6a715501b5c03c0c/</guid><description>Row 和 Column 可以通过调整主轴方向达到不同布局效果。</description><pubDate>Wed, 03 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 方向部件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Row
&lt;ul&gt;
&lt;li&gt;水平方向&lt;/li&gt;
&lt;li&gt;主轴方向：向右&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Column
&lt;ul&gt;
&lt;li&gt;垂直方向&lt;/li&gt;
&lt;li&gt;主轴方向：向下&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stack
&lt;ul&gt;
&lt;li&gt;屏幕内外方向&lt;/li&gt;
&lt;li&gt;主轴方向：向屏幕外&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 主轴方向调整&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Row&lt;/code&gt; 和 &lt;code&gt;Column&lt;/code&gt; 可以通过调整主轴方向达到不同布局效果。&lt;/p&gt;
&lt;h4&gt;1. 默认主轴方向&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;class MyRowColumn extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      alignment: Alignment(1, 0),// x 对 Row 没影响 y 对 Column 无效
      child: Column(
        children: [
          Container(
            child: Icon(Icons.add_a_photo),
            color: Colors.red,
            alignment: Alignment(0,0),
          ),
          Container(
            child: Icon(Icons.add_alarm),
            color: Colors.yellow,
            alignment: Alignment(-1,0),
          ),
          Container(
            child: Icon(Icons.add_alert),
            color: Colors.green,
            alignment: Alignment(1,0),
          ),
        ],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/debe3c6371cf795b.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;2. MainAxisAlignment.center&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c7ea7da166917c07.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/497172df0c7a24d2.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;3. MainAxisAlignment.end&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9245023f967e78f8.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;4. MainAxisAlignment.spaceAround&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0c9fe0ddfb6f02b9.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;5. MainAxisAlignment.start&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/162d455f2f863c19.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;6. MainAxisAlignment.spaceBetween&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/88ce51e70b8bea3d.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;7. MainAxisAlignment.spaceEvenly&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4e3577e52e0eb620.webp&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.3 交叉轴&lt;/h3&gt;
&lt;p&gt;交叉轴 是垂直与主轴方向的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyRowColumn extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.brown,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            child: Icon(
              Icons.add_a_photo,
              size: 100,
            ),
            color: Colors.red,
          ),
          Container(
            child: Icon(
              Icons.add_alarm,
              size: 50,
            ),
            color: Colors.yellow,
          ),
          Container(
            child: Icon(
              Icons.add_alert,
              size: 30,
            ),
            color: Colors.green,
          ),
        ],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/69f6582785a4d2db.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.4 Stack&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9277e6e79b7e91d1.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如下布局我们可以得到如图效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyStack extends StatelessWidget {
  const MyStack({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Stack(
        children: [
          Container(
            color: Colors.red,
            child: const Icon(
            Icons.add_alarm,
            size: 200,
            color: Colors.green,
            ),
          ),
          Container(
            color: Colors.green,
            child: const Icon(
            Icons.add_a_photo,
            size: 100,
            color: Colors.yellow,
            ),
          ),
          Container(
            color: Colors.black,
            child: const Icon(
            Icons.add_a_photo_rounded,
            size: 60,
            color: Colors.blue,
            ),
          ),
        ],
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1. 子Widget布局调整&lt;/h4&gt;
&lt;p&gt;我们可以发现，默认都是从左上角开始布局的。如果想要调整子Widget的位置我们该怎么做呢？&lt;/p&gt;
&lt;h4&gt;2. Positioned&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;A [Positioned] widget must be a descendant of a [Stack].&lt;/p&gt;
&lt;p&gt;只可用于 &lt;code&gt;Stack&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过 Positioned 我们可以调整 子Widget 相对 父Widget 的位置&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d0549807b7c67704.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Positioned(
    right: 12,
    top: 12,
    child: Container(
        color: Colors.black,
        child: const Icon(
        Icons.add_a_photo_rounded,
        size: 60,
        color: Colors.blue,
        ),
    ),
),
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. margin&lt;/h4&gt;
&lt;p&gt;外边距&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9558914718245734.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Container(
    margin: const EdgeInsets.only(left: 12),
    color: Colors.green,
    child: const Icon(
    Icons.add_a_photo,
    size: 100,
    color: Colors.yellow,
    ),
),
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. padding&lt;/h4&gt;
&lt;p&gt;内边距，通过对比可以明显发现红色区域被由内而外的撑大了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e0dcf7c9337fbb55.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Container(
    padding: const EdgeInsets.all(50),
    color: Colors.red,
    child: const Icon(
    Icons.add_alarm,
    size: 200,
    color: Colors.green,
    ),
),
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. Positioned 与 margin 的区别&lt;/h4&gt;
&lt;p&gt;从上面的效果可以看出，这里好像 Positioned 与 margin 的效果基本一致。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Positioned
&lt;ul&gt;
&lt;li&gt;尽可用于 Stack&lt;/li&gt;
&lt;li&gt;相对于父Widget的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;margin
&lt;ul&gt;
&lt;li&gt;外边距&lt;/li&gt;
&lt;li&gt;相对于相邻Widget的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 宽高比&lt;/h2&gt;
&lt;p&gt;用 &lt;code&gt;AspectRatio&lt;/code&gt; 可以来指定 宽高比&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果同时设定了宽高那么 &lt;code&gt;AspectRatio&lt;/code&gt; 就会失效&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c9d98aba14c61f8e.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyRatio extends StatelessWidget {
  const MyRatio({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Container(
        color: Colors.red,
        width: 100,
        child: const AspectRatio(aspectRatio: 1/3),
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>序列化二叉树</title><link>https://blog.lab-lawliet.app/posts/article-37-3a60850125e3c654/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-37-3a60850125e3c654/</guid><description>请实现两个函数，分别用来序列化和反序列化二叉树。</description><pubDate>Wed, 27 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;请实现两个函数，分别用来序列化和反序列化二叉树。&lt;/p&gt;
&lt;p&gt;你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑，你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。&lt;/p&gt;
&lt;p&gt;提示：输入输出格式与 LeetCode 目前使用的方式一致，详情请参阅 &lt;a href=&quot;https://support.leetcode-cn.com/hc/kb/article/1194353/&quot;&gt;LeetCode 序列化二叉树的格式&lt;/a&gt; 。 你并非必须采取这种方式，你也可以采用其他的方法解决这个问题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：root = [1,2,3,null,null,4,5]
输出：[1,2,3,null,null,4,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本题官方没有 Swift 用例。本体难度为困难。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;凭空想象是很难想到解决办法，我们用例子就很容易找到规律了。本题和 &lt;a href=&quot;/posts/article-32-i-f447a573547d396e/&quot;&gt;32-I.从上到下打印二叉树&lt;/a&gt; 较为相似。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;编码&lt;/code&gt; 的解题基本思路是逐层扫描。可采用辅助队列进行解决。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;解码&lt;/code&gt; 的思路和 &lt;code&gt;编码&lt;/code&gt; 相似，除了辅助队列，我们还需要额外一个指针进行辅助。&lt;/p&gt;
&lt;h2&gt;三、 代码&lt;/h2&gt;
&lt;h3&gt;3.1 编码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func serialize(root: TreeNode) -&amp;gt; String {
    var result = &quot;&quot;
    var queue: [TreeNode?] = [root]
    
    while queue.isEmpty == false {
        if let first = queue.first, let node = first {
            result.append((result.isEmpty ? &quot;\(node.val)&quot; : &quot;,\(node.val)&quot;))
            
            queue.append(node.left)
            queue.append(node.right)
        }
        else {
            result.append(&quot;,null&quot;)
        }
        
        queue.removeFirst()
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 解码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func deserialize(data: String) -&amp;gt; TreeNode? {
    let array: [String] = data.components(separatedBy: &quot;,&quot;)
    var nodes: [TreeNode?] = []
    array.forEach { item in
        if let v = Int(item) {
            nodes.append(TreeNode(v))
        }
        else {
            nodes.append(nil)
        }
    }
    
    if nodes.isEmpty == true { return nil }
    
    var queue: [TreeNode] = []
    var index = 0
    
    if let f = nodes.first, let n = f {
        queue.append(n)
    }
    
    while queue.isEmpty == false {
        if let node = queue.first, index + 1 &amp;lt; nodes.count, index + 2 &amp;lt; nodes.count {
            node.left = nodes[index + 1]
            node.right = nodes[index + 2]
            
            if let l = node.left {
                queue.append(l)
            }
            
            if let r = node.right {
                queue.append(r)
            }
            
            index += 2
        }
        
        queue.removeFirst()
    }
    
    return nodes.first ?? nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 测试用例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let root1 = TreeNode(1)
let root2 = TreeNode(2)
let root3 = TreeNode(3)
let root4 = TreeNode(4)
let root5 = TreeNode(5)

root1.left = root2
root1.right = root3

root3.left = root4
root3.right = root5

// 编码
let s1 = serialize(root: root1)

// 解码
let node = deserialize(data: s1)

// 再编码验证
if let n = node {
    serialize(root: n)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bc9ab8372bfacb45.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>二叉搜索树与双向链表</title><link>https://blog.lab-lawliet.app/posts/article-36-97ba6649ef05ea51/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-36-97ba6649ef05ea51/</guid><description>输入一棵二叉搜索树，将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点，只能调整树中节点指针的指向。</description><pubDate>Wed, 20 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;输入一棵二叉搜索树，将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点，只能调整树中节点指针的指向。&lt;/p&gt;
&lt;p&gt;为了让您更好地理解问题，以下面的二叉搜索树为例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/54550911b9352137.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表，第一个节点的前驱是最后一个节点，最后一个节点的后继是第一个节点。&lt;/p&gt;
&lt;p&gt;下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2268c300488a4801.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;特别地，我们希望可以就地完成转换操作。当转化完成以后，树中节点的左指针需要指向前驱，树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;p&gt;由于没有官方没有 Swift 的用例，这里自己写了用例来测试&lt;/p&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var head: TreeNode?, last: TreeNode?

func treeToDoublyList(root: TreeNode) -&amp;gt; TreeNode {
    handle(root: root)
    last?.right = head
    head?.left = last
    return root
}

func handle(root: TreeNode) -&amp;gt; TreeNode {
    if let left = root.left {
        if let rightOfLeft = left.right {
            let node = handle(root: rightOfLeft)
            root.left = node
            node.left = left
            node.right = root
        }
        
        if let leftOfLeft = left.left, leftOfLeft.left == nil {
            leftOfLeft.right = left
            head = leftOfLeft
        }
    }
    
    if let right = root.right {
        if let leftOfRight = right.left {
            let node = handle(root: leftOfRight)
            root.right = node
            node.right = right
            node.left = root
        }
        
        if let rightOfRight = right.right, rightOfRight.right == nil {
            rightOfRight.left = right
            last = rightOfRight
        }
    }
    
    return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;用例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let t1 = TreeNode(1)
let t2 = TreeNode(2)
let t3 = TreeNode(3)
let t4 = TreeNode(4)
let t5 = TreeNode(5)
let t6 = TreeNode(6)
let t7 = TreeNode(7)

t4.left = t2
t4.right = t6

t2.left = t1
t2.right = t3

t6.left = t5
t6.right = t7

let arr = [t1, t2, t3, t4, t5, t6, t7]

func log(root: TreeNode) {
    if let left = root.left {
        print(&quot;\(root.val)左：\(left.val)&quot;)
    }
    else {
        print(&quot;\(root.val)左：nil&quot;)
    }
    
    if let right = root.right {
        print(&quot;\(root.val)右：\(right.val)&quot;)
    }
    else {
        print(&quot;\(root.val)右：nil&quot;)
    }
}

arr.forEach { log(root: $0) }

print(&quot;=== 双向转换 ===&quot;)
treeToDoublyList(root: t4)

arr.forEach { log(root: $0) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1左：nil
1右：nil
2左：1
2右：3
3左：nil
3右：nil
4左：2
4右：6
5左：nil
5右：nil
6左：5
6右：7
7左：nil
7右：nil
=== 双向转换 ===
1左：7
1右：2
2左：1
2右：3
3左：2
3右：4
4左：3
4右：5
5左：4
5右：6
6左：5
6右：7
7左：6
7右：1
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>应用图标圆角</title><link>https://blog.lab-lawliet.app/posts/article-03-9e91734e4fcd3e65/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-03-9e91734e4fcd3e65/</guid><description>Icon1024.png - 1024px - 179.649 Icon512.png - 512px - 89.825 Icon.png - 57px - 10 Icon@2x.png - 114px - 20 Icon-72.png - 72px - 12.632 Icon-72@2x.png </description><pubDate>Sun, 17 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;应用图标圆角&lt;/h2&gt;
&lt;p&gt;Icon1024.png - 1024px - 179.649
Icon512.png - 512px - 89.825
Icon.png - 57px - 10
Icon@2x.png - 114px - 20
Icon-72.png - 72px - 12.632
Icon-72@2x.png - 144px - 25.263
Icon-Small.png - 29px - 5.088
Icon-Small@2x.png - 58px - 10.175&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2105289/iphone-app-icons-exact-radius/10239376#10239376&quot;&gt;iPhone App Icons - Exact Radius?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>通过符号表找到符号</title><link>https://blog.lab-lawliet.app/posts/article-10-8840bdec322500b1/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-10-8840bdec322500b1/</guid><description>懒加载符号表是再用到的时候才会进行符号绑定的表。 我们分析MachO可以发现NSLog就在这里面</description><pubDate>Sun, 26 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、需要用到的结构&lt;/h1&gt;
&lt;h2&gt;1.1 懒加载符号表&lt;/h2&gt;
&lt;p&gt;懒加载符号表是再用到的时候才会进行符号绑定的表。
我们分析MachO可以发现NSLog就在这里面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/985eadcc79377b51.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.2 总符号表&lt;/h2&gt;
&lt;p&gt;完整的符号表在这里&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b8ad46f019f3464c.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.3 间接符号表&lt;/h2&gt;
&lt;p&gt;在懒加载符号表中，我们发现，都有个&lt;code&gt;Indirect Pointer&lt;/code&gt;。
这就说名这里是间接符号。
先在我们来到间接符号表：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/28e1c3336591f9c0.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以发现这里面和懒加载符号表中标有&lt;code&gt;Indirect Pointer&lt;/code&gt;的有对应关系。&lt;/p&gt;
&lt;h2&gt;二、找到NSLog的符号&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;IndirectSymbols&lt;/code&gt;中我们找到NSLog。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dc661193ecf8bd9c.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 Data=000000B8&lt;/h3&gt;
&lt;p&gt;这里的值代表了当前间接符号在总表中的下标。我们换算一下十进制：&lt;code&gt;184&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;2.2 Symbols&lt;/h3&gt;
&lt;p&gt;找到下标184的，Data为&lt;code&gt;000000CE&lt;/code&gt;，description:&lt;code&gt;String table index&lt;/code&gt;。也就是&lt;code&gt;String Table&lt;/code&gt;的下标。&lt;/p&gt;
&lt;h3&gt;2.3 StringTables&lt;/h3&gt;
&lt;p&gt;来到&lt;code&gt;StringTable&lt;/code&gt;，第一个地址为&lt;code&gt;00011230&lt;/code&gt;，我们加上下标&lt;code&gt;000000CE&lt;/code&gt;，得到&lt;code&gt;0x112FE&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b5f981276d341a21.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们就找到了&lt;code&gt;_NSLog&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;三、fishhook是和我们这种反向的一个过程&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/article-11-fishhook-c591ed58e9fe4e72/&quot;&gt;11.fishhook原理&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>LLVM</title><link>https://blog.lab-lawliet.app/posts/ios-01-llvm-65d4d00bfa230d4d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-01-llvm-65d4d00bfa230d4d/</guid><description>python 、 JS 就属于常见的 解释型语言 。他们无需生成 可执行文件 就能够执行。</description><pubDate>Sun, 26 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 编程语言类型&lt;/h2&gt;
&lt;h3&gt;1.1 解释型语言&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;python&lt;/code&gt; 、 &lt;code&gt;JS&lt;/code&gt; 就属于常见的 &lt;code&gt;解释型语言&lt;/code&gt; 。他们无需生成 &lt;code&gt;可执行文件&lt;/code&gt; 就能够执行。&lt;/p&gt;
&lt;p&gt;他们的执行过程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b32d9fc6d3092406.webp&quot; alt=&quot;解释型语言&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.2 编译型语言&lt;/h3&gt;
&lt;p&gt;我们常用的 &lt;code&gt;C\C++&lt;/code&gt; 、 &lt;code&gt;Objective-C&lt;/code&gt; 、 &lt;code&gt;Swift&lt;/code&gt; 则属于 &lt;code&gt;编译型语言&lt;/code&gt; 需要经过下面的过程生成 &lt;code&gt;可执行文件&lt;/code&gt; 后才能执行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/68d3d2b772f25c3e.webp&quot; alt=&quot;编译型语言&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 LLVM 概述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;LLVM&lt;/code&gt; 是 &lt;code&gt;构架编译器(compiler)&lt;/code&gt; 的 &lt;code&gt;框架系统&lt;/code&gt; ，以 &lt;code&gt;C++&lt;/code&gt; 编写而成，用于优化以任意程序语言编写的程序的 &lt;code&gt;编译时间(compile-time)&lt;/code&gt; 、 &lt;code&gt;链接时间(link-time)&lt;/code&gt; 、 &lt;code&gt;运行时间(run-time)&lt;/code&gt; 以及 &lt;code&gt;空闲时间(idle-time)&lt;/code&gt; ，对开发者保持开放，并兼容已有脚本。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;LLVM&lt;/code&gt; 计划启动于2000年，最初由美国UIUC大学的 &lt;code&gt;ChrisLattner&lt;/code&gt; 博士主持开展。2006年 &lt;code&gt;ChrisLattner&lt;/code&gt; 加盟 &lt;code&gt;AppleInc&lt;/code&gt; 并致力于 &lt;code&gt;LLVM&lt;/code&gt; 在 &lt;code&gt;Apple&lt;/code&gt; 开发体系中的应用。 &lt;code&gt;Apple&lt;/code&gt; 也是 &lt;code&gt;LLVM&lt;/code&gt; 计划的主要资助者。&lt;/p&gt;
&lt;p&gt;目前 &lt;code&gt;LLVM&lt;/code&gt; 已经被 &lt;code&gt;苹果IOS开发工具&lt;/code&gt; 、 &lt;code&gt;Xilinx Vivado&lt;/code&gt; 、 &lt;code&gt;Facebook&lt;/code&gt; 、 &lt;code&gt;Google&lt;/code&gt; 等各大公司采用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ChrisLattner&lt;/code&gt; 也是 &lt;code&gt;Swift&lt;/code&gt; 之父。给大佬🧎‍♂️了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.1 传统编译器设计&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bda961af84fbe705.webp&quot; alt=&quot;传统编译器设计&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;编译器前端&lt;/h4&gt;
&lt;p&gt;编译器前端的任务是 &lt;code&gt;解析源代码&lt;/code&gt; 。它会进行： &lt;code&gt;词法分析&lt;/code&gt; ， &lt;code&gt;语法分析&lt;/code&gt; ， &lt;code&gt;语义分析&lt;/code&gt; ，&lt;code&gt;检查源代码是否存在错误&lt;/code&gt; ，然后构建 &lt;code&gt;抽象语法树(Abstract Syntax Tree, AST)&lt;/code&gt; 。 &lt;code&gt;LLVM&lt;/code&gt; 的前端还会生成 &lt;code&gt;中间代码(intermediate representation，IR)&lt;/code&gt; 。&lt;/p&gt;
&lt;h4&gt;优化器&lt;/h4&gt;
&lt;p&gt;优化器负责进行 &lt;code&gt;各种优化&lt;/code&gt; ， &lt;code&gt;改善代码的运行时间&lt;/code&gt; ，例如消除冗余计算等。&lt;/p&gt;
&lt;h4&gt;后端 / 代码生成器&lt;/h4&gt;
&lt;p&gt;将代码映射到 &lt;code&gt;目标指令集&lt;/code&gt; ， &lt;code&gt;生成机器语言&lt;/code&gt; ， 并且 &lt;code&gt;进行机器相关的代码优化&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;2.2 iOS的编译器架构&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ObjectiveC / C / C++&lt;/code&gt; 使用的编译器前端是 &lt;code&gt;Clang&lt;/code&gt; ， &lt;code&gt;Swift&lt;/code&gt; 是 &lt;code&gt;Swift&lt;/code&gt; ， &lt;code&gt;后端&lt;/code&gt; 都是 &lt;code&gt;LLVM&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/add92fbb7cccd9b6.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 LLVM的设计&lt;/h3&gt;
&lt;p&gt;当编译器决定支持多种源语言或多种硬件架构时， &lt;code&gt;LLVM&lt;/code&gt; 最重要的地方就来了。&lt;/p&gt;
&lt;p&gt;其他的编译器如 &lt;code&gt;GCC&lt;/code&gt; 是非常成功的一款编译器，但由于它是作为整体应用程序设计的，因此它的用途受到了很大的限制。&lt;/p&gt;
&lt;p&gt;LLVM 设计的最重要方面是，使用 &lt;code&gt;通用的代码表示形式(IR)&lt;/code&gt; ，它是用来在编译器中表示代码的形式。所以 &lt;code&gt;LLVM&lt;/code&gt; 可以为任何编程语言 &lt;code&gt;独立编写前端&lt;/code&gt; ，并且可以为任意硬件架构 &lt;code&gt;独立编写后端&lt;/code&gt; 。&lt;/p&gt;
&lt;h2&gt;三、 Clang&lt;/h2&gt;
&lt;p&gt;对于我们的开发人员来说，接触最多的就是我们的 &lt;code&gt;Clang&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Clang&lt;/code&gt; 是 &lt;code&gt;LLVM&lt;/code&gt; 项目中的一个子项目。它是基于 &lt;code&gt;LLVM架构&lt;/code&gt; 的 &lt;code&gt;轻量级编译器&lt;/code&gt; ，诞生之初是为了替代 &lt;code&gt;GCC&lt;/code&gt; ，提供更快的编译速度。它是负责编译 &lt;code&gt;C、C++、Objecte-C&lt;/code&gt; 的编译器，它属于整个 &lt;code&gt;LLVM&lt;/code&gt; 架构中的，编译器前端。对于开发者来说，研究 &lt;code&gt;Clang&lt;/code&gt; 可以给我们带来很多好处。&lt;/p&gt;
&lt;h2&gt;四、 Objective-C 的编译流程探索&lt;/h2&gt;
&lt;p&gt;我们以下面的代码为例进行探索&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;

int addResult(int a, int b) {
    return a + b;
}

#define ADD_FUNCTION(x, y) addResult(x, y)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int numA = 1;
        int b1 = 1;
        int b2 = 2;
        int numB = b1 + b2;
        int result = ADD_FUNCTION(numA, numB);
        NSLog(@&quot;结果是：%d&quot;, result);
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.1 编译阶段概览&lt;/h3&gt;
&lt;p&gt;通过下面的命令我们可以列出完整的编译阶段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -ccc-print-phases main.m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/049a94385d3ad7f8.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0:输入文件
&lt;ul&gt;
&lt;li&gt;找到源文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1:预处理
&lt;ul&gt;
&lt;li&gt;这个过程处理包括宏的替换，头文件的导入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2:编译
&lt;ul&gt;
&lt;li&gt;进行词法分析、语法分析、检测语法是否正确，最终生成IR&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3:后端
&lt;ul&gt;
&lt;li&gt;这里LLVM会通过一个一个的Pass(可以理解为一个节点)去优化，每个Pass做一些事情，最终生成汇编代码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;4:汇编
&lt;ul&gt;
&lt;li&gt;生成目标文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5:链接
&lt;ul&gt;
&lt;li&gt;链接需要的动态库和静态库，生成相应的镜像可执行文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6:绑定结构
&lt;ul&gt;
&lt;li&gt;根据不同的系统架构，生成对应的可执行文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 预处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;clang -E main.m &amp;gt;&amp;gt; 预处理.m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/58c40ae5c196b4e2.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过对比，很明显的发现这里的宏定义被替换掉了。&lt;/p&gt;
&lt;h3&gt;4.3 编译阶段&lt;/h3&gt;
&lt;h4&gt;词法分析&lt;/h4&gt;
&lt;p&gt;这里会把代码切成一个个 &lt;code&gt;Token&lt;/code&gt; ，比如 &lt;code&gt;大小括号&lt;/code&gt; ， &lt;code&gt;等于号&lt;/code&gt; 还有 &lt;code&gt;字符串&lt;/code&gt; 等。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7907b914631b5be5.webp&quot; alt=&quot;03&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;语法分析&lt;/h4&gt;
&lt;p&gt;语法分析的任务是 &lt;code&gt;验证语法是否正确&lt;/code&gt; 。在词法分析的基础上将单词序列组合成各类语法短语，如 &lt;code&gt;“程序”&lt;/code&gt; ， &lt;code&gt;“语句”&lt;/code&gt; ， &lt;code&gt;“表达式”&lt;/code&gt; 等，然后将所有节点组成 &lt;code&gt;抽象语法树(AbstractSyntaxTree，AST)&lt;/code&gt; 。语法分析其目的就是 &lt;em&gt;&lt;strong&gt;对源程序进行分析判断，在结构上是否正确&lt;/strong&gt;&lt;/em&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fe66fcc2e7a7761f.webp&quot; alt=&quot;04&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;生产中间代码IR&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;clang -S -fobjc-arc -emit-llvm main.m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里会生成 &lt;code&gt;.ll&lt;/code&gt; 文件，抽取一段 &lt;code&gt;addResult&lt;/code&gt; 相关代码简单进行一下分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @addResult(i32 %0, i32 %1) #1 {
  %3 = alloca i32, align 4 // 开辟 32 bit 空间， 4个字节， 为 %3
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4 // 将 %0 参数存到 %3
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4 // 从 %3 中读取到 %5
  %6 = load i32, i32* %4, align 4
  %7 = add nsw i32 %5, %6 // %5 + %6 的值存到 %7
  ret i32 %7 // 返回 %7 32bit
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; 函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; Function Attrs: noinline optnone ssp uwtable
define i32 @main(i32 %0, i8** %1) #2 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca i32, align 4
  %7 = alloca i32, align 4
  %8 = alloca i32, align 4
  %9 = alloca i32, align 4
  %10 = alloca i32, align 4
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %11 = call i8* @llvm.objc.autoreleasePoolPush() #3
  store i32 1, i32* %6, align 4
  store i32 1, i32* %7, align 4
  store i32 2, i32* %8, align 4
  %12 = load i32, i32* %7, align 4
  %13 = load i32, i32* %8, align 4
  %14 = add nsw i32 %12, %13
  store i32 %14, i32* %9, align 4
  %15 = load i32, i32* %6, align 4
  %16 = load i32, i32* %9, align 4
  %17 = call i32 @addResult(i32 %15, i32 %16)
  store i32 %17, i32* %10, align 4
  %18 = load i32, i32* %10, align 4
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 %18)
  call void @llvm.objc.autoreleasePoolPop(i8* %11)
  ret i32 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;编译优化&lt;/h4&gt;
&lt;p&gt;这里 &lt;code&gt;debug&lt;/code&gt; 模式下默认没有进行优化。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/51e8d7f8fb8346e5.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们传入对应的优化等级，再生成一次 &lt;code&gt;.ll&lt;/code&gt; 看看（这里用了和 &lt;code&gt;release&lt;/code&gt; 一样的 &lt;code&gt;Os&lt;/code&gt; ）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -Os -S -fobjc-arc -emit-llvm main.m -o main优化过.ll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;addResult&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; Function Attrs: norecurse nounwind optsize readnone ssp uwtable willreturn
define i32 @addResult(i32 %0, i32 %1) local_unnamed_addr #1 {
  %3 = add nsw i32 %1, %0
  ret i32 %3
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;main&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; Function Attrs: optsize ssp uwtable
define i32 @main(i32 %0, i8** nocapture readnone %1) local_unnamed_addr #2 {
  %3 = tail call i8* @llvm.objc.autoreleasePoolPush() #3
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*), i32 4) #5, !clang.arc.no_objc_arc_exceptions !9
  tail call void @llvm.objc.autoreleasePoolPop(i8* %3) #3
  ret i32 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里发现优化后的中间代码简介了不少，并且代码中冗余的计算逻辑直接生成了结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ce2e1533d1e54009.webp&quot; alt=&quot;06&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;bitCode&lt;/h4&gt;
&lt;p&gt;这是 &lt;code&gt;xcode7&lt;/code&gt; 以后开启 &lt;code&gt;bitcode&lt;/code&gt; 苹果会做进一步的优化，生成 &lt;code&gt;bc&lt;/code&gt; 的中间代码。我们可以试试用优化后的 &lt;code&gt;IR&lt;/code&gt; 代码生成 &lt;code&gt;bc&lt;/code&gt; 代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -emit-llvm -c main优化过.ll -o main优化过.bc
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的 &lt;code&gt;bc&lt;/code&gt; 文件无法使用文本方式进行查看&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;生成汇编代码&lt;/h4&gt;
&lt;p&gt;可以通过的 &lt;code&gt;.bc&lt;/code&gt; 或者 &lt;code&gt;.ll&lt;/code&gt; 代码生成汇编代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -S -fobjc-arc main优化过.bc -o main优化过.s
或
clang -S -fobjc-arc main未优化.ll -o main未优化.s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对比一下优化前后的汇编代码，发现优化后的指令少了很多：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a867cb148fb5f255.webp&quot; alt=&quot;07&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;生成目标文件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;clang -fmodules -c main未优化.s -o main未优化.o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标文件的生成，是 &lt;code&gt;汇编器&lt;/code&gt; 以汇编代码作为 &lt;code&gt;输入&lt;/code&gt; ，将 &lt;code&gt;汇编代码&lt;/code&gt; 转换为 &lt;code&gt;机器代码&lt;/code&gt; ，最后输出 目&lt;code&gt;标文件(object-file)&lt;/code&gt; ，这个阶段就是属于 &lt;code&gt;编译器后端&lt;/code&gt; 的工作了。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;查看符号&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xcrun nm -nm main未优化.o
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                 (undefined) external _NSLog
                 (undefined) external ___CFConstantStringClassReference
                 (undefined) external _objc_autoreleasePoolPop
                 (undefined) external _objc_autoreleasePoolPush
0000000000000000 (__TEXT,__text) external _addResult
0000000000000020 (__TEXT,__text) external _main
000000000000008e (__TEXT,__ustring) non-external l_.str
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_NSLog&lt;/code&gt; 是一个是 &lt;code&gt;undefined external&lt;/code&gt; 的
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;表示在当前文件暂时找不到符号 &lt;code&gt;_NSLog&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;external&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;表示这个符号是外部可以访问的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;有兴趣可以移步 &lt;a href=&quot;/posts/article-10-8840bdec322500b1/&quot;&gt;通过符号表找到符号&lt;/a&gt; 有介绍查找 &lt;code&gt;_NSLog&lt;/code&gt; 的过程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;生成可执行文件&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;连接器&lt;/code&gt; 把编译产生的 &lt;code&gt;.o文件&lt;/code&gt; 和 &lt;code&gt;(dylib .a)文件&lt;/code&gt; ，生成一个 &lt;code&gt;mach-o文件(可执行文件)&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang main.o -o main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Undefined symbols for architecture x86_64:
  &quot;_NSLog&quot;, referenced from:
      _main in main.o
  &quot;___CFConstantStringClassReference&quot;, referenced from:
      CFString in main.o
  &quot;_objc_autoreleasePoolPop&quot;, referenced from:
      _main in main.o
  &quot;_objc_autoreleasePoolPush&quot;, referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/54f2ea4de68dcb03.webp&quot; alt=&quot;08&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因为这里用到了 &lt;code&gt;Foundation&lt;/code&gt; 框架内的符号 &lt;code&gt;NSLog&lt;/code&gt; 不能直接生成，我们加上参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -framework Foundation main.o -o main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成成功：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/05b0adc921c3e0cd.webp&quot; alt=&quot;09&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;查看符号&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xcrun nm -nm main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                 (undefined) external _NSLog (from Foundation)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external _objc_autoreleasePoolPop (from libobjc)
                 (undefined) external _objc_autoreleasePoolPush (from libobjc)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003ed0 (__TEXT,__text) external _addResult
0000000100003ef0 (__TEXT,__text) external _main
0000000100008018 (__DATA,__data) non-external __dyld_private
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的符号就已经和对应的框架对应起来了，如： &lt;code&gt;_NSLog (from Foundation)&lt;/code&gt; 。而且 &lt;code&gt;mach-o&lt;/code&gt; 文件中的偏移地址也有了。&lt;/p&gt;
&lt;h2&gt;五、 Swift 编译流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;分析输出AST
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -dump-parse&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分析并且检查类型输出AST
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -dump-ast&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成中间体语言(SIL)，未优化
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -emit-silgen&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成中间体语言(SIL)，优化后的
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -emit-sil&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成LLVM中间体语言 (.ll文件)
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -emit-ir&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成LLVM中间体语言 (.bc文件)
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -emit-bc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成汇编
&lt;ul&gt;
&lt;li&gt;swiftc main.swift -emit-assembly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;编译生成可执行.out文件
&lt;ul&gt;
&lt;li&gt;swiftc -o main.o main.swift&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/llvm/llvm-project&quot;&gt;LLVM&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://clang.llvm.org&quot;&gt;Clang&lt;/a&gt;
1
&lt;a href=&quot;https://stackoverflow.com/questions/40940357/which-osx-library-to-link-against-command-line-to-use-nslog&quot;&gt;Which OSX library to link against (command line) to use NSLog?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>关于Block你所该知道的一切</title><link>https://blog.lab-lawliet.app/posts/ios-32-block-468995f39c78075b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-32-block-468995f39c78075b/</guid><description>全局 block 、 堆 block 、 栈 block 。</description><pubDate>Sat, 18 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 block 的分类&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;全局 block&lt;/code&gt; 、 &lt;code&gt;堆 block&lt;/code&gt; 、 &lt;code&gt;栈 block&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;1.1 &lt;strong&gt;NSGlobalBlock&lt;/strong&gt; 全局 block&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位于全局区&lt;/li&gt;
&lt;li&gt;未捕获任何变量 或 只捕获全局静态变量&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;未捕获任何变量&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/324087604e88a07d.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSGlobalBlock__: 0x100004030&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;只捕获全局静态变量&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d79286e4a11f46ae.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.2 堆 block&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位于堆区&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;block&lt;/code&gt; 内部使用 &lt;code&gt;局部变量&lt;/code&gt; 或者 &lt;code&gt;OC属性&lt;/code&gt; ，并且赋值给 &lt;code&gt;强引用&lt;/code&gt; 或者 &lt;code&gt;Copy修饰&lt;/code&gt; 的变量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f3d36ce96a3d8a62.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSMallocBlock__: 0x1007049e0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3  栈 block&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;位于栈区&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;block&lt;/code&gt; 内部使用 &lt;code&gt;局部变量&lt;/code&gt; 或者 &lt;code&gt;OC属性&lt;/code&gt; ，但 &lt;code&gt;不&lt;/code&gt; 赋值给 &lt;code&gt;强引用&lt;/code&gt; 或者 &lt;code&gt;Copy修饰&lt;/code&gt; 的变量&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/57af4a8ac98cd6c1.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSStackBlock__: 0x7ffeefbff378&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.4 面试题（一）&lt;/h3&gt;
&lt;p&gt;下面输出的结果会是什么样？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSObject *obj = [NSObject new];
NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));

void (^blockA)(void) = ^{
    NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();

void (^ __weak blockB)(void) = ^{
    NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();

void (^blockC)(void) = [blockB copy];
blockC();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里涉及到关于 &lt;code&gt;block类型&lt;/code&gt; 的底层知识，我们到最后在进行解析。&lt;/p&gt;
&lt;h3&gt;1.5 面试题（二）&lt;/h3&gt;
&lt;p&gt;下面输出的结果会是什么样？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ __weak blockB)(void) = ^{
        NSLog(@&quot;%d&quot;, num);
    };
    blockA = blockB;
    NSLog(@&quot;=&quot;);
}
blockA();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.6 面试题（三）&lt;/h3&gt;
&lt;p&gt;下面输出的结果会是什么样？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int num = 0;
void (^ __weak blockA)(void) = nil;
{
    void (^ blockB)(void) = ^{
        NSLog(@&quot;%d&quot;, num);
    };
    blockA = blockB;
    NSLog(@&quot;=&quot;);
}
blockA();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 堆、栈block的生命周期&lt;/h2&gt;
&lt;p&gt;这里结合 &lt;code&gt;二、三面试题&lt;/code&gt; 来进行探索。&lt;/p&gt;
&lt;h3&gt;2.1 面试题二解读&lt;/h3&gt;
&lt;p&gt;很可能有同学会给出只会打印 &lt;code&gt;=&lt;/code&gt; 的答案。这就是对于 &lt;code&gt;堆、栈block的生命周期&lt;/code&gt; 理解的不够深入。正确的输出如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/55246976bf5b5e4d.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为什么呢？关键是对 &lt;code&gt;blockB&lt;/code&gt; 的生命周期理解错误。一般会这么理解：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/11cfce32098c0f87.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里先不讲为什么，我们再看下 &lt;code&gt;面试题三&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.2 面试题三解读&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c8f7cde99ea1e5f7.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里发生了野指针访问崩溃了。&lt;/p&gt;
&lt;h3&gt;2.3 堆 block 的生命周期&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/cce4c916df5dad10.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在大括号内部下断点可以看到， 这时 blockB 为 &lt;code&gt;堆block&lt;/code&gt; 且有值。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c51a421e0b760fbb.webp&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;赋值给 &lt;code&gt;blockA&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9ad1c5c3bec12701.webp&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;出了大括号后 &lt;code&gt;blockA&lt;/code&gt; 为空了，说明 &lt;code&gt;blockB&lt;/code&gt; 释放掉了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为作为 &lt;code&gt;存储在堆上&lt;/code&gt; 的 &lt;code&gt;堆block&lt;/code&gt; 其生命周期仅存在于大括号内部。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.4 栈 block 的生命周期&lt;/h3&gt;
&lt;p&gt;那么 &lt;code&gt;面试题二&lt;/code&gt; 中的 &lt;code&gt;blockB&lt;/code&gt; 的生命周期又是怎样的呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fdda502c48031162.webp&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;blockB&lt;/code&gt; 为 &lt;code&gt;栈block&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b90ac2f0d1940682.webp&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;赋值给 &lt;code&gt;blockA&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9e47072233caca86.webp&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;出了大括号的作用域了， &lt;code&gt;blockA&lt;/code&gt; 的地址依旧没变，说明 &lt;code&gt;blockB&lt;/code&gt; 没有被释放。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;因为作为 &lt;code&gt;存储在栈中&lt;/code&gt; 的 &lt;code&gt;栈block&lt;/code&gt; 其生命周期与 &lt;code&gt;调用栈&lt;/code&gt; 相同。函数调用栈结束后释放。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 捕获变量的底层实现&lt;/h2&gt;
&lt;h3&gt;3.1 普通变量的捕获&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;int num = 1;
void (^block)(void) = ^{
    NSLog(@&quot;%d&quot;, num);
};
block();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们将上面的代码还原成 &lt;code&gt;C++&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/48035bb5df3ece1a.webp&quot; alt=&quot;14&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;block&lt;/code&gt; 的本质是一个结构体 &lt;code&gt;__main_block_impl_0&lt;/code&gt; ，如果有需要捕获的变量，结构体内部会生成一个成员用来存储，例子中是 &lt;code&gt;int num;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__main_block_func_0&lt;/code&gt; 保存的是 &lt;code&gt;block&lt;/code&gt; 的具体需要执行的函数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__block_impl&lt;/code&gt; 是 &lt;code&gt;block&lt;/code&gt; 的具体具体的一些数据，如函数等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__main_block_desc_0&lt;/code&gt; 是描述 &lt;code&gt;block&lt;/code&gt; 特征的一个数据结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这段代码中做了些什么呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一、 创建 &lt;code&gt;block&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;1、 调用 &lt;code&gt;__main_block_impl_0&lt;/code&gt; 的 &lt;code&gt;构造函数&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2、 传入 &lt;code&gt;__main_block_func_0 函数&lt;/code&gt; 和 &lt;code&gt;__main_block_desc_0_DATA 的地址&lt;/code&gt; 和 &lt;code&gt;变量 num&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3、 指定 block 类型：&lt;code&gt;impl.isa = &amp;amp;_NSConcreteStackBlock;&lt;/code&gt; （这里的堆block对应的是 &lt;code&gt;_NSConcreteStackBlock&lt;/code&gt; ）&lt;/p&gt;
&lt;p&gt;4、 存储标志位： &lt;code&gt;impl.Flags = flags;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;5、 存储函数： &lt;code&gt;impl.FuncPtr = fp;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;6、 描述内容赋值： &lt;code&gt;Desc = desc;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;二、 执行 &lt;code&gt;block&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;1、 获取 &lt;code&gt;__main_block_func_0&lt;/code&gt; 函数： &lt;code&gt;((void (*)(__block_impl *))((__block_impl *)block)-&amp;gt;FuncPtr)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2、 将 &lt;code&gt;block&lt;/code&gt; 作为参数传入： &lt;code&gt;((void (*)(__block_impl *))((__block_impl *)block)-&amp;gt;FuncPtr)((__block_impl *)block);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;三、 执行函数 &lt;code&gt;__main_block_func_0&lt;/code&gt; ：&lt;/p&gt;
&lt;p&gt;1、 从 &lt;code&gt;__main_block_impl_0&lt;/code&gt; 中取出变量 &lt;code&gt;num&lt;/code&gt; ： &lt;code&gt;int num = __cself-&amp;gt;num;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;2、 执行 &lt;code&gt;NSLog&lt;/code&gt; ： &lt;code&gt;NSLog((NSString *)&amp;amp;__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_main_e543c2_mi_1, num);&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.2 __block 普通变量的捕获&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;__block int num = 0;
void (^block)(void) = ^{
    num ++;
    NSLog(@&quot;%d&quot;, num);
};
block();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将上面的代码转为 &lt;code&gt;C++&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e8be78ac5927c06.webp&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们发现多了几种数据类型： &lt;code&gt;__Block_byref_num_0&lt;/code&gt; 、 &lt;code&gt;__main_block_copy_0&lt;/code&gt; 、 &lt;code&gt;__main_block_dispose_0&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;加了 &lt;code&gt;__block&lt;/code&gt; 的变量，这里被用 &lt;code&gt;__Block_byref_num_0&lt;/code&gt; 数据结构包装了一下，这里传入的 num 也是通过地址的形式传入的。&lt;/p&gt;
&lt;h3&gt;3.3 捕获实例变量&lt;/h3&gt;
&lt;p&gt;不活实例变量和 &lt;code&gt;3.2&lt;/code&gt; 没有什么区别。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSNumber *num = @100;
void (^block)(void) = ^{
    NSLog(@&quot;%@&quot;, num);
};
block();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/08cb26751137e020.webp&quot; alt=&quot;16&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.4 全局 block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static const NSString *string = @&quot;aaaaaaa&quot;;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@&quot;%@&quot;, string);
        };
        block();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/899a1c5328776fdf.webp&quot; alt=&quot;17&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.5 栈 block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;int num = 0;
void (^ __weak block)(void) = ^{
    NSLog(@&quot;%d&quot;, num);
};
block();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里还原 C++ 你可能会遇到包错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error: cannot create __weak reference because the current deployment target does not support weak references
        void (^ __attribute__((objc_ownership(weak))) block)(void)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加上一些参数即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m -o main.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e23887a0f73107ea.webp&quot; alt=&quot;18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d84f5a30483fd2d8.webp&quot; alt=&quot;19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里发现 &lt;code&gt;__weak&lt;/code&gt; 实际是用 &lt;code&gt;__attribute__((objc_ownership(weak)))&lt;/code&gt; 进行了包装，这里还没有什么更有用的线索，我们先继续探究。&lt;/p&gt;
&lt;h3&gt;3.6 _NSConcreteStackBlock&lt;/h3&gt;
&lt;p&gt;相信细心的你一定能发现一个问题，不论什么类型的 &lt;code&gt;block&lt;/code&gt; ，还原出的 &lt;code&gt;C++&lt;/code&gt; 底层源码中都是 &lt;code&gt;impl.isa = &amp;amp;_NSConcreteStackBlock;&lt;/code&gt;  看名字是个 &lt;code&gt;栈 block&lt;/code&gt; 。和实际运行打断点运行时看到的不一样啊。下面我们就来分析一下这里发生了什么。&lt;/p&gt;
&lt;h2&gt;四、 运行时 block 类型的确定&lt;/h2&gt;
&lt;p&gt;由于这里并没有什么具体线索，我们通过下断点，并打开汇编。这里是一个 &lt;code&gt;全局block&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dc269046688fc226.webp&quot; alt=&quot;20&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.1 进入汇编断点&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3422a9a407463faa.webp&quot; alt=&quot;21&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.2 objc_retainBlock&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c71ba92a2edc98c8.webp&quot; alt=&quot;22&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过 &lt;em&gt;&lt;strong&gt;libobjc.A.dylib`objc_retainBlock:&lt;/strong&gt;&lt;/em&gt; 发现它在 &lt;em&gt;&lt;strong&gt;libobjc&lt;/strong&gt;&lt;/em&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/63f7d9a2817516c0.webp&quot; alt=&quot;23&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 oc 源码中定位到了相关代码，但更进一步调用的 &lt;code&gt;_Block_copy&lt;/code&gt; 并没有找到，我们继续回到汇编，继续执行下一步。&lt;/p&gt;
&lt;h3&gt;4.3 _Block_copy&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6f7c038dd5c9f19e.webp&quot; alt=&quot;24&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这例进入了 &lt;code&gt;_Block_copy&lt;/code&gt; 的汇编，发现它是 &lt;code&gt;libsystem_blocks&lt;/code&gt; 中的，我们来 &lt;code&gt;libclosure&lt;/code&gt; 中寻找一下线索，找到了下面的源码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock-&amp;gt;flags &amp;amp; BLOCK_NEEDS_FREE) {
        /// 根据标志位判断，需要释放
        // latches on high
        latching_incr_int(&amp;amp;aBlock-&amp;gt;flags);
        return aBlock;
    }
    else if (aBlock-&amp;gt;flags &amp;amp; BLOCK_IS_GLOBAL) {
        /// 根据标志位判断，为一个 全局block 直接返回
        return aBlock;
    }
    else {
        // 是一个 堆 block 就进行 copy 开辟空间的操作，拷贝到了堆上
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock-&amp;gt;descriptor-&amp;gt;size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock-&amp;gt;descriptor-&amp;gt;size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result-&amp;gt;invoke = aBlock-&amp;gt;invoke;
#endif
        result-&amp;gt;flags &amp;amp;= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result-&amp;gt;flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // 设置 isa 为 堆block
        result-&amp;gt;isa = _NSConcreteMallocBlock;
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、 汇编验证 block 的类型转换&lt;/h2&gt;
&lt;h3&gt;5.1 全局block&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3535490b11e06085.webp&quot; alt=&quot;25&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一直来到 &lt;code&gt;_Block_copy&lt;/code&gt; 内，读取 &lt;code&gt;x0 寄存器&lt;/code&gt; （ARM64下，x0这时存的是消息的接收者）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/241055240a41bb8b.webp&quot; alt=&quot;26&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;_Block_copy&lt;/code&gt; 时，就已经标识为 全局block 了。&lt;/p&gt;
&lt;p&gt;我们发现输出了这样数据结构 &lt;code&gt;signature&lt;/code&gt; &lt;code&gt;invoke&lt;/code&gt; ，后面再具体看他们代表了什么。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSGlobalBlock__: 0x1006f8028&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x1006f5edc (/private/var/containers/Bundle/Application/49DA1CBF-A462-4433-80DE-19721201954A/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 堆block&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/77439534a1236bc3.webp&quot; alt=&quot;27&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;_Block_copy&lt;/code&gt; 时，显示的是 栈block 。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5184d1dd6c2154b7.webp&quot; alt=&quot;28&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSStackBlock__: 0x16d6651b8&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和源码所写的一样，这里进行了 malloc ：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8a800456a2c87c3f.webp&quot; alt=&quot;29&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;_Block_copy&lt;/code&gt; &lt;code&gt;return&lt;/code&gt; 之前我们再看一下 x0 ：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/40876a10564a3703.webp&quot; alt=&quot;30&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里已经变成了 堆block&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSMallocBlock__: 0x281030ab0&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x10279de90 (/private/var/containers/Bundle/Application/A3A281E1-1993-4E28-8F30-3EED05043A20/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;_block 捕获变量的变化&lt;/h4&gt;
&lt;p&gt;进入 &lt;code&gt;_Block_copy&lt;/code&gt; 时：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b5f62249c967bfc3.webp&quot; alt=&quot;31&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSStackBlock__: 0x16b21d1a0&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x104be5da0 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x104be5e04 (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x104be5e3c (/private/var/containers/Bundle/Application/76397764-704E-4CA4-80F3-D94500849B81/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;_Block_copy&lt;/code&gt; &lt;code&gt;return&lt;/code&gt; 之前&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1355d91f90bd6730.webp&quot; alt=&quot;32&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSMallocBlock__: 0x2823a83f0&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的数据结构又发生了变化，多了 &lt;code&gt;copy&lt;/code&gt; &lt;code&gt;dispose&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;5.3 栈block&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/86ff4ca906700485.webp&quot; alt=&quot;33&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过查看调用栈，我们发现并没有调用 &lt;code&gt;objc_retainBlock&lt;/code&gt; 。没有进行 &lt;code&gt;_Block_copy&lt;/code&gt; 操作。&lt;/p&gt;
&lt;h2&gt;六、 Block_layout 结构分析&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2192dcc6f82f4b4f.webp&quot; alt=&quot;35&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.1 isa&lt;/h3&gt;
&lt;p&gt;block 的类型：全局、堆、栈&lt;/p&gt;
&lt;h3&gt;6.2 flags&lt;/h3&gt;
&lt;p&gt;这些标志位都标明了不同的使用时间，有 &lt;code&gt;运行时：runtime&lt;/code&gt; 有 &lt;code&gt;编译期：compiler&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Values for Block_layout-&amp;gt;flags to describe block objects
enum {
    /// 正在释放
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    /// 引用计数掩码
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    /// 需要释放
    BLOCK_NEEDS_FREE =        (1 &amp;lt;&amp;lt; 24), // runtime
    /// 是否拥有拷贝辅助函数 有的话就有 Block_descriptor_2
    BLOCK_HAS_COPY_DISPOSE =  (1 &amp;lt;&amp;lt; 25), // compiler
    /// 是否拥有 block 析构函数
    BLOCK_HAS_CTOR =          (1 &amp;lt;&amp;lt; 26), // compiler: helpers have C++ code
    /// 标志是否有垃圾回收 MAC
    BLOCK_IS_GC =             (1 &amp;lt;&amp;lt; 27), // runtime
    /// 是否是 全局block
    BLOCK_IS_GLOBAL =         (1 &amp;lt;&amp;lt; 28), // compiler
    /// 和 BLOCK_HAS_SIGNATURE 对应
    BLOCK_USE_STRET =         (1 &amp;lt;&amp;lt; 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    /// 有签名 有的话就有 Block_descriptor_3
    BLOCK_HAS_SIGNATURE  =    (1 &amp;lt;&amp;lt; 30), // compiler
    /// 有拓展 Layout
    BLOCK_HAS_EXTENDED_LAYOUT=(1 &amp;lt;&amp;lt; 31)  // compiler
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 reserved&lt;/h3&gt;
&lt;p&gt;保留字段&lt;/p&gt;
&lt;h3&gt;6.4 invoke&lt;/h3&gt;
&lt;p&gt;函数指针，即block中具体需要执行的内容。&lt;/p&gt;
&lt;h3&gt;6.5 descriptor&lt;/h3&gt;
&lt;p&gt;这里我们在看下其中的 &lt;code&gt;Block_descriptor&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3c7003cf8d0551a3.webp&quot; alt=&quot;36&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里就和我们之前输出的内容对应上了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;__NSMallocBlock__: 0x2823a83f0&amp;gt;
 signature: &quot;v8@?0&quot;
 invoke   : 0x100825da0 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__41-[ViewController touchesBegan:withEvent:]_block_invoke)
 copy     : 0x100825e04 (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__copy_helper_block_e8_32r)
 dispose  : 0x100825e3c (/private/var/containers/Bundle/Application/D167E1D0-2772-42C4-83F1-819FC7250D2D/BlockÊé¢Á¥¢.app/BlockÊé¢Á¥¢`__destroy_helper_block_e8_32r)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dd29d5ff6c02e6f7.webp&quot; alt=&quot;37&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然 &lt;code&gt;Block_layout&lt;/code&gt; 的内存结构中只有 &lt;code&gt;Block_descriptor_1&lt;/code&gt; ，但是通过解读源码，可以发现， &lt;code&gt;Block_descriptor_2&lt;/code&gt; 和 &lt;code&gt;Block_descriptor_3&lt;/code&gt; 的读取是通过内存平移实现的，具体有哪些数据可读则是根据 &lt;code&gt;标识位&lt;/code&gt; 来判别。&lt;/p&gt;
&lt;h4&gt;Block_descriptor_1&lt;/h4&gt;
&lt;p&gt;基础数据，一定有的&lt;/p&gt;
&lt;h4&gt;Block_descriptor_2&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;COPY&lt;/code&gt; 相关字段，有 &lt;code&gt;BLOCK_HAS_COPY_DISPOSE&lt;/code&gt; 标志位时有。&lt;/p&gt;
&lt;h4&gt;Block_descriptor_3&lt;/h4&gt;
&lt;p&gt;签名相关数据，有 &lt;code&gt;BLOCK_HAS_SIGNATURE&lt;/code&gt; 标志位时有。&lt;/p&gt;
&lt;h2&gt;七、 block 签名&lt;/h2&gt;
&lt;p&gt;上面的 &lt;code&gt;Block_descriptor_3&lt;/code&gt; 中咱们提到了签名。如上面例子输出的 &lt;code&gt;signature: &quot;v8@?0&quot;&lt;/code&gt; 很眼熟，它其实是 &lt;code&gt;Type Encodings 类型编码&lt;/code&gt;，我们在 &lt;a href=&quot;/posts/ios-07-4432d5fe0e833d8f/&quot;&gt;属性&lt;/a&gt; 中有遇到过。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v8@?0&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;v&lt;/code&gt; 表示返回值为空&lt;/li&gt;
&lt;li&gt;&lt;code&gt;8&lt;/code&gt; 表示参数的总大小&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@?&lt;/code&gt; 表示block，&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; 表示从0号字节开始&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;具体可以对照&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;NSMethodSignature&lt;/h4&gt;
&lt;p&gt;我们也可以通过 &lt;code&gt;NSMethodSignature&lt;/code&gt; 来输出详细它的代表信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po [NSMethodSignature signatureWithObjCTypes:&quot;v8@?0&quot;]
&amp;lt;NSMethodSignature: 0xac5982543c221e27&amp;gt;
    number of arguments = 1
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) &apos;v&apos;
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) &apos;@?&apos;
        flags {isObject, isBlock} // 是个对象，也是一个 block
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;八、 __block 捕获变量的生命周期&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/58bec564eda1c5b4.webp&quot; alt=&quot;38&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们再来研究下捕获变量的原理，我们继续通过汇编探索。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6155ca16c913426e.webp&quot; alt=&quot;39&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里会调用到 &lt;code&gt;_Block_object_assign&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们找到相关源码：&lt;/p&gt;
&lt;h3&gt;8.1 捕获变量类型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    /// 对象
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    /// block
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    /// __block 修饰的变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    /// 
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.2 _Block_object_assign&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags &amp;amp; BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        // 一、 捕获的是对象
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        /**
        _Block_retain_object 的实现：
        static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

        _Block_retain_object_default 的实现：
        static void _Block_retain_object_default(const void *ptr __unused) { }

        其实什么都没做
        */
        _Block_retain_object(object);

        // 指针拷贝，引用计数 +1
        *dest = object;
        break;

        // 二、 捕获的是 block
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        /**
        前文有关于 _Block_copy 的讲解么这里就不多说了
        */
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
      // 三、 捕获的是 __block / __weak __block 修饰的变量
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.3 _Block_byref_copy&lt;/h3&gt;
&lt;p&gt;__Block 捕获外界变量的操作 内存拷贝 等&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 使用 Block_byref 结构转换一下
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src-&amp;gt;forwarding-&amp;gt;flags &amp;amp; BLOCK_REFCOUNT_MASK) == 0) {
        // 如果是通过引用计数管理的对象，就进行 malloc ，创建一个 copy
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src-&amp;gt;size);
        copy-&amp;gt;isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy-&amp;gt;flags = src-&amp;gt;flags | BLOCK_BYREF_NEEDS_FREE | 4;

        /**
        统一 forwarding 指向 copy
        所以 __block 修饰的指向的是同一片内存空间
        */ 
        copy-&amp;gt;forwarding = copy; // patch heap copy to point to itself
        src-&amp;gt;forwarding = copy;  // patch stack to point to heap copy


        copy-&amp;gt;size = src-&amp;gt;size;

        if (src-&amp;gt;flags &amp;amp; BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2-&amp;gt;byref_keep = src2-&amp;gt;byref_keep;
            copy2-&amp;gt;byref_destroy = src2-&amp;gt;byref_destroy;

            if (src-&amp;gt;flags &amp;amp; BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3-&amp;gt;layout = src3-&amp;gt;layout;
            }

            /**
            捕获到了外部变量
            调用 byref_keep 
            内存处理
            被捕获变量生命周期的保存
            */
            (*src2-&amp;gt;byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src-&amp;gt;size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src-&amp;gt;forwarding-&amp;gt;flags &amp;amp; BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&amp;amp;src-&amp;gt;forwarding-&amp;gt;flags);
    }
    
    return src-&amp;gt;forwarding;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们遇到了用来包装捕获变量的 &lt;code&gt;Block_byref&lt;/code&gt; 结构体，还有个一个 &lt;code&gt;byref_keep&lt;/code&gt; 与生命周期相关的函数调用，但是他们的来源并没有什么线索，我们只能继续从 &lt;code&gt;C++&lt;/code&gt; 中寻找一些线索。&lt;/p&gt;
&lt;h3&gt;8.4 Block_byref&lt;/h3&gt;
&lt;p&gt;其中除了 &lt;code&gt;Block_byref&lt;/code&gt; 还有 &lt;code&gt;Block_byref_2&lt;/code&gt; ， &lt;code&gt;Block_byref_3&lt;/code&gt; 。他们和前面的 &lt;code&gt;Block_descriptor_1&lt;/code&gt; 一样，也是通过标志位区分，通过内存平移进行读写的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// __Block 修饰的结构体
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

// __Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

// 拓展 layout
struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.5 通过 C++ 理解 Block_byref 结构&lt;/h3&gt;
&lt;p&gt;位了更深入的理解 &lt;code&gt;Block_byref&lt;/code&gt; 的结构，我们通过还原 &lt;code&gt;C++&lt;/code&gt; 找一下线索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__block NSNumber *num = @100;
void (^block)(void) = ^{
    num = @101;
    NSLog(@&quot;%@&quot;, num);
};
block();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
/**
对应的 Block_byref 类型结构体
*/
struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSNumber *num;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num-&amp;gt;__forwarding) {
    impl.isa = &amp;amp;_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

...

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // Block_byref 构造函数
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {
            /**
            Block_byref 的 
            void *isa;
            */
            (void*)0, 
            /**
            Block_byref 的 
            struct Block_byref *forwarding;
            */
            (__Block_byref_num_0 *)&amp;amp;num, 
            /**
            Block_byref 的 
            volatile int32_t flags;
            */ 
            33554432, 
            /**
            Block_byref 的 
            uint32_t size;
            */
            sizeof(__Block_byref_num_0), 
            /**
            Block_byref_2 的
            BlockByrefKeepFunction byref_keep;
            */ __Block_byref_id_object_copy
            __Block_byref_id_object_copy_131, 
            /**
            Block_byref_2 的
            BlockByrefDestroyFunction byref_destroy;
            */
            __Block_byref_id_object_dispose_131, 
            // *num
            ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass(&quot;NSNumber&quot;), sel_registerName(&quot;numberWithInt:&quot;), 100)
            };
            
        void (*block)(void) = ((void (*)())&amp;amp;__main_block_impl_0((void *)__main_block_func_0, &amp;amp;__main_block_desc_0_DATA, (__Block_byref_num_0 *)&amp;amp;num, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)-&amp;gt;FuncPtr)((__block_impl *)block);

    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;byref_keep&lt;/h4&gt;
&lt;p&gt;再次调用 &lt;code&gt;_Block_object_assign&lt;/code&gt; （详见 8.2），这时传入的 &lt;code&gt;flag&lt;/code&gt; 为 &lt;code&gt;131&lt;/code&gt; ，二进制为 &lt;code&gt;1000 0011&lt;/code&gt;，而 &lt;code&gt;BLOCK_FIELD_IS_OBJECT = 3&lt;/code&gt; ，所以会执行 &lt;code&gt;BLOCK_FIELD_IS_OBJECT&lt;/code&gt; 相关的分支，进行 &lt;code&gt;拷贝&lt;/code&gt; 与 &lt;code&gt;引用计数 + 1&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;byref_destroy&lt;/h4&gt;
&lt;p&gt;调用 &lt;code&gt;_Block_object_dispose&lt;/code&gt; 进行销毁&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;九、 补充：面试题一解析&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/84dffbe94331225c.webp&quot; alt=&quot;40&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;这里是 1 应该都没什么疑问&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;NSObject *obj = [NSObject new];
NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;这里为什么是 3 呢？&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void (^blockA)(void) = ^{
    NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockA();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这是引用了一个局部变量
&lt;ul&gt;
&lt;li&gt;内部创建了一个结构体对 &lt;code&gt;obj&lt;/code&gt; 进行了 &lt;code&gt;引用&lt;/code&gt; ，引用计数+1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这个 &lt;code&gt;block&lt;/code&gt; 被 &lt;code&gt;强引用&lt;/code&gt; ，它是一个 &lt;code&gt;堆block&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;从上面的源码学习中我们得知， &lt;code&gt;block&lt;/code&gt; 的类型是运行时确定的&lt;/li&gt;
&lt;li&gt;而在这个过程中，进行了 &lt;code&gt;_Block_copy&lt;/code&gt;，引用计数 +1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;所以这里打印出了 3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;为什么是 4&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void (^ __weak blockB)(void) = ^{
    NSLog(@&quot;%ld&quot;, CFGetRetainCount((__bridge CFTypeRef)(obj)));
};
blockB();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这是引用了一个局部变量
&lt;ul&gt;
&lt;li&gt;内部创建了一个结构体对 &lt;code&gt;obj&lt;/code&gt; 进行了 &lt;code&gt;引用&lt;/code&gt; ，引用计数+1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这个 &lt;code&gt;block&lt;/code&gt; 被 &lt;code&gt;弱引用&lt;/code&gt; ，它是一个 &lt;code&gt;栈block&lt;/code&gt;，引用计数不变&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;为什么是5&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void (^blockC)(void) = [blockB copy];
blockC();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;将 栈blockB 进行了 copy，引用计数 +1&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;__block&lt;/code&gt; 捕获变量进行了三重拷贝：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;变量A&lt;/code&gt; 被 &lt;code&gt;__block&lt;/code&gt; 修饰，就会被 &lt;code&gt;blockB&lt;/code&gt; 从 &lt;code&gt;栈&lt;/code&gt; 上 &lt;code&gt;拷贝&lt;/code&gt; 到 &lt;code&gt;堆&lt;/code&gt; 上
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_Block_copy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blockB&lt;/code&gt; 捕获 &lt;code&gt;Block_byref C&lt;/code&gt; 并 &lt;code&gt;拷贝&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_Block_object_assign&lt;/code&gt; - &lt;code&gt;_Block_byref_copy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Block_byref C&lt;/code&gt; 对捕获的 &lt;code&gt;变量A&lt;/code&gt; 进行拷贝
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;_Block_object_assign&lt;/code&gt; - &lt;code&gt;byref_keep&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.apple.com/source/libclosure/&quot;&gt;libclosure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>锁｜递归锁</title><link>https://blog.lab-lawliet.app/posts/ios-30-216061756abf499f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-30-216061756abf499f/</guid><description>提到锁的分类一般都能说出两种：互斥锁、自旋锁。这些年我一直是按上厕所理解的，印象挺深的。</description><pubDate>Tue, 14 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 锁的分类&lt;/h2&gt;
&lt;p&gt;提到锁的分类一般都能说出两种：&lt;code&gt;互斥锁&lt;/code&gt;、&lt;code&gt;自旋锁&lt;/code&gt;。这些年我一直是按上厕所理解的，印象挺深的。&lt;/p&gt;
&lt;h3&gt;1.1 互斥锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有人在用厕所了
&lt;ul&gt;
&lt;li&gt;上锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;来一个人就外面等着
&lt;ul&gt;
&lt;li&gt;外面的也要排队&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;里面的完事儿了
&lt;ul&gt;
&lt;li&gt;解锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;按排队的顺序下一个进去，继续循环&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键就是两点：&lt;code&gt;互斥&lt;/code&gt;、&lt;code&gt;同步（顺序等待）&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;1.2 自旋锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有人在用厕所了
&lt;ul&gt;
&lt;li&gt;上锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;来一个人就外面等着
&lt;ul&gt;
&lt;li&gt;但一个个都是暴脾气，不想排队，都不停在拽门，想抢先进去&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;里面的完事儿了
&lt;ul&gt;
&lt;li&gt;解锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这堆人里面就有一个抢进去了
&lt;ul&gt;
&lt;li&gt;其他的继续不停的拽门&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;关键就是两点：&lt;code&gt;互斥&lt;/code&gt;、&lt;code&gt;忙等（死循环）&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实锁还有一些更细分的类型，下面我们就来看看&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 递归锁 &amp;amp; 非递归锁&lt;/h2&gt;
&lt;h3&gt;2.1 多线程混乱&lt;/h3&gt;
&lt;p&gt;我们来看一个例子，下面打印输出会是什么样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQ&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)

func testLog(times: Int) {
    print(&quot;\(times) 号选手上完厕所了，线程： \(Thread.current)&quot;)
    if times &amp;gt; 0 {
        testLog(times: times - 1)
    }
}

func demo() {
    for _ in 0..&amp;lt;10 {
        queue.async {
            testLog(times: 10)
        }
    }
}

demo()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里很简单，上厕所会十分的混乱。输出如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x10073cab0&amp;gt;{number = 2, name = (null)}
9 号选手上完厕所了，线程： &amp;lt;NSThread: 0x10073cab0&amp;gt;{number = 2, name = (null)}
10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x100536080&amp;gt;{number = 3, name = (null)}
9 号选手上完厕所了，线程： &amp;lt;NSThread: 0x100536080&amp;gt;{number = 3, name = (null)}
8 号选手上完厕所了，线程： &amp;lt;NSThread: 0x100536080&amp;gt;{number = 3, name = (null)}
7 号选手上完厕所了，线程： &amp;lt;NSThread: 0x100536080&amp;gt;{number = 3, name = (null)}
6 号选手上完厕所了，线程： &amp;lt;NSThread: 0x100536080&amp;gt;{number = 3, name = (null)}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 NSLock&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let lock1 = NSLock()

func demo1() {
    for _ in 0..&amp;lt;10 {
        queue.async {
            lock1.lock()
            testLog(times: 10)
            lock1.unlock()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加了锁后，大家都在很好的排队上厕所了，完美。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
9 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
8 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
7 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
6 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
5 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
4 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
3 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
2 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
1 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
0 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104845250&amp;gt;{number = 2, name = (null)}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 NSLock 递归加锁的问题&lt;/h3&gt;
&lt;p&gt;这次我们换个地方加锁，也和我们一般的业务场景相似，大多情况是在最上层具体业务场景加锁。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let lock1 = NSLock()

func testLog1(times: Int) {
    lock1.lock() // 不断递归加锁
    print(&quot;\(times) 号选手上完厕所了，线程： \(Thread.current)&quot;)
    if times &amp;gt; 0 {
        testLog1(times: times - 1)
    }
    lock1.unlock()
}

func demo11() {
    for _ in 0..&amp;lt;10 {
        queue.async {
            testLog1(times: 10)
        }
    }
}

demo11()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时的输出就有些不一样了，只输出了这一行。因为 &lt;code&gt;NSLock&lt;/code&gt; 是非递归锁，但是这里又在循环递归加锁，这里就产生了类似死锁的现象。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x104031980&amp;gt;{number = 2, name = (null)}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 NSRecursiveLock&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;NSRecursiveLock&lt;/code&gt; 是可递归的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let lock2 = NSRecursiveLock()

func demo22() {
    for _ in 0..&amp;lt;10 {
        queue.async {
            testLog2(times: 10)
        }
    }
}

func testLog2(times: Int) {
    lock2.lock()
    print(&quot;\(times) 号选手上完厕所了，线程： \(Thread.current)&quot;)
    if times &amp;gt; 0 {
        testLog2(times: times - 1)
    }
    lock2.unlock()
}

demo22()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时厕所又恢复了秩序。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
9 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
8 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
7 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
6 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
5 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
4 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
3 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
2 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
1 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
0 号选手上完厕所了，线程： &amp;lt;NSThread: 0x101012b50&amp;gt;{number = 2, name = (null)}
10 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
9 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
8 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
7 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
6 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
5 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
4 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
3 号选手上完厕所了，线程： &amp;lt;NSThread: 0x1010130d0&amp;gt;{number = 3, name = (null)}
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;多线程递归加锁的问题&lt;/h4&gt;
&lt;p&gt;按照&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW20&quot;&gt;官方文档&lt;/a&gt;的说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NSRecursiveLock 类定义了一个可以被同一个线程多次获取而不会导致线程死锁的锁。 递归锁会跟踪它被成功获取的次数。 每次成功获取锁都必须通过相应的解锁调用来平衡。 只有当所有的 lock 和 unlock 调用都平衡时，锁才真正被释放，以便其他线程可以获取它。&lt;/li&gt;
&lt;li&gt;顾名思义，这种类型的锁通常用于递归函数内部，以防止递归阻塞线程。 您可以类似地在非递归情况下使用它来调用语义要求它们也获取锁的函数。 这是一个通过递归获取锁的简单递归函数的示例。 如果您没有为此代码使用 NSRecursiveLock 对象，则再次调用该函数时线程将死锁。&lt;/li&gt;
&lt;li&gt;注意
&lt;ul&gt;
&lt;li&gt;因为在所有锁调用与解锁调用平衡之前不会释放递归锁，所以您应该仔细权衡使用性能锁的决定与潜在的性能影响。 长时间持有任何锁可能会导致其他线程阻塞，直到递归完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;总结一下： &lt;code&gt;NSRecursiveLock&lt;/code&gt; 不支持多线程递归重复加锁。这也是平时更多使用 &lt;code&gt;@synchronized&lt;/code&gt; 的一个原因，更全面，支持多线程递归加锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是你可能发现了，我这里的代码使用没有任何问题。我怀疑是不是 &lt;code&gt;Swift&lt;/code&gt; 下的 &lt;code&gt;NSRecursiveLock&lt;/code&gt; 有所不同引起的。我再写用个 &lt;code&gt;OC&lt;/code&gt; 看看。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9d611ddeadfc31c4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;果然是会崩溃。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可见 &lt;code&gt;Swift&lt;/code&gt; 下有所不同，至于为什么我还没有找到线索，如果你知道的话欢迎交流。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apple/swift-corelibs-foundation&quot;&gt;Swift-Foundation可以查看相关锁的源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1&quot;&gt;Threading Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>锁｜NSConditionLock</title><link>https://blog.lab-lawliet.app/posts/ios-31-nsconditionlock-ab7394cd474c0915/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-31-nsconditionlock-ab7394cd474c0915/</guid><description>先来看一个面试题，下面代码输出的顺序会是什么样。</description><pubDate>Tue, 14 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 NSConditionLock&lt;/h2&gt;
&lt;p&gt;先来看一个面试题，下面代码输出的顺序会是什么样。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let conditionLock = NSConditionLock(condition: 2)
let queue1 = DispatchQueue.global(qos: .unspecified)
let queue2 = DispatchQueue.global(qos: .background)
let queue3 = DispatchQueue.global(qos: .default)

queue1.async {
    conditionLock.lock(whenCondition: 1)
    print(&quot;1&quot;)
    conditionLock.unlock(withCondition: 0)
}

queue2.async {
    conditionLock.lock(whenCondition: 2)
    print(&quot;2&quot;)
    conditionLock.unlock(withCondition: 1)
}

queue3.async {
    conditionLock.lock()
    print(&quot;3&quot;)
    conditionLock.unlock()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 API 简单介绍&lt;/h3&gt;
&lt;p&gt;相信很多同学和我一样没怎么用过这个锁，对着几个API也是蒙的。下面就先介绍一下这些API吧。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lock(whenCondition:)
&lt;ul&gt;
&lt;li&gt;在锁定操作成功之前，接收者的条件必须等于 &lt;code&gt;Condition&lt;/code&gt; 。 这个方法会阻塞线程的执行，直到可以获取到锁，即满足 &lt;code&gt;Condition&lt;/code&gt; 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unlock(withCondition:)
&lt;ul&gt;
&lt;li&gt;解锁并设置 &lt;code&gt;Condition&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;lock
&lt;ul&gt;
&lt;li&gt;如果没有其他线程获得锁那它能执⾏此⾏以下代码，如果已经有其他线程获得锁，则等待，直⾄其他线程解锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unlock
&lt;ul&gt;
&lt;li&gt;解锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 分析&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;queue1&lt;/code&gt; 、 &lt;code&gt;queue2&lt;/code&gt; 、 &lt;code&gt;queue3&lt;/code&gt;根据优先级从高到低： &lt;code&gt;queue1&lt;/code&gt; &amp;gt; &lt;code&gt;queue3&lt;/code&gt; &amp;gt; &lt;code&gt;queue2&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果不看锁的部分，执行的顺序大概率是 1 3 2。但是这里有锁，所以这里的优先级是一个迷惑项。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;执行步骤如下：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;condition = 2
&lt;ul&gt;
&lt;li&gt;满足 2 的进入条件，但是 &lt;code&gt;queue3&lt;/code&gt; 优先级更高，所以 3 大概率优先输出&lt;/li&gt;
&lt;li&gt;3 输出&lt;/li&gt;
&lt;li&gt;condition 不变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;condition 满足 2 的条件
&lt;ul&gt;
&lt;li&gt;可以执行 2&lt;/li&gt;
&lt;li&gt;2 输出&lt;/li&gt;
&lt;li&gt;解锁 condition = 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;condition 满足 1 的条件
&lt;ul&gt;
&lt;li&gt;可以执行 1&lt;/li&gt;
&lt;li&gt;1 输出&lt;/li&gt;
&lt;li&gt;解锁 condition = 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0396c3896b8be0f8.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apple/swift-corelibs-foundation&quot;&gt;Swift-Foundation可以查看相关锁的源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i-CH1-SW1&quot;&gt;Threading Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>锁｜性能分析</title><link>https://blog.lab-lawliet.app/posts/ios-28-5b03a269c01676c3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-28-5b03a269c01676c3/</guid><description>锁是面试中较为常见的内容，在这个越来越卷的行情下，你觉得你对锁的知识了解的够深（卷）么？</description><pubDate>Mon, 13 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;锁&lt;/code&gt;是面试中较为常见的内容，在这个越来越卷的行情下，你觉得你对&lt;code&gt;锁&lt;/code&gt;的知识了解的够深（卷）么？&lt;/p&gt;
&lt;h2&gt;一、 iOS的锁&lt;/h2&gt;
&lt;h3&gt;1.1 经典的性能对比图&lt;/h3&gt;
&lt;p&gt;下面是一张经典的各种锁方案的性能对比图，相信很多人都见过，出自&lt;a href=&quot;https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/&quot;&gt;ibireme&lt;/a&gt;大佬。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2c967e5435f67c27.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是这已经是多年以前的测试对比了，现如今又是什么样的结果呢？我们自己来测试一下吧！&lt;/p&gt;
&lt;h3&gt;1.2 锁的性能测试&lt;/h3&gt;
&lt;h4&gt;测试代码&lt;/h4&gt;
&lt;p&gt;用 &lt;code&gt;Swift&lt;/code&gt; 的方式来测试一下各个锁 &lt;code&gt;10W&lt;/code&gt; 次加锁解锁的耗时吧，顺便熟悉下 &lt;code&gt;Swift&lt;/code&gt; 下各种锁的用法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .brown
    }

    override func touchesBegan(_ touches: Set&amp;lt;UITouch&amp;gt;, with event: UIEvent?) {
        test_OSSpinLock()
        test_dispatch_semaphore_t()
        test_os_unfair_lock_lock()
        test_pthread_mutex_t()
        test_NSlock()
        test_NSCondition()
        test_PTHREAD_MUTEX_RECURSIVE()
        test_NSRecursiveLock()
        test_NSConditionLock()
        test_synchronized()
    }

    let excuteTimes = 100_000
    
    func test_OSSpinLock() {
        var lock = OS_SPINLOCK_INIT
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            OSSpinLockLock(&amp;amp;lock)
            OSSpinLockUnlock(&amp;amp;lock)
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_OSSpinLock: \(deta)ms&quot;)
    }
    
    func test_dispatch_semaphore_t() {
        let lock = DispatchSemaphore(value: 1)
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            _ = lock.wait(timeout: .distantFuture)
            lock.signal()
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_dispatch_semaphore_t: \(deta)ms&quot;)
    }
    
    func test_os_unfair_lock_lock() {
        var lock = os_unfair_lock.init()
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            os_unfair_lock_lock(&amp;amp;lock)
            os_unfair_lock_unlock(&amp;amp;lock)
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_os_unfair_lock_lock: \(deta)ms&quot;)
    }
    
    func test_pthread_mutex_t() {
        var lock = pthread_mutex_t()
        pthread_mutex_init(&amp;amp;lock, nil)
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            pthread_mutex_lock(&amp;amp;lock)
            pthread_mutex_unlock(&amp;amp;lock)
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_pthread_mutex_t: \(deta)ms&quot;)
    }
    
    func test_NSlock() {
        let lock = NSLock()
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            lock.lock()
            lock.unlock()
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_NSlock: \(deta)ms&quot;)
    }
    
    func test_NSCondition() {
        let lock = NSCondition()
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            lock.lock()
            lock.unlock()
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_NSCondition: \(deta)ms&quot;)
    }
    
    func test_PTHREAD_MUTEX_RECURSIVE() {
        var mutext_recurive = pthread_mutex_t()
        var attr = pthread_mutexattr_t()
        pthread_mutexattr_init(&amp;amp;attr)
        pthread_mutexattr_settype(&amp;amp;attr, PTHREAD_MUTEX_RECURSIVE)
        pthread_mutex_init(&amp;amp;mutext_recurive, &amp;amp;attr)
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            pthread_mutex_lock(&amp;amp;mutext_recurive)
            pthread_mutex_unlock(&amp;amp;mutext_recurive)
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_PTHREAD_MUTEX_RECURSIVE: \(deta)ms&quot;)
    }
    
    func test_NSRecursiveLock() {
        let lock = NSRecursiveLock()
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            lock.lock()
            lock.unlock()
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_NSRecursiveLock: \(deta)ms&quot;)
    }
    
    func test_NSConditionLock() {
        let lock = NSConditionLock()
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            lock.lock()
            lock.unlock()
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_NSConditionLock: \(deta)ms&quot;)
    }
    
    func test_synchronized() {
        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..&amp;lt;excuteTimes {
            // Swift 的 @synchronized
            objc_sync_enter(self)
            objc_sync_exit(self)
        }
        let end = CFAbsoluteTimeGetCurrent()
        let deta = (end - start) * 1000
        print(&quot;test_synchronized: \(deta)ms&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;ARM64&lt;/code&gt; 架构&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;iPhoneX&lt;/code&gt; 下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test_OSSpinLock: 70.26898860931396ms
test_dispatch_semaphore_t: 37.284016609191895ms
test_os_unfair_lock_lock: 31.321048736572266ms
test_pthread_mutex_t: 32.119035720825195ms
test_NSlock: 33.952951431274414ms
test_NSCondition: 33.511996269226074ms
test_PTHREAD_MUTEX_RECURSIVE: 32.86397457122803ms
test_NSRecursiveLock: 34.38997268676758ms
test_NSConditionLock: 40.94898700714111ms
test_synchronized: 49.324989318847656ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从快到慢：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;os_unfair_lock_lock&lt;/li&gt;
&lt;li&gt;pthread_mutex_t&lt;/li&gt;
&lt;li&gt;PTHREAD_MUTEX_RECURSIVE&lt;/li&gt;
&lt;li&gt;NSCondition&lt;/li&gt;
&lt;li&gt;NSlock&lt;/li&gt;
&lt;li&gt;NSRecursiveLock&lt;/li&gt;
&lt;li&gt;dispatch_semaphore_t&lt;/li&gt;
&lt;li&gt;NSConditionLock&lt;/li&gt;
&lt;li&gt;synchronized&lt;/li&gt;
&lt;li&gt;OSSpinLock&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;非 &lt;code&gt;ARM64&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;iPhone8&lt;/code&gt; 模拟器下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test_OSSpinLock: 37.75501251220703ms
test_dispatch_semaphore_t: 39.17098045349121ms
test_os_unfair_lock_lock: 37.19806671142578ms
test_pthread_mutex_t: 35.0489616394043ms
test_NSlock: 37.10508346557617ms
test_NSCondition: 34.54601764678955ms
test_PTHREAD_MUTEX_RECURSIVE: 32.772064208984375ms
test_NSRecursiveLock: 34.829020500183105ms
test_NSConditionLock: 39.30199146270752ms
test_synchronized: 39.29495811462402ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从快到慢：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PTHREAD_MUTEX_RECURSIVE&lt;/li&gt;
&lt;li&gt;NSCondition&lt;/li&gt;
&lt;li&gt;NSRecursiveLock&lt;/li&gt;
&lt;li&gt;pthread_mutex_t&lt;/li&gt;
&lt;li&gt;NSlock&lt;/li&gt;
&lt;li&gt;os_unfair_lock_lock&lt;/li&gt;
&lt;li&gt;OSSpinLock&lt;/li&gt;
&lt;li&gt;dispatch_semaphore_t&lt;/li&gt;
&lt;li&gt;synchronized&lt;/li&gt;
&lt;li&gt;NSConditionLock&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;总结&lt;/h4&gt;
&lt;p&gt;此处的结果并不能作为绝对结论。根据设备硬件及运行情况有不同，有兴趣可以自行进行调试。但总体各方案间的差距并没有到非常致命的地步。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;synchronized&lt;/code&gt; 仍然是开发中我们最常使用的一种，性能OK、使用方便（不用操心 &lt;code&gt;lock&lt;/code&gt; &lt;code&gt;unlock&lt;/code&gt;）。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/&quot;&gt;不再安全的 OSSpinLock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>锁｜@synchronized</title><link>https://blog.lab-lawliet.app/posts/ios-29-synchronized-9f76f41e5c30e90a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-29-synchronized-9f76f41e5c30e90a/</guid><description>那么 synchronized 是怎么实现的呢？在 Xcode 中 @synchronized 是无法直接点进去的。我们尝试在 objc 源码中找线索。</description><pubDate>Mon, 13 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 定位到 synchronized 相关源码&lt;/h2&gt;
&lt;p&gt;那么 &lt;code&gt;synchronized&lt;/code&gt; 是怎么实现的呢？在 &lt;code&gt;Xcode&lt;/code&gt; 中 &lt;code&gt;@synchronized&lt;/code&gt; 是无法直接点进去的。我们尝试在 &lt;code&gt;objc&lt;/code&gt; 源码中找线索。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c059c5c90d03f853.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里找到了成对出现的 &lt;code&gt;objc_sync_exit&lt;/code&gt; / &lt;code&gt;objc_sync_enter&lt;/code&gt;。怀疑他就是 &lt;code&gt;@synchronized&lt;/code&gt; 的底层实现。那么如何来验证一下呢？&lt;/p&gt;
&lt;p&gt;这里我决定将 &lt;code&gt;OC&lt;/code&gt; 代码还原为 &lt;code&gt;C++&lt;/code&gt; 代码来看看。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/87e4769e59d947df.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;clang -rewrite-objc main.m -o main.cpp&lt;/code&gt; 来进行还原。定位到 &lt;code&gt;main&lt;/code&gt; 函数，其中的确成对出现了 &lt;code&gt;objc_sync_exit&lt;/code&gt; / &lt;code&gt;objc_sync_enter&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(&quot;NSObject&quot;), sel_registerName(&quot;new&quot;));
        { id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj);
            try {
                struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
                    ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
                    id sync_exit;
                } _sync_exit(_sync_obj);
                
                NSLog((NSString *)&amp;amp;__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_main_7d2c6c_mi_0);
            } catch (id e) {_rethrow = e;}
            { struct _FIN { _FIN(id reth) : rethrow(reth) {}
                ~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
                id rethrow;
            } _fin_force_rethow(_rethrow);}
        }
        
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来我们可以放心的在 &lt;code&gt;objc&lt;/code&gt; 源码中继续探索了。&lt;/p&gt;
&lt;h2&gt;二、 objc_sync_enter&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    if (obj) {
        // 通过传入的 obj 创建一个 SyncData
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        data-&amp;gt;mutex.lock();
    } else {
        /// 如果传入的 obj 为空的话什么都不做
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform(&quot;NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug&quot;);
        }
        objc_sync_nil();
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这里会利用传入的 &lt;code&gt;obj&lt;/code&gt; 通过 &lt;code&gt;id2data&lt;/code&gt; 获取一个 &lt;code&gt;SyncData&lt;/code&gt; 结构体指针&lt;/li&gt;
&lt;li&gt;如果传入的 &lt;code&gt;obj&lt;/code&gt; 为空，什么都不做&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 objc_sync_exit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    if (obj) {
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            // 尝试解锁
            bool okay = data-&amp;gt;mutex.tryUnlock();
            if (!okay) {
                // 解锁失败
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、 SyncData 的数据结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr&amp;lt;objc_object&amp;gt; object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;nextData
&lt;ul&gt;
&lt;li&gt;SyncData 指针，可见是个 &lt;code&gt;单向链表&lt;/code&gt; 的结构&lt;/li&gt;
&lt;li&gt;这个结构是 &lt;code&gt;synchronized&lt;/code&gt; 可嵌套使用的关键点，后续会降到&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;object
&lt;ul&gt;
&lt;li&gt;传入的 obj&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;threadCount
&lt;ul&gt;
&lt;li&gt;有多少个线程在使用当前 block&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mutex
&lt;ul&gt;
&lt;li&gt;递归锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 id2data&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;static SyncData* id2data(id object, enum usage why)
{
    spinlock_t *lockp = &amp;amp;LOCK_FOR_OBJ(object);
    SyncData **listp = &amp;amp;LIST_FOR_OBJ(object);
    SyncData* result = NULL;

    。。。
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.1 找到 object 对应的数据结构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ca791b007abec8cd.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LOCK_FOR_OBJ&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;找到对应的&lt;code&gt;锁 spinlock_t&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LIST_FOR_OBJ&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;找到对应的 &lt;code&gt;SyncData&lt;/code&gt; 的&lt;em&gt;&lt;strong&gt;指针的指针&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过源码是从一个&lt;em&gt;&lt;strong&gt;全剧静态哈希表&lt;/strong&gt;&lt;/em&gt; &lt;code&gt;static StripedMap&amp;lt;SyncList&amp;gt; sDataLists&lt;/code&gt; 中获取的。&lt;/p&gt;
&lt;h3&gt;5.2 sDataLists 全剧静态哈希表&lt;/h3&gt;
&lt;p&gt;通过源码可以发现，貌似一个Hash表的结构。&lt;/p&gt;
&lt;h4&gt;容量大小 StripeCount&lt;/h4&gt;
&lt;p&gt;真机容量为 &lt;code&gt;8&lt;/code&gt;，其他为 &lt;code&gt;64&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#if TARGET_OS_IPHONE &amp;amp;&amp;amp; !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;范型容器 array&lt;/h4&gt;
&lt;p&gt;这里容器的类型为 &lt;code&gt;SyncList&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct PaddedT {
    T value alignas(CacheLineSize);
};

PaddedT array[StripeCount];
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;容器元素 SyncList&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct SyncList {
    SyncData *data;
    spinlock_t lock;
    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;哈希下标算法 indexForPointer&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;地址 &lt;code&gt;addr&lt;/code&gt; 右移 &lt;code&gt;4位&lt;/code&gt; 得到 &lt;code&gt;A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;地址 &lt;code&gt;addr&lt;/code&gt; 右移 &lt;code&gt;9位&lt;/code&gt; 得到 &lt;code&gt;B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; 异或 &lt;code&gt;B&lt;/code&gt; 得到 &lt;code&gt;C&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;C&lt;/code&gt; 模 &lt;code&gt;容量&lt;/code&gt; 得到下标
&lt;ul&gt;
&lt;li&gt;保证了不会越界&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;static unsigned int indexForPointer(const void *p) {
    uintptr_t addr = reinterpret_cast&amp;lt;uintptr_t&amp;gt;(p);
    return ((addr &amp;gt;&amp;gt; 4) ^ (addr &amp;gt;&amp;gt; 9)) % StripeCount;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;思考一下：下标冲突了怎么办？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;哈希冲突：拉链法（单向链表）&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;上面的图很好的描绘了哈希冲突时的情况
&lt;ul&gt;
&lt;li&gt;obj 计算出的下标冲突时会在链表中添加一个节点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1e973b6c0babf4a8.webp&quot; alt=&quot;sDataLists&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.3 查找TLS快速缓存（如果支持的话）&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;线程局部存储（TLS：Thread Local Storage）&lt;/code&gt; 查找快速缓存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/961653b393053662.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.4 fetch_cache 查找缓存&lt;/h3&gt;
&lt;p&gt;内部操作和 &lt;code&gt;查找TLS快速缓存&lt;/code&gt; 基本一致。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/675df63a0d0cfe9c.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.5 未命中缓存&lt;/h3&gt;
&lt;p&gt;遍历链表查找&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/113ecdaa0b4ebcb6.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.6 第一次进入 及 链表操作&lt;/h3&gt;
&lt;p&gt;这里通过头插法插入解决哈希冲突&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/66e8c15bb32fa672.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;到这里再回头看拉链法的那张图是否就更清楚了？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;六、 SyncCacheItem&lt;/h2&gt;
&lt;p&gt;作为缓存的数据结构，其中的 &lt;code&gt;lockCount&lt;/code&gt; 代表了，这个一个 &lt;code&gt;Block&lt;/code&gt; 在 &lt;code&gt;当前线程&lt;/code&gt; 锁了多少次（即嵌套锁）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct {
    SyncData *data;
    unsigned int lockCount;  // number of times THIS THREAD locked this block
} SyncCacheItem;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;七、 总结&lt;/h2&gt;
&lt;p&gt;这里的设计十分巧妙，刚开始发现在真机下容器的大小只有 &lt;code&gt;8&lt;/code&gt; 的时候还在想，难道最多只能有 &lt;code&gt;8&lt;/code&gt; 个锁么？结果其中还存在&lt;code&gt;单向链表&lt;/code&gt;的数据结构，同时使用&lt;code&gt;单向链表&lt;/code&gt;还解决了&lt;code&gt;哈希冲突&lt;/code&gt;。赞叹真的是很精妙的设计呀！👍&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从源码中我们也可以注意到一些点：
&lt;ul&gt;
&lt;li&gt;锁的对象不要为空&lt;/li&gt;
&lt;li&gt;锁的对象的生命周期要合适，不要中途就被释放了
&lt;ul&gt;
&lt;li&gt;这也是平时我们用 &lt;code&gt;self&lt;/code&gt; 的原因&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;尽量选择一样的 obj 来加锁，可以使 &lt;code&gt;sDataLists&lt;/code&gt; 结构更简单&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://yulingtianxia.com/blog/2015/11/01/More-than-you-want-to-know-about-synchronized/&quot;&gt;关于 @synchronized，这儿比你想知道的还要多&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/&quot;&gt;不再安全的 OSSpinLock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>栅栏函数</title><link>https://blog.lab-lawliet.app/posts/ios-26-cffc005ebc757592/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-26-cffc005ebc757592/</guid><description>最近在 Swift 中想用到栅栏函数，结果犯了难，怎么调用写不出来，也是稍微查了一下才知道怎么样用。</description><pubDate>Sat, 11 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在 &lt;code&gt;Swift&lt;/code&gt; 中想用到&lt;code&gt;栅栏函数&lt;/code&gt;，结果犯了难，怎么调用写不出来，也是稍微查了一下才知道怎么样用。&lt;/p&gt;
&lt;p&gt;顺手总结回顾一下&lt;code&gt;栅栏函数&lt;/code&gt;吧。&lt;/p&gt;
&lt;h2&gt;什么是栅栏函数&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatch_barrier&quot;&gt;Dispatch Barrier&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们瞅瞅官方文档的定义：&lt;em&gt;&lt;strong&gt;在并发调度队列中执行的任务的同步点&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;说人话：&lt;em&gt;&lt;strong&gt;可以控制&lt;code&gt;并发对列&lt;/code&gt;任务的执行顺序&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;1.1 常用的API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;dispatch_barrier_async
&lt;ul&gt;
&lt;li&gt;前面的任务执行完才会执行这里面的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dispatch_barrier_sync
&lt;ul&gt;
&lt;li&gt;阻塞线程，前面的任务执行完才会执行这里面的，然后再执行后面的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面我们看看实际的使用效果&lt;/p&gt;
&lt;h2&gt;二、 Swift 中该怎么写栅栏函数呢&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dispatch_barrier_async&lt;/code&gt; / &lt;code&gt;dispatch_barrier_sync&lt;/code&gt; 这两个 API 是用不了的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/09d8f539a69f706c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 创建并发队列&lt;/h3&gt;
&lt;p&gt;根据提示，我们先创建一个队列：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQueue&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;参数
&lt;ul&gt;
&lt;li&gt;label
&lt;ul&gt;
&lt;li&gt;队列名字&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;qos
&lt;ul&gt;
&lt;li&gt;优先级&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;attributes
&lt;ul&gt;
&lt;li&gt;属性列表，可以在这里设置同步异步（添加了 &lt;code&gt;.concurrent&lt;/code&gt; 就是并发对列，否则就是穿行队列）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;autoreleaseFrequency
&lt;ul&gt;
&lt;li&gt;block 内制动释放的频率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;target
&lt;ul&gt;
&lt;li&gt;想要 block 在哪个对列执行，默认是当前对列&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 创建栅栏函数&lt;/h3&gt;
&lt;p&gt;通过文档发现这个&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchqueue/2016098-async&quot;&gt;API&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参数
&lt;ul&gt;
&lt;li&gt;group
&lt;ul&gt;
&lt;li&gt;调度组&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;qos
&lt;ul&gt;
&lt;li&gt;优先级&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;flags
&lt;ul&gt;
&lt;li&gt;附加属性，可以在这里设置为栅栏函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;work
&lt;ul&gt;
&lt;li&gt;block&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;queue.async(group: nil, qos: .default, flags: .barrier) {
    xxx
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 异步栅栏函数 + 自定义队列&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQueue&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
        
queue.async {
    print(&quot;1: \(Thread.current)&quot;)
    sleep(1)
}
// 异步栅栏函数
queue.async(group: nil, qos: .default, flags: .barrier) {
    print(&quot;barrier - async: \(Thread.current)&quot;)
    for _ in 0...100 {
        print(&quot;...&quot;)
    }
}

queue.async {
    print(&quot;2: \(Thread.current)&quot;)
}

queue.async {
    print(&quot;3: \(Thread.current)&quot;)
}

queue.async {
    print(&quot;4: \(Thread.current)&quot;)
}

print(&quot;=========: \(Thread.current)&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的代码你觉得会是怎么样的执行结果呢？&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1: &amp;lt;NSThread: 0x6000018f1040&amp;gt;{number = 6, name = (null)}
=========: &amp;lt;NSThread: 0x6000018b0800&amp;gt;{number = 1, name = main}
barrier - async: &amp;lt;NSThread: 0x6000018f1040&amp;gt;{number = 6, name = (null)}
...

...
...
4: &amp;lt;NSThread: 0x6000018f6fc0&amp;gt;{number = 4, name = (null)}
3: &amp;lt;NSThread: 0x6000018f0540&amp;gt;{number = 5, name = (null)}
2: &amp;lt;NSThread: 0x6000018f1040&amp;gt;{number = 6, name = (null)}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 流程分析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/57da4002a8f82643.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图中蓝线代表了代码执行的顺序&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2/3/4 &lt;/code&gt;异步任务顺序不一定相信大家很好理解&lt;/li&gt;
&lt;li&gt;队列中存在了 &lt;code&gt;异步栅栏函数&lt;/code&gt; 它之后的任务将不会得到调度&lt;/li&gt;
&lt;li&gt;&lt;code&gt;======&lt;/code&gt; 为队列之外的代码，所以不受 &lt;code&gt;异步栅栏函数&lt;/code&gt; 影响，因为其他都是异步的任务，它输出位置不一定但是极大概率在 &lt;code&gt;异步栅栏函数&lt;/code&gt; 之前执行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四、 同步栅栏函数 + 自定义队列&lt;/h2&gt;
&lt;p&gt;代码稍作调整：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQueue&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
        
queue.async {
    print(&quot;1: \(Thread.current)&quot;)
    sleep(1)
}

// 同步栅栏函数
queue.sync(flags: .barrier) {
    print(&quot;barrier - sync: \(Thread.current)&quot;)
    for _ in 0...100 {
        print(&quot;...&quot;)
    }
}

queue.async {
    print(&quot;2: \(Thread.current)&quot;)
}

queue.async {
    print(&quot;3: \(Thread.current)&quot;)
}

queue.async {
    print(&quot;4: \(Thread.current)&quot;)
}

print(&quot;=========: \(Thread.current)&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的代码你觉得会是怎么样的执行结果呢？&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1: &amp;lt;NSThread: 0x6000038b3640&amp;gt;{number = 13, name = (null)}
barrier - sync: &amp;lt;NSThread: 0x6000038f83c0&amp;gt;{number = 1, name = main}
...
...
...
...

...
...
...
=========: &amp;lt;NSThread: 0x6000038f83c0&amp;gt;{number = 1, name = main}
2: &amp;lt;NSThread: 0x6000038b3640&amp;gt;{number = 13, name = (null)}
4: &amp;lt;NSThread: 0x6000038b3640&amp;gt;{number = 13, name = (null)}
3: &amp;lt;NSThread: 0x6000038ddb00&amp;gt;{number = 11, name = (null)}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.1 流程分析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/05714769733b90be.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图中蓝线代表了代码执行的顺序&lt;/li&gt;
&lt;li&gt;&lt;code&gt;同步栅栏函数&lt;/code&gt; ，会阻断当前代码区间线程代码的执行&lt;/li&gt;
&lt;li&gt;如图所示，只有当 &lt;code&gt;同步栅栏函数&lt;/code&gt; 内的任务执行完成，才会继续外部后续代码&lt;/li&gt;
&lt;li&gt;所以 &lt;code&gt;====&lt;/code&gt; &amp;amp; &lt;code&gt;2/3/4&lt;/code&gt; 一定在 &lt;code&gt;同步栅栏函数&lt;/code&gt; 后&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 全局并发队列&lt;/h2&gt;
&lt;p&gt;前面有提到，栅&lt;code&gt;栏函数&lt;/code&gt;只能在&lt;code&gt;并发队列&lt;/code&gt;中使用，但是上面都是用到了&lt;code&gt;自定义并发队列&lt;/code&gt;。那么全局并发队列可以用么？来验证一下吧&lt;/p&gt;
&lt;h3&gt;5.1 异步栅栏函数 + 全局并发队列&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/95bb5dfd5257fc8c.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过反复调试，发现栅栏函数失效了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2: &amp;lt;NSThread: 0x6000026bd280&amp;gt;{number = 7, name = (null)}
=========: &amp;lt;NSThread: 0x6000026f0180&amp;gt;{number = 1, name = main}
3: &amp;lt;NSThread: 0x6000026bc540&amp;gt;{number = 3, name = (null)}
1: &amp;lt;NSThread: 0x6000026be080&amp;gt;{number = 6, name = (null)}
4: &amp;lt;NSThread: 0x6000026f1bc0&amp;gt;{number = 5, name = (null)}
barrier - async: &amp;lt;NSThread: 0x6000026bc540&amp;gt;{number = 3, name = (null)}
...
...
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面再试试同步的&lt;/p&gt;
&lt;h3&gt;5.1 同步栅栏函数 + 全局并发队列&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/90cd50cabbeb409f.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同样失去了原本的效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;barrier - sync: &amp;lt;NSThread: 0x60000156c800&amp;gt;{number = 1, name = main}
...

...
...
...
...
=========: &amp;lt;NSThread: 0x60000156c800&amp;gt;{number = 1, name = main}
1: &amp;lt;NSThread: 0x60000152a280&amp;gt;{number = 9, name = (null)}
2: &amp;lt;NSThread: 0x60000152c4c0&amp;gt;{number = 4, name = (null)}
4: &amp;lt;NSThread: 0x600001528700&amp;gt;{number = 10, name = (null)}
3: &amp;lt;NSThread: 0x60000152ec40&amp;gt;{number = 11, name = (null)}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 思考&lt;/h3&gt;
&lt;p&gt;全局对列作为全局的对列，会有很多任务，很多是我们不可控的，如果被阻拦下来了会导致很多未知问题。所以这里设计者就将其设计为六不可使用栅栏函数👍。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根据题的逻辑只有源码能够带来答案，有兴趣可以去&lt;a href=&quot;https://opensource.apple.com/tarballs/libdispatch/&quot;&gt;GCD源码：libdispatch&lt;/a&gt;探索下&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchqueue&quot;&gt;DispatchQueue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.apple.com/tarballs/libdispatch/&quot;&gt;GCD源码：libdispatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>信号量</title><link>https://blog.lab-lawliet.app/posts/ios-27-171d28c39b520097/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-27-171d28c39b520097/</guid><description>_dispatch_semaphore_wait_slow</description><pubDate>Sat, 11 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;h2&gt;一、 控制 GCD 最大并发数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQueue&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
let semaphore = DispatchSemaphore(value: 2)

for i in 0...5 {
    queue.async {
        _ = semaphore.wait(timeout: .distantFuture)
        print(&quot;&amp;lt;&amp;lt;&amp;lt; 下载开始：\(i) 日期：\(Date())&quot;)
        sleep(5)
        print(&quot;下载结束：\(i) &amp;gt;&amp;gt;&amp;gt; 日期：\(Date())&quot;)
        semaphore.signal()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt; 下载开始：1 日期：2021-09-11 08:28:56 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：0 日期：2021-09-11 08:28:56 +0000

下载结束：0 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:01 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：2 日期：2021-09-11 08:29:01 +0000
下载结束：1 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:01 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：3 日期：2021-09-11 08:29:01 +0000

下载结束：2 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:06 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：4 日期：2021-09-11 08:29:06 +0000
下载结束：3 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:06 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：5 日期：2021-09-11 08:29:06 +0000

下载结束：5 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:11 +0000
下载结束：4 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 08:29:11 +0000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 源码解读&lt;/h2&gt;
&lt;h3&gt;2.1 dispatch_semaphore_create&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/*!
 * @function dispatch_semaphore_create
 *
 * @abstract
 * 用初始值创建新的计数信号量
 *
 * @discussion
 * 当两个线程需要协调时，为该值传递0是有用的 完成某一特定事件。
 * 传递一个大于零的值 用于管理有限的资源池，其中池大小相等 的价值
 * 
 * @param value
 * 信号量的起始值。传递一个小于零的值 导致返回NULL。
 * 
 *
 * @result
 * 新创建的信号量，如果失败则为NULL
 */
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_semaphore_t
dispatch_semaphore_create(intptr_t value);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 dispatch_semaphore_signal&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/*!

 * @function dispatch_semaphore_signal
 *
 * @abstract
 * Signal (increment) a semaphore.
 *
 * @discussion
 * 增加计数信号量。如果之前的值小于零
 * 这个函数在返回之前唤醒一个等待线程
 *
 * @param dsema The counting semaphore.
 * 在这个参数中传递NULL的结果是未定义的
 *
 * @result
 * 如果线程被唤醒，这个函数返回非零值。否则,返回零
 * 
 */


intptr_t
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
    // +1 的操作
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value &amp;gt; 0)) {
	return 0;
    }
    
    // wait 操作过多 和signal信号不平衡 抛出异常
    if (unlikely(value == LONG_MIN)) {
	DISPATCH_CLIENT_CRASH(value,
				&quot;Unbalanced call to dispatch_semaphore_signal()&quot;);
    }
    //因为是基于pthread下层的封装，为了避免下层的异常，这里做异常处理
    return _dispatch_semaphore_signal_slow(dsema);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 dispatch_semaphore_wait&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/*!
 * @function dispatch_semaphore_wait
 *
 * @abstract
 * 等待(递减)信号量
 *
 * @discussion
 * 递减计数信号量。如果结果值小于零， 
 * 这个函数在返回之前等待一个信号出现。
 *
 * @param dsema
 * 信号量。在这个参数中传递NULL的结果是未定义的
 *
 * @param timeout
 * 何时超时(参见dispatch_time)。为了方便，
 * 有 DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。
 *
 * @result
 * 成功时返回零，如果超时则返回非零
 */

intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
    // -1 操作
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value &amp;gt;= 0)) {
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;_dispatch_semaphore_wait_slow&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
		dispatch_time_t timeout)
{
    long orig;

    _dispatch_sema4_create(&amp;amp;dsema-&amp;gt;dsema_sema, _DSEMA4_POLICY_FIFO);
        
    //判断timeout
    switch (timeout) {
    default:
	if (!_dispatch_sema4_timedwait(&amp;amp;dsema-&amp;gt;dsema_sema, timeout)) {
		break;
	}
	// Fall through and try to undo what the fast path did to
	// dsema-&amp;gt;dsema_value
        case DISPATCH_TIME_NOW:
            orig = dsema-&amp;gt;dsema_value;
            while (orig &amp;lt; 0) {
		if (os_atomic_cmpxchgv2o(dsema, dsema_value, orig, orig + 1,
					&amp;amp;orig, relaxed)) {
                    // 处理超时
                    return _DSEMA4_TIMEOUT();
		}
            }
            // Another thread called semaphore_signal().
            // Fall through and drain the wakeup.
	case DISPATCH_TIME_FOREVER:
            //等待操作
            _dispatch_sema4_wait(&amp;amp;dsema-&amp;gt;dsema_sema);
            break;
	}
	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;_dispatch_sema4_wait&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
	int ret = 0;
	do {
		ret = sem_wait(sema);
	} while (ret == -1 &amp;amp;&amp;amp; errno == EINTR);
	DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出，内部其实是个 &lt;code&gt;do-while&lt;/code&gt; 循环实现的。&lt;/p&gt;
&lt;h2&gt;三、 一个思考题&lt;/h2&gt;
&lt;p&gt;下面代码会是怎样的执行结果呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let queue = DispatchQueue.init(label: &quot;RyukieQueue&quot;, qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
let semaphore = DispatchSemaphore(value: 0)

queue.async {
    _ = semaphore.wait(timeout: .distantFuture)
    print(&quot;&amp;lt;&amp;lt;&amp;lt; 下载开始：\(1) 日期：\(Date())&quot;)
    sleep(5)
    print(&quot;下载结束：\(1) &amp;gt;&amp;gt;&amp;gt; 日期：\(Date())&quot;)
}

queue.async {
    print(&quot;&amp;lt;&amp;lt;&amp;lt; 下载开始：\(2) 日期：\(Date())&quot;)
    sleep(5)
    print(&quot;下载结束：\(2) &amp;gt;&amp;gt;&amp;gt; 日期：\(Date())&quot;)
    semaphore.signal()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt; 下载开始：2 日期：2021-09-11 09:05:24 +0000

下载结束：2 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 09:05:29 +0000
&amp;lt;&amp;lt;&amp;lt; 下载开始：1 日期：2021-09-11 09:05:29 +0000

下载结束：1 &amp;gt;&amp;gt;&amp;gt; 日期：2021-09-11 09:05:34 +0000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchqueue&quot;&gt;DispatchQueue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://opensource.apple.com/tarballs/libdispatch/&quot;&gt;GCD源码：libdispatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>复杂链表的复制(无Swift用例)</title><link>https://blog.lab-lawliet.app/posts/article-35-7d0f704ed76e1ea5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-35-7d0f704ed76e1ea5/</guid><description>请实现 copyRandomList 函数，复制一个复杂链表。在复杂链表中，每个节点除了有一个 next 指针指向下一个节点，还有一个 random 指针指向链表中的任意节点或者 null。</description><pubDate>Tue, 07 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;请实现 copyRandomList 函数，复制一个复杂链表。在复杂链表中，每个节点除了有一个 next 指针指向下一个节点，还有一个 random 指针指向链表中的任意节点或者 null。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fcd314ced4eb39ca.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：head = [[7,null],[13,0],[11,4],[10,2],[1,0]]&lt;/p&gt;
&lt;p&gt;输出：[[7,null],[13,0],[11,4],[10,2],[1,0]]&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/33ab34308db184dc.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：head = [[1,1],[2,1]]&lt;/p&gt;
&lt;p&gt;输出：[[1,1],[2,1]]&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 3：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7656e03d27d94691.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输入：head = [[3,null],[3,0],[3,null]]&lt;/p&gt;
&lt;p&gt;输出：[[3,null],[3,0],[3,null]]&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 4：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;输入：head = []&lt;/p&gt;
&lt;p&gt;输出：[]&lt;/p&gt;
&lt;p&gt;解释：给定的链表为空（空指针），因此返回 null。&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;-10000 &amp;lt;= Node.val &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Node.random&lt;/code&gt; 为&lt;code&gt;空（null）&lt;/code&gt;或指向链表中的节点。&lt;/p&gt;
&lt;p&gt;节点数目不超过 &lt;code&gt;1000&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;注意：本题与主站 138 题相同：https://leetcode-cn.com/problems/copy-list-with-random-pointer/&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func copyRandomList2(_ head: ComplexListNode) -&amp;gt; ComplexListNode? {
    var p: ComplexListNode? = head
    
    // A a B b C c D d
    while let current = p {
        let node = ComplexListNode(current.val)
        node.next = current.next
        p?.next = node
        p = current.next?.next
    }
    
    // 处理 random
    p = head
    while let current = p {
        let clone = current.next
        clone?.random = current.random?.next
        p = clone?.next
    }
    
    // 删除原来的节点
    let result: ComplexListNode? = head.next
    p = result
    while let current = p {
        p?.next = current.next?.next
        p = p?.next
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Swift项目编译速度优化</title><link>https://blog.lab-lawliet.app/posts/swift-23-swift-41fa502b1f6bf291/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-23-swift-41fa502b1f6bf291/</guid><description>本文将从代码层面探究如何分析并优化 Swift 代码的编译速度。</description><pubDate>Tue, 07 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;本文将从代码层面探究如何分析并优化 &lt;code&gt;Swift&lt;/code&gt; 代码的编译速度。&lt;/p&gt;
&lt;h2&gt;一、 检查代码中的编译瓶颈&lt;/h2&gt;
&lt;h3&gt;1.1 项目配置&lt;/h3&gt;
&lt;p&gt;通过警告的形式标识出超出设定值的代码，下面以设置 &lt;code&gt;100ms&lt;/code&gt; 为界限为例&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;BuildSettings&lt;/code&gt; 中 &lt;code&gt;other swift flags&lt;/code&gt; 下添加下面的配置：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e15f1d5a302e4acc.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-Xfrontend -warn-long-function-bodies=100&lt;/li&gt;
&lt;li&gt;-Xfrontend -warn-long-expression-type-checking=100&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;仅在 &lt;code&gt;DEBUG&lt;/code&gt; 下添加就行了，毕竟只是用来检测&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.2 编译检查&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Clean&lt;/code&gt; 后编译一次，这里就可以看到很多的警告了：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/86a99e74576f8eb2.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c9d5c6118cb69b6c.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;你可能觉得这样检测有点不方便，毕竟项目的警告可能有几百上千个。下面介绍一个工具：&lt;code&gt;BuildTimeAnalyzer-for-Xcode&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;二、 BuildTimeAnalyzer-for-Xcode 使用说明&lt;/h2&gt;
&lt;p&gt;首先 Clone 项目代码（或者直接&lt;a href=&quot;https://github.com/RyukieSama/BuildTimeAnalyzer-for-Xcode&quot;&gt;下载代码&lt;/a&gt;）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/RyukieSama/BuildTimeAnalyzer-for-Xcode.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 项目配置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;BuildSettings&lt;/code&gt; 中 &lt;code&gt;other swift flags&lt;/code&gt; 下添加下面的配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-Xfrontend -debug-time-function-bodies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然后 &lt;code&gt;Clean&lt;/code&gt; 项目，重新 &lt;code&gt;Build&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.2 打开 BuildTimeAnalyzer.xcodeproj&lt;/h3&gt;
&lt;p&gt;这里有三种方式来使用&lt;/p&gt;
&lt;h4&gt;a. 直接运行&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/34624e44cb082c41.webp&quot; alt=&quot;直接运行&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;b. 导出App&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Product - Archive&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d316186abe05f5c7.webp&quot; alt=&quot;导出App&quot; /&gt;&lt;/p&gt;
&lt;p&gt;导出一个App文件，以后即可直接使用了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1a72670db83a84c6.webp&quot; alt=&quot;App&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;c. 懒人方式-下载即用&lt;/h4&gt;
&lt;p&gt;这里我搞好了一个包，直接可用。支持 &lt;code&gt;Intel&lt;/code&gt;， &lt;code&gt;Apple Silicon&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/BuildTimeAnalyzer-for-Xcode&quot;&gt;下载：BuildTimeAnalyzer.zip即可&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;2.3 查看编译耗时日志&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e9991c7eb9b8128d.webp&quot; alt=&quot;04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在列表中点击刚才自己的配置完成并编译后的项目，即可查看日志。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ebd52b1a4d901d9d.webp&quot; alt=&quot;05&quot; /&gt;&lt;/p&gt;
&lt;p&gt;显示了整体编译耗时，还标有具体代码的行数与函数名，点击可跳转。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每次时间不一定一样，根据电脑运行情况会有一些变化，但是整体的瓶颈趋势还是基本一致的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 优化项目&lt;/h2&gt;
&lt;p&gt;这里个人建议结合上面两种方式来定位。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BuildTimeAnalyzer&lt;/code&gt; 用来找到瓶颈更加直观&lt;/li&gt;
&lt;li&gt;第一个直接添加警告的方式用来定位具体代码更准确&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 项目配置&lt;/h3&gt;
&lt;p&gt;为了结合两种方式，这里我的配置如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/61f66ad298a8e179.webp&quot; alt=&quot;09&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;code&gt;BuildTimeAnalyzer&lt;/code&gt; 分析结果&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7ab1d478784dc2b3.webp&quot; alt=&quot;我的电脑配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/72173087fdb3347d.webp&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最高： &lt;code&gt;DataBaseDataCenter+Overview&lt;/code&gt; 耗时 &lt;code&gt;35075ms&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;案例1：较长的逻辑&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/499b4b78baf13558.webp&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里是使用 &lt;code&gt;WCDB&lt;/code&gt; 瓶颈显示的是连续的调用 &lt;code&gt;WCDB&lt;/code&gt; 中的范型函数来获得一个 &lt;code&gt;condition&lt;/code&gt; 进行数据库操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/caab17c19a9ba61e.webp&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;优化：拆分&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a7b26e0c3a7d4359.webp&quot; alt=&quot;14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Clean 后再 Build，查看是否有效：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/768da522e55b01a6.webp&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DataBaseDataCenter+Overview&lt;/code&gt; 耗时由 &lt;code&gt;35075ms&lt;/code&gt; 下降至 &lt;code&gt;19007ms&lt;/code&gt;，减少了近一半的时间。那么再把 &lt;code&gt;DataBaseDataCenter+Overview&lt;/code&gt; 其他相同场景的代码也优化一下看看结果吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ab784adf8ef1e2e0.webp&quot; alt=&quot;16&quot; /&gt;&lt;/p&gt;
&lt;p&gt;优化了另一处一样的场景后，时间降到了 &lt;code&gt;915ms&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;案例2：字符串拼接&lt;/h3&gt;
&lt;p&gt;这里使用了 &lt;code&gt;+&lt;/code&gt; 进行字符串的拼接&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e09028a683ebc74e.webp&quot; alt=&quot;19&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b1ceb5244e9b6eb1.webp&quot; alt=&quot;17&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此处总耗时 &lt;code&gt;2596ms&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;优化：使用 &quot;xx\(xx)xx&quot; 拼接&lt;/h4&gt;
&lt;p&gt;使用 &lt;code&gt;\(preString)~\(endString)&lt;/code&gt; 的方式进行拼接&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9eadbe06956a8d74.webp&quot; alt=&quot;20&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b5169f50f1dd7da4.webp&quot; alt=&quot;18&quot; /&gt;&lt;/p&gt;
&lt;p&gt;效果明显，降到了 &lt;code&gt;617ms&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;案例3：少用 ??&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3491278f94d0bbda.webp&quot; alt=&quot;22&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/97b702039a93614e.webp&quot; alt=&quot;21&quot; /&gt;&lt;/p&gt;
&lt;p&gt;此处用了较多的 &lt;code&gt;??&lt;/code&gt;。整体耗时 &lt;code&gt;3561ms&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;优化：提前 gurad/if let 解包或者减少调用 ?? 次数&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e62efa16c321ef79.webp&quot; alt=&quot;23&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a0237a3fb593adb7.webp&quot; alt=&quot;24&quot; /&gt;&lt;/p&gt;
&lt;p&gt;整体降到了 &lt;code&gt;855ms&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;四、 总结&lt;/h2&gt;
&lt;p&gt;此处列举了一些我在项目中的一些成功优化的点，当然还有一些没有找到合适优化方式的点。再有一些有价值的点结局的话我会再来更新进文中。而且这里是以个人项目作为小白鼠进行的优化，企业项目中一般可优化的地方就更多了。而且我们也不能为了优化而优化，不能影响了源代码的可读性与连贯性。&lt;/p&gt;
&lt;p&gt;也欢迎大家交流补充&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/RyukieSama/BuildTimeAnalyzer-for-Xcode&quot;&gt;BuildTimeAnalyzer中文手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apple/swift/commit/18c75928639acf0ccf0e1fb6729eea75bc09cbd5&quot;&gt;配置说明&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/-wgBhE11xEXDS7Hqgq3FjA&quot;&gt;iOS 微信编译速度优化分享&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/RyukieSama/Optimizing-Swift-Build-Times&quot;&gt;Optimizing Swift build times&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>二叉树中和为某一值的路径</title><link>https://blog.lab-lawliet.app/posts/article-34-421355e908323b15/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-34-421355e908323b15/</guid><description>输入一棵二叉树和一个整数，打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。</description><pubDate>Mon, 06 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;输入一棵二叉树和一个整数，打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。&lt;/p&gt;
&lt;p&gt;示例:
给定如下二叉树，以及目标和 target = 22，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;               5
              / \
             4   8
            /   / \
           11  13  4
          /  \    / \
         7    2  5   1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [
    [5,4,11,2],
    [5,8,4,5]
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;p&gt;将问题拆解为子树的问题，递归解决&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func pathSum(_ root: TreeNode?, _ target: Int) -&amp;gt; [[Int]] {
    guard let rootV = root?.val else {
        return []
    }
    var result: [[Int]] = []
    let deta = target - rootV
    var temp: [TreeNode] = []
    
    if let left = root?.left {
        temp.append(left)
    }
    
    if let right = root?.right {
        temp.append(right)
    }

    if temp.isEmpty { // 到达叶节点了
        if target == rootV {
            result.append([rootV])
        }
    }
    else {
        temp.forEach { tree in
            pathSum(tree, deta).forEach { datas in
                result.append([rootV] + datas)
            }
        }
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f6099b5338775901.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>二叉搜索树的后序遍历序列</title><link>https://blog.lab-lawliet.app/posts/article-33-c4c795bb5de11534/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-33-c4c795bb5de11534/</guid><description>输入一个整数数组，判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true，否则返回 false。假设输入的数组的任意两个数字都互不相同。</description><pubDate>Sun, 05 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;输入一个整数数组，判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true，否则返回 false。假设输入的数组的任意两个数字都互不相同。&lt;/p&gt;
&lt;p&gt;参考以下这颗二叉搜索树：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      5
     / \
    2   6
   / \
  1   3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: [1,6,3,2,5]
输出: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: [1,3,2,6,5]
输出: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：
数组长度 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;什么是「二叉搜索树」&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;二叉搜索树&lt;/code&gt;（BST，Binary Search Tree），也称 &lt;code&gt;二叉排序树&lt;/code&gt; 或 &lt;code&gt;二叉查找树&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一颗二叉树，可以为空；如果不为空，满足以下性质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;非空左子树&lt;/code&gt;的所有键值&lt;code&gt;小于&lt;/code&gt;其&lt;code&gt;根节点&lt;/code&gt;的键值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;非空右子树&lt;/code&gt;的所有键值&lt;code&gt;大于&lt;/code&gt;其&lt;code&gt;根节点&lt;/code&gt;的键值&lt;/li&gt;
&lt;li&gt;&lt;code&gt;左、右子树&lt;/code&gt;都是&lt;code&gt;二叉搜索树&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;知道了 &lt;code&gt;二叉搜索树&lt;/code&gt; 的特性我们可以知道，它的后序遍历的结果结构为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;左节点集合A 右节点集合B 根节点&lt;/code&gt; 元素这样分布的数组&lt;/li&gt;
&lt;li&gt;且 &lt;code&gt;A&lt;/code&gt; 的所有元素小于跟节点， &lt;code&gt;B&lt;/code&gt; 的所有元素大于跟节点&lt;/li&gt;
&lt;li&gt;而 &lt;code&gt;A&lt;/code&gt; 、 &lt;code&gt;B&lt;/code&gt; 也都是 &lt;code&gt;二叉搜索树&lt;/code&gt;，这里就可以通过递归解决了，将问题转化为&lt;em&gt;&lt;strong&gt;所有子树的后序遍历结果是否符合要求&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func verifyPostorder(_ postorder: [Int]) -&amp;gt; Bool {
    guard let root = postorder.last else {
        return true
    }
    var left: [Int] = []
    var right: [Int] = []
    var flag = false
    
    for offset in 1..&amp;lt;postorder.count {
        let v = postorder[postorder.count - 1 - offset]
        if v &amp;lt; root {
            if flag == false { flag = true }
            left.insert(v, at: 0)
        }
        else if v &amp;gt; root, flag == false {
            right.insert(v, at: 0)
        }
        else {
            return false
        }
    }
    
    return verifyPostorder(left) &amp;amp;&amp;amp; verifyPostorder(right)
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>多线程原理与atomic</title><link>https://blog.lab-lawliet.app/posts/ios-23-atomic-35cb63e8bb575841/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-23-atomic-35cb63e8bb575841/</guid><description>线程 也被称为 轻量级进程 ，是程序执行流程的最小单元。一个标准的线程由 线程ID、当前指令指针PC、寄存器集合 和 堆栈 组成。</description><pubDate>Sun, 05 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 什么是线程&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;线程&lt;/strong&gt;&lt;/em&gt; 也被称为 &lt;em&gt;&lt;strong&gt;轻量级进程&lt;/strong&gt;&lt;/em&gt; ，是程序执行流程的最小单元。一个标准的线程由 &lt;code&gt;线程ID&lt;/code&gt;、&lt;code&gt;当前指令指针PC&lt;/code&gt;、&lt;code&gt;寄存器集合&lt;/code&gt; 和 &lt;code&gt;堆栈&lt;/code&gt; 组成。&lt;/p&gt;
&lt;p&gt;通常来说，一个&lt;code&gt;进程&lt;/code&gt;由一个到多个&lt;code&gt;线程&lt;/code&gt;组成，各个线程之间共享程序的内存空间（包括代码段、数据段、堆等）以及一些进程资源（如打开文件和信号）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5b888cb87aba0a8f.webp&quot; alt=&quot;进程内的线程&quot; /&gt;&lt;/p&gt;
&lt;p&gt;大多软件中，线程都不止一个。多个线程可以互不相干扰地并发执行，并共享进程的全局变量和堆数据。那么多线程与单线程的进程相比，又有哪些优势呢？&lt;/p&gt;
&lt;h3&gt;1.1 使用多线程的原因&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;某个操作可能会陷入长时间的等待，等待的线程会进入睡眠状态，无法继续执行。多线程执行可以有效利用等待时间。典型的例子是等待网络响应，这可能需要花费数秒甚至数十秒。&lt;/li&gt;
&lt;li&gt;某个操作（常常是计算）会消耗大量的时间，如果只有一个线程，程序和用户之间的交互就会中断。多线程可以让一个线程负责交互，另一个负责计算。&lt;/li&gt;
&lt;li&gt;程序逻辑本身就要求兵法操作，例如一个多任务下载软件。&lt;/li&gt;
&lt;li&gt;多CPU或者多核计算机，本身具备同时执行多个线程的能力，因此单线程程序无法全面发挥计算机的全部计算性能。&lt;/li&gt;
&lt;li&gt;相对于多进程应用，多线程在数据共享方面的效率要高很多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 线程的访问权限&lt;/h2&gt;
&lt;p&gt;线程的访问非常自由，它可以访问进程内村里的所有数据，甚至包括其他线程的堆栈（如果知道其他线程的堆栈地址的话，一般不会），线程野拥有自己的私有存储空间，包括以下方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;栈（尽管并非完全无法被其他线程访问，但一般情况下仍然可以认为是私有的数据）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;线程局部存储（TLS：Thread Local Storage）&lt;/code&gt;。线程局部存储是某些操作系统为线程单独提供的私有空间，但通常只有非常有限的容量。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;寄存器（包括PC寄存器）&lt;/code&gt;，寄存器是执行流的基本结构，因此位线程私有。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从 C 程序员的角度看，数据在线程之间是否私有如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程私有
&lt;ul&gt;
&lt;li&gt;局部变量&lt;/li&gt;
&lt;li&gt;函数的参数&lt;/li&gt;
&lt;li&gt;TLS 数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;线程之间共享（进程所有）
&lt;ul&gt;
&lt;li&gt;全局变量&lt;/li&gt;
&lt;li&gt;堆上的数据&lt;/li&gt;
&lt;li&gt;函数里的静态变量&lt;/li&gt;
&lt;li&gt;程序代码，任何线程都有权利读取并执行任何代码&lt;/li&gt;
&lt;li&gt;打开的文件，A线程打开的文件可以由B线程读写&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 线程调度&lt;/h2&gt;
&lt;p&gt;不论是多核还是单核计算机，线程总是“并发”执行的。当线程数小于等于处理器核数时（并且操作系统支持多处理器），线程的并发是真正的并发，不同线程运行在不同处理器上，彼此互不干涉。对于线程数大于处理器数量的情况，线程的并发会受到一些阻碍，因为此时至少有一个处理器会运行多个线程。&lt;/p&gt;
&lt;p&gt;在但处理器对应多线程的情况下，并发是一种模拟出来的状态。操作系统会让这些多线程程序轮流执行，每次仅执行一小段时间（通常是几十到几百毫秒），这样每个线程就“看起来”在同时执行。这种在一个处理器上不断切换的线程的行为称为 &lt;em&gt;&lt;strong&gt;线程调度（Thread Schedule）&lt;/strong&gt;&lt;/em&gt;，有三个主要状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行
&lt;ul&gt;
&lt;li&gt;此时线程正在运行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;就绪
&lt;ul&gt;
&lt;li&gt;此时线程可以立即执行，但CPU已经被占用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;等待
&lt;ul&gt;
&lt;li&gt;此时线程正在等待某一事件（通常是I/O或同步）发生，无法执行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;处于运行中的线程，拥有一段可以执行的时间，称为&lt;em&gt;&lt;strong&gt;时间片&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当时间片用尽，该进程将进入&lt;em&gt;&lt;strong&gt;就绪状态&lt;/strong&gt;&lt;/em&gt;。&lt;/li&gt;
&lt;li&gt;如果在时间片用尽之前进程就开始等待某事件，那么他将进入&lt;em&gt;&lt;strong&gt;等待状态&lt;/strong&gt;&lt;/em&gt;。&lt;/li&gt;
&lt;li&gt;每当一个线程&lt;em&gt;&lt;strong&gt;离开运行状态&lt;/strong&gt;&lt;/em&gt;，调度系统就会选择一个其他的就绪线程继续执行。&lt;/li&gt;
&lt;li&gt;在一个处于&lt;em&gt;&lt;strong&gt;等待状态&lt;/strong&gt;&lt;/em&gt;的线程多等待的事件发生后，该线程就讲进入&lt;em&gt;&lt;strong&gt;就绪状态&lt;/strong&gt;&lt;/em&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fa1e3599c5d971b3.webp&quot; alt=&quot;线程状态切换&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 优先级&lt;/h2&gt;
&lt;p&gt;现在主流的调度方式尽管不尽相同，但都有***优先级调度（Priority Schedule）&lt;em&gt;&lt;strong&gt;和&lt;/strong&gt;&lt;/em&gt;轮转法（Round Robin）***的痕迹。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优先级调度
&lt;ul&gt;
&lt;li&gt;决定了线程按照什么顺序轮流执行&lt;/li&gt;
&lt;li&gt;具有高优先级的线程会更早地执行&lt;/li&gt;
&lt;li&gt;低优先级的线程常常要等到系统中已经没有高优先级的可执行线程存在时才能够执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;轮转法
&lt;ul&gt;
&lt;li&gt;让每个线程轮流执行一小段时间&lt;/li&gt;
&lt;li&gt;这决定了线程之间交错执行的特点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;线程的优先级布景可以手动设置，系统也会根据线程的表现自动调整优先级，以使得调度更加高效。通常情况下，&lt;em&gt;&lt;strong&gt;频繁进入等待状态的线程&lt;/strong&gt;&lt;/em&gt;（进入等待状态，会放弃之后仍然可用的时间份额，例如I/O处理的线程）比&lt;em&gt;&lt;strong&gt;频繁进行大量计算，以至于每次都要吧时间片全部用尽的线程&lt;/strong&gt;&lt;/em&gt;要更瘦欢迎。原因很简单，频繁等待的线程通常只占用很少的时间，CPU也喜欢先捏软柿子。&lt;/p&gt;
&lt;p&gt;我们一般把频繁等待的线程称之为&lt;em&gt;&lt;strong&gt;IO密集型线程&lt;/strong&gt;&lt;/em&gt;，而把很少等待的线程称之为&lt;em&gt;&lt;strong&gt;CPU密集型线程&lt;/strong&gt;&lt;/em&gt;。IO密集型总是比CPU密集型容易得到优先级的提升。&lt;/p&gt;
&lt;h3&gt;4.1 总结优先级的改变方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户指定优先级&lt;/li&gt;
&lt;li&gt;根据进入等待状态的频繁程度提升或者降低优先级&lt;/li&gt;
&lt;li&gt;长时间得不到执行而被提升优先级（防止&lt;em&gt;&lt;strong&gt;饿死（Starvation）&lt;/strong&gt;&lt;/em&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 atomic的实质&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;atomic&lt;/strong&gt;&lt;/em&gt; 是我们常用来保证属性线程安全的关键字。保证同一时间只有一个线程能够&lt;em&gt;&lt;strong&gt;写入&lt;/strong&gt;&lt;/em&gt;，多线程可&lt;em&gt;&lt;strong&gt;读取&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;大家可能都知道它是一个&lt;em&gt;&lt;strong&gt;自旋锁&lt;/strong&gt;&lt;/em&gt;。那么你怎么知道它是一个自旋锁呢？我们来看下源码（基于：objc4-818.2）&lt;/p&gt;
&lt;h3&gt;5.1 源码解读&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;reallySetProperty&lt;/strong&gt;&lt;/em&gt;是属性set方法的实现源码。其中我们可以看到很多熟悉的面孔： atomic、 copy&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    // copy 关键字的处理
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    // atomic 关键字的处理
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t&amp;amp; slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看到 &lt;code&gt;atomic&lt;/code&gt; 用了一个 &lt;code&gt;spinlock_t&lt;/code&gt;。它是一个自旋锁么？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/240f16b832dcb1e3.webp&quot; alt=&quot;spinlock_t&quot; /&gt;&lt;/p&gt;
&lt;p&gt;它实际上是 &lt;code&gt;mutex_tt&lt;/code&gt; C++的自旋锁。&lt;/p&gt;
&lt;p&gt;get 方法也做了类似的处理。&lt;/p&gt;
&lt;h2&gt;六、 一定线程安全么&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i &amp;lt; 1000; i++) {
            self.number = self.number + 1;
            NSLog(@&quot;number: %ld&quot;, self.number);
        }
    });
    
    // 线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i &amp;lt; 1000; i++) {
            self.number = self.number + 1;
            NSLog(@&quot;number: %ld&quot;, self.number);
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;属性是 atomic 修饰的，按理说应该是线程安全的，两个线程各对 number 做了 1000 次循环 +1，最终 number 的值应该是2000，但输出的值却不是期望的 2000。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 最后的几行输出
2020-03-07 22:37:21.713683+0800 TestObjC[23813:2171198] number: 1986
2020-03-07 22:37:21.714004+0800 TestObjC[23813:2171198] number: 1987
2020-03-07 22:37:21.714267+0800 TestObjC[23813:2171198] number: 1988
2020-03-07 22:37:21.714541+0800 TestObjC[23813:2171198] number: 1989
2020-03-07 22:37:21.714844+0800 TestObjC[23813:2171198] number: 1990
2020-03-07 22:37:21.715027+0800 TestObjC[23813:2171198] number: 1991
2020-03-07 22:37:21.715442+0800 TestObjC[23813:2171198] number: 1992
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上是输出的最后几行，最终的值只加到1992。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是因为两个线程在并发的调用setter和getter，在setter和getter内部是加了锁&lt;/li&gt;
&lt;li&gt;但是在做+1操作的时候并没有加锁，导致在某一时刻，线程一调用了getter取到值&lt;/li&gt;
&lt;li&gt;线程2恰好紧跟着调用了getter，取到相同的值&lt;/li&gt;
&lt;li&gt;然后两个线程对取到的值分别+1，再分别调用setter，使得两次setter其实赋值了相等的值&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;因此使用atomic修饰属性时对属性的操作是否是线程安全的，虽然在内部加了锁，但并不能保证绝对的线程安全。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;我们知道 &lt;code&gt;atomic&lt;/code&gt; 实际上是对&lt;code&gt;属性&lt;/code&gt;的 &lt;code&gt;set/get&lt;/code&gt; 方法做了处理，那么如果通过 &lt;code&gt;KVC&lt;/code&gt; 直接进行&lt;code&gt;成员变量&lt;/code&gt;的读写线程安全么？&lt;/p&gt;
</content:encoded></item><item><title>任务与队列的几个面试题</title><link>https://blog.lab-lawliet.app/posts/ios-24-2ee6312453f173d9/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-24-2ee6312453f173d9/</guid><description>下面的打印输出会是什么样？</description><pubDate>Sun, 05 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 我们先看个面试题热热身&lt;/h2&gt;
&lt;h3&gt;面试题一 - 串行队列&lt;/h3&gt;
&lt;p&gt;下面的打印输出会是什么样？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// 串行队列
dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie.Queue&quot;, DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{
    NSLog(@&quot;1&quot;);
});

dispatch_async(queue, ^{
    NSLog(@&quot;2&quot;);
});

dispatch_sync(queue, ^{
    NSLog(@&quot;3&quot;);
});

NSLog(@&quot;0&quot;);

dispatch_async(queue, ^{
    NSLog(@&quot;7&quot;);
});

dispatch_async(queue, ^{
    NSLog(@&quot;8&quot;);
});

dispatch_async(queue, ^{
    NSLog(@&quot;9&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;A: 1230789&lt;/li&gt;
&lt;li&gt;B: 1237890&lt;/li&gt;
&lt;li&gt;C: 3120789&lt;/li&gt;
&lt;li&gt;D: 2137890&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分析&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt; 的同步任务，阻碍了&lt;code&gt;0 7 8 9&lt;/code&gt; 的异步任务，故 &lt;code&gt;3&lt;/code&gt; 一定在 &lt;code&gt;0 7 8 9&lt;/code&gt; 前。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;7 8 9&lt;/code&gt; 三个任务的顺序不一定，但在 &lt;code&gt;0&lt;/code&gt; 之后。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 2 3&lt;/code&gt; 的顺序不一定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;满足上面几点的答案为：A C&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里很可能会觉得 &lt;code&gt;12&lt;/code&gt; 一定在 &lt;code&gt;3&lt;/code&gt; 之前，要清楚 &lt;code&gt;3&lt;/code&gt; 阻塞的是 &lt;code&gt;3&lt;/code&gt; 之后的任务，&lt;code&gt;123&lt;/code&gt; 的顺序并不一定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;如果是异步队列呢？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;123 789 都是乱序的&lt;/p&gt;
&lt;h3&gt;面试题二 - while&lt;/h3&gt;
&lt;p&gt;输出会是多少呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__block int num = 0;

while (num &amp;lt; 10) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        num++;
    });
}

printf(&quot;%d\n&quot;, num);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;由于 num++ 是异步的，所以执行的次数大于等于 10&lt;/li&gt;
&lt;li&gt;由于 while 循环条件阻塞，打印一定在 num &amp;gt;= 10 条件下输出&lt;/li&gt;
&lt;li&gt;输出大于等于 10&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/85da270c661f89ab.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;面试题三 - for&lt;/h3&gt;
&lt;p&gt;输出会是多少呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__block int num = 0;

for (int i = 0; i &amp;lt; 1000; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        num++;
    });
}

printf(&quot;%d\n&quot;, num);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;for 循环保证了循环次数，一定是1000次&lt;/li&gt;
&lt;li&gt;for循环不会阻塞后面的 printf 函数执行&lt;/li&gt;
&lt;li&gt;故：打印结果小于等于 1000（可能打印的时候还有 ++ 任务未执行完)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/31594f94c951da6c.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;面试题三 - 死锁&lt;/h3&gt;
&lt;p&gt;下面几个分别输出是什么&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)deadLock1 {
    //    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, NULL);
    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, DISPATCH_QUEUE_SERIAL);
    NSLog(@&quot;1&quot;);
    dispatch_async(queue, ^{
        NSLog(@&quot;2&quot;);
        dispatch_sync(queue, ^{
            NSLog(@&quot;3&quot;);
        });
        NSLog(@&quot;4&quot;);
    });
    NSLog(@&quot;5&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出
&lt;ul&gt;
&lt;li&gt;1，5，2 死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;分析
&lt;ul&gt;
&lt;li&gt;串行队列&lt;/li&gt;
&lt;li&gt;async 放入队列一个 blockA 任务&lt;/li&gt;
&lt;li&gt;blockA 内部又向队列内部添加了一个 blockB 任务&lt;/li&gt;
&lt;li&gt;队列需要等待 blockA 执行完毕再执行下一个任务&lt;/li&gt;
&lt;li&gt;但是添加 blockB 时，sync 阻断了 blockA 内部的执行，导致 blockA 无法执行完成&lt;/li&gt;
&lt;li&gt;所以死锁了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)deadLock2 {
    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, DISPATCH_QUEUE_CONCURRENT);
    NSLog(@&quot;1&quot;);
    dispatch_async(queue, ^{
        NSLog(@&quot;2&quot;);
        dispatch_sync(queue, ^{
            NSLog(@&quot;3&quot;);
        });
        NSLog(@&quot;4&quot;);
    });
    NSLog(@&quot;5&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出
&lt;ul&gt;
&lt;li&gt;1，5，2，3，4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)deadLock3 {
    //    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, NULL);
    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, DISPATCH_QUEUE_SERIAL);
    NSLog(@&quot;1&quot;);
    dispatch_async(queue, ^{
        NSLog(@&quot;2&quot;);
        dispatch_sync(queue, ^{
            NSLog(@&quot;3&quot;);
        });
    });
    NSLog(@&quot;5&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出
&lt;ul&gt;
&lt;li&gt;1，5，2 死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;和第一个一样&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)deadLock4 {
    dispatch_queue_t queue = dispatch_queue_create(&quot;Ryukie&quot;, DISPATCH_QUEUE_SERIAL);
    NSLog(@&quot;1&quot;);
    NSLog(@&quot;2&quot;);
    dispatch_sync(queue, ^{
        NSLog(@&quot;3&quot;);
    });
    NSLog(@&quot;4&quot;);
    NSLog(@&quot;5&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;输出
&lt;ul&gt;
&lt;li&gt;1，2，3，4，5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;串行队列
&lt;ul&gt;
&lt;li&gt;同步任务，没有阻塞队列内任务的执行，没有死锁，正常执行&lt;/li&gt;
&lt;li&gt;所以 同步任务 + 串行队列 不一定死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 任务与队列&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/afd8abfb42ce7e7a.webp&quot; alt=&quot;任务与队列&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>dispatch_once单例实现原理</title><link>https://blog.lab-lawliet.app/posts/ios-25-dispatch-once-443badbfdbd2e6d6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-25-dispatch-once-443badbfdbd2e6d6/</guid><description>日常使用单例的时候会这样用，但是你有考虑过内部具体是怎么实现的么？本文将带你通过源码探究 dispatch_once 的实现原理。</description><pubDate>Sun, 05 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;日常使用单例的时候会这样用，但是你有考虑过内部具体是怎么实现的么？本文将带你通过源码探究 &lt;code&gt;dispatch_once&lt;/code&gt; 的实现原理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (instancetype)shared {
    static RYModel *object;
    static dispatch_once_t onceToken;
    dispatch_once(&amp;amp;onceToken, ^{
        object = [[RYModel alloc] init];
    });
    return object;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一、 dispatch_once 源码解析&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b8f4f2c574b73460.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;入参：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dispatch_once_t
&lt;ul&gt;
&lt;li&gt;一个静态标记&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dispatch_block_t
&lt;ul&gt;
&lt;li&gt;执行的 block&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 dispatch_once_f&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;对 &lt;code&gt;static dispatch_once_t onceToken&lt;/code&gt; 进行类型转换&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;onceToken&lt;/code&gt; 判断是否第一次执行&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt; DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    // onceToken 全局静态变量类型转换
	dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    // 通过 onceToken 
	uintptr_t v = os_atomic_load(&amp;amp;l-&amp;gt;dgo_once, acquire);
	if (likely(v == DLOCK_ONCE_DONE)) {
        // 已经执行过了
		return;
	}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	if (likely(DISPATCH_ONCE_IS_GEN(v))) {
		return _dispatch_once_mark_done_if_quiesced(l, v);
	}
#endif
#endif
	if (_dispatch_once_gate_tryenter(l)) {// 判断是否可以执行（原子性）
        // 第一次执行
		return _dispatch_once_callout(l, ctxt, func);
	}
	return _dispatch_once_wait(l);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;_dispatch_once_gate_tryenter&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;这里尝试进入，原子性的操作，线程安全。锁在当前的线程空间。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
	return os_atomic_cmpxchg(&amp;amp;l-&amp;gt;dgo_once, DLOCK_ONCE_UNLOCKED,
			(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 _dispatch_once_callout&lt;/h3&gt;
&lt;p&gt;第一次执行的相关操作&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
		dispatch_function_t func)
{
    // 执行任务
    _dispatch_client_callout(ctxt, func);
    // 广播通知执行完了
	_dispatch_once_gate_broadcast(l);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 _dispatch_once_gate_broadcast&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
	dispatch_lock value_self = _dispatch_lock_value_for_self();
	uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	v = _dispatch_once_mark_quiescing(l);
#else
	v = _dispatch_once_mark_done(l);
#endif
	if (likely((dispatch_lock)v == value_self)) return;
	_dispatch_gate_broadcast_slow(&amp;amp;l-&amp;gt;dgo_gate, (dispatch_lock)v);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;_dispatch_once_mark_done 改变标识&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e893f377a05ed70.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;回头看一下 &lt;code&gt;dispatch_once_f&lt;/code&gt; 执行过就会被直接 &lt;code&gt;return&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1a3e04fbe83762b8.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://opensource.apple.com/tarballs/libdispatch/&quot;&gt;GCD源码：libdispatch&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>从上到下花式打印二叉树</title><link>https://blog.lab-lawliet.app/posts/article-32-82ae58a70e7956e1/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-32-82ae58a70e7956e1/</guid><description>二叉树的常规便利： 前序遍历 、 中序遍历 、 后序遍历 可能都已经非常熟悉了。</description><pubDate>Wed, 01 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;二叉树的常规便利： &lt;code&gt;前序遍历&lt;/code&gt; 、 &lt;code&gt;中序遍历&lt;/code&gt; 、 &lt;code&gt;后序遍历&lt;/code&gt; 可能都已经非常熟悉了。&lt;/p&gt;
&lt;p&gt;本文将结合一个题目的三种形态来玩一下花式遍历。&lt;/p&gt;
&lt;h2&gt;一、 顺序打印&lt;/h2&gt;
&lt;h3&gt;1.1 题目&lt;/h3&gt;
&lt;p&gt;从上到下打印出二叉树的每个节点，同一层的节点按照从左到右的顺序打印。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    3
   / \
  9  20
    /  \
   15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[3,9,20,15,7]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;1.2 分析&lt;/h3&gt;
&lt;p&gt;由于二叉树并非一个有序队列，但题目要求我们有序的输出。此时可以考虑借助一个辅助队列进行解题。&lt;/p&gt;
&lt;p&gt;通过分析我们可以找到规律：每次打印一个节点的时候，如果该节点存在子节点，则把该节点的子节点插入队列末尾。接下来从队列头部取出节点重复打印操作。直至所有节点打印完成。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;当前节点&lt;/th&gt;
&lt;th&gt;辅助队列&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9,20&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;3,9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;15,7&lt;/td&gt;
&lt;td&gt;3,9,20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3,9,20,15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;3,9,20,15,7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.3 编码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [Int] {
    guard let r = root else { return [] }
    var result: [Int] = []
    var queue: [TreeNode] = [r]
    
    while queue.isEmpty == false {
        let first = queue[0]
        result.append(first.val)
        
        if let left = first.left {
            queue.append(left)
        }
        
        if let right = first.right {
            queue.append(right)
        }
        
        queue.removeFirst()
    }
        
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b2712defeda7f134.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 逐行打印&lt;/h2&gt;
&lt;h3&gt;2.1 题目&lt;/h3&gt;
&lt;p&gt;从上到下按层打印二叉树，同一层的节点按从左到右的顺序打印，每一层打印到一行。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回其层次遍历结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [
   [3],
   [9,20],
   [15,7]
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 分析&lt;/h3&gt;
&lt;p&gt;在第一种遍历的基础上，这本题加上了换行的逻辑。我们只需要加上换行的判断逻辑就可以了。&lt;/p&gt;
&lt;h3&gt;2.3 题解&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [[Int]] {
    var queue: [TreeNode] = []
    /// 当前行需要继续打印的数量
    var toBePrint = 0
    /// 下一行的数量
    var nextLevelCount = 0
    var result: [[Int]] = []
    var temp: [Int] = []
    
    if let r = root {
        queue.append(r)
        toBePrint += 1
    }

    while queue.isEmpty == false {
        if let first = queue.first {
            temp.append(first.val)
            queue.removeFirst()
            toBePrint -= 1
            
            if let left = first.left {
                queue.append(left)
                nextLevelCount += 1
            }
            
            if let right = first.right {
                queue.append(right)
                nextLevelCount += 1
            }
            
            if toBePrint == 0 {
                result.append(temp)
                temp = []
                
                toBePrint = nextLevelCount
                nextLevelCount = 0
            }
        }
    }

    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/87222c80261b7d5a.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、 之字形打印&lt;/h2&gt;
&lt;h3&gt;3.1 题目&lt;/h3&gt;
&lt;p&gt;请实现一个函数按照之字形顺序打印二叉树，即第一行按照从左到右的顺序打印，第二层按照从右到左的顺序打印，第三行再按照从左到右的顺序打印，其他行以此类推。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回其层次遍历结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [
   [3],
   [20,9],
   [15,7]
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 易错点&lt;/h3&gt;
&lt;p&gt;本题很容易想当然的觉得和第二种差不多，&lt;code&gt;左右&lt;/code&gt;换成&lt;code&gt;右左&lt;/code&gt;就可以了嘛～于是稍微改一改就写出下面的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [[Int]] {
    var queue: [TreeNode] = []
    var toBePrint = 0
    var nextLevelCount = 0
    var result: [[Int]] = []
    var temp: [Int] = []
    
    var flag = false
    
    if let r = root {
        queue.append(r)
        toBePrint += 1
    }
    
    while queue.isEmpty == false {
        if let first = queue.first {
            temp.append(first.val)
            queue.removeFirst()
            toBePrint -= 1
            
            if flag {
                if let left = first.left {
                    queue.append(left)
                    nextLevelCount += 1
                }
                
                if let right = first.right {
                    queue.append(right)
                    nextLevelCount += 1
                }
            }
            else {
                if let right = first.right {
                    queue.append(right)
                    nextLevelCount += 1
                }
                
                if let left = first.left {
                    queue.append(left)
                    nextLevelCount += 1
                }
            }
            
            if toBePrint == 0 {
                result.append(temp)
                temp = []
                
                toBePrint = nextLevelCount
                nextLevelCount = 0
                
                flag.toggle()
            }
        }
    }

    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后顺序就乱掉了：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2fcca2669064f813.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.3 分析&lt;/h3&gt;
&lt;p&gt;这里不将思维局限于遍历的方向。这里还是按顺序逐层便利。一个队列A存放当前行，一个队列B存放下一行的。当一行遍历完，将B赋值给A，再用B来存放下一行。而打印结果则根据当前行的方向决定是从头插入还是从末尾插入。&lt;/p&gt;
&lt;h3&gt;3.4 题解&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [[Int]] {
    guard let r = root else {
        return []
    }
    
    var queue: [TreeNode] = []
    var queueB: [TreeNode] = []
    var toBePrint = 0
    var nextLevelCount = 0
    var result: [[Int]] = []
    var flag = true
    
    queue.append(r)
    toBePrint += 1
    
    var temp = Array(repeating: 0, count: queue.count)
    
    while queue.isEmpty == false {
        let first = queue.removeFirst()
        temp[flag ? (temp.count - toBePrint) : (toBePrint - 1)] = first.val
        toBePrint -= 1
        
        if let left = first.left {
            queueB.append(left)
            nextLevelCount += 1
        }
        
        if let right = first.right {
            queueB.append(right)
            nextLevelCount += 1
        }
        
        if toBePrint == 0 {
            result.append(temp)
            queue = queueB
            temp = Array(repeating: 0, count: nextLevelCount)
            
            toBePrint = nextLevelCount
            nextLevelCount = 0
            queueB = []
            flag.toggle()
        }
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8210fd5a938e8323.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>II.从上到下打印二叉树II</title><link>https://blog.lab-lawliet.app/posts/article-32-ii-ii-4830f7d9415bfb63/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-32-ii-ii-4830f7d9415bfb63/</guid><description>从上到下按层打印二叉树，同一层的节点按从左到右的顺序打印，每一层打印到一行。</description><pubDate>Wed, 01 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;从上到下按层打印二叉树，同一层的节点按从左到右的顺序打印，每一层打印到一行。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回其层次遍历结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [
   [3],
   [9,20],
   [15,7]
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 102 题相同：https://leetcode-cn.com/problems/binary-tree-level-order-traversal/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [[Int]] {
    var queue: [TreeNode] = []
    var toBePrint = 0
    var nextLevelCount = 0
    var result: [[Int]] = []
    var temp: [Int] = []
    
    if let r = root {
        queue.append(r)
        toBePrint += 1
    }

    while queue.isEmpty == false {
        if let first = queue.first {
            temp.append(first.val)
            queue.removeFirst()
            toBePrint -= 1
            
            if let left = first.left {
                queue.append(left)
                nextLevelCount += 1
            }
            
            if let right = first.right {
                queue.append(right)
                nextLevelCount += 1
            }
            
            if toBePrint == 0 {
                result.append(temp)
                temp = []
                
                toBePrint = nextLevelCount
                nextLevelCount = 0
            }
        }
    }
    
    if temp.isEmpty == false {
        result.append(temp)
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/87222c80261b7d5a.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>III.从上到下打印二叉树III</title><link>https://blog.lab-lawliet.app/posts/article-32-iii-iii-b4e38ecf9ceabf0f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-32-iii-iii-b4e38ecf9ceabf0f/</guid><description>请实现一个函数按照之字形顺序打印二叉树，即第一行按照从左到右的顺序打印，第二层按照从右到左的顺序打印，第三行再按照从左到右的顺序打印，其他行以此类推。</description><pubDate>Wed, 01 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;请实现一个函数按照之字形顺序打印二叉树，即第一行按照从左到右的顺序打印，第二层按照从右到左的顺序打印，第三行再按照从左到右的顺序打印，其他行以此类推。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     3
    / \
   9  20
     /  \
    15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回其层次遍历结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [
   [3],
   [20,9],
   [15,7]
 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [[Int]] {
    guard let r = root else {
        return []
    }
    
    var queue: [TreeNode] = []
    var queueB: [TreeNode] = []
    var toBePrint = 0
    var nextLevelCount = 0
    var result: [[Int]] = []
    var flag = true
    
    queue.append(r)
    toBePrint += 1
    
    var temp = Array(repeating: 0, count: queue.count)
    
    while queue.isEmpty == false {
        let first = queue.removeFirst()
        temp[flag ? (temp.count - toBePrint) : (toBePrint - 1)] = first.val
        toBePrint -= 1
        
        if let left = first.left {
            queueB.append(left)
            nextLevelCount += 1
        }
        
        if let right = first.right {
            queueB.append(right)
            nextLevelCount += 1
        }
        
        if toBePrint == 0 {
            result.append(temp)
            queue = queueB
            temp = Array(repeating: 0, count: nextLevelCount)
            
            toBePrint = nextLevelCount
            nextLevelCount = 0
            queueB = []
            flag.toggle()
        }
    }
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8210fd5a938e8323.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>I.从上到下打印二叉树</title><link>https://blog.lab-lawliet.app/posts/article-32-i-f447a573547d396e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-32-i-f447a573547d396e/</guid><description>从上到下打印出二叉树的每个节点，同一层的节点按照从左到右的顺序打印。</description><pubDate>Tue, 31 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;从上到下打印出二叉树的每个节点，同一层的节点按照从左到右的顺序打印。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;给定二叉树: [3,9,20,null,null,15,7],&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    3
   / \
  9  20
    /  \
   15   7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[3,9,20,15,7]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;节点总数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func levelOrder(_ root: TreeNode?) -&amp;gt; [Int] {
    guard let r = root else { return [] }
    var result: [Int] = []
    var queue: [TreeNode] = [r]
    
    while queue.isEmpty == false {
        let first = queue[0]
        result.append(first.val)
        
        if let left = first.left {
            queue.append(left)
        }
        
        if let right = first.right {
            queue.append(right)
        }
        
        queue.removeFirst()
    }
        
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b2712defeda7f134.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>栈的压入、弹出序列</title><link>https://blog.lab-lawliet.app/posts/article-31-b3d060971d52c962/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-31-b3d060971d52c962/</guid><description>输入两个整数序列，第一个序列表示栈的压入顺序，请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如，序列 {1,2,3,4,5} 是某栈的压栈序列，序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列，但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。</description><pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入两个整数序列，第一个序列表示栈的压入顺序，请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如，序列 {1,2,3,4,5} 是某栈的压栈序列，序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列，但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：pushed = [1,2,3,4,5], popped = [4,5,3,2,1]&lt;/li&gt;
&lt;li&gt;输出：true&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;解释：我们可以按以下顺序执行：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;push(1), push(2), push(3), push(4), pop() -&amp;gt; 4,&lt;/p&gt;
&lt;p&gt;push(5), pop() -&amp;gt; 5, pop() -&amp;gt; 3, pop() -&amp;gt; 2, pop() -&amp;gt; 1&lt;/p&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：pushed = [1,2,3,4,5], popped = [4,3,5,1,2]&lt;/li&gt;
&lt;li&gt;输出：false&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解释：1 不能在 2 之前弹出。&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= pushed.length == popped.length &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= pushed[i], popped[i] &amp;lt; 1000&lt;/p&gt;
&lt;p&gt;pushed 是 popped 的排列。&lt;/p&gt;
&lt;p&gt;注意：本题与主站 946 题相同：https://leetcode-cn.com/problems/validate-stack-sequences/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;p&gt;本题一上来难以找到思路。对于这样的问题，举例子找规律是比较合适的。这样在面试的时候面试官也更能理解我们的思路，而不是上来直接把答案 &lt;code&gt;默写&lt;/code&gt; 出来。&lt;/p&gt;
&lt;p&gt;解决这个问题很直观的想法就是建立一个辅助栈，把输入的第一个序列中的元素依次压入，并按照第二个序列的顺序依次冲该栈中弹出元素。&lt;/p&gt;
&lt;h3&gt;2.1 举例分析&lt;/h3&gt;
&lt;h4&gt;输入一：pushed = [1,2,3,4,5], popped = [4,5,3,2,1]&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;栈&lt;/th&gt;
&lt;th&gt;弹出数字&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;压入1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;压入2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;压入3&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;压入4&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;弹出4&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;压入5&lt;/td&gt;
&lt;td&gt;1235&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;弹出5&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;弹出3&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;弹出2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;弹出1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;输入二：pushed = [1,2,3,4,5], popped = [4,3,5,1,2]&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;栈&lt;/th&gt;
&lt;th&gt;弹出数字&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;压入1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;压入2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;压入3&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;压入4&lt;/td&gt;
&lt;td&gt;1234&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;弹出4&lt;/td&gt;
&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;弹出3&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;压入5&lt;/td&gt;
&lt;td&gt;125&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;弹出5&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;尝试弹出下一个1，但是栈顶元素为2，无法弹出&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;规律总结&lt;/h4&gt;
&lt;p&gt;从这里我们发现了如下规律：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;栈顶元素如果等于下一个弹出元素：可以弹出&lt;/li&gt;
&lt;li&gt;栈顶元素不等于下一个弹出元素：可以压入下一个待压入的元素
&lt;ul&gt;
&lt;li&gt;直到将下一个需要弹出的元素入栈&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果所有元素都入栈后，没有下一个能弹出的元素，那么就不是一个弹出序列&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 编码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func validateStackSequences(_ pushed: [Int], _ popped: [Int]) -&amp;gt; Bool {
    if pushed.isEmpty {
        return true
    }
    
    var result: [Int] = [], pA = 0, pB = 0
    
    while pB &amp;lt; popped.count {
        let b = popped[pB]
        
        // 栈顶元素如果等于下一个弹出元素：可以弹出
        if let top = result.last, top == b {
            result.removeLast()
            pB += 1
        }
        // 栈顶元素不等于下一个弹出元素：可以压入下一个待压入的元素
        else if pA &amp;lt; pushed.count {
            let a = pushed[pA]
            result.append(a)
            pA += 1
        }
        // 如果所有元素都入栈后，没有下一个能弹出的元素，那么就不是一个弹出序列
        else {
            return false
        }
    }
    
    return result.isEmpty
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d607cc465005d11f.webp&quot; alt=&quot;LeetCode&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 本题考点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;考察分析复杂问题的能力。刚听到这个题的时候很多人可能都没有思路。这时候可以通过举一两个例子，一步步分析压栈、弹出的过程，进而寻找规律&lt;/li&gt;
&lt;li&gt;考察对栈的理解&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>数值的整数次方</title><link>https://blog.lab-lawliet.app/posts/article-16-3052eaa93120e192/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-16-3052eaa93120e192/</guid><description>实现 pow(x, n) ，即计算 x 的 n 次幂函数（即，xn）。不得使用库函数，同时不需要考虑大数问题。</description><pubDate>Thu, 12 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;实现 pow(x, n) ，即计算 x 的 n 次幂函数（即，xn）。不得使用库函数，同时不需要考虑大数问题。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：x = 2.00000, n = 10
输出：1024.00000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：x = 2.10000, n = 3
输出：9.26100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 3：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：x = 2.00000, n = -2
输出：0.25000
解释：2^(-2) = 1 / (2^(2)) = 1/4 = 0.25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-100.0 &amp;lt; x &amp;lt; 100.0
-231 &amp;lt;= n &amp;lt;= 231-1
-104 &amp;lt;= xn &amp;lt;= 104
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 快速幂&lt;/h2&gt;
&lt;p&gt;幂运算是我们最常用的运算之一。如果我们要求 x 的 N 次幂，那么想当然的就会写出一个 N 次的循环，然后累乘得到结果。这种幂运算的复杂度是O(N)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var res = 1
for _ in 0..&amp;lt;n {
    res *= x
}
return res
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;那么有没有更快的运算方式呢？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在计算机领域有一种常用的快速幂算法：&lt;code&gt;蒙哥马利幂（Montgomery reduction）算法&lt;/code&gt;。将复杂度从&lt;em&gt;&lt;strong&gt;O(N)&lt;/strong&gt;&lt;/em&gt; 降到了 &lt;em&gt;&lt;strong&gt;O(logN)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2.1 思路一：递归&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;若 N 为偶数，可以先求 x&amp;lt;sup&amp;gt;N/2&amp;lt;/sup&amp;gt;，然后再平方。&lt;/li&gt;
&lt;li&gt;若 N 为奇数，可以先求 x&amp;lt;sup&amp;gt;(N-1)/2&amp;lt;/sup&amp;gt;，然后再平方，再乘以x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;快速幂的最基础思想就是上面两条了。&lt;/p&gt;
&lt;p&gt;求解 x&amp;lt;sup&amp;gt;N/2&amp;lt;/sup&amp;gt;，x&amp;lt;sup&amp;gt;(N-1)/2&amp;lt;/sup&amp;gt; 时也可以用到上面的思路，每次将指数除以2，每次都可以将复杂度降低一半，知道最后指数为1。&lt;/p&gt;
&lt;h4&gt;Swift实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里不考虑大数，仅作为思路展示&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func quickpow(_ x: Int, _ n: Int) -&amp;gt; Int {
    if n == 0 {// 直到最后指数为0
        return 1
    }
    let res = quickpow(x, n/2)
    if n%2 != 0 {// n 为奇数
        return res * res * x
    }
    // n为偶数
    return res * res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 思路二：二进制拆分&lt;/h3&gt;
&lt;p&gt;我们以 x&amp;lt;sup&amp;gt;14&amp;lt;/sup&amp;gt; 为例进行推导。&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;
&amp;lt;font size = 30&amp;gt;
x&amp;lt;sup&amp;gt;14&amp;lt;sub&amp;gt;(10)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;1110&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;
&amp;lt;/font&amp;gt;
&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;换个方向，更直观：&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;
&amp;lt;font size = 30&amp;gt;
x&amp;lt;sup&amp;gt;1110&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;
&amp;lt;/font&amp;gt;
&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设 x 的 N 次幂等于 res
&lt;ul&gt;
&lt;li&gt;1: 在指数 N 的二进制形式下，从最低位开始左移一位，x自乘一次（和指数N左移一位保持一致）&lt;/li&gt;
&lt;li&gt;如果当前位为1
&lt;ul&gt;
&lt;li&gt;当前位a到最低位，中间位全部置0时表示的值b，x&amp;lt;sup&amp;gt;b&amp;lt;/sup&amp;gt;的累乘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果当前位位0
&lt;ul&gt;
&lt;li&gt;执行1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;循环直到指数N等于0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;当前位&lt;/th&gt;
&lt;th&gt;指数b&lt;/th&gt;
&lt;th&gt;res&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;111&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;0&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;code&gt;1&lt;/code&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;code&gt;1&lt;/code&gt;10&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;110&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;Swift实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里不考虑大数，仅作为思路展示&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func quickpow(_ x: Int, _ n: Int) -&amp;gt; Int {
    var res = 1
    var x = x
    var n = n
    
    while n &amp;gt; 0 {
        if n &amp;amp; 1 == 1 { // 如果n的当前末位为1
            res *= x // res乘上当前的x
        }
        x *= x// x自乘，当前指数左移一位
        n &amp;gt;&amp;gt;= 1// n右移一位，想当于 n / 2
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 解题&lt;/h2&gt;
&lt;p&gt;本题的条件与上面介绍的快速幂中的例子略有不同，调整一下即可。&lt;/p&gt;
&lt;h3&gt;3.1 递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func myPow(_ x: Double, _ n: Int) -&amp;gt; Double {
    // 特殊处理 0 1 -1 可以直接 return 结果
    if x == 0 || x == 1 {
        return x
    }
    else if x == -1 {
        return (n &amp;amp; 1 == 1 ? -1 : 1)
    }
    
    if n == 0 {
        return 1
    }
    else if n &amp;lt; 0 {
        return myPow(1/x, -n)
    }
    else if n == 1 {
        return x
    }
    
    let temp = myPow(x, n &amp;gt;&amp;gt; 1)
    return temp * temp * ((n &amp;amp; 1 == 1) ? x : 1)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d1b6e93e5f9bb76c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 非递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func myPow(_ x: Double, _ n: Int) -&amp;gt; Double {
    // 特殊处理 0 1 -1 可以直接 return 结果
    if x == 0 || x == 1 {
        return x
    }
    else if x == -1 {
        return (n &amp;amp; 1 == 1 ? -1 : 1)
    }
    
    if n == 0 {
        return 1
    }
    
    var ans: Double = 1
    var power = abs(n)
    var newX = x
    while power &amp;gt; 0 {
        if power &amp;amp; 1 == 1 {
            ans *= newX
        }
        newX *= newX
        power = power &amp;gt;&amp;gt; 1
    }
    return n &amp;lt; 0 ? 1 / ans : ans
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/83c61e3d10af52f6.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>树的子结构</title><link>https://blog.lab-lawliet.app/posts/article-26-bc3c2ba71df5bca2/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-26-bc3c2ba71df5bca2/</guid><description>输入两棵二叉树A和B，判断B是不是A的子结构。(约定空树不是任意一个树的子结构)</description><pubDate>Wed, 11 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;输入两棵二叉树A和B，判断B是不是A的子结构。(约定空树不是任意一个树的子结构)&lt;/p&gt;
&lt;p&gt;B是A的子结构， 即 A中有出现和B相同的结构和节点值。&lt;/p&gt;
&lt;p&gt;例如:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;给定的树 A:&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      3
     / \
    4   5
   / \
  1   2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;给定的树 B：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    4
   /
  1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回 true，因为 B 与 A 的一个子树拥有相同的结构和节点值。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：A = [1,2,3], B = [3,1]&lt;/li&gt;
&lt;li&gt;输出：false&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入：A = [3,4,5,1,2], B = [4,1]&lt;/li&gt;
&lt;li&gt;输出：true&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;限制：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 节点个数 &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;h3&gt;拆分为两个步骤&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;A树&lt;/code&gt; 中找到和 &lt;code&gt;B树&lt;/code&gt; 的跟节点值一样的 &lt;code&gt;R&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;判断 &lt;code&gt;A树&lt;/code&gt; 中以 &lt;code&gt;R&lt;/code&gt; 为根节点的子树是不是包含和 &lt;code&gt;B树&lt;/code&gt; 一样的结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;成立条件&lt;/h3&gt;
&lt;p&gt;满足下面任意一条&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;条件一：&lt;code&gt;B树&lt;/code&gt; 是 &lt;code&gt;A树&lt;/code&gt;的左树的子树&lt;/li&gt;
&lt;li&gt;条件二：&lt;code&gt;B树&lt;/code&gt; 是 &lt;code&gt;A树&lt;/code&gt;的右树的子树
&lt;ul&gt;
&lt;li&gt;上面这两个条件明显可以递归了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;条件三：&lt;code&gt;A树&lt;/code&gt; 某节点为&lt;code&gt;根节点&lt;/code&gt;的树包含 &lt;code&gt;B树&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;这个看来需要辅助函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;伪代码： &lt;code&gt;return (三 || 一 || 二)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func isSubStructure(_ A: TreeNode?, _ B: TreeNode?) -&amp;gt; Bool {
    guard let treeA = A, let treeB = B else { return false }
    return (isContain(A, treeB) || isSubStructure(treeA.left, treeB) || isSubStructure(treeA.right, treeB))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;完成 &lt;code&gt;条件三&lt;/code&gt; 的辅助函数：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isContain(_ A: TreeNode?, _ B: TreeNode?) -&amp;gt; Bool {
    if B == nil {
        return true // B遍历完了 不论A还有没有 A已经包含了A了
    }
    if A == nil {
        return false // A遍历完了 B还有 肯定不包含
    }
    
    guard let treeA = A, let treeB = B, treeA.val == treeB.val else { return false }
    
    return isContain(treeA.left, treeB.left) &amp;amp;&amp;amp; isContain(treeA.right, treeB.right)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8286d73850950e0c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;复杂度分析&lt;/h3&gt;
&lt;p&gt;***时间复杂度O(MN)：***其中 M N 分别为 A B 树的节点个数。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;空间复杂度O(M)：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>二进制重排实践</title><link>https://blog.lab-lawliet.app/posts/ios-03-fe6ede4cd6882a9c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-03-fe6ede4cd6882a9c/</guid><description>近两年二进制重排在启动优化上还是经常被提到的，但自己没有尝试过。</description><pubDate>Tue, 10 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;近两年二进制重排在启动优化上还是经常被提到的，但自己没有尝试过。&lt;/p&gt;
&lt;p&gt;继上一次&lt;a href=&quot;https://juejin.cn/post/6992183244267454494&quot;&gt;「iOS官方瘦身方案ODR（二）：换肤系统改造｜践行 On-Demand Resources」&lt;/a&gt;后，再次拿自己个人项目小白鼠「梦见账本」来实践一下。&lt;/p&gt;
&lt;h2&gt;一、 为什么要二进制重排呢？&lt;/h2&gt;
&lt;p&gt;虽然听着很厉害的样子，但其实是个老概念了。&lt;/p&gt;
&lt;h3&gt;1.1 虚拟内存和分页&lt;/h3&gt;
&lt;p&gt;我们知道，现代操作系统一般都采用虚拟内存管理机制，用&lt;code&gt;分段（segment）&lt;/code&gt;和&lt;code&gt;分页（page）&lt;/code&gt;管理虚拟内存。&lt;/p&gt;
&lt;p&gt;分段即是区分&lt;code&gt;数据段&lt;/code&gt;、&lt;code&gt;代码段&lt;/code&gt;、&lt;code&gt;堆内存&lt;/code&gt;、&lt;code&gt;栈内存&lt;/code&gt;等，不同的段数据的读写权限不一样。以 &lt;code&gt;iOS&lt;/code&gt; 为例，&lt;code&gt;代码段（_TEXT）&lt;/code&gt;是可读可执行但不能写的。&lt;/p&gt;
&lt;p&gt;分页则是为了方便高效的进行内存管理。由于采用了虚拟内存管理机制，就要建立&lt;code&gt;虚拟内存&lt;/code&gt;到&lt;code&gt;物理内存&lt;/code&gt;的&lt;code&gt;映射表&lt;/code&gt;，称为&lt;code&gt;页表&lt;/code&gt;。如果在设计上将每一个字节的虚拟内存和物理内存一一对应，这样粒度足够细，虽然不会产生内存浪费（内存碎片），但需要维护巨大的页表；但如果一页数据过大，比如5M，那么存储1个字节就要分配一个5M的页面，是非常大的浪费。内存页过大或过小都有弊端，目前大多数系统的页大小都设置在了&lt;code&gt;4096字节&lt;/code&gt;，通过页号和页内偏移进行寻址。可以使用&lt;code&gt;pagesize&lt;/code&gt;命令查看当前系统的页大小。&lt;/p&gt;
&lt;h3&gt;1.2 Page Fault&lt;/h3&gt;
&lt;p&gt;使用虚拟内存的目的之一是解决物理内存资源紧张的问题。&lt;code&gt;dyld&lt;/code&gt; 在加载二进制时，会使用 &lt;code&gt;mmap&lt;/code&gt; 将 &lt;code&gt;Mach-O&lt;/code&gt; 文件映射到&lt;code&gt;虚拟内存&lt;/code&gt;地址空间中，此时并不会占用过多的&lt;code&gt;物理内存&lt;/code&gt;。当读取一个&lt;code&gt;虚拟内存地址&lt;/code&gt;时，如果该地址在&lt;code&gt;物理内存&lt;/code&gt;中并不存在，会触发一次&lt;code&gt;缺页中断（Page Fault）&lt;/code&gt;，这个时候才将文件内容读取至物理内存中。&lt;/p&gt;
&lt;p&gt;缺页中断发生时会执行下面的操作：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分配内存&lt;/strong&gt;&lt;/em&gt;
由&lt;code&gt;内存管理单元&lt;/code&gt;找到空闲内存并分配。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;IO操作&lt;/strong&gt;&lt;/em&gt;
从磁盘中读文件并写入内存中。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;解密验签&lt;/strong&gt;&lt;/em&gt;
如果是从 &lt;code&gt;AppStore&lt;/code&gt; 上下载的 &lt;code&gt;APP&lt;/code&gt;，&lt;code&gt;iOS&lt;/code&gt; 系统还有对每一页（仅针对 &lt;code&gt;_TEXT&lt;/code&gt; 段的数据，&lt;code&gt;_DATA&lt;/code&gt; 段数据不需要）进行解密和签名验证。&lt;/p&gt;
&lt;p&gt;以上操作在每一次 &lt;code&gt;Page Fault&lt;/code&gt; 时都会发生，如果在启动 &lt;code&gt;APP&lt;/code&gt; 时，存在大量的 &lt;code&gt;Page Fault&lt;/code&gt; 情况，势必影响启动速度。&lt;/p&gt;
&lt;h2&gt;二、 什么是二进制重排&lt;/h2&gt;
&lt;p&gt;频繁的发生 &lt;code&gt;Page Fault&lt;/code&gt; 会影响启动速度，那么，是否可以干预 &lt;code&gt;Mach-O&lt;/code&gt; 的 &lt;code&gt;_TEXT&lt;/code&gt; 段函数的映射顺序，将 &lt;code&gt;APP&lt;/code&gt; 启动时需要用到的方法集中在一页或几页呢？答案是肯定的，二进制重排的原理就是字面上的理解，通过减少 &lt;code&gt;Page Fault&lt;/code&gt; 发生次数，减少启动耗时。&lt;/p&gt;
&lt;p&gt;理论上 &lt;code&gt;Page Fault&lt;/code&gt; 确实会影响启动速度，但影响的大小要区分看待。一般来说，是要在常规的优化手段都做完之后，再考虑进行二进制重排。且对于小型APP来说，如果本身启动时执行的方法并不算多，那么二进制重排的意义就不是很大。&lt;/p&gt;
&lt;p&gt;对于 &lt;code&gt;iOS 13&lt;/code&gt; 系统来说，由于启用了 &lt;code&gt;dyld3&lt;/code&gt;，&lt;code&gt;Page Fault&lt;/code&gt;发生时已经不需要执行解密验签（提前生成了 &lt;code&gt;lauch closure&lt;/code&gt; 文件），对性能的影响就更小了。&lt;/p&gt;
&lt;h2&gt;三、 System Trace 查看耗时&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;建议重装应用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;选中指定的设备，选中安装的 App 点击 &lt;code&gt;[*]&lt;/code&gt; 按钮，应用第一个页面（非启动页）显示后停止。&lt;/li&gt;
&lt;li&gt;找到自己的项目&lt;/li&gt;
&lt;li&gt;选中 &lt;code&gt;Main Thread&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;选中 &lt;code&gt;Virtual Memory&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fbfcd77d9e905bb4.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/45660d0cde14a044.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;File Backed Page In&lt;/code&gt; 次数就是 &lt;code&gt;Page Fault&lt;/code&gt; 的次数。「梦见账本」耗时 &lt;code&gt;341ms&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0cebddd21887c842.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点击这里的小箭头，可以看到调用堆栈&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5343a09c57c30052.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当然，我们不可能人工的来整理这些。那么有什么办法可以获取到所有调用呢？&lt;/p&gt;
&lt;h2&gt;四、 获取启动时调用的所有方法&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;现有方案对比&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hook &lt;code&gt;objc_msgSend&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;只能捕获基于 &lt;code&gt;objc&lt;/code&gt; 的方法调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;静态扫描 MachO 文件里的符号和函数数据 + 解析 Trace 文件
&lt;ul&gt;
&lt;li&gt;容易获取 &lt;code&gt;+load&lt;/code&gt;、&lt;code&gt;C++构造函数&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;initialize hook不到&lt;/li&gt;
&lt;li&gt;部分block hook不到&lt;/li&gt;
&lt;li&gt;C++通过寄存器的间接函数调用静态扫描不出来&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;编译器插桩 &lt;code&gt;Clang&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;可以拿到 &lt;code&gt;OC&lt;/code&gt;、 &lt;code&gt;Swift&lt;/code&gt;、 &lt;code&gt;C&lt;/code&gt;、 &lt;code&gt;block&lt;/code&gt; 全部调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 Clang 插桩&lt;/h2&gt;
&lt;h3&gt;5.1 基于 Clang SanitizerCoverage 的方案&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;SanitizerCoverage&lt;/code&gt; 是 &lt;code&gt;Clang&lt;/code&gt; 内置的一个代码覆盖工具。它把一系列以 &lt;code&gt;__sanitizer_cov_trace_pc_&lt;/code&gt; 为前缀的函数调用插入到用户定义的函数里，借此实现了全局 &lt;code&gt;AOP&lt;/code&gt;。其覆盖了/ &lt;code&gt;Swift/Objective-C/C/C++&lt;/code&gt; 等语言，&lt;code&gt;Method/Function/Block&lt;/code&gt; 全支持。&lt;/p&gt;
&lt;p&gt;开启 &lt;code&gt;SanitizerCoverage&lt;/code&gt; 的方法是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;build settings&lt;/code&gt; 里的 &lt;code&gt;Other C Flags&lt;/code&gt; 中添加 &lt;code&gt;-fsanitize-coverage=func,trace-pc-guard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果含有 &lt;code&gt;Swift&lt;/code&gt; 代码的话
&lt;ul&gt;
&lt;li&gt;需要在 &lt;code&gt;Other Swift Flags&lt;/code&gt; 中加入 &lt;code&gt;-sanitize-coverage=func&lt;/code&gt; 和 &lt;code&gt;-sanitize=undefined&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;所有链接到 &lt;code&gt;App&lt;/code&gt; 中的二进制都需要开启 &lt;code&gt;SanitizerCoverage&lt;/code&gt;，这样才能完全覆盖到所有调用
&lt;ul&gt;
&lt;li&gt;例如Pod库，就要在Target里设置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在&lt;a href=&quot;https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs&quot;&gt;SanitizerCoverage&lt;/a&gt;中可以看到 &lt;code&gt;LLVM&lt;/code&gt; 官方对 &lt;code&gt;SanitizerCoverage&lt;/code&gt; 的详细介绍，包含了示例代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.2 获取 order 文件&lt;/h3&gt;
&lt;p&gt;这里直接使用了&lt;a href=&quot;https://github.com/yulingtianxia/AppOrderFiles&quot;&gt;AppOrderFiles&lt;/a&gt;来进行获取。就不贴代码了，源码也不多，有兴趣可以自行查看。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;AppDelegate&lt;/code&gt; 中调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 我是放在 `func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool` 最后调用的
AppOrderFiles { path in
    if let path = path { print(&quot;AppOrderFiles: \(path)&quot;) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装运行一次后，从 &lt;code&gt;Xcode&lt;/code&gt; 获取应用的设备的 &lt;code&gt;.xcappdata&lt;/code&gt; 文件中按照路径，取到 &lt;code&gt;app.order&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/febdd8d05bdbf6c3.webp&quot; alt=&quot;05&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.3 设置 order 文件路径&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3f33e862b9b290f6.webp&quot; alt=&quot;06&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;没必要加入 &lt;code&gt;Bundle&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.4 验证顺序 LinkMap&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;开启 &lt;code&gt;LinkMap&lt;/code&gt; 文件输出&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b0a12480289c19bc.webp&quot; alt=&quot;07&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;编译获取 &lt;code&gt;LinkMap&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;先在项目的 Product 文件夹中找到 &lt;code&gt;.app&lt;/code&gt; 的目录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1b6011e740feb3cd.webp&quot; alt=&quot;08&quot; /&gt;&lt;/p&gt;
&lt;p&gt;再按如图所示路径找到 &lt;code&gt;linkmap&lt;/code&gt; 文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/60009fe340c6596e.webp&quot; alt=&quot;09&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;对比 &lt;code&gt;linkmap&lt;/code&gt; 和 &lt;code&gt;order&lt;/code&gt; 文件&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;搜索 &lt;code&gt;Address Size File Name&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ba07a170b53f4938.webp&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现顺序是一样的了&lt;/p&gt;
&lt;h3&gt;5.5 System Trace 检查效果&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/749ba43e720bbc0b.webp&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;只有 &lt;code&gt;141ms&lt;/code&gt; 了，优化了一大半。具体效果根据不同项目会有所不同。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/&quot;&gt;App 二进制文件重排已经被玩坏了&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs&quot;&gt;SanitizerCoverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yulingtianxia/AppOrderFiles&quot;&gt;AppOrderFiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@michael.eisel/improving-app-performance-with-order-files-c7fff549907f&quot;&gt;Improving App Performance with Order Files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&amp;amp;mid=2247485101&amp;amp;idx=1&amp;amp;sn=abbbb6da1aba37a04047fc210363bcc9&amp;amp;chksm=e9d0cd4fdea7445989cf26623a16fc8ce2876bf3bda95a5532bb0e5e5b1420765653df0b94d1&amp;amp;mpshare=1&amp;amp;scene=1&amp;amp;srcid=0316fLf0VRLHLhRPFDH6LuQo&amp;amp;sharer_sharetime=1595570815854&amp;amp;sharer_shareid=ff29c649ff1b5cea91081f325b5ada59#rd&quot;&gt;抖音研发实践：基于二进制文件重排的解决方案 APP启动速度提升超15%&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://easeapi.com/blog/blog/143-ios-startup.html&quot;&gt;iOS启动优化之二进制重排&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>详解KVC</title><link>https://blog.lab-lawliet.app/posts/ios-20-kvc-efadcc8f9772bd15/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-20-kvc-efadcc8f9772bd15/</guid><description>NSObject 的分类提供了 NSKeyValueCoding协议 的默认实现。</description><pubDate>Sun, 08 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;h2&gt;一、 KVC set/get 过程&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NSObject&lt;/code&gt; 的分类提供了 &lt;code&gt;NSKeyValueCoding协议&lt;/code&gt; 的默认实现。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0dbc8cb60170c474.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文中的描述使用&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;或&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;作为键字符串的占位符，该字符串在键值编码协议方法之一中作为参数出现，然后该方法将其用作辅助方法调用或变量名称查找的一部分。映射的属性名称遵循占位符的大小写。例如，对于 getter &lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt; 和 &lt;code&gt;is&amp;lt;key&amp;gt;&lt;/code&gt;，名为 &lt;code&gt;hidden&lt;/code&gt; 的属性映射到 &lt;code&gt;hidden&lt;/code&gt; 和 &lt;code&gt;isHidden&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.1 基本 Getter 的搜索过程&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;valueForKey:&lt;/code&gt; 的默认实现，给定一个key参数作为输入，执行以下过程。&lt;/p&gt;
&lt;h4&gt;1.1.1&lt;/h4&gt;
&lt;p&gt;在实例中按顺序搜索&lt;code&gt;方法&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;get&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;is&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;_&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果找到，则调用它并使用结果继续执行&lt;a href=&quot;#115&quot;&gt;步骤 5&lt;/a&gt;，否则继续下一步&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;验证一下&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RYModel : NSObject {
    @public
    NSString *_name;
    NSString *name;
    NSString *isName;
    NSString *_isName;
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f44491fc6da62e60.webp&quot; alt=&quot;getName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6475b540fc05e216.webp&quot; alt=&quot;name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/887e19acd5a7d3c3.webp&quot; alt=&quot;isName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1dad1ef87d70cddb.webp&quot; alt=&quot;_name&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;1.1.2&lt;/h4&gt;
&lt;p&gt;在实例中搜索名称&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;countOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt; 和&lt;/li&gt;
&lt;li&gt;objectIn&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;AtIndex:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;AtIndexes:&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;对应于 &lt;code&gt;NSArray&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果找到这些中的第一个和其他两个中的至少一个，则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则，继续执行 &lt;a href=&quot;#113&quot;&gt;步骤 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;代理对象随后将任何 NSArray 接收到的一些组合的消息 &lt;em&gt;&lt;strong&gt;countOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt; ， &lt;em&gt;&lt;strong&gt;objectIn&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;AtIndex:&lt;/strong&gt;&lt;/em&gt; 和 &lt;em&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;AtIndexes:&lt;/strong&gt;&lt;/em&gt; 消息给键-值编码创建它兼容的对象。&lt;/p&gt;
&lt;p&gt;如果原始对象还实现了一个可选的方法，其名称类似于 &lt;em&gt;&lt;strong&gt;get&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;:range:&lt;/strong&gt;&lt;/em&gt; ，则代理对象也会在适当的时候使用它。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，与 &lt;code&gt;KVC&lt;/code&gt; 兼容的对象一起工作的代理对象允许底层属性表现得好像它是 &lt;code&gt;NSArray&lt;/code&gt; ，即使它不是。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;1.1.3&lt;/h4&gt;
&lt;p&gt;如果没有找到简单的访问方法或阵列访问方法组，寻找：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;countOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;enumeratorOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;memberOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;对应于 &lt;code&gt;NSSet&lt;/code&gt; 类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果找到所有三个方法，则创建一个响应所有 &lt;code&gt;NSSet&lt;/code&gt; 方法的集合代理对象并返回该对象。否则，继续执行&lt;a href=&quot;#114&quot;&gt;步骤 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;此代理对象随后将任何 &lt;code&gt;NSSet&lt;/code&gt; 接收到的一些组合信息 &lt;em&gt;&lt;strong&gt;countOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt; ， &lt;em&gt;&lt;strong&gt;enumeratorOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt; 和 &lt;em&gt;&lt;strong&gt;memberOf&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;:&lt;/strong&gt;&lt;/em&gt; 消息以创建它的对象。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上，与 &lt;code&gt;KVC&lt;/code&gt; 兼容的对象一起工作的代理对象允许底层属性表现得好像它是 &lt;code&gt;NSSet&lt;/code&gt;，即使它不是。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;1.1.4&lt;/h4&gt;
&lt;p&gt;如果前面都没有找到，且类方法 &lt;code&gt;accessInstanceVariablesDirectly&lt;/code&gt; 返回 &lt;code&gt;YES（默认）&lt;/code&gt; ，按顺序查找名为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;_is&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;is&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;的成员变量，如果找到，直接获取实例变量的值并进行&lt;a href=&quot;#115&quot;&gt;步骤5&lt;/a&gt;，否则进行&lt;a href=&quot;#116&quot;&gt;步骤6&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;验证&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e5334e8880f02ab7.webp&quot; alt=&quot;_name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6168c0fe6f064982.webp&quot; alt=&quot;isName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/64943a8f5868ac89.webp&quot; alt=&quot;name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f0c1ff2f5c43acf8.webp&quot; alt=&quot;isName&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;1.1.5&lt;/h4&gt;
&lt;p&gt;如果检索到的属性值是一个对象指针，只需返回结果即可。&lt;/p&gt;
&lt;p&gt;如果该值是 &lt;code&gt;NSNumber&lt;/code&gt; 支持的基础数据类型，则将其存储在一个 &lt;code&gt;NSNumber&lt;/code&gt; 实例中并返回该实例。&lt;/p&gt;
&lt;p&gt;如果结果是 &lt;code&gt;NSNumber&lt;/code&gt; 不支持的数据类型，则转换为 &lt;code&gt;NSValue&lt;/code&gt; 对象并返回。&lt;/p&gt;
&lt;h4&gt;1.1.6&lt;/h4&gt;
&lt;p&gt;如果所有其他方法都失败，请调用 &lt;code&gt;valueForUndefinedKey:&lt;/code&gt;。 默认情况下，这会引发异常，但NSObject的子类可能会提供特定于Key的处理。&lt;/p&gt;
&lt;h3&gt;1.2 基本 Setter 的搜索过程&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;setValue:forKey:&lt;/code&gt; 的流程按顺序查找&lt;code&gt;方法&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;_set&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果找到，调用。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;验证方法查找&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/11d075de7c74b214.webp&quot; alt=&quot;setName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9dd95f9746ebc390.webp&quot; alt=&quot;_setName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果没有找到，如果类方法 &lt;code&gt;accessInstanceVariablesDirectly&lt;/code&gt; 返回 &lt;code&gt;YES（默认）&lt;/code&gt; ，按顺序寻找一个实例变量与名称类似：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;_is&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;is&lt;code&gt;&amp;lt;key&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果找到，设置。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;验证变量查找&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/197c68ded1d9ea31.webp&quot; alt=&quot;_name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d13307a456ab3f12.webp&quot; alt=&quot;isName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5043977033eab46c.webp&quot; alt=&quot;name&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e9d794483fc2e8a7.webp&quot; alt=&quot;isName&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果找不到，调用 &lt;code&gt;setValue:forUndefinedKey:&lt;/code&gt; 。 默认情况下，这会引发异常，但 &lt;code&gt;NSObject&lt;/code&gt; 的子类可能会提供特定于Key的处理。&lt;/p&gt;
&lt;h2&gt;二、 关于 accessInstanceVariablesDirectly 的思考&lt;/h2&gt;
&lt;p&gt;控制是否可以通过 KVC 给成员变量赋值，默认 &lt;code&gt;YES&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果 NO，会怎样呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0529f80bc5fb7820.webp&quot; alt=&quot;accessInstanceVariablesDirectly&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里给了我一点启发，如果自己封装的一些东西不希望使用者通过 &lt;code&gt;KVC&lt;/code&gt; 修改一些私有成员变量的话可以在这里返回 &lt;code&gt;NO&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107i&quot;&gt;Key-Value Coding Programming Guide&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>KVO几个被忽视的细节</title><link>https://blog.lab-lawliet.app/posts/ios-21-kvo-a92963ba0d64db50/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-21-kvo-a92963ba0d64db50/</guid><description>上面是 KVO 官方文档中的一句重要提醒：重要：为了能理解 KVO ，必须先理解 KVC。</description><pubDate>Sun, 08 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Important: In order to understand key-value observing, you must first understand key-value coding.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上面是 KVO 官方文档中的一句重要提醒：&lt;em&gt;&lt;strong&gt;重要：为了能理解 KVO ，必须先理解 KVC。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;一、 Options&lt;/h2&gt;
&lt;p&gt;它影响通知中提供的更改字典的内容以及生成通知的方式。&lt;/p&gt;
&lt;h3&gt;NSKeyValueObservingOptionOld&lt;/h3&gt;
&lt;p&gt;更改之前的值&lt;/p&gt;
&lt;h3&gt;NSKeyValueObservingOptionNew&lt;/h3&gt;
&lt;p&gt;接收新值&lt;/p&gt;
&lt;p&gt;您可以通过这些选项的按位 OR 接收旧值和新值。&lt;/p&gt;
&lt;h3&gt;NSKeyValueObservingOptionInitial&lt;/h3&gt;
&lt;p&gt;您指示被观察对象使用选项 &lt;code&gt;NSKeyValueObservingOptionInitial&lt;/code&gt; 发送立即更改通知（在 &lt;code&gt;addObserver:forKeyPath:options:context:returns&lt;/code&gt; 之前）。您可以使用这个额外的一次性通知来获取初始值。&lt;/p&gt;
&lt;h3&gt;NSKeyValueObservingOptionPrior&lt;/h3&gt;
&lt;p&gt;您可以通过包含选项 &lt;code&gt;NSKeyValueObservingOptionPrior&lt;/code&gt; 指示被观察对象在属性更改之前发送通知（除了更改之后的通常通知之外）。&lt;/p&gt;
&lt;p&gt;变更字典通过包含键 &lt;code&gt;NSKeyValueChangeNotificationIsPriorKey&lt;/code&gt; 和包含 &lt;code&gt;YES&lt;/code&gt; 的 &lt;code&gt;NSNumber&lt;/code&gt; 值来表示预更改通知。&lt;/p&gt;
&lt;h2&gt;二、 Context&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;[tool.ryuk addObserver:tool forKeyPath:@&quot;name&quot; options:NSKeyValueObservingOptionNew context:NULL];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在平时使用时，我们一般就传个 &lt;code&gt;NULL&lt;/code&gt; 但是他到底是做什么的有想过么？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;addObserver:forKeyPath:options:context:&lt;/code&gt; 消息中的 &lt;code&gt;Context&lt;/code&gt; 指针可以包含任意数据，这些数据将在相应的更改通知中传递回观察者。您可以指定 &lt;code&gt;NULL&lt;/code&gt; 并完全依赖键路径字符串来确定更改通知的来源，但是这种方法可能会导致对象的父类由于不同原因也在观察相同的键路径时出现问题。&lt;/p&gt;
&lt;p&gt;一种更安全、更可扩展的方法是使用 &lt;code&gt;Context&lt;/code&gt; 来确保您收到的通知是发送给您的观察者而不是父类的。&lt;/p&gt;
&lt;p&gt;类中唯一命名的静态变量的地址是一个很好的 &lt;code&gt;Context&lt;/code&gt; 。在父类或子类中以类似方式选择的 &lt;code&gt;Context&lt;/code&gt; 不太可能重叠。您可以为整个类选择一个 &lt;code&gt;Context&lt;/code&gt; ，并依靠通知消息中的关键路径字符串来确定发生了什么变化。&lt;/p&gt;
&lt;p&gt;或者，您可以为每个观察到的键路径创建一个不同的 &lt;code&gt;Context&lt;/code&gt; ，这完全绕过了字符串比较，从而提高了通知解析的效率。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;创建 &lt;code&gt;Context&lt;/code&gt; 指针&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void *PersonAccountBalanceContext = &amp;amp;PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &amp;amp;PersonAccountInterestRateContext;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;注册观察者&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@&quot;balance&quot;
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@&quot;interestRate&quot;
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 对可变数组的观察&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7a121684522a48de.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;相对可变数组进行 &lt;code&gt;KVO&lt;/code&gt; 很可能会写出这样的代码。但是明显是没用的。这就不得不提文章开始的那句官方提示 &lt;em&gt;&lt;strong&gt;重要：为了能理解 KVO ，必须先理解 KVC。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/AccessingCollectionProperties.html#//apple_ref/doc/uid/10000107i-CH4-SW1&quot;&gt;访问集合属性 Accessing Collection Properties&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;为了使可变集合内部的变化，能够被监听到需要调整一下 API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mutableArrayValueForKey:&lt;/code&gt; and &lt;code&gt;mutableArrayValueForKeyPath:&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;return &lt;code&gt;NSMutableArray&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mutableSetValueForKey:&lt;/code&gt; and &lt;code&gt;mutableSetValueForKeyPath:&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;return &lt;code&gt;NSMutableSet&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mutableOrderedSetValueForKey:&lt;/code&gt; and &lt;code&gt;mutableOrderedSetValueForKeyPath:&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;return &lt;code&gt;NSMutableOrderedSet&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d9bf229a85955473.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 KVO 开关控制&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你写了一个库，不希望内部的一些属性被监听到。有没有办法阻止掉呢？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;系统提供了上面的方法，默认是返回 &lt;code&gt;YES&lt;/code&gt;，即：所有属性都可以被监听。如果你不希望部分或者全部被监听可以返回 &lt;code&gt;NO&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;4.1 全部禁止&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d35a1963ad128ef.webp&quot; alt=&quot;03&quot; /&gt;&lt;/p&gt;
&lt;p&gt;没有触发变动的通知&lt;/p&gt;
&lt;h3&gt;4.2 部分禁止&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8cd4da05b2beb145.webp&quot; alt=&quot;04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt; 的通知没有触发，&lt;code&gt;books&lt;/code&gt; 的通知正常触发了。&lt;/p&gt;
&lt;h3&gt;4.3 禁止后手动发送通知&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dd07be44d4d88a69.webp&quot; alt=&quot;05&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然 &lt;code&gt;automaticallyNotifiesObserversForKey&lt;/code&gt; 禁止了通知，但是手动触发后依旧可以正常监听到&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177i&quot;&gt;Key-Value Observing Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107-SW1&quot;&gt;Key-Value Coding Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>KVO底层原理</title><link>https://blog.lab-lawliet.app/posts/ios-22-kvo-796c8cee1aa14c29/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-22-kvo-796c8cee1aa14c29/</guid><description>01</description><pubDate>Sun, 08 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 中间类&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a090371c8a932fac.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;一般提到 &lt;code&gt;KVO&lt;/code&gt; 底层，大家可能都知道是生成类一个 &lt;code&gt;NSKVONotifying_XXX&lt;/code&gt; 的类。但是再往深了问可能就答不出来了。&lt;/p&gt;
&lt;h3&gt;1.1 中间类是动态生成的还是编译生成的？&lt;/h3&gt;
&lt;p&gt;在设置观察者之前我们通过调用 &lt;code&gt;objc_getClass(&quot;NSKVONotifying_RYModel&quot;)&lt;/code&gt;，看能否获取到 &lt;code&gt;NSKVONotifying_RYModel&lt;/code&gt; 类。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9208aaad82302aba.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;添加观察者之后&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d5b5712911708584.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;***结论：***可见 &lt;code&gt;NSKVONotifying_RYModel&lt;/code&gt; 确实是动态生成的，而不是编译完成后就已经存在的。&lt;/p&gt;
&lt;h3&gt;1.2 中间类 和 本类 的关系&lt;/h3&gt;
&lt;p&gt;先猜测 &lt;code&gt;NSKVONotifying_RYModel&lt;/code&gt; 是否是 &lt;code&gt;RYModel&lt;/code&gt; 的子类，来验证一下。&lt;/p&gt;
&lt;p&gt;打印自己与所有子类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)logSubClassChain:(Class)cls {
    int count = objc_getClassList(NULL, 0);
    
    NSMutableArray *tempArr = @[cls].mutableCopy;
    Class *classes = (Class*)malloc(sizeof(Class) * count);
    objc_getClassList(classes, count);
    for (int i = 0; i &amp;lt; count; i++) {
        if (cls == class_getSuperclass(classes[i])) {
            [tempArr addObject:classes[i]];
        }
    }
    free(classes);
    NSLog(@&quot;Classes : %@&quot;, tempArr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e91ff337994e68a.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发现添加观察者后输出的多了一个 &lt;code&gt;NSKVONotifying_RYModel&lt;/code&gt; 证明了它确实是 &lt;code&gt;RYModel&lt;/code&gt; 的子类&lt;/p&gt;
&lt;p&gt;***结论：***KVO动态创建的类是本类的一个子类。&lt;/p&gt;
&lt;h3&gt;1.3 中间类的生命周期是怎样的？是否会移除？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8c3db2d24036906a.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;***结论：***移除观察者，观察者释放后并不会 &lt;code&gt;NSKVONotifying_XXX&lt;/code&gt; 页依旧存在。&lt;/p&gt;
&lt;p&gt;***思考：***由于中间类一旦生成就一直存在，所以理论上也不宜过多使用 &lt;code&gt;KVO&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;1.4 中间类的方法列表和本类有什么区别？&lt;/h3&gt;
&lt;p&gt;添加打印方法的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (void)logAllMethodOf:(Class)cls {
    NSLog(@&quot;%s %@&quot;, __func__, cls);
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &amp;amp;count);
    for (int i = 0; i &amp;lt; count; i++) {
        Method method = methodList[i];
        SEL sel = method_getName(method);
        IMP imp = method_getImplementation(method);
        NSLog(@&quot;SEL: %@ IMP: %p&quot;, NSStringFromSelector(sel), imp);
    }
    free(methodList);
    NSLog(@&quot;\n&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2478108d492e403b.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;RYModel&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;SEL: name IMP: 0x100003210&lt;/li&gt;
&lt;li&gt;SEL: .cxx_destruct IMP: 0x1000032a0&lt;/li&gt;
&lt;li&gt;SEL: setName: IMP: 0x100003170&lt;/li&gt;
&lt;li&gt;SEL: books IMP: 0x100003240&lt;/li&gt;
&lt;li&gt;SEL: setBooks: IMP: 0x100003260&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;NSKVONotifying_RYModel&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;SEL: setBooks: IMP: 0x7fff212b9d2e&lt;/li&gt;
&lt;li&gt;SEL: setName: IMP: 0x7fff212b9d2e&lt;/li&gt;
&lt;li&gt;SEL: class IMP: 0x7fff2127cc06&lt;/li&gt;
&lt;li&gt;SEL: dealloc IMP: 0x7fff212bf9f5&lt;/li&gt;
&lt;li&gt;SEL: _isKVOA IMP: 0x7fff213e3e3a&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.5 中间类的方法是继承的还是重写的？&lt;/h3&gt;
&lt;p&gt;新建一个子类，重写父类的方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@implementation RYSubModel

- (void)setBooks:(NSMutableArray&amp;lt;NSString *&amp;gt; *)books {
    [super setBooks:books];
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 子类
+[KVOTool logAllMethodOf:] RYSubModel
SEL: setBooks: IMP: 0x100003240

// 本类
添加观察者之前：RYModel
+[KVOTool logAllMethodOf:] RYModel
SEL: name IMP: 0x100003160
SEL: .cxx_destruct IMP: 0x1000031f0
SEL: setName: IMP: 0x1000030c0
SEL: books IMP: 0x100003190
SEL: setBooks: IMP: 0x1000031b0

// 中间类
添加观察者之后：NSKVONotifying_RYModel
+[KVOTool logAllMethodOf:] NSKVONotifying_RYModel
SEL: setBooks: IMP: 0x7fff212b9d2e
SEL: setName: IMP: 0x7fff212b9d2e
SEL: class IMP: 0x7fff2127cc06
SEL: dealloc IMP: 0x7fff212bf9f5
SEL: _isKVOA IMP: 0x7fff213e3e3a
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.6 KVO 开关对方法列表的影响&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@&quot;name&quot;]) {
        return NO;
    }
    return YES;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;停止 name 自动发送通知后，中间类的方法列表中就没有类 &lt;code&gt;setName&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1f60fed216b2ac77.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 ISA 指向变换&lt;/h2&gt;
&lt;p&gt;上面我们知道被观察的对象类型变成类一个中间类。那么它是怎么变的呢？&lt;/p&gt;
&lt;h3&gt;2.1 改变&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;添加观察者之前&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a1a79fc0a9c10ab3.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;添加观察者之后&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d7c8d1433f75ecf.webp&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;很明显实例的 ISA 的指向发生了变化。&lt;/p&gt;
&lt;h3&gt;2.2 恢复&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;开始移除观察者：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/08d4b4d068a005ea.webp&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;移除最后一个观察者：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e3c600be56230a52.webp&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;观察者全部移除：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1dfa3801846260e4.webp&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;恢复了 &lt;code&gt;RYModel&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;思考：全部Key移除了观察者才ISA恢复的本类，如果不完全移除呢？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/06d0f5169cfbfbb7.webp&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8fd5f7e568e9378e.webp&quot; alt=&quot;14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;剩了一个 Key 没有移除观察者&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f9ec6384c389e5fe.webp&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;p&gt;到最后实例的 ISA 指向还是 中间类。&lt;/p&gt;
&lt;h2&gt;三、 触发通知的过程&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c489153355800f1b.webp&quot; alt=&quot;16&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们在 KVO 通知接收的地方下个断点，调用堆栈：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tool.ryuk.name = @&quot;Ryukie&quot;;
&lt;ul&gt;
&lt;li&gt;Foundation`_NSSetObjectValueAndNotify
&lt;ul&gt;
&lt;li&gt;Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
&lt;ul&gt;
&lt;li&gt;Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
&lt;ul&gt;
&lt;li&gt;Foundation`NSKeyValueDidChange
&lt;ul&gt;
&lt;li&gt;Foundation`NSKeyValueNotifyObserver
&lt;ul&gt;
&lt;li&gt;-[KVOTool observeValueForKeyPath:ofObject:change:context:]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;到此，我们知道了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 KVO 过程中动态生成了中间类&lt;/li&gt;
&lt;li&gt;中间类和本类是继承关系，同时重写了本类的一些方法&lt;/li&gt;
&lt;li&gt;自动监听开关对与中间类的结构是有影响的&lt;/li&gt;
&lt;li&gt;被观察者 ISA 的指向会发生变化，完全移除所有观察Key后会恢复。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>快速排序</title><link>https://blog.lab-lawliet.app/posts/article-04-22a967d551e9bfdb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-04-22a967d551e9bfdb/</guid><description>快速排序由C. A. R. Hoare在1962年提出。它的基本思想是：通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都比另外一部分的所有数据都要小，然后再按此方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，以此达到整个数据变成有序序列。</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 什么是快速排序&lt;/h2&gt;
&lt;p&gt;快速排序由&lt;code&gt;C. A. R. Hoare&lt;/code&gt;在1962年提出。它的基本思想是：通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都比另外一部分的所有数据都要小，然后再按此方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，以此达到整个数据变成有序序列。&lt;/p&gt;
&lt;h2&gt;二、 核心流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;选取一个基准元素&lt;/li&gt;
&lt;li&gt;以该元素为基准，将比该元素大的放到右侧，小的放在左侧，从而一趟排序就可以确定基准元素的位置&lt;/li&gt;
&lt;li&gt;对左右两个分块重复上面的步骤（递归）&lt;/li&gt;
&lt;li&gt;直到所有元素都是有序的&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、 流程图&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;来自：&lt;a href=&quot;https://www.cnblogs.com/MOBIN/p/4681369.html&quot;&gt;MOBIN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ef74563f0791600b.webp&quot; alt=&quot;流程图&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 方案一：辅助空间&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func quickSort&amp;lt;T: Comparable&amp;gt;(_ arr: [T]) -&amp;gt; [T] {
    guard let base = arr.first, arr.count &amp;gt; 1 else {
        return arr
    }
    
    var left: [T] = []
    var right: [T] = []
    var result: [T] = []

    for index in 1..&amp;lt;arr.count {
        let value = arr[index]
        if value &amp;lt; base {
            left.append(value)
        }
        else {
            right.append(value)
        }
    }
    
    print(&quot;left = \(left)   right = \(right) &quot;)
    
    result = quickSort(left)
    result.append(base)
    result.append(contentsOf: quickSort(right))
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>MethodSwizzing方法交换的坑</title><link>https://blog.lab-lawliet.app/posts/ios-19-methodswizzing-7f2388f9835c5a08/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-19-methodswizzing-7f2388f9835c5a08/</guid><description>MethodSwizzing 方法交换是比较常用的所谓 黑魔法。但正如武侠小说中的绝世武功一般，也存在使用不恰当发生 伤敌一千，自损八百 的情况。</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;MethodSwizzing&lt;/code&gt; 方法交换是比较常用的所谓 &lt;code&gt;黑魔法&lt;/code&gt;。但正如武侠小说中的绝世武功一般，也存在使用不恰当发生 &lt;code&gt;伤敌一千，自损八百&lt;/code&gt; 的情况。&lt;/p&gt;
&lt;p&gt;本文就带你来探索一下其中的坑，避免走火入魔。&lt;/p&gt;
&lt;h2&gt;一、 MethodSwizzingTool&lt;/h2&gt;
&lt;p&gt;为了方便，我们封装一下常规的方法交换逻辑&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@implementation MethodSwizzingTool

+ (void)swizzingClass:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if (!cls) { return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    method_exchangeImplementations(oldM, newM);
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 验证是否有效&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@implementation RYModel

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&amp;amp;onceToken, ^{
        [MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(functionB)];
    });
}

- (void)functionA {
    NSLog(@&quot;%s&quot;, __func__);
}

- (void)functionB {
    NSLog(@&quot;%s&quot;, __func__);
    [self functionB];
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;调用：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYModel *ry = [[RYModel alloc] init];
[ry functionA];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-08-07 17:01:53.954201+0800 MethodSwizzing[72309:15057493] -[RYModel functionB]
2021-08-07 17:01:53.954534+0800 MethodSwizzing[72309:15057493] -[RYModel functionA]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;嗯～感觉封装的没问题。&lt;/p&gt;
&lt;h2&gt;二、 子类的坑&lt;/h2&gt;
&lt;h3&gt;2.1 用自类方法替换父类方法，会怎样？&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;思考：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在子类 &lt;code&gt;RYSubModel&lt;/code&gt; 中用 &lt;code&gt;子类&lt;/code&gt; 的 &lt;code&gt;subFunctionA&lt;/code&gt; 替换 &lt;code&gt;父类&lt;/code&gt; 的 &lt;code&gt;functionA&lt;/code&gt; ，&lt;code&gt;子类实例&lt;/code&gt;和&lt;code&gt;父类实例&lt;/code&gt;分别调用 &lt;code&gt;functionA&lt;/code&gt; 会是什么样的结果呢？（父类中未做交换）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;子类代码如下：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@implementation RYSubModel

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&amp;amp;onceToken, ^{
        [MethodSwizzingTool swizzingClass:self oldSEL:@selector(functionA) toNewSel:@selector(subFunctionA)];
    });
}

- (void)subFunctionA {
    NSLog(@&quot;%s&quot;, __func__);
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;调用：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYModel *ry = [[RYModel alloc] init];
[ry functionA];

RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-08-07 17:10:37.990097+0800 MethodSwizzing[72705:15063434] -[RYSubModel subFunctionA]
2021-08-07 17:10:37.990530+0800 MethodSwizzing[72705:15063434] -[RYSubModel subFunctionA]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分析&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;用子类中的方法 &lt;code&gt;subFunctionA&lt;/code&gt; 替换父类中的方法 &lt;code&gt;functionA&lt;/code&gt;， &lt;code&gt;functionA&lt;/code&gt; 的实现变成了 &lt;code&gt;subFunctionA&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.2 调用原实现&lt;/h3&gt;
&lt;p&gt;一般我们交换方法后想要继续调用原本的实现一般会如&lt;a href=&quot;#11-%E9%AA%8C%E8%AF%81%E6%98%AF%E5%90%A6%E6%9C%89%E6%95%88&quot;&gt;上文中functionB&lt;/a&gt;那样调用一下自己。&lt;/p&gt;
&lt;p&gt;那么我们在用子类中的方法 &lt;code&gt;subFunctionA&lt;/code&gt; 替换了父类中的方法 &lt;code&gt;functionA&lt;/code&gt; 后想要继续调用 &lt;code&gt;functionA&lt;/code&gt; 同理应该这么写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)subFunctionA {
    [self subFunctionA];
    NSLog(@&quot;%s&quot;, __func__);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;调用：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYModel *ry = [[RYModel alloc] init];
[ry functionA];

RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/14c9465ccf1371c6.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么呢？居然找不到了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;分析&lt;/h4&gt;
&lt;p&gt;我们在用子类中的方法 &lt;code&gt;subFunctionA&lt;/code&gt; 替换了父类中的方法 &lt;code&gt;functionA&lt;/code&gt; 后&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;父类中：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;functionA&lt;/code&gt; 调用 &lt;code&gt;subFunctionA&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但是父类本身方法列表中并没有 &lt;code&gt;subFunctionA&lt;/code&gt; ，所以父类就报了 &lt;code&gt;unrecognized selector&lt;/code&gt; 的错误。&lt;/p&gt;
&lt;h4&gt;修改调用&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;如果我们只调用子类：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYSubModel *sub = [[RYSubModel alloc] init];
[sub functionA];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-08-07 17:33:28.874108+0800 MethodSwizzing[73690:15075457] -[RYModel functionA]
2021-08-07 17:33:28.874564+0800 MethodSwizzing[73690:15075457] -[RYSubModel subFunctionA]
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这里就是正常的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 优化 MethodSwizzingTool&lt;/h2&gt;
&lt;p&gt;我们能不能优化 &lt;code&gt;MethodSwizzingTool&lt;/code&gt; 来防止这样的问题出现呢？&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;可以！&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;3.1 优化思路&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;出现上面找不到方法的原因是：&lt;em&gt;&lt;strong&gt;子类用自己的实现直接替换了父类的方法&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;那么我们能不能为子类动态添加一个和父类一样的方法呢？子类中进行替换的时候就不会影响父类了&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 编写优化代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if (!cls) { return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    
    // 先尝试给 cls 添加方法（SEL: oldSel  IMP: newM），防止子类直接替换父类中的方法
    BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
    
    if (addSuccess) { // 添加成功即：原本没有 oldSel，成功为子类添加了一个 oldSel - newM 的方法
        // 这里将原 newSel的imp替换为 oldM 的 IMP
        class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else {
        method_exchangeImplementations(oldM, newM);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 优化代码流程分析&lt;/h3&gt;
&lt;p&gt;本案例子类调用流程分析：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用方法，准备用子类中的方法 &lt;code&gt;subFunctionA&lt;/code&gt; 替换的方法 &lt;code&gt;functionA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method oldM&lt;/code&gt; 是从父类获取到的方法 &lt;code&gt;（SEL: functionA, IMP: functionA）&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Method newM&lt;/code&gt; 是从子类自己获取到的方法 &lt;code&gt;（SEL: subFunctionA, IMP: subFunctionA）&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否可以成功添加方法：&lt;code&gt;（SEL: functionA, IMP: subFunctionA）&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;否：已存在 &lt;code&gt;SEL: functionA&lt;/code&gt; 的方法&lt;/li&gt;
&lt;li&gt;是：不存在 &lt;code&gt;SEL: functionA&lt;/code&gt; 的方法
&lt;ul&gt;
&lt;li&gt;将子类的 &lt;code&gt;（SEL: subFunctionA, IMP: subFunctionA）&lt;/code&gt; 替换为 &lt;code&gt;（SEL: subFunctionA, IMP: functionA）&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1ec6975fbcf651a3.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;父类实例
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[ry functionA]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用没有受子类方法交换的影响&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;子类实例
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[sub functionA]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;没有出现父类调用找不到方法的情况&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四、 父类的 functionA 也没有实现呢？&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fbe1dec95fe6fbdf.webp&quot; alt=&quot;死循环了&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.1 分析&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8e2e9f5cd9f95152.webp&quot; alt=&quot;04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;由于并不存在 &lt;code&gt;functionA&lt;/code&gt; 的实现，所以这里的替换方法并没有成功。 &lt;code&gt;subFunctionA&lt;/code&gt; 的调用就直接递归死循环了。&lt;/p&gt;
&lt;h3&gt;4.2 再优化&lt;/h3&gt;
&lt;p&gt;通过设置默认实现的方式来避免死循环，新实现也为空的情景可以类似处理，这里就不赘述了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (void)swizzingClassB:(Class)cls oldSEL:(SEL)oldSel toNewSel:(SEL)newSel {
    if (!cls) { return; }
    Method oldM = class_getInstanceMethod(cls, oldSel);
    Method newM = class_getInstanceMethod(cls, newSel);
    
    if (!oldM) {
        // 先用新的实现来，临时添加一个（这里忽略新实现也为空的情况，可以类似的处理）
        class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(newM));
        // 对 oldM 变量重新赋值
        oldM = class_getInstanceMethod(cls, oldSel);
        // 创建默认实现，可以进行一些日志收集之类的
        IMP defaultIMP = imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@&quot;一些处理&quot;);
        });
        // 为 oldM 设置 IMP
        method_setImplementation(oldM, defaultIMP);
    }
    
    // 先尝试给 cls 添加方法（SEL: oldSel  IMP: newM），防止子类直接替换父类中的方法
    BOOL addSuccess = class_addMethod(cls, oldSel, method_getImplementation(newM), method_getTypeEncoding(oldM));
    
    if (addSuccess) { // 添加成功即：原本没有 oldSel，成功为子类添加了一个 oldSel - newM 的方法
        // 这里将原 newSel的imp替换为 oldM 的 IMP
        class_replaceMethod(cls, newSel, method_getImplementation(oldM), method_getTypeEncoding(oldM));
    }
    else {
        method_exchangeImplementations(oldM, newM);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e6c7c68ce0f9034e.webp&quot; alt=&quot;05&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;黑魔法虽好，使用也得倍加小心！&lt;/p&gt;
&lt;p&gt;尤其要对方法的本质以及方法调用的流程烂熟于心才能随心所欲，内功到家，进阶武功才不会伤及自身。&lt;/p&gt;
</content:encoded></item><item><title>Form</title><link>https://blog.lab-lawliet.app/posts/swiftui-01-form-cea4a58edcae51b2/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-01-form-cea4a58edcae51b2/</guid><description>Form相较于List更适合用于类似设置页面这样的场景 下面的图和代码可以直观感受一下</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;Form&lt;/code&gt;相较于&lt;code&gt;List&lt;/code&gt;更适合用于类似设置页面这样的场景
下面的图和代码可以直观感受一下&lt;/p&gt;
&lt;h2&gt;01.Form预览&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/76d2dd8302de891f.webp&quot; alt=&quot;List&quot; /&gt;
&lt;img src=&quot;/content-assets/d5bd483d5699d768.webp&quot; alt=&quot;List&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;02.List预览&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6e21d1f8e0f49d81.webp&quot; alt=&quot;List&quot; /&gt;
&lt;img src=&quot;/content-assets/3b07d981505c6f50.webp&quot; alt=&quot;List&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;03.List Group 预览&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ac5917acb6ddb94f.webp&quot; alt=&quot;List&quot; /&gt;
&lt;img src=&quot;/content-assets/5987524c449da0c5.webp&quot; alt=&quot;List&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;04.修改背景色&lt;/h2&gt;
&lt;p&gt;想要修改&lt;code&gt;Form&lt;/code&gt;的背景色，第一反应会这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Form {
    Section {
        Text(&quot;VIP&quot;)
    }

    Section {
        Text(&quot;Coin&quot;)
    }
}
.background(Color.red)
.scaledToFill()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然鹅并没有效果
&lt;img src=&quot;/content-assets/41d703e07ec87d82.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;为什么呢？&lt;/h3&gt;
&lt;p&gt;实际上&lt;code&gt;Form&lt;/code&gt;是基于&lt;code&gt;TableView&lt;/code&gt;的。
这里需要进行以下操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;init() {
        UITableView.appearance().backgroundColor = .clear
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4aa2cc963a6f642b.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;05.隐藏滑块&lt;/h2&gt;
&lt;p&gt;依旧需要通过&lt;code&gt;UITableView&lt;/code&gt;实现，可见现在&lt;code&gt;SwiftUI&lt;/code&gt;完成度还不高。
总是通过使用&lt;code&gt;UIKit&lt;/code&gt;来进行一些常用属性的设置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;init() {
        UITableView.appearance().showsVerticalScrollIndicator = false
    }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Navigation</title><link>https://blog.lab-lawliet.app/posts/swiftui-02-navigation-49d0a732578d1640/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-02-navigation-49d0a732578d1640/</guid><description>默认情况下，在SwiftUI中创建的Navigation都是开启LargeTitle样式的。 如果不想使用改样式可以用过下面的方式调整：</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;LargeTitle&lt;/h2&gt;
&lt;p&gt;默认情况下，在&lt;code&gt;SwiftUI&lt;/code&gt;中创建的&lt;code&gt;Navigation&lt;/code&gt;都是开启LargeTitle样式的。
如果不想使用改样式可以用过下面的方式调整：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var body: some View {
        Form {
            Section {
                Text(&quot;Hello World 1&quot;)
                Text(&quot;Hello World 2&quot;)
                Text(&quot;Hello World 3&quot;)
                Text(&quot;Hello World 4&quot;)
                Text(&quot;Hello World 5&quot;)
            }
        }
        .navigationBarTitle(&quot;Add pose&quot;, displayMode: .large)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;隐藏导航栏&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;.navigationBarHidden(true)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ViewBuilder参数10个限制</title><link>https://blog.lab-lawliet.app/posts/swiftui-03-viewbuilder-10-2fa9301cf3fb9050/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-03-viewbuilder-10-2fa9301cf3fb9050/</guid><description>01 01</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;看看下面的代码，发现当&lt;code&gt;ViewBuilder&lt;/code&gt;中超过10个元素的时候会报错，10个就没问题，
代码逻辑本身是没问题的
具体可以看下下面的文档，其实是将参数的个数做了限制&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5b4a2a3c5504dd0b.webp&quot; alt=&quot;01&quot; /&gt;
&lt;img src=&quot;/content-assets/33fe5ef5d742595b.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/viewbuilder&quot;&gt;Apple文档：ViewBuilder&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;static func buildBlock&amp;lt;C0, C1, C2, C3, C4, C5, C6, C7, C8, C9&amp;gt;(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -&amp;gt; TupleView&amp;lt;(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;不清楚苹果这样做的具体原因，但这样可以强迫咱们注意封装拆分。强行解释一波&amp;lt;_&amp;lt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>UIKit混编时Dismiss掉HostController</title><link>https://blog.lab-lawliet.app/posts/swiftui-04-uikit-dismiss-hostcontroller-0291e81512ae8c4a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-04-uikit-dismiss-hostcontroller-0291e81512ae8c4a/</guid><description>下面的实现方式在正常的SwiftUI体系下是可以正常工作的 但是在UIKit混编时就失效了</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;场景：
在主&lt;code&gt;UIKit&lt;/code&gt;的项目中&lt;code&gt;Present&lt;/code&gt;出一个包含&lt;code&gt;SwiftUI&lt;/code&gt;的&lt;code&gt;UIHostingController&lt;/code&gt;
当需要&lt;code&gt;Dismiss&lt;/code&gt;该控制器的时候遇到了麻烦&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一：在SwiftUI中Dismiss一个页面的正常操作&lt;/h2&gt;
&lt;p&gt;下面的实现方式在正常的&lt;code&gt;SwiftUI&lt;/code&gt;体系下是可以正常工作的
但是在&lt;code&gt;UIKit&lt;/code&gt;混编时就失效了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct ContentView: View, SwiftUIBridgeProtocol {
    @Environment(\.presentationMode) var presentationMode: Binding&amp;lt;PresentationMode&amp;gt;
    
    var body: some View {
        ZStack {
            VStack {
                StoreTopView(dismissClosure: {
                    presentationMode.wrappedValue.dismiss()
                })
                .padding()
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们看下打印的信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po presentationMode
▿ Binding&amp;lt;PresentationMode&amp;gt;
  ▿ transaction : Transaction
    ▿ plist : []
      - elements : nil
  ▿ location : &amp;lt;LocationBox&amp;lt;FunctionalLocation&amp;lt;PresentationMode&amp;gt;&amp;gt;: 0x2838313c0&amp;gt;
  ▿ _value : PresentationMode
    - isPresented : false

(lldb) po presentationMode.wrappedValue
▿ PresentationMode
  - isPresented : false
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二：如何才能获取到&lt;code&gt;HostController&lt;/code&gt;呢&lt;/h2&gt;
&lt;h3&gt;方案1. 一些方案&lt;/h3&gt;
&lt;p&gt;下面的文正提供了一些尝试的方案，但不够SwiftUI风格&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/mobile-app-development-publication/controlling-uihostingcontroller-with-swiftui-view-2f6cd3b2a178&quot;&gt;Controlling UIHostingController with SwiftUI View&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;方案2.@Environment&lt;/h3&gt;
&lt;p&gt;通过一段时间的&lt;code&gt;SwiftUI&lt;/code&gt;学习，发现通过&lt;code&gt;@Environment&lt;/code&gt;可以获取到很多当前页面的一些有用信息。
但是通过观察可用的字段并没有发现有关&lt;code&gt;HostController&lt;/code&gt;的线索。&lt;/p&gt;
&lt;h3&gt;那是否可以添加自定义字段呢？&lt;/h3&gt;
&lt;p&gt;可以！
查看&lt;code&gt;EnvironmentKey&lt;/code&gt;相关的文档或者注释我们可以发现，其中已经提供了自定义的示例代码：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can create custom environment values by extending the EnvironmentValues structure with new properties. First declare a new environment key type and specify a value for the required defaultValue property:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: String = &quot;Default value&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The Swift compiler automatically infers the associated Value type as the type you specify for the default value. Then use the key to define a new environment value property:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;extension EnvironmentValues {
    var myCustomValue: String {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Clients of your environment value never use the key directly. Instead, they use the key path of your custom environment value property. To set the environment value for a view and all its subviews, add the environment(&lt;em&gt;:&lt;/em&gt;:) view modifier to that view:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;MyView()
    .environment(\.myCustomValue, &quot;Another string&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;As a convenience, you can also define a dedicated view modifier to apply this environment value:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;extension View {
    func myCustomValue(_ myCustomValue: String) -&amp;gt; some View {
        environment(\.myCustomValue, myCustomValue)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This improves clarity at the call site:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;MyView()
    .myCustomValue(&quot;Another string&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;To read the value from inside MyView or one of its descendants, use the Environment property wrapper:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;struct MyView: View {
    @Environment(\.myCustomValue) var customValue: String

    var body: some View {
        Text(customValue) // Displays &quot;Another value&quot;.
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三：下面按照上面的思路实现一下&lt;/h2&gt;
&lt;h3&gt;3.1 用以承载&lt;code&gt;ViewController&lt;/code&gt;的容器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public struct ViewControllerHolder {
    public weak var value: UIViewController?
    init(_ value: UIViewController?) {
        self.value = value
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 EnvironmentKey&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public struct ViewControllerKey: EnvironmentKey {
    public static var defaultValue: ViewControllerHolder { return ViewControllerHolder(nil) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 Extension&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;extension EnvironmentValues {
    public var viewController: ViewControllerHolder {
        get { return self[ViewControllerKey.self] }
        set { self[ViewControllerKey.self] = newValue }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 打开页面&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
extension UIViewController {
    public func present&amp;lt;Content: View&amp;gt;(presentationStyle: UIModalPresentationStyle = .automatic, transitionStyle: UIModalTransitionStyle = .coverVertical, animated: Bool = true, completion: @escaping () -&amp;gt; Void = {}, @ViewBuilder builder: () -&amp;gt; Content) {
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = presentationStyle
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, ViewControllerHolder(toPresent))
        )
        if presentationStyle == .overCurrentContext {
            toPresent.view.backgroundColor = .clear
        }
        self.present(toPresent, animated: animated, completion: completion)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 关闭页面&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct ShopContentView: View {
...
    // MARK: - Body
    var body: some View {
        ZStack(alignment: .top) {
            ...
            viewControllerHolder.value?.dismiss(animated: false, completion: nil)
            ...
        })
    }
    
...
    // MARK: -  Var: Environment
    @Environment(\.viewController) var viewControllerHolder
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.6 SwiftUIEx&lt;/h3&gt;
&lt;p&gt;这里进行了封装&lt;a href=&quot;https://github.com/RyukieSama/SwiftUIEx/blob/main/SwiftUIEx/UIKitBridge/Environment%2BUIViewController.swift&quot;&gt;SwiftUIEx&lt;/a&gt;，觉得有用欢迎留下一颗⭐️~&lt;/p&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;通过&lt;code&gt;@Environment&lt;/code&gt;实现&lt;code&gt;Dismiss&lt;/code&gt;，了解了其更灵活的使用方式。也为解决之后开发中遇到的问题提供了一点思路。同时也使解决方式更加&lt;code&gt;SwiftUI&lt;/code&gt;风格&lt;/p&gt;
</content:encoded></item><item><title>如何在SwiftUI中使用ImagePicker？</title><link>https://blog.lab-lawliet.app/posts/swiftui-05-imagepicker-583295fea466f9ab/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-05-imagepicker-583295fea466f9ab/</guid><description>这里使用了UIViewControllerRepresentable，我们的SwiftUIImagePicker就已经是一个标准的SwiftUI的View了。</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一：问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我的新项目中尝试性的使用了&lt;code&gt;SwiftUI&lt;/code&gt;进行了一部分功能的构建。
但当我需要访问相册选择图片的时候，缺犯难了。
报出了下面的错误:
&lt;img src=&quot;/content-assets/83dd8f057d916976.webp&quot; alt=&quot;Error&quot; /&gt;
因为原有的一些代理需要要求&lt;code&gt;NSObjectProtocol&lt;/code&gt;而我们SwifUI中的视图用的是&lt;code&gt;Struct&lt;/code&gt;
而系统并没有提供在&lt;code&gt;SwiftUI&lt;/code&gt;下调用相册的相关API，所以这里只能考虑自己去封装了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二：SwiftUIImagePicker&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct SwiftUIImagePicker {
    @Environment(\.presentationMode) var presentationMode
    @Binding var image: UIImage?
}

extension SwiftUIImagePicker: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -&amp;gt; some UIViewController {
        let picker = UIImagePickerController()
                return picker
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 UIViewControllerRepresentable&lt;/h3&gt;
&lt;p&gt;这里使用了&lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;，我们的&lt;code&gt;SwiftUIImagePicker&lt;/code&gt;就已经是一个标准的&lt;code&gt;SwiftUI&lt;/code&gt;的&lt;code&gt;View&lt;/code&gt;了。&lt;/p&gt;
&lt;p&gt;当创建一个&lt;code&gt;SwiftUIImagePicker&lt;/code&gt;，SwiftUI会自动调用&lt;code&gt;makeUIViewController()&lt;/code&gt;并返回一个&lt;code&gt;UIImagePickerController&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;2.2 Coordinator 代理中间件&lt;/h3&gt;
&lt;p&gt;由于&lt;code&gt;SwiftUIImagePicker&lt;/code&gt;作为一个SwiftUI的View依旧不能使用&lt;code&gt;NSObjectProtocol&lt;/code&gt;，所以我们需要一个中间件来完成代理。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;中贴心的提供了这样一个中间件的定义，我们只要提供实现即可~&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// A type to coordinate with the view controller.
    associatedtype Coordinator = Void

    /// Creates the custom instance that you use to communicate changes from
    /// your view controller to other parts of your SwiftUI interface.
    ///
    /// Implement this method if changes to your view controller might affect
    /// other parts of your app. In your implementation, create a custom Swift
    /// instance that can communicate with other parts of your interface. For
    /// example, you might provide an instance that binds its variables to
    /// SwiftUI properties, causing the two to remain synchronized. If your view
    /// controller doesn&apos;t interact with other parts of your app, providing a
    /// coordinator is unnecessary.
    ///
    /// SwiftUI calls this method before calling the
    /// ``UIViewControllerRepresentable/makeUIViewController(context:)`` method.
    /// The system provides your coordinator either directly or as part of a
    /// context structure when calling the other methods of your representable
    /// instance.
    func makeCoordinator() -&amp;gt; Self.Coordinator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们来实现一下下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;internal func makeCoordinator() -&amp;gt; SwiftUIImagePickerCoordinator {
    SwiftUIImagePickerCoordinator(self)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;顺便实现一下选择完图片的代理方法吧：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let uiImage = info[.originalImage] as? UIImage {
        parent.image = uiImage
    }
    parent.presentationMode.wrappedValue.dismiss()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;别忘了设置一下代理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;picker.delegate = context.coordinator
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三：参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.hackingwithswift.com/books/ios-swiftui/importing-an-image-into-swiftui-using-uiimagepickercontroller&quot;&gt;Importing an image into SwiftUI using UIImagePickerController&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>从some看Swift不透明类型</title><link>https://blog.lab-lawliet.app/posts/swiftui-06-some-swift-54872df5986bb038/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swiftui-06-some-swift-54872df5986bb038/</guid><description>| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 | |:----:|:----:|:----:| |类型|游戏|财务| |AppStore|Elic|Umemi|</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Hi 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wechat: RyukieW&lt;/li&gt;
&lt;li&gt;📦 &lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/&quot;&gt;技术文章归档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href=&quot;https://github.com/RyukieSama&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;我的个人项目&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;游戏&lt;/td&gt;
&lt;td&gt;财务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/developer/rongqing-wang/id1264542103&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;Elic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;Umemi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;一、 我遇到的问题&lt;/h2&gt;
&lt;p&gt;今天在写一个 &lt;code&gt;SwiftUI&lt;/code&gt; 的demo的时候，准备用一个 &lt;code&gt;枚举&lt;/code&gt; 来作为列表的数数据源。&lt;/p&gt;
&lt;h3&gt;1.1 列表的主体&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct ContentView: View {
    var body: some View {
        NavigationView {
            Form {
                ForEach(ContentTypes.allCases, content: { item in
                    NavigationLink(destination: item.destinationView) {
                        Text(item.rawValue)
                    }
                })
            }
            .navigationBarTitle(&quot;SwiftUITourDemos&quot;, displayMode: .automatic)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 枚举&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;enum ContentTypes: String, CaseIterable, Identifiable {
    var id: String { rawValue }
    
    case StateAndStructClass = &quot;@State &amp;amp; struct/class&quot;

    var destinationView: View {
        switch self {
        case .StateAndStructClass:
            return StateAndStructClassView()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 问题&lt;/h3&gt;
&lt;p&gt;代码乍一看好像没什么问题，然而：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0ea5d1e6a94b9f70.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Protocol &apos;View&apos; can only be used as a generic constraint because it has Self or associated type requirements&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个使用协议时 &lt;code&gt;protocol&lt;/code&gt; 经典的报错。&lt;/p&gt;
&lt;h3&gt;1.4 View 是什么&lt;/h3&gt;
&lt;p&gt;通过点进 &lt;code&gt;View&lt;/code&gt; 的定义，可以看到，它是一个协议。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/90537cdc873d1cd1.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二、 解决问题&lt;/h2&gt;
&lt;p&gt;在 SwiftUI 默认生产的代码中有这样一段&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6cae9cb6271e1439.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;既然这里的 &lt;code&gt;body (View协议中定义的)&lt;/code&gt; 是用 &lt;code&gt;some View&lt;/code&gt; 来做为类型，那么我在 &lt;code&gt;ContentTypes&lt;/code&gt; 的 &lt;code&gt;destinationView&lt;/code&gt; 是否也可以使用 &lt;code&gt;some View&lt;/code&gt; 呢？&lt;/p&gt;
&lt;h3&gt;2.1 试一试&lt;/h3&gt;
&lt;p&gt;诶嘿～ 可以了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2967126bfea75dd7.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;那么 &lt;code&gt;some&lt;/code&gt; 究竟是什么呢？它代表 &lt;code&gt;不透明类型&lt;/code&gt;，通常与协议搭配使用。其实并不是 &lt;code&gt;SwiftUI&lt;/code&gt; 的特性，而是 &lt;code&gt;Swift&lt;/code&gt; 的特性。只不过是我平时没见过而已😂。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 不透明类型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html&quot;&gt;LanguageGuide-OpaqueTypes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://swiftgg.gitbook.io/swift/swift-jiao-cheng/23_opaque_types&quot;&gt;不透明类型&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;具有&lt;code&gt;不透明返回类型&lt;/code&gt;的函数或方法会&lt;code&gt;隐藏返回值的类型信息&lt;/code&gt;。函数&lt;code&gt;不再提供具体的类型&lt;/code&gt;作为返回类型，而是根据它支持的&lt;code&gt;协议&lt;/code&gt;来描述返回值。在处理&lt;code&gt;模块&lt;/code&gt;和&lt;code&gt;调用代码&lt;/code&gt;之间的关系时，隐藏类型信息非常有用，因为返回的底层数据类型仍然可以&lt;code&gt;保持私有&lt;/code&gt;。而且不同于返回协议类型，不透明类型能保证类型一致性：编译器能获取到类型信息，同时模块使用者却不能获取到。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.1 需要解决的问题&lt;/h3&gt;
&lt;p&gt;举个例子，假设你正在写一个模块，用来绘制 &lt;code&gt;ASCII&lt;/code&gt; 符号构成的几何图形。它的基本特征是有一个 &lt;code&gt;draw()&lt;/code&gt; 方法，会返回一个代表最终几何图形的字符串，你可以用包含这个方法的 &lt;code&gt;Shape&lt;/code&gt; 协议来描述：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol Shape {
    func draw() -&amp;gt; String
}

struct Triangle: Shape {
    var size: Int
    func draw() -&amp;gt; String {
        var result: [String] = []
        for length in 1...size {
            result.append(String(repeating: &quot;*&quot;, count: length))
        }
        return result.joined(separator: &quot;\n&quot;)
    }
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以利用泛型来实现垂直翻转之类的操作，就像下面这样。然而，这种方式有一个很大的局限：翻转操作的结果会暴露我们用于构造结果的泛型类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct FlippedShape&amp;lt;T: Shape&amp;gt;: Shape {
    var shape: T
    func draw() -&amp;gt; String {
        let lines = shape.draw().split(separator: &quot;\n&quot;)
        return lines.reversed().joined(separator: &quot;\n&quot;)
    }
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如下方代码所示，用同样的方式定义了一个 &lt;code&gt;JoinedShape&amp;lt;T: Shape, U: Shape&amp;gt;&lt;/code&gt; 结构体，能将几何图形垂直拼接起来。如果拼接一个翻转三角形和一个普通三角形，它就会得到类似于 &lt;code&gt;JoinedShape&amp;lt;FlippedShape&amp;lt;Triangle&amp;gt;, Triangle&amp;gt;&lt;/code&gt; 这样的类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct JoinedShape&amp;lt;T: Shape, U: Shape&amp;gt;: Shape {
    var top: T
    var bottom: U
    func draw() -&amp;gt; String {
        return top.draw() + &quot;\n&quot; + bottom.draw()
    }
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;暴露构造所用的具体类型会造成类型信息的泄露，因为 &lt;code&gt;ASCII&lt;/code&gt; 几何图形模块的部分公开接口必须声明完整的返回类型，而实际上这些类型信息并不应该被公开声明。输出同一种几何图形，模块内部可能有多种实现方式，而外部使用时，应该与内部各种变换顺序的实现逻辑无关。诸如 &lt;code&gt;JoinedShape&lt;/code&gt; 和 &lt;code&gt;FlippedShape&lt;/code&gt; 这样包装后的类型，模块使用者并不关心，它们也不应该可见。模块的公开接口应该由拼接、翻转等基础操作组成，这些操作也应该返回独立的 &lt;code&gt;Shape&lt;/code&gt; 类型的值。&lt;/p&gt;
&lt;h3&gt;3.2 返回不透明类型&lt;/h3&gt;
&lt;p&gt;你可以认为不透明类型和泛型相反。泛型允许调用一个方法时，为这个方法的形参和返回值指定一个与实现无关的类型。举个例子，下面这个函数的返回值类型就由它的调用者决定：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func max&amp;lt;T&amp;gt;(_ x: T, _ y: T) -&amp;gt; T where T: Comparable { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;x&lt;/code&gt; 和 &lt;code&gt;y&lt;/code&gt; 的值由调用 &lt;code&gt;max(_:_:)&lt;/code&gt; 的代码决定，而它们的类型决定了 &lt;code&gt;T&lt;/code&gt; 的具体类型。调用代码可以使用任何遵循了 &lt;code&gt;Comparable&lt;/code&gt; 协议的类型，函数内部也要以一种通用的方式来写代码，才能应对调用者传入的各种类型。&lt;code&gt;max(_:_:)&lt;/code&gt; 的实现就只使用了所有遵循 &lt;code&gt;Comparable&lt;/code&gt; 协议的类型共有的特性。&lt;/p&gt;
&lt;p&gt;而在返回不透明类型的函数中，上述角色发生了互换。不透明类型允许函数实现时，选择一个与调用代码无关的返回类型。比如，下面的例子返回了一个梯形，却没直接输出梯形的底层类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Square: Shape {
    var size: Int
    func draw() -&amp;gt; String {
        let line = String(repeating: &quot;*&quot;, count: size)
        let result = Array&amp;lt;String&amp;gt;(repeating: line, count: size)
        return result.joined(separator: &quot;\n&quot;)
    }
}

func makeTrapezoid() -&amp;gt; some Shape {
    let top = Triangle(size: 2)
    let middle = Square(size: 2)
    let bottom = FlippedShape(shape: top)
    let trapezoid = JoinedShape(
        top: top,
        bottom: JoinedShape(top: middle, bottom: bottom)
    )
    return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子中，&lt;code&gt;makeTrapezoid()&lt;/code&gt; 函数将返回值类型定义为 &lt;code&gt;some Shape&lt;/code&gt;；因此，该函数返回遵循 &lt;code&gt;Shape&lt;/code&gt; 协议的给定类型，而不需指定任何具体类型。这样写 &lt;code&gt;makeTrapezoid()&lt;/code&gt; 函数可以表明它公共接口的基本性质：&lt;code&gt;返回的是一个几何图形&lt;/code&gt;，而不是部分的公共接口生成的特殊类型。上述实现过程中使用了两个三角形和一个正方形，还可以用其他多种方式重写画梯形的函数，都不必改变返回类型。&lt;/p&gt;
&lt;p&gt;这个例子凸显了不透明返回类型和泛型的相反之处。&lt;code&gt;makeTrapezoid()&lt;/code&gt; 中代码可以返回任意它需要的类型，只要这个类型是遵循 Shape 协议的，就像调用泛型函数时可以使用任何需要的类型一样。这个函数的调用代码需要采用通用的方式，就像泛型函数的实现代码一样，这样才能让 &lt;code&gt;makeTrapezoid()&lt;/code&gt; 返回的任何 &lt;code&gt;Shape&lt;/code&gt; 类型的值都能被正常使用。&lt;/p&gt;
&lt;p&gt;你也可以将不透明返回类型和泛型结合起来，下面的两个泛型函数也都返回了遵循 Shape 协议的不透明类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func flip&amp;lt;T: Shape&amp;gt;(_ shape: T) -&amp;gt; some Shape {
    return FlippedShape(shape: shape)
}
func join&amp;lt;T: Shape, U: Shape&amp;gt;(_ top: T, _ bottom: U) -&amp;gt; some Shape {
    JoinedShape(top: top, bottom: bottom)
}

let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个例子中 &lt;code&gt;opaqueJoinedTriangles&lt;/code&gt; 的值和前文中关于泛型的那个例子中的 &lt;code&gt;joinedTriangles&lt;/code&gt; 完全一样。不过和前文不一样的是，&lt;code&gt;flip(-:)&lt;/code&gt; 和 &lt;code&gt;join(-:-:)&lt;/code&gt; 将对泛型参数的操作后的返回结果包装成了不透明类型，这样保证了在结果中泛型参数类型不可见。两个函数都是泛型函数，因为他们都依赖于泛型参数，而泛型参数又将 &lt;code&gt;FlippedShape&lt;/code&gt; 和 &lt;code&gt;JoinedShape&lt;/code&gt; 所需要的类型信息传递给它们。&lt;/p&gt;
&lt;p&gt;如果函数中有多个地方返回了不透明类型，那么所有可能的返回值都必须是同一类型。即使对于泛型函数，不透明返回类型可以使用泛型参数，但仍需保证返回类型唯一。比如，下面就是一个&lt;em&gt;非法&lt;/em&gt;示例 —— 包含针对 &lt;code&gt;Square&lt;/code&gt; 类型进行特殊处理的翻转函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func invalidFlip&amp;lt;T: Shape&amp;gt;(_ shape: T) -&amp;gt; some Shape {
    if shape is Square {
        return shape // 错误：返回类型不一致
    }
    return FlippedShape(shape: shape) // 错误：返回类型不一致
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你调用这个函数时传入一个 &lt;code&gt;Square&lt;/code&gt; 类型，那么它会返回 &lt;code&gt;Square&lt;/code&gt; 类型；否则，它会返回一个 &lt;code&gt;FlippedShape&lt;/code&gt; 类型。这违反了返回值类型唯一的要求，所以 &lt;code&gt;invalidFlip(_:)&lt;/code&gt; 不正确。修正 &lt;code&gt;invalidFlip(_:)&lt;/code&gt; 的方法之一就是将针对 &lt;code&gt;Square&lt;/code&gt; 的特殊处理移入到 &lt;code&gt;FlippedShape&lt;/code&gt; 的实现中去，这样就能保证这个函数始终返回 &lt;code&gt;FlippedShape&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct FlippedShape&amp;lt;T: Shape&amp;gt;: Shape {
    var shape: T
    func draw() -&amp;gt; String {
        if shape is Square {
            return shape.draw()
        }
        let lines = shape.draw().split(separator: &quot;\n&quot;)
        return lines.reversed().joined(separator: &quot;\n&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回类型始终唯一的要求，并不会影响在返回的不透明类型中使用泛型。比如下面的函数，就是在返回的底层类型中使用了泛型参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func `repeat`&amp;lt;T: Shape&amp;gt;(shape: T, count: Int) -&amp;gt; some Collection {
    return Array&amp;lt;T&amp;gt;(repeating: shape, count: count)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种情况下，返回的底层类型会根据 &lt;code&gt;T&lt;/code&gt; 的不同而发生变化：但无论什么形状被传入，&lt;code&gt;repeat(shape:count:)&lt;/code&gt; 都会创建并返回一个元素为相应形状的数组。尽管如此，返回值始终还是同样的底层类型 &lt;code&gt;[T]&lt;/code&gt;， 所以这符合不透明返回类型始终唯一的要求。&lt;/p&gt;
&lt;h3&gt;3.3 不透明类型和协议类型的区别&lt;/h3&gt;
&lt;p&gt;虽然使用不透明类型作为函数返回值，看起来和返回协议类型非常相似，但这两者有一个主要区别，就在于是否需要保证类型一致性。一个不透明类型只能对应一个具体的类型，即便函数调用者并不能知道是哪一种类型；协议类型可以同时对应多个类型，只要它们都遵循同一协议。总的来说，协议类型更具灵活性，底层类型可以存储更多样的值，而不透明类型对这些底层类型有更强的限定。&lt;/p&gt;
&lt;p&gt;比如，这是 &lt;code&gt;flip(_:)&lt;/code&gt; 方法不采用不透明类型，而采用返回协议类型的版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func protoFlip&amp;lt;T: Shape&amp;gt;(_ shape: T) -&amp;gt; Shape {
    return FlippedShape(shape: shape)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个版本的 &lt;code&gt;protoFlip(_:)&lt;/code&gt; 和 &lt;code&gt;flip(_:)&lt;/code&gt; 有相同的函数体，并且它也始终返回唯一类型。但不同于 &lt;code&gt;flip(_:)&lt;/code&gt;，&lt;code&gt;protoFlip(_:)&lt;/code&gt; 返回值其实不需要始终返回唯一类型 —— 返回类型只需要遵循 &lt;code&gt;Shape&lt;/code&gt; 协议即可。换句话说，&lt;code&gt;protoFlip(_:)&lt;/code&gt; 比起 &lt;code&gt;flip(_:)&lt;/code&gt; 对 API 调用者的约束更加松散。它保留了返回多种不同类型的灵活性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func protoFlip&amp;lt;T: Shape&amp;gt;(_ shape: T) -&amp;gt; Shape {
    if shape is Square {
        return shape
    }

    return FlippedShape(shape: shape)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改后的代码根据代表形状的参数的不同，可能返回 &lt;code&gt;Square&lt;/code&gt; 实例或者 &lt;code&gt;FlippedShape&lt;/code&gt; 实例，所以同样的函数可能返回完全不同的两个类型。当翻转相同形状的多个实例时，此函数的其他有效版本也可能返回完全不同类型的结果。&lt;code&gt;protoFlip(_:)&lt;/code&gt; 返回类型的不确定性，意味着很多依赖返回类型信息的操作也无法执行了。举个例子，这个函数的返回结果就不能用 == 运算符进行比较了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing  // 错误
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的例子中，最后一行的错误来源于多个原因。最直接的问题在于，&lt;code&gt;Shape&lt;/code&gt; 协议中并没有包含对 == 运算符的声明。如果你尝试加上这个声明，那么你会遇到新的问题，就是 == 运算符需要知道左右两侧参数的类型。这类运算符通常会使用 &lt;code&gt;Self&lt;/code&gt; 类型作为参数，用来匹配符合协议的具体类型，但是由于将协议当成类型使用时会发生类型擦除，所以并不能给协议加上对 &lt;code&gt;Self&lt;/code&gt; 的实现要求。&lt;/p&gt;
&lt;p&gt;将协议类型作为函数的返回类型能更加灵活，函数只要返回遵循协议的类型即可。然而，更具灵活性导致牺牲了对返回值执行某些操作的能力。上面的例子就说明了为什么不能使用 == 运算符 —— 它依赖于具体的类型信息，而这正是使用协议类型所无法提供的。&lt;/p&gt;
&lt;p&gt;这种方法的另一个问题在于，变换形状的操作不能嵌套。翻转三角形的结果是一个 &lt;code&gt;Shape&lt;/code&gt; 类型的值，而 &lt;code&gt;protoFlip(_:)&lt;/code&gt; 方法的则将遵循 &lt;code&gt;Shape&lt;/code&gt; 协议的类型作为形参，然而协议类型的值并不遵循这个协议；&lt;code&gt;protoFlip(_:)&lt;/code&gt; 的返回值也并不遵循 &lt;code&gt;Shape&lt;/code&gt; 协议。这就是说 &lt;code&gt;protoFlip(protoFlip(smallTriange))&lt;/code&gt; 这样的多重变换操作是非法的，因为经过翻转操作后的结果类型并不能作为 &lt;code&gt;protoFlip(_:)&lt;/code&gt; 的形参。&lt;/p&gt;
&lt;p&gt;相比之下，不透明类型则保留了底层类型的唯一性。Swift 能够推断出关联类型，这个特点使得作为函数返回值，不透明类型比协议类型有更大的使用场景。比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -&amp;gt; Item { get }
}
extension Array: Container { }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你不能将 &lt;code&gt;Container&lt;/code&gt; 作为方法的返回类型，因为此协议有一个关联类型。你也不能将它用于对泛型返回类型的约束，因为函数体之外并没有暴露足够多的信息来推断泛型类型。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 错误：有关联类型的协议不能作为返回类型。
func makeProtocolContainer&amp;lt;T&amp;gt;(item: T) -&amp;gt; Container {
    return [item]
}

// 错误：没有足够多的信息来推断 C 的类型。
func makeProtocolContainer&amp;lt;T, C: Container&amp;gt;(item: T) -&amp;gt; C {
    return [item]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而使用不透明类型 &lt;code&gt;some Container&lt;/code&gt; 作为返回类型，就能够明确地表达所需要的 API 契约 —— 函数会返回一个集合类型，但并不指明它的具体类型：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func makeOpaqueContainer&amp;lt;T&amp;gt;(item: T) -&amp;gt; some Container {
    return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// 输出 &quot;Int&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;twelve&lt;/code&gt; 的类型可以被推断出为 &lt;code&gt;Int&lt;/code&gt;， 这说明了类型推断适用于不透明类型。在 &lt;code&gt;makeOpaqueContainer(item:)&lt;/code&gt; 的实现中，底层类型是不透明集合 &lt;code&gt;[T]&lt;/code&gt;。在上述这种情况下，&lt;code&gt;T&lt;/code&gt; 就是 &lt;code&gt;Int&lt;/code&gt; 类型，所以返回值就是整数数组，而关联类型 &lt;code&gt;Item&lt;/code&gt; 也被推断出为 &lt;code&gt;Int&lt;/code&gt;。&lt;code&gt;Container&lt;/code&gt; 协议中的 &lt;code&gt;subscipt&lt;/code&gt; 方法会返回 &lt;code&gt;Item&lt;/code&gt;，这也意味着 &lt;code&gt;twelve&lt;/code&gt; 的类型也被能推断出为 &lt;code&gt;Int&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;四、 总结&lt;/h2&gt;
&lt;p&gt;了解了 &lt;code&gt;some 不透明类型&lt;/code&gt; 后，突然想起之前遇到的一些协议使用时的问题可以更优雅的解决了。嘿嘿😄&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.swift.org/swift-book/LanguageGuide/OpaqueTypes.html&quot;&gt;LanguageGuide-OpaqueTypes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://swiftgg.gitbook.io/swift/swift-jiao-cheng/23_opaque_types&quot;&gt;不透明类型&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/56433665/what-is-the-some-keyword-in-swiftui&quot;&gt;What is the &lt;code&gt;some&lt;/code&gt; keyword in Swift(UI)?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>包含min函数的栈（容易被误导的一题）</title><link>https://blog.lab-lawliet.app/posts/article-30-min-42df1ab01b67ad0e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-30-min-42df1ab01b67ad0e/</guid><description>| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 | |:----:|:----:|:----:| |类型|游戏|财务| |AppStore|Elic|Umemi|</description><pubDate>Thu, 05 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Hi 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wechat: RyukieW&lt;/li&gt;
&lt;li&gt;📦 &lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/&quot;&gt;技术文章归档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href=&quot;https://github.com/RyukieSama&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;我的个人项目&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;游戏&lt;/td&gt;
&lt;td&gt;财务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/developer/rongqing-wang/id1264542103&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;Elic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;Umemi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;定义栈的数据结构，请在该类型中实现一个能够得到栈的最小元素的 &lt;code&gt;min&lt;/code&gt; 函数在该栈中，调用 &lt;code&gt;min&lt;/code&gt;、&lt;code&gt;push&lt;/code&gt; 及 &lt;code&gt;pop&lt;/code&gt; 的时间复杂度都是 &lt;code&gt;O(1)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min();   --&amp;gt; 返回 -3.
minStack.pop();
minStack.top();      --&amp;gt; 返回 0.
minStack.min();   --&amp;gt; 返回 -2.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;各函数的调用总次数不超过 &lt;code&gt;20000&lt;/code&gt; 次&lt;/p&gt;
&lt;p&gt;注意：本题与主站 155 题相同：https://leetcode-cn.com/problems/min-stack/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 分析&lt;/h2&gt;
&lt;p&gt;本题难点在于：&lt;em&gt;&lt;strong&gt;调用 &lt;code&gt;min&lt;/code&gt;、&lt;code&gt;push&lt;/code&gt; 及 &lt;code&gt;pop&lt;/code&gt; 的时间复杂度都是 &lt;code&gt;O(1)&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;并且本题很容易走进一个误区：&lt;em&gt;&lt;strong&gt;以为MinStack内部是有序的&lt;/strong&gt;&lt;/em&gt;（不要问我为什么知道有这个误区😂）&lt;/p&gt;
&lt;p&gt;既然要满足 &lt;code&gt;O(1)&lt;/code&gt; 的条件，一个有序集合必定是不可能完成的。&lt;/p&gt;
&lt;p&gt;那要不考虑一下使用一个辅助的集合？&lt;/p&gt;
&lt;h3&gt;2.1 Top &amp;amp; Min&lt;/h3&gt;
&lt;p&gt;本题的示例对我来说也很有误导性...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/eb5416b4cbce50e5.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过这个我以为：&lt;code&gt;Top&lt;/code&gt; 是最大值，整个 &lt;code&gt;MinStack&lt;/code&gt; 是有序的...&lt;/p&gt;
&lt;p&gt;然后整个思路就被带偏了🙈&lt;/p&gt;
&lt;h3&gt;2.2 辅助栈&lt;/h3&gt;
&lt;p&gt;我们需要准备两个栈 &lt;code&gt;A：存放所有元素&lt;/code&gt; &amp;amp; &lt;code&gt;B：辅助集合&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.3 push 入栈&lt;/h3&gt;
&lt;p&gt;每当有元素入栈，正常入 &lt;code&gt;A栈&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后判断 &lt;code&gt;B栈&lt;/code&gt; 的 &lt;code&gt;栈顶元素&lt;/code&gt; 是否 &lt;code&gt;&amp;gt;= 当前入栈元素&lt;/code&gt; ，如果满足条件，也将元素入 &lt;code&gt;B栈&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这样就保证了 &lt;code&gt;B栈&lt;/code&gt; 的栈顶元素始终是 &lt;code&gt;最小的&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;2.4 pop 出栈&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;A栈&lt;/code&gt; 正常出栈，同时对比一下 &lt;code&gt;B栈&lt;/code&gt; 的栈顶，如果相等，那么 &lt;code&gt;B栈&lt;/code&gt; 也出栈。这样来保证 &lt;code&gt;B栈&lt;/code&gt; 与 &lt;code&gt;A栈&lt;/code&gt; 的同步性。&lt;/p&gt;
&lt;h3&gt;2.5 top 栈顶元素&lt;/h3&gt;
&lt;p&gt;正常返回 &lt;code&gt;A栈&lt;/code&gt; 栈顶元素即可。&lt;/p&gt;
&lt;h3&gt;2.6 min 最小元素&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;B栈&lt;/code&gt; 栈顶既是最小元素。&lt;/p&gt;
&lt;h2&gt;三、 代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class MinStack {
    
    var elements: [Int] = []
    
    var smaller: [Int] = []
    
    init() {
        
    }
    
    func push(_ x: Int) {
        elements.append(x)
        if smaller.last == nil || smaller.last ?? 0 &amp;gt;= x {
            smaller.append(x)
        }
    }
    
    func pop() {
        if elements.removeLast() == smaller.last {
            smaller.removeLast()
        }
    }
    
    func top() -&amp;gt; Int {
        return elements.last ?? 0
    }
    
    func min() -&amp;gt; Int {
        return smaller.last ?? 0
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7c13154a5aefafaf.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Apple官方资源瘦身方案ODR（二）：践行｜换肤系统改造</title><link>https://blog.lab-lawliet.app/posts/ios-02-apple-odr-95a5de5aced33750/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-02-apple-odr-95a5de5aced33750/</guid><description>既然知道了 ODR 能干什么了，那就拿自己的项目开个刀。</description><pubDate>Wed, 04 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你还不太清楚 &lt;code&gt;ODR: On-Demand Resources&lt;/code&gt; 是什么，可以看看&lt;a href=&quot;/posts/ios-01-apple-odr-4af20c653337ac4d/&quot;&gt;Apple官方资源瘦身方案ODR（一）：初见&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;既然知道了 &lt;code&gt;ODR&lt;/code&gt; 能干什么了，那就拿自己的项目开个刀。&lt;/p&gt;
&lt;p&gt;这里使用我的个人项目&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;梦见账本&lt;/a&gt;，由于项目中有多套皮肤可以更换，所以存了很多套图标，这些图标就很适合使用 &lt;code&gt;ODR&lt;/code&gt; 来优化。&lt;/p&gt;
&lt;p&gt;关于 &lt;code&gt;ODR&lt;/code&gt; 除了官方文档，也没找到很多实践的资料，这里就结合文档手摸手一起来实践一波吧。&lt;/p&gt;
&lt;h3&gt;先来个卖家秀&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;ODR&lt;/code&gt; 之前&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/610fe0e9716211a4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;ODR&lt;/code&gt; 之后！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a15aae4a09ffbd91.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;TestFlight&lt;/code&gt; 上看的大小，写文这会儿 &lt;code&gt;2.7.1&lt;/code&gt; 还没提审发布，不过和真实下载的不会有多少差别。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、 启用ODR&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/71160acb181d748b.webp&quot; alt=&quot;build settings&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从 &lt;code&gt;iOS9&lt;/code&gt; 开始就是默认开启的了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 创建标签&lt;/h2&gt;
&lt;p&gt;标签用于识别和管理一组 &lt;code&gt;ODR&lt;/code&gt;。将一个或多个标签分配给项目中的资源可将其标识为 &lt;code&gt;ODR&lt;/code&gt;。在运行时，所有 &lt;code&gt;ODR&lt;/code&gt; 的访问都与标签（而非单个资源）配合工作。&lt;/p&gt;
&lt;p&gt;在 Target 的 Resource tags 可以看到添加标签的入口，这里可以选择项目中的资源，以添加到标签下。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/437736a7bc12c2e3.webp&quot; alt=&quot;Resource tags&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;项目之前的资源组织方式：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/65185a41893ac05a.webp&quot; alt=&quot;之前的资源目录&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;这里为了方便标签管理，进行一下资源包的拆分：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/659e6c7ea4b61f65.webp&quot; alt=&quot;03&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 为 Assets 添加标签&lt;/h3&gt;
&lt;p&gt;将 &lt;code&gt;Assets&lt;/code&gt; 添加到 Tag 下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4d05d4c3a489f951.webp&quot; alt=&quot;04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e57d2b0620934eb.webp&quot; alt=&quot;05&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 确认资源标签设置成功&lt;/h3&gt;
&lt;p&gt;打开资源包查看资源信息，就可以看到这里匹配的资源标签了。（一个资源可以设置多个标签）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2c10eed18a95f13f.webp&quot; alt=&quot;06&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在构建项目时，&lt;code&gt;Xcode&lt;/code&gt; 会检查项目中的所有标记项目，并生成供操作系统使用的资源包。&lt;/p&gt;
&lt;h3&gt;2.3 为单个资源快速添加标签&lt;/h3&gt;
&lt;p&gt;也可以通过直接输入联想，快速为单个资源添已有标签，或者快速创建一个标签。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b056e5d8cb127510.webp&quot; alt=&quot;07&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、 标签分类&lt;/h2&gt;
&lt;p&gt;通过切换到 &lt;code&gt;Prefetched&lt;/code&gt; 我们看到了三种分类：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f90d2e55cf882463.webp&quot; alt=&quot;08&quot; /&gt;&lt;/p&gt;
&lt;p&gt;创建的标签的默认类别是&lt;code&gt;仅按需下载ODR&lt;/code&gt;。标签可以在类别之间拖动。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;***Initial install tags. ***
&lt;ul&gt;
&lt;li&gt;资源与应用程序同时下载。资源的大小包含在应用商店中应用的总大小中。当没有任何 &lt;code&gt;NSBundleResourceRequest&lt;/code&gt; 访问时，可能会被清除。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***Prefetch tag order. ***
&lt;ul&gt;
&lt;li&gt;安装应用后，资源开始下载。按标签顺序下载。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***Dowloaded only on demand. ***
&lt;ul&gt;
&lt;li&gt;按需加载。当应用请求时，将下载这些标签。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;当操作系统下载标签时，它只会下载设备上没有的资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.1 设置标签分类&lt;/h3&gt;
&lt;p&gt;设置标签分类很简单，从默认的 ODR 列表中拖到相应的分类下即可。&lt;/p&gt;
&lt;p&gt;这里我把默认的两个皮肤的资源设置为：&lt;code&gt;Initial install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bfbb88f1afaf59a1.webp&quot; alt=&quot;09&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 大小限制&lt;/h2&gt;
&lt;p&gt;应用剪切后，标签中资源的总大小不得超过 &lt;code&gt;512 MB&lt;/code&gt;。应用商店中存储的按需资源的总大小不得超过 &lt;code&gt;20 GB&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;标签的理想大小不大于 &lt;code&gt;64 MB&lt;/code&gt;。此大小在下载速度和本地存储之间提供了良好的平衡，以便在设备的本地存储处于低位时可以清除。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ca60eb3428f69f8c.webp&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slicing.
&lt;ul&gt;
&lt;li&gt;复选标记（✓）表示大小反映的是应用剪切后的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;App bundle.
&lt;ul&gt;
&lt;li&gt;下载到设备的剪切后的应用程序包的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Asset packs.
&lt;ul&gt;
&lt;li&gt;资产包由 Xcode 生成。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***Initial install tags. ***
&lt;ul&gt;
&lt;li&gt;标记为初始安装的标记的总剪切后的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***Initial install and prefetched tags.. ***
&lt;ul&gt;
&lt;li&gt;标记为 &lt;code&gt;Initial install&lt;/code&gt; 和 &lt;code&gt;Prefetch tag&lt;/code&gt; 的资源剪切后的总大小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***In use on-demand resources. ***
&lt;ul&gt;
&lt;li&gt;一次行最多可用 &lt;code&gt;ODR&lt;/code&gt; 的剪切后的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;***Hosted on-demand resources. ***
&lt;ul&gt;
&lt;li&gt;所有 &lt;code&gt;ODR&lt;/code&gt; 大小，不剪切的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 管理ODR&lt;/h2&gt;
&lt;p&gt;配置了标签后，直接运行项目。可以看出所有的 &lt;code&gt;ODR&lt;/code&gt; 资源均没有下载。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e8fa29a1068dd587.webp&quot; alt=&quot;11&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.1 ODR 更新时机确定&lt;/h3&gt;
&lt;p&gt;这里先描述下 &lt;code&gt;梦见账本&lt;/code&gt; 的换肤逻辑，来帮助确定 ODR 的更新时机:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;皮肤分为两种模式：普通模式、暗黑模式。
&lt;ul&gt;
&lt;li&gt;即：用户可以为两种模式分别设置皮肤，切换系统暗黑模式开关即可无缝切换&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;设置皮肤后会重新设置根控制器
&lt;ul&gt;
&lt;li&gt;这里是需要更新 ODR 的一个节点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;另一个检查更新的节点是应用启动的时候
&lt;ul&gt;
&lt;li&gt;检查当前的两种皮肤，准备相关标签的资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.1 NSBundleResourceRequest 请求资源&lt;/h3&gt;
&lt;p&gt;一个标签可以被多个实例管理，下面列举三个重要的 &lt;code&gt;API&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;初始化&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;tags: 标签集合&lt;/li&gt;
&lt;li&gt;bundle: 一般是 main&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public init(tags: Set&amp;lt;String&amp;gt;, bundle: Bundle)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;检查状态&lt;/h4&gt;
&lt;p&gt;检查请求的标签的资源是否全部都已经在设备中保存了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;open func conditionallyBeginAccessingResources(completionHandler: @escaping (Bool) -&amp;gt; Void)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;开始请求资源&lt;/h4&gt;
&lt;p&gt;请求不在本地的标签资源。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;open func beginAccessingResources(completionHandler: @escaping (Error?) -&amp;gt; Void)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、改造「梦见账本」&lt;/h2&gt;
&lt;h3&gt;6.1 封装 OdrTool&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;protocol OdrTagProtocol {
    var tagName: String { get }
}

struct OdrTool {
    static func requestODR(of tags: OdrTagProtocol..., in bundle: Bundle = Bundle.main) {
        var tagsSet: Set&amp;lt;String&amp;gt; = []
        tags.forEach { tagsSet.insert($0.tagName) }
        let request = NSBundleResourceRequest(tags: tagsSet, bundle: bundle)
        request.conditionallyBeginAccessingResources { hasCache in
            if hasCache == false {
                // 这些资源不全在本地
                request.beginAccessingResources { error in
                    if let _ = error {
                        // 下载失败
                    }
                    else {
                        // 下载成功
                    }
                }
            }
            else {
                // 这些资源已经在本地了
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 为皮肤系统 AppearanceStyle 拓展 Odr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;AppearanceStyle&lt;/code&gt; 是真个换肤系统的核心，主体是一个 &lt;code&gt;枚举&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里让原先的皮肤都匹配一下自己的标签名就好了。&lt;/li&gt;
&lt;li&gt;然后添加一个 &lt;code&gt;reloadOdr&lt;/code&gt; 方法来更新当前两个皮肤的 &lt;code&gt;ODR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;extension AppearanceStyle: OdrTagProtocol {
    static func reloadOdr() {
        OdrTool.requestODR(of: currentLightStyle, currentDarkStyle)
    }
    
    var tagName: String {
        switch self {
        case .Charge, .ChargeMain:
            return &quot;Charge&quot;
        case .ChargeLight:
            return &quot;ChargeLight&quot;
        case .Eternal, .Hachimitsu:
            return &quot;Eternal&quot;
        case .Spark:
            return &quot;Sakura&quot;
        case .SparkMain:
            return &quot;Spark&quot;
        case .NYXL:
            return &quot;NYXL&quot;
        case .Justice, .Dream, .Phoenix:
            return &quot;Justice&quot;
        case .Kiwi:
            return &quot;Kiwi&quot;
        case .Banshee:
            return &quot;Banshee&quot;
        case .Punk:
            return &quot;Punk&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.3 在合适节点更新Odr资源&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;节点一：应用启动&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AppearanceStyle.reloadOdr()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;节点二：设置皮肤&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static func reloadApp() {
    guard let tabVC = UIStoryboard(name: &quot;Home&quot;, bundle: nil).instantiateInitialViewController() else {
        return
    }
    UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController = tabVC
    reloadOdr()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.4 运行项目，检查效果&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Xcode&lt;/code&gt; 中显示我设置的两个皮肤的资源已经下载完成了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7b74cf99cbea75b8.webp&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是打开 App，发现还是空白？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/29d87f35e7f6115b.webp&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.5 解决问题&lt;/h3&gt;
&lt;p&gt;通过之前的了解，如果 ODR 资源没有被任何 &lt;code&gt;NSBundleResourceRequest&lt;/code&gt; 使用的话就会被清理。而上面的封装并没有持有 &lt;code&gt;NSBundleResourceRequest&lt;/code&gt;。对代码进行一下调整。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;OdrTool&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct OdrTool {
    static func requestODR(of tags: OdrTagProtocol..., in bundle: Bundle = Bundle.main) -&amp;gt; NSBundleResourceRequest {
        ...
        let request = NSBundleResourceRequest(tags: tagsSet, bundle: bundle)
        ...
        // 把 NSBundleResourceRequest 抛出
        return request
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;AppearanceStyle&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extension AppearanceStyle: OdrTagProtocol {
    // 添加一个静态变量保存
    static var odrRequest: NSBundleResourceRequest?
    
    static func reloadOdr() {
        odrRequest = OdrTool.requestODR(of: currentLightStyle, currentDarkStyle)
    }
    
    var tagName: String {
        switch self {
            ...
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.6 检查修改后的效果&lt;/h3&gt;
&lt;p&gt;完美～ 又测了下切换皮肤的功能也是没问题的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c12e30d1dc1fa34b.webp&quot; alt=&quot;14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;再看 &lt;code&gt;Xcode&lt;/code&gt; 发现，标签的状态不一样了，两个正在使用中的标签成了 &lt;code&gt;In use&lt;/code&gt; 正在使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f5944c3eb73868a2.webp&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6.7 使用资源的地方需要改动么？&lt;/h3&gt;
&lt;p&gt;不用&lt;/p&gt;
&lt;h2&gt;七、 问题&lt;/h2&gt;
&lt;h3&gt;7.1 覆盖安装的问题&lt;/h3&gt;
&lt;p&gt;我遇到了这样的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设备安装了线上发布的版本（ODR优化过的），运行展示都没问题&lt;/li&gt;
&lt;li&gt;准备进行下个版本的开发，直接运行，覆盖正式包，无法展示资源&lt;/li&gt;
&lt;li&gt;尝试一：猜测和版本号有关，修改版本号，依旧无效&lt;/li&gt;
&lt;li&gt;尝试二：删除旧包再运行，有效&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;上面的问题均可以 100% 复现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;体验下来还是很顺滑的，感觉接入成本不高。个人项目自己比较熟，而且换肤体系封的比较好😜也为接入 ODR 带来了很大便利。而且我单个皮肤的资源自由 &lt;code&gt;2M&lt;/code&gt; 左右下载也挺快。如果是一些较大的资源加载可能就不能像我处理的这么简单了。而且 &lt;code&gt;NSBundleResourceRequest&lt;/code&gt; 还有很多设置我没用，想深入了解的可以看下官方文档，这里我就抛个砖🧱。&lt;/p&gt;
&lt;p&gt;而且任何优化方案的选择还是要结合实际项目来，能一股脑照搬的方案是不存在的。苹果的这个 &lt;code&gt;ODR&lt;/code&gt; 挺好的，却感觉一直没有进入到广大 &lt;code&gt;iOSer&lt;/code&gt; 的视野中，感觉挺可惜，如果感觉合适你的项目真的可以用起来哦～&lt;/p&gt;
&lt;h2&gt;👋 Bye～&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wechat: RyukieW&lt;/li&gt;
&lt;li&gt;📦 &lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/&quot;&gt;技术文章归档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href=&quot;https://github.com/RyukieSama&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;我的个人项目&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;游戏&lt;/td&gt;
&lt;td&gt;财务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/developer/rongqing-wang/id1264542103&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;Elic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;Umemi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/ios-01-apple-odr-4af20c653337ac4d/&quot;&gt;Apple官方资源瘦身方案ODR（一）：初见&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/devbbdc5ce4f&quot;&gt;What is app thinning? (iOS, tvOS, watchOS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/index.html#//apple_ref/doc/uid/TP40015083-CH2-SW1&quot;&gt;On-Demand Resources Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Apple官方资源瘦身方案ODR（一）：初见</title><link>https://blog.lab-lawliet.app/posts/ios-01-apple-odr-4af20c653337ac4d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-01-apple-odr-4af20c653337ac4d/</guid><description>| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 | |:----:|:----:|:----:| |类型|游戏|财务| |AppStore|Elic|Umemi|</description><pubDate>Mon, 02 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Hi 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wechat: RyukieW&lt;/li&gt;
&lt;li&gt;📦 &lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/&quot;&gt;技术文章归档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href=&quot;https://github.com/RyukieSama&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;我的个人项目&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;游戏&lt;/td&gt;
&lt;td&gt;财务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/developer/rongqing-wang/id1264542103&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;Elic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;Umemi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;应用瘦身是个老生常谈的话题。苹果其实对于应用瘦身也有着自己的一些说明。本文就将结合官方文档进行阐述。&lt;/p&gt;
&lt;h2&gt;一、 什么是应用瘦身？&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;iOS&lt;/code&gt;, &lt;code&gt;tvOS&lt;/code&gt;, &lt;code&gt;watchOS&lt;/code&gt; 上 &lt;code&gt;AppStore&lt;/code&gt; 和操作系统根据用户设备和操作系统的性能对App进行裁剪优化。这个优化的过程就是 &lt;code&gt;应用瘦身&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你可以用最少的空间创建功能齐全持续更新的应用&lt;/li&gt;
&lt;li&gt;更快的下载速度&lt;/li&gt;
&lt;li&gt;更多的空间安装其他App&lt;/li&gt;
&lt;li&gt;带来更好的用户体验&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 剪切 Slicing (iOS, tvOS)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Slicing&lt;/code&gt; 是针对不同设备不同系统创建部署变体应用包的过程。每个变体只包含当前设备和操作系统所需要的资源和可执行文件。你只需要打包上传完整版本的应用到 &lt;code&gt;AppConnect&lt;/code&gt;。&lt;code&gt;AppStore&lt;/code&gt; 将会根据不同的设备及操作系统版本创建分发不同的变体。&lt;/p&gt;
&lt;p&gt;用 &lt;code&gt;asset&lt;/code&gt; 储存图片资源以便 &lt;code&gt;AppStore&lt;/code&gt; 为不同的变体选择资源加载。当用户安装应用时，将会下载安装一个针对其设备系统版本的变体。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Xcode&lt;/code&gt; 将会在本地 &lt;code&gt;build&lt;/code&gt; 和 &lt;code&gt;run&lt;/code&gt; 的时候模拟进行 &lt;code&gt;Slicing&lt;/code&gt;。当你 &lt;code&gt;archive&lt;/code&gt; 的时候 &lt;code&gt;Xcode&lt;/code&gt; 将会包含所有构建版本并允许你导出变体包。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;下图很好的演示了这个过程&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/969ffe8ecb799702.webp&quot; alt=&quot;Xcode&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;思考&lt;/h3&gt;
&lt;p&gt;这里提到 &lt;code&gt;asset&lt;/code&gt; 会根据架构不同进行资源加载，所以我们平时不要把资源直接放到 &lt;code&gt;Bundle&lt;/code&gt; 中，而跳过了这个优化的过程。&lt;/p&gt;
&lt;h2&gt;三、 Bitcode&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Bitcode&lt;/code&gt; 是编译器的一层间接。你上传到 &lt;code&gt;AppConnect&lt;/code&gt; 的打开了&lt;code&gt;Bitcode&lt;/code&gt;的 &lt;code&gt;App&lt;/code&gt; 将会在 &lt;code&gt;AppStore&lt;/code&gt; 被 &lt;code&gt;编译&lt;/code&gt; 和 &lt;code&gt;链接&lt;/code&gt;。包含了 &lt;code&gt;Bitcode&lt;/code&gt; 将会使&lt;code&gt;Apple&lt;/code&gt;  可以在未来再优化你的应用包而不用再重新上传。&lt;/p&gt;
&lt;p&gt;对于 &lt;code&gt;iOS&lt;/code&gt; 应用 &lt;code&gt;Bitcode&lt;/code&gt; 是默认非必须选的。&lt;code&gt;watchOS&lt;/code&gt; 和 &lt;code&gt;tvOS&lt;/code&gt; 是必选的。如果想要支持 &lt;code&gt;Bitcode&lt;/code&gt; 那么你所有用到的库都要支持 &lt;code&gt;Bitcode&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;符号表&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Xcode&lt;/code&gt; 默认会隐藏你的应用的符号表，所以对于 &lt;code&gt;Apple&lt;/code&gt; 它是不可见的。当你上传你的 &lt;code&gt;App&lt;/code&gt; 到 &lt;code&gt;AppConnect&lt;/code&gt; 的时候你&lt;code&gt;可以选择包含符号表``。包含符号表会使Apple&lt;/code&gt; 为你提供崩溃日志。&lt;/p&gt;
&lt;p&gt;如果你想要自己收集崩溃日志，那么你可以不上传符号表。你可以在发布应用后下载 &lt;code&gt;Bitcode&lt;/code&gt; 编译后的符号表。&lt;/p&gt;
&lt;h2&gt;四、 ODR: On-Demand Resources (iOS, tvOS)&lt;/h2&gt;
&lt;p&gt;这些资源主要是图片和声音。&lt;code&gt;AppStore&lt;/code&gt; 将其保存管理在苹果的服务器。&lt;code&gt;AppStore&lt;/code&gt; 将会按需使用资源优化变体包。后面将以 &lt;code&gt;ODR&lt;/code&gt; 进行简称。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ODR&lt;/code&gt; 提供了更好的用户体验：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用体积更小，下载更快，提升初次启动速度&lt;/li&gt;
&lt;li&gt;资源会在后台下载&lt;/li&gt;
&lt;li&gt;操作系统将会在磁盘资源不够的时候清理 &lt;code&gt;ODR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d7ea3d8ef45c6d3e.webp&quot; alt=&quot;on_demand_resources&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;想要支持&lt;code&gt;ODR&lt;/code&gt;，可以阅读文档：
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/index.html#//apple_ref/doc/uid/TP40015083&quot;&gt;On-Demand Resources Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/nsbundleresourcerequest&quot;&gt;NSBundleResourceRequest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.1 使用须知&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;App&lt;/code&gt; 负责请求这些资源，操作系统负责 &lt;code&gt;下载&lt;/code&gt; 和 &lt;code&gt;存储&lt;/code&gt;。应用使用资源，然后请求。下载后，资源可能会通过多个启动周期保留在设备上，使访问速度更快。&lt;/p&gt;
&lt;p&gt;资源可以是 &lt;code&gt;Bundle&lt;/code&gt; 支持的任何类型的资源，但 &lt;code&gt;可执行程序&lt;/code&gt; 除外。&lt;code&gt;Table 1-1&lt;/code&gt; 显示支持按需资源类型，并指示这些类型是否以 &lt;code&gt;文件&lt;/code&gt; 或 &lt;code&gt;Asset&lt;/code&gt; 包含在项目中。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Table 1-1:&lt;/strong&gt;&lt;/em&gt; On-demand resource types&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Asset catalog&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data file&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenGL shader&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpriteKit particle&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpriteKit scene&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpriteKit texture atlas&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apple TV Image Stack&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;数据文件可以包含任何类型的数据，但可执行的 &lt;code&gt;Swift&lt;/code&gt;、 &lt;code&gt;Objective-C&lt;/code&gt;、 &lt;code&gt;C&lt;/code&gt; 或 &lt;code&gt;C++&lt;/code&gt; 代码除外。&lt;code&gt;脚本语言生成的文件&lt;/code&gt; 可以是按需加载的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.2 ODR 的好处&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;较小的应用程序大小。
&lt;ul&gt;
&lt;li&gt;用户下载的应用捆绑包的大小较小，因此下载速度更快，设备上的存储空间更大。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用资源懒加载。
&lt;ul&gt;
&lt;li&gt;该应用程序的资源仅在某些状态下使用。当应用可能进入适当的状态时，需要资源。&lt;/li&gt;
&lt;li&gt;例如，在具有多个级别的游戏中，用户只需要与当前和下一个级别相关的资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;远程存储很少使用的资源。
&lt;ul&gt;
&lt;li&gt;该资源很少使用。根据需要要求提供资源。&lt;/li&gt;
&lt;li&gt;例如，应用教程通常在第一次打开应用后显示一次，可能永远不会再次使用。该应用程序要求在第一次启动时请求教程，然后仅在需要或添加新功能时再请求教程。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用内购买资源的远程存储。
&lt;ul&gt;
&lt;li&gt;如果应用程序提供应用内购买，包括其他资源。应用启动后会请求购买模块的资源。&lt;/li&gt;
&lt;li&gt;例如，用户在键盘应用中购买表情包。应用程序在启动完成后请求该包。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、 ODR 标签的工作原理&lt;/h2&gt;
&lt;p&gt;通过分配 &lt;code&gt;一个或多个标签&lt;/code&gt; 来识别开发过程中的按需加载资源。&lt;code&gt;标签&lt;/code&gt; 是您创建的字符串标识符。您可以使用 &lt;code&gt;标签的名称&lt;/code&gt; 来识别应用中如何使用包含的资源。
&lt;em&gt;&lt;strong&gt;例如&lt;/strong&gt;&lt;/em&gt; 在游戏中，使用标记 &lt;code&gt;level-5&lt;/code&gt; 来处理与 &lt;code&gt;level-5&lt;/code&gt; 相关的每个资源。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在运行时
&lt;ul&gt;
&lt;li&gt;通过指定一组标签请求访问远程资源。操作系统下载标记为这些标签的资源，然后存储，直到应用使用完为止。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当操作系统需要更多存储时
&lt;ul&gt;
&lt;li&gt;它会清除不被使用的一个或多个标签相关的本地缓存。&lt;/li&gt;
&lt;li&gt;标记的资源集可能会在设备上保留一段时间，然后才被清除。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.1 游戏举例&lt;/h3&gt;
&lt;p&gt;继续使用游戏示例，在分为多个级别的游戏中，用户只需要与用户正在玩的水平和下一个可能级别相关的资源。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;下图显示一个应用，其中包含所有级别的所有资源。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b7ed848663ac1fe8.webp&quot; alt=&quot;adventure_no_odr_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过为不同级别以及不需要包含在应用中的其他共享资源创建标签，可以缩小应用包的大小。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;下图显示了一个更小的应用，其标记的资源集托管在 &lt;code&gt;App Store&lt;/code&gt; 上。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b2dd43b31279e49d.webp&quot; alt=&quot;adventure_odr_2x&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.2 额外设置&lt;/h3&gt;
&lt;p&gt;您可以指定&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;应用从应用商店安装时必须加载的标签&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;为资源请求设置加载优先级&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;跟踪下载进度&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;以及&lt;code&gt;为不再使用的下载标签设置保存优先级&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;六、 ODR 的生命周期&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;用户首次启动该应用程序时，设备上唯一的按需资源是用于&lt;code&gt;预匹配的资源&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;当用户与应用交互时，应用请求代表一组资源的标签，使用与标签相关的资源，然后通知操作系统它已使用完标签。&lt;/li&gt;
&lt;li&gt;操作系统会在之后的某个合适的时间，清除一个或多个标签。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;应用按标签请求，而不是特定的资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;6.1 共享 ODR&lt;/h3&gt;
&lt;p&gt;当您使用 &lt;code&gt;ODR&lt;/code&gt; 开发应用时，您可能会注意到请求一个标签会同时下载的其他标签相关的资源。这是因为操作系统与用于下载共享资源的优化资产包配合使用。一个标签可能存在多个 &lt;code&gt;Asset&lt;/code&gt; 中。构建应用时，&lt;code&gt;Asset&lt;/code&gt; 由 &lt;code&gt;Xcode&lt;/code&gt; 生成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个例子是一个游戏，其中&lt;code&gt;级别1&lt;/code&gt;和&lt;code&gt;级别2&lt;/code&gt;标签共享一些资源。&lt;code&gt;Xcode&lt;/code&gt; 生成三个 &lt;code&gt;Asset&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;Level 1. 只有 &lt;code&gt;Level1&lt;/code&gt; 标签&lt;/li&gt;
&lt;li&gt;Level 2. 只有 &lt;code&gt;Level2&lt;/code&gt; 标签&lt;/li&gt;
&lt;li&gt;Level 1 + Level 2. 同时有 &lt;code&gt;Level1&lt;/code&gt; 标签和 &lt;code&gt;Level2&lt;/code&gt; 标签&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6.2 流程&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ec860377aaa0a97b.webp&quot; alt=&quot;ODR_flow_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;1.应用程序从操作系统请求标签。操作系统将所要求的标签转换为包含相关资源的一组资产包。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在下图中应用请求与 &lt;code&gt;Level1&lt;/code&gt; 和 &lt;code&gt;Forest&lt;/code&gt; 标签相关的资源。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d78960b4cf7aba56.webp&quot; alt=&quot;step2_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;2.标签的 &lt;code&gt;Asset&lt;/code&gt; 位于本地存储中，则生命周期移动到&lt;code&gt;第6步&lt;/code&gt;。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;3.标签的一个或多个 &lt;code&gt;Asset&lt;/code&gt; 托管在 &lt;code&gt;App Store&lt;/code&gt; 上，原因要么是它是应用的第一次发布，要么是之前加载在设备上的资源被清除（参见下面的 &lt;code&gt;第9步&lt;/code&gt;）。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在下图中，所有 &lt;code&gt;ODR&lt;/code&gt; 都在应用商店中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b7fedf1a05b68490.webp&quot; alt=&quot;step1_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;4.操作系统开始下载与尚未存储在本地存储中的相关的资源。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;5.与所要求的标签相关的 &lt;code&gt;Asset&lt;/code&gt; 资源完成下载到设备。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在下图中，与 &lt;code&gt;Level1&lt;/code&gt; 和 &lt;code&gt;Forest&lt;/code&gt; 标签相关的资源将下载到了设备中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2ad1cbb111091c0e.webp&quot; alt=&quot;step3_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;6.如果成功下载了与所要求的标签相关的资源，或者如果资源已在设备存储中，则操作会增加 &lt;code&gt;Asset&lt;/code&gt; 的引用计数，并通知应用所请求的标签可用。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在下图中，应用程序被告知与 &lt;code&gt;Level1&lt;/code&gt; 和 &lt;code&gt;Forest&lt;/code&gt; 标签相关的资源是可用的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/33e2044dcd237192.webp&quot; alt=&quot;step4_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;标签可用后，应用将使用与这些标签相关的资源。&lt;code&gt;ODR&lt;/code&gt; 的访问方式与与下载的应用捆绑的资源相同。下图显示下载的资源作为应用程序的虚拟成员。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/cddb5367994f659d.webp&quot; alt=&quot;step5_2x&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;7.应用程序通知操作系统，它已经完成了对请求标签的使用。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;8.操作系统在本地存储中释放标签。这是通过减少与标签相关的 &lt;code&gt;Asset&lt;/code&gt; 的引用计数来完成的。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;对设备上已使用的标签进行另一个请求将移动到 &lt;code&gt;第1步&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;9.操作系统从本地存储中清除与 &lt;code&gt;Asset&lt;/code&gt; 相关的缓存资源。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当任何请求不再关联标签时，&lt;code&gt;Asset&lt;/code&gt; 就可以进行清除。与标签相关的资源可能会在设备上保留一段时间，然后才清除，包括跨应用（AppGroup）。&lt;/li&gt;
&lt;li&gt;对标签提出另一个请求会将生命周期移回 &lt;code&gt;第1步&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;可以通过为标签设置保存优先级来影响清除顺序。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;七、 总结&lt;/h2&gt;
&lt;p&gt;本文只是初步的对 &lt;code&gt;ODR&lt;/code&gt; 有了一个整体初步的了解。很多细节还并没有涉及比如项目怎么配置，代码怎么写。我将在后续文章中继续深入探索。&lt;/p&gt;
&lt;p&gt;欢迎点赞关注收藏～&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://help.apple.com/xcode/mac/current/#/devbbdc5ce4f&quot;&gt;What is app thinning? (iOS, tvOS, watchOS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>在个人站点配置app-ads</title><link>https://blog.lab-lawliet.app/posts/article-02-app-ads-7865387b19837642/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-02-app-ads-7865387b19837642/</guid><description>| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 | |:----:|:----:|:----:| |类型|游戏|财务| |AppStore|Elic|Umemi|</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Hi 👋&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Wechat: RyukieW&lt;/li&gt;
&lt;li&gt;📦 &lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/&quot;&gt;技术文章归档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;🐙 &lt;a href=&quot;https://github.com/RyukieSama&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;我的个人项目&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;游戏&lt;/td&gt;
&lt;td&gt;财务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/developer/rongqing-wang/id1264542103&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;Elic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;Umemi&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/054534d9240c3ba8.webp&quot; alt=&quot;AdMob警告&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0223d4abda66879b.webp&quot; alt=&quot;AdMob警告&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;扫雷Elic 无尽天梯&lt;/a&gt;的广告接的是 &lt;code&gt;Google&lt;/code&gt; 的 &lt;code&gt;AdMob&lt;/code&gt;。国内的没有公司主体基本没法儿接。后台也一直警告我要添加 &lt;code&gt;app-ads.txt&lt;/code&gt; 但是没有个人站点就一直没加。广告量也明显感觉和早期差得很远。&lt;/p&gt;
&lt;p&gt;但是前段时间买了域名用 &lt;code&gt;Wordpress&lt;/code&gt; 简单建了个站。就准备把这个 &lt;code&gt;app-ads.txt&lt;/code&gt; 加下。&lt;/p&gt;
&lt;h2&gt;一、 什么是 app-ads.txt&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;授权应用卖方（或 app-ads.txt）&lt;/code&gt;是一项 &lt;code&gt;IAB Tech Lab&lt;/code&gt; 计划，有助于确保只通过您认定的授权渠道销售您的应用广告资源。创建 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件后，您可以更好地掌控哪些卖方有权销售您应用上的广告空间，并可防止向广告客户展示仿冒广告资源。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app-ads.txt&lt;/code&gt; 文件是公开的，可供&lt;code&gt;广告交易平台&lt;/code&gt;、&lt;code&gt;供应方平台 (SSP) &lt;/code&gt;以及&lt;code&gt;其他买方和第三方供应商&lt;/code&gt;抓取。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;授权应用卖方 (app-ads.txt) &lt;/code&gt;是&lt;code&gt;授权数字卖方 (ads.txt) 计划&lt;/code&gt;的延伸和扩展，后者最初设计用于保护网络广告资源。&lt;code&gt;app-ads.txt&lt;/code&gt; 在 &lt;code&gt;ads.txt&lt;/code&gt; 的基础上扩展了兼容性，使之支持移动应用中展示的广告。&lt;/p&gt;
&lt;p&gt;为避免损失大量广告收入，您需要实施 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://support.google.com/admob/answer/9787936?hl=zh-Hans&amp;amp;ref_topic=9675856&quot;&gt;app-ads.txt 简介&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 如何为应用设置 app-ads.txt&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;如果您还没有创建 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件，请使用 &lt;code&gt;IAB Tech Lab&lt;/code&gt; 提供的规范进行创建。&lt;/li&gt;
&lt;li&gt;将后台提供的代码段复制并粘贴到您的 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件中&lt;/li&gt;
&lt;li&gt;在开发者网站的根目录（例如：&lt;code&gt;sampledomain.com/app-ads.txt&lt;/code&gt;）上发布您的 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件。请确保您输入的域名与 &lt;code&gt;Google Play&lt;/code&gt; 或 &lt;code&gt;App Store&lt;/code&gt; 中列出的域名完全一致。&lt;/li&gt;
&lt;li&gt;请等待至少 &lt;code&gt;24&lt;/code&gt; 小时，以便 &lt;code&gt;AdMob&lt;/code&gt; 抓取并验证您的 &lt;code&gt;app-ads.txt&lt;/code&gt; 文件。&lt;/li&gt;
&lt;li&gt;返回 &lt;code&gt;AdMob&lt;/code&gt; 检查您的 &lt;code&gt;app-ads.txt&lt;/code&gt; 状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f3623341d27f05b8.webp&quot; alt=&quot;设置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2b95322a09eb7767.webp&quot; alt=&quot;后台&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、 www域名&lt;/h2&gt;
&lt;p&gt;我的域名是 &lt;code&gt;www.xxx.com&lt;/code&gt;，通过 &lt;code&gt;www.xxx.com/app-ads.txt&lt;/code&gt;，是可以正常获取到数据的。&lt;/p&gt;
&lt;p&gt;但是 &lt;code&gt;AdMob&lt;/code&gt;，是通过 &lt;code&gt;xxx.com/app-ads.txt&lt;/code&gt; 来抓取的。而 &lt;code&gt;xxx.com&lt;/code&gt; 是无法解析我的域名的。&lt;/p&gt;
&lt;h3&gt;3.1 DNS配置无www域名解析&lt;/h3&gt;
&lt;p&gt;我是在 &lt;code&gt;GoDaddy&lt;/code&gt; 上买的域名，&lt;code&gt;DNS&lt;/code&gt; 也是在上面配的但是它不支持 &lt;code&gt;无www&lt;/code&gt; 域名的解析。这里&lt;a href=&quot;https://blog.csdn.net/qq_19069509/article/details/105467272&quot;&gt;参考&lt;/a&gt;，将 &lt;code&gt;DNS&lt;/code&gt; 解析迁移到了 &lt;code&gt;DNSPod&lt;/code&gt; 上。完成了 &lt;code&gt;xxx.com&lt;/code&gt; 的解析。&lt;/p&gt;
&lt;h3&gt;3.2 域名端口映射&lt;/h3&gt;
&lt;p&gt;完成了 &lt;code&gt;xxx.com&lt;/code&gt; 的解析后，访问 &lt;code&gt;xxx.com/app-ads.txt&lt;/code&gt; 时，直接 &lt;code&gt;404&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;并且任何子页面也都访问不了。&lt;/p&gt;
&lt;p&gt;这里就需要到网站后台添加映射了，和 &lt;code&gt;www&lt;/code&gt; 的域名指向同一个就行了：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0cd787ce1478a091.webp&quot; alt=&quot;域名端口映射&quot; /&gt;&lt;/p&gt;
&lt;p&gt;再尝试 &lt;code&gt;xxx.com/app-ads.txt&lt;/code&gt;，就可以正常抓取到了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/41433776692ebb44.webp&quot; alt=&quot;Success&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>深入探究属性</title><link>https://blog.lab-lawliet.app/posts/ios-07-4432d5fe0e833d8f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-07-4432d5fe0e833d8f/</guid><description>我们知道，属性会生成带下划线的成员变量和对应的set、get方法。我们将上面用到的RYModel，编译成C++看下。</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;@interface RYModel : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) float height;
@property (nonatomic, strong) RYModel *father;
@property (nonatomic, assign) BOOL isBoy;

- (void)dosomething;

- (void)dosomethingWith:(NSString *)title;

+ (void)classDoSomething;

- (NSString *)sayMyName;

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一、成员变量和属性&lt;/h2&gt;
&lt;p&gt;我们知道，属性会生成带下划线的成员变量和对应的set、get方法。我们将上面用到的RYModel，编译成C++看下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static NSString * _I_RYModel_name(RYModel * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RYModel$_name)); }
extern &quot;C&quot; __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_RYModel_setName_(RYModel * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct RYModel, _name), (id)name, 0, 1); }

static NSInteger _I_RYModel_age(RYModel * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_RYModel$_age)); }
static void _I_RYModel_setAge_(RYModel * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_RYModel$_age)) = age; }

static float _I_RYModel_height(RYModel * self, SEL _cmd) { return (*(float *)((char *)self + OBJC_IVAR_$_RYModel$_height)); }
static void _I_RYModel_setHeight_(RYModel * self, SEL _cmd, float height) { (*(float *)((char *)self + OBJC_IVAR_$_RYModel$_height)) = height; }

static RYModel * _I_RYModel_father(RYModel * self, SEL _cmd) { return (*(RYModel **)((char *)self + OBJC_IVAR_$_RYModel$_father)); }
static void _I_RYModel_setFather_(RYModel * self, SEL _cmd, RYModel *father) { (*(RYModel **)((char *)self + OBJC_IVAR_$_RYModel$_father)) = father; }

static BOOL _I_RYModel_isBoy(RYModel * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_RYModel$_isBoy)); }
static void _I_RYModel_setIsBoy_(RYModel * self, SEL _cmd, BOOL isBoy) { (*(BOOL *)((char *)self + OBJC_IVAR_$_RYModel$_isBoy)) = isBoy; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.1 copy 修饰符&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt; 属性用的是 &lt;code&gt;copy&lt;/code&gt; 来修饰的。很容易发现它的set方法明显有不同。&lt;/p&gt;
&lt;p&gt;他用的是 &lt;code&gt;objc_setProperty&lt;/code&gt; ，而其他的都是直接通过内存平移对成员变量进行赋值操作。&lt;/p&gt;
&lt;h2&gt;二、objc_setProperty&lt;/h2&gt;
&lt;p&gt;我们看下 &lt;code&gt;objc_setProperty&lt;/code&gt; 的源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// 对象 sel 偏移量 值 原子性 是否copy
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    // 是否是 Copy
    bool copy = (shouldCopy &amp;amp;&amp;amp; shouldCopy != MUTABLE_COPY);
    // 是否是 可变的Copy
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现他实际调用了 &lt;code&gt;reallySetProperty&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;2.1 reallySetProperty&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) { 
        // 修改 ISA
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    // 定位（首地址+偏移量）
    id *slot = (id*) ((char*)self + offset);

    // Copy相关操作
    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;// 相同的话不做操作
        // 引用计数+1
        newValue = objc_retain(newValue); 
    }

    // 原子性相关
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
      // 加锁
        spinlock_t&amp;amp; slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    // release 旧值 引用计数 -1
    objc_release(oldValue);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 Set 流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/43e88efe4b97a3d9.webp&quot; alt=&quot;Set方法&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、Type Encodings 类型编码&lt;/h2&gt;
&lt;p&gt;我们会看到这样一些 &lt;code&gt;objc_selector&lt;/code&gt;，其中有 &lt;code&gt;v24@0:8@16&lt;/code&gt; 类似这样的一些看不懂的字符串。他们叫类型编码&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;具体可以对照&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html&quot;&gt;官方文档&lt;/a&gt;这里就不展开聊了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以 &lt;code&gt;setName&lt;/code&gt; 为例分析一次：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;v&lt;/th&gt;
&lt;th&gt;24&lt;/th&gt;
&lt;th&gt;@&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;:&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;@&lt;/th&gt;
&lt;th&gt;16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;返回值类型-void&lt;/td&gt;
&lt;td&gt;占用大小-24&lt;/td&gt;
&lt;td&gt;参数-id&lt;/td&gt;
&lt;td&gt;从偏移量0开始&lt;/td&gt;
&lt;td&gt;SEL&lt;/td&gt;
&lt;td&gt;从偏移量8开始&lt;/td&gt;
&lt;td&gt;参数-id&lt;/td&gt;
&lt;td&gt;从偏移量16开始&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[21];
} _OBJC_$_INSTANCE_METHODS_RYModel __attribute__ ((used, section (&quot;__DATA,__objc_const&quot;))) = {
	sizeof(_objc_method),
	21,
	{{(struct objc_selector *)&quot;ry_protocolDoSomething&quot;, &quot;v16@0:8&quot;, (void *)_I_RYModel_ry_protocolDoSomething},
	{(struct objc_selector *)&quot;name&quot;, &quot;@16@0:8&quot;, (void *)_I_RYModel_name},
	{(struct objc_selector *)&quot;setName:&quot;, &quot;v24@0:8@16&quot;, (void *)_I_RYModel_setName_},
	{(struct objc_selector *)&quot;age&quot;, &quot;q16@0:8&quot;, (void *)_I_RYModel_age},
	{(struct objc_selector *)&quot;setAge:&quot;, &quot;v24@0:8q16&quot;, (void *)_I_RYModel_setAge_},
	{(struct objc_selector *)&quot;height&quot;, &quot;f16@0:8&quot;, (void *)_I_RYModel_height},
	{(struct objc_selector *)&quot;setHeight:&quot;, &quot;v20@0:8f16&quot;, (void *)_I_RYModel_setHeight_},
	{(struct objc_selector *)&quot;father&quot;, &quot;@16@0:8&quot;, (void *)_I_RYModel_father},
	{(struct objc_selector *)&quot;setFather:&quot;, &quot;v24@0:8@16&quot;, (void *)_I_RYModel_setFather_},
	{(struct objc_selector *)&quot;isBoy&quot;, &quot;c16@0:8&quot;, (void *)_I_RYModel_isBoy},
	{(struct objc_selector *)&quot;setIsBoy:&quot;, &quot;v20@0:8c16&quot;, (void *)_I_RYModel_setIsBoy_},
    ...
	{(struct objc_selector *)&quot;isBoy&quot;, &quot;c16@0:8&quot;, (void *)_I_RYModel_isBoy},
	{(struct objc_selector *)&quot;setIsBoy:&quot;, &quot;v20@0:8c16&quot;, (void *)_I_RYModel_setIsBoy_}}
};

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Class-cache_t</title><link>https://blog.lab-lawliet.app/posts/ios-10-class-cache-t-551f1a8b6b2847f3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-10-class-cache-t-551f1a8b6b2847f3/</guid><description>之前进行类的结构的分析的时候，我们还有一个重要的东西没有进行研究，就是 cache_t。</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Class之cache_t&lt;/h1&gt;
&lt;p&gt;之前进行类的结构的分析的时候，我们还有一个重要的东西没有进行研究，就是 &lt;code&gt;cache_t&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;一、cache_t 的基本结构&lt;/h2&gt;
&lt;p&gt;和之前一样，我们在源码环境下编译调试。&lt;/p&gt;
&lt;p&gt;回顾一下class的基础结构：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;ISA&lt;/th&gt;
&lt;th&gt;superClass&lt;/th&gt;
&lt;th&gt;cache&lt;/th&gt;
&lt;th&gt;bits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;说明&lt;/td&gt;
&lt;td&gt;ISA（结构体指针）&lt;/td&gt;
&lt;td&gt;父类（结构体指针）&lt;/td&gt;
&lt;td&gt;方法缓存&lt;/td&gt;
&lt;td&gt;类的具体信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;1.1 源码&lt;/h3&gt;
&lt;p&gt;其核心数据结构如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct cache_t {

// &amp;lt;&amp;lt;&amp;lt;&amp;lt; 主要数据结构
private:
    explicit_atomic&amp;lt;uintptr_t&amp;gt; _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic&amp;lt;mask_t&amp;gt;    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic&amp;lt;preopt_cache_t *&amp;gt; _originalPreoptCache;
    };
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;_bucketsAndMaybeMask
&lt;ul&gt;
&lt;li&gt;_bucketsAndMaybeMask is a buckets_t pointer&lt;/li&gt;
&lt;li&gt;它是一个 &lt;code&gt;buckets_t&lt;/code&gt; 类型的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_maybeMask
&lt;ul&gt;
&lt;li&gt;_maybeMask is the buckets mask&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_occupied&lt;/li&gt;
&lt;li&gt;_originalPreoptCache&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 LLDB调试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x RYModel.class
(Class) $10 = 0x00000001000087c8 RYModel

// 平移取 cache
(lldb) p (cache_t *)(0x00000001000087c8 + 0x10)
(cache_t *) $11 = 0x00000001000087d8
(lldb) p $11
(cache_t *) $11 = 0x00000001000087d8

// 取出 cache 内容
(lldb) p *$11
(cache_t) $12 = {
  _bucketsAndMaybeMask = {
    std::__1::atomic&amp;lt;unsigned long&amp;gt; = {
      Value = 4314911728
    }
  }
   = {
     = {
      _maybeMask = {
        std::__1::atomic&amp;lt;unsigned int&amp;gt; = {
          Value = 3
        }
      }
      _flags = 32820
      _occupied = 1
    }
    _originalPreoptCache = {
      std::__1::atomic&amp;lt;preopt_cache_t *&amp;gt; = {
        Value = 0x0001803400000003
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;我们无法直接输出相关成员内容了，为了继续输出相关属性我们继续看源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;相关get方法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public:
    // The following four fields are public for objcdt&apos;s use only.
    // objcdt reaches into fields while the process is suspended
    // hence doesn&apos;t care for locks and pesky little details like this
    // and can safely use these.
    unsigned capacity() const;// 容量
    struct bucket_t *buckets() const;
    Class cls() const;

#if CONFIG_USE_PREOPT_CACHES
    const preopt_cache_t *preopt_cache() const;
#endif

    mask_t occupied() const;// 缓存数量
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 &lt;code&gt;buckets()&lt;/code&gt; 方法继续输出调试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $12.buckets()[0]
(bucket_t) $13 = {
  _sel = {
    std::__1::atomic&amp;lt;objc_selector *&amp;gt; = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic&amp;lt;unsigned long&amp;gt; = {
      Value = 0
    }
  }
}
(lldb) p $12.buckets()[1]
(bucket_t) $14 = {
  _sel = {
    std::__1::atomic&amp;lt;objc_selector *&amp;gt; = (null) {
      Value = nil
    }
  }
  _imp = {
    std::__1::atomic&amp;lt;unsigned long&amp;gt; = {
      Value = 0
    }
  }
}
(lldb) p $12.buckets()[2]
(bucket_t) $15 = {
  _sel = {
    std::__1::atomic&amp;lt;objc_selector *&amp;gt; = &quot;&quot; {
      Value = &quot;&quot;
    }
  }
  _imp = {
    std::__1::atomic&amp;lt;unsigned long&amp;gt; = {
      Value = 48856
    }
  }
}
(lldb) p $15.sel()
(SEL) $16 = &quot;dosomething&quot;

(lldb) p $15.imp(nil, RYModel.class)
(IMP) $17 = 0x0000000100003910 (KCObjcBuild`-[RYModel dosomething])
(lldb) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里你会发现用到了 &lt;code&gt;$12.buckets()[2]&lt;/code&gt; 的下标是2，0和1都是空的。因为这里 &lt;code&gt;buckets&lt;/code&gt; 不是数组，是哈希表的结构。&lt;/p&gt;
&lt;h3&gt;1.3、bucket_t的数据结构&lt;/h3&gt;
&lt;p&gt;包含了 &lt;code&gt;SEL&lt;/code&gt; 和 &lt;code&gt;IMP&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    explicit_atomic&amp;lt;uintptr_t&amp;gt; _imp;
    explicit_atomic&amp;lt;SEL&amp;gt; _sel;
#else
    explicit_atomic&amp;lt;SEL&amp;gt; _sel;
    explicit_atomic&amp;lt;uintptr_t&amp;gt; _imp;
#endif
...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、自己实现 cache_t 数据结构&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;使用LLDB进行调试感觉到太麻烦了，为了更方便的进行调试我们尝试对数据结构进行模仿。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;objc/runtime.h&amp;gt;

typedef uint32_t mask_t;  // x86_64 &amp;amp; arm64 asm are less efficient with 16-bits

struct lw_bucket_t {
    SEL _sel;
    IMP _imp;
};
struct lw_cache_t {
    struct lw_bucket_t *_bukets;
    mask_t    _maybeMask;
    uint16_t  _flags;
    uint16_t  _occupied;
};

struct lw_class_data_bits_t {
    uintptr_t bits;
};

// cache class
struct lw_objc_class {
    Class isa;
    Class superclass;
    struct lw_cache_t cache;
    struct lw_class_data_bits_t bits;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 调试代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RYModel *obj = [RYModel alloc];
[obj dosomethingA];
[obj dosomethingB];
struct lw_objc_class *lw_class = (__bridge struct lw_objc_class *)(RYModel.class);
NSLog(@&quot;缓存数量：%hu - 容量：%u&quot;,lw_class-&amp;gt;cache._occupied,lw_class-&amp;gt;cache._maybeMask);

for (mask_t i = 0; i&amp;lt;lw_class-&amp;gt;cache._maybeMask; i++) {
    struct lw_bucket_t bucket = lw_class-&amp;gt;cache._bukets[i];
    NSLog(@&quot;SEL:%@ - IMP:%pf&quot;,NSStringFromSelector(bucket._sel),bucket._imp);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-[RYModel dosomethingA]
-[RYModel dosomethingB]
缓存数量：2 - 容量：3
SEL:dosomethingA - IMP:0xb300f
SEL:dosomethingB - IMP:0xb330f
EL:(null) - IMP:0x0f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 扩容&lt;/h3&gt;
&lt;p&gt;我们调整一下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[obj dosomethingA];
[obj dosomethingB];
[obj dosomethingC];

输出：
2021-06-27 16:00:51.939958+0800 LWCacheT[73396:867351] -[RYModel dosomethingA]
2021-06-27 16:00:51.940352+0800 LWCacheT[73396:867351] -[RYModel dosomethingB]
2021-06-27 16:00:51.940441+0800 LWCacheT[73396:867351] -[RYModel dosomethingC]
2021-06-27 16:00:51.940602+0800 LWCacheT[73396:867351] 缓存数量：1 - 容量：7
2021-06-27 16:00:51.940668+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940742+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940780+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940814+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940900+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.941025+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.941124+0800 LWCacheT[73396:867351] SEL:dosomethingC - IMP:0xbcd8f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现这里进行了扩容。这里又是什么逻辑呢？&lt;/p&gt;
&lt;h2&gt;三、insert 与扩容&lt;/h2&gt;
&lt;p&gt;为了更好的理解扩容，我们先了解一下方法缓存是怎么插入的。&lt;/p&gt;
&lt;h3&gt;3.1 核心源码解读&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void cache_t::insert(SEL sel, IMP imp, id receiver)
{

    ...

    // Use the cache as-is if until we exceed our expected fill ratio.
    // 计数 + 1
    mask_t newOccupied = occupied() + 1;
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        // 判空，没有缓存创建缓存，根据架构不同，通过位运算初始化一个一定大小的容器
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied + CACHE_END_MARKER &amp;lt;= cache_fill_ratio(capacity))) {
        // Cache is less than 3/4 or 7/8 full. Use it as-is.
        // 未达到扩容临界点
    }
#if CACHE_ALLOW_FULL_UTILIZATION
    else if (capacity &amp;lt;= FULL_UTILIZATION_CACHE_SIZE &amp;amp;&amp;amp; newOccupied + CACHE_END_MARKER &amp;lt;= capacity) {
        // Allow 100% cache utilization for small buckets. Use it as-is.

        // Allow 100% cache utilization for smaller cache sizes. This has the same
        // advantages and disadvantages as the fill ratio. A very large percentage
        // of caches end up with very few entries and the worst case of collision
        // chains in small tables is relatively small.
        // NOTE: objc_msgSend properly handles a cache lookup with a full cache.

        // 
    }
#endif
    else {
      // 扩容操作
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity &amp;gt; MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    // 循环找到合适的下标
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            /// 插入逻辑
            b[i].set&amp;lt;Atomic, Encoded&amp;gt;(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    // 未找到合适的下标 crash
    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;a. INIT_CACHE_SIZE 初始化缓存&lt;/h4&gt;
&lt;p&gt;通过源码发现，这里是通过位运算进行的缓存空间大小初始化。&lt;/p&gt;
&lt;p&gt;如，当前我们的是 &lt;code&gt;1 &amp;lt;&amp;lt; 2 = 4&lt;/code&gt;。同时我们还能发现最大值为 &lt;code&gt;1 &amp;lt;&amp;lt; 16&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */
enum {
#if CACHE_END_MARKER || (__arm64__ &amp;amp;&amp;amp; !__LP64__)
    // When we have a cache end marker it fills a bucket slot, so having a
    // initial cache size of 2 buckets would not be efficient when one of the
    // slots is always filled with the end marker. So start with a cache size
    // 4 buckets.
    INIT_CACHE_SIZE_LOG2 = 2,
#else
    // Allow an initial bucket size of 2 buckets, since a large number of
    // classes, especially metaclasses, have very few imps, and we support
    // the ability to fill 100% of the cache before resizing.
    INIT_CACHE_SIZE_LOG2 = 1,
#endif
    INIT_CACHE_SIZE      = (1 &amp;lt;&amp;lt; INIT_CACHE_SIZE_LOG2),
    MAX_CACHE_SIZE_LOG2  = 16,
    MAX_CACHE_SIZE       = (1 &amp;lt;&amp;lt; MAX_CACHE_SIZE_LOG2),
    FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3,
    FULL_UTILIZATION_CACHE_SIZE = (1 &amp;lt;&amp;lt; FULL_UTILIZATION_CACHE_SIZE_LOG2),
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b. 扩容临界点&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;newOccupied + CACHE_END_MARKER &amp;lt;= cache_fill_ratio(capacity)&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;// Cache is less than 3/4 or 7/8 full. Use it as-is.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;真机为 7/8&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1

// Historical fill ratio of 75% (since the new objc runtime was introduced).
static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

#elif __arm64__ &amp;amp;&amp;amp; !__LP64__
#define CACHE_END_MARKER 0

// Historical fill ratio of 75% (since the new objc runtime was introduced).
static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

#elif __arm64__ &amp;amp;&amp;amp; __LP64__

// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0

// Allow 87.5% fill ratio in the fast path for all cache sizes.
// Increasing the cache fill ratio reduces the fragmentation and wasted space
// in imp-caches at the cost of potentially increasing the average lookup of
// a selector in imp-caches by increasing collision chains. Another potential
// change is that cache table resizes / resets happen at different moments.
static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 7 / 8;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c. 扩容操作&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;扩容按 &lt;code&gt;x2&lt;/code&gt; 进行扩容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity &amp;gt; MAX_CACHE_SIZE) {
    capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;d. 扩容后&lt;/h4&gt;
&lt;p&gt;我们看一下触发扩容的那次Log：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-06-27 16:00:51.940602+0800 LWCacheT[73396:867351] 缓存数量：1 - 容量：7
2021-06-27 16:00:51.940668+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940742+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940780+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940814+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.940900+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.941025+0800 LWCacheT[73396:867351] SEL:(null) - IMP:0x0f
2021-06-27 16:00:51.941124+0800 LWCacheT[73396:867351] SEL:dosomethingC - IMP:0xbcd8f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次扩容后，我们发现。除了最新的，之前的缓存都没有了。&lt;/p&gt;
&lt;p&gt;这是因为 &lt;code&gt;扩容&lt;/code&gt; 并不是真正意义上的扩展之前的空间，而是重新开辟一块空间。&lt;/p&gt;
&lt;h3&gt;3.2 cache_hash 计算哈希下标&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value &amp;gt;&amp;gt; 7;// 真机 &amp;amp; m1
#endif
    return (mask_t)(value &amp;amp; mask);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 cache_next 寻找插入位置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) &amp;amp; mask; // 向后找
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask; // 反复横跳
}
#else
#error unexpected configuration
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4 set 差入逻辑&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;Atomicity atomicity, IMPEncoding impEncoding&amp;gt;
void bucket_t::set(bucket_t *base, SEL newSel, IMP newImp, Class cls)
{
...
    // objc_msgSend uses sel and imp with no locks.
    // It is safe for objc_msgSend to see new imp but NULL sel
    // (It will get a cache miss but not dispatch to the wrong place.)
    // It is unsafe for objc_msgSend to see old imp and new sel.
    // Therefore we write new imp, wait a lot, then write new sel.
    
    /// 对 IMP 进行编码操作
    uintptr_t newIMP = (impEncoding == Encoded
                        ? encodeImp(base, newImp, newSel, cls)
                        : (uintptr_t)newImp);
...
  保存
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将方法插入缓存，并不是直接插入的。而是进行了编码。&lt;code&gt;(uintptr_t)newImp ^ (uintptr_t)cls;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;3.5 流程图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f6a48936bde68f4f.webp&quot; alt=&quot;cache_t&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;关于类型转换的思考&lt;/h2&gt;
&lt;p&gt;这里使用自己定义的类型进行转换，感觉是不是很懵，感觉这也行？&lt;/p&gt;
&lt;p&gt;我们抛开代码，思考一下：我们为什么要定义各种数据结构？为了能读写数据。&lt;/p&gt;
&lt;p&gt;而能正确读写数据的前提是，计算机能认识这些数据。而我们定义的数据结构，就是计算机认识他们的基准。&lt;/p&gt;
&lt;p&gt;只要两个数据结构的内存结构一致，那么进行转换就没有问题。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/penghao_1/article/details/107631820&quot;&gt;HashMap的负载因子为什么默认是0.75&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>源码解读objc_msgSend</title><link>https://blog.lab-lawliet.app/posts/ios-11-runtime-objc-msgsend-bd912115db00d354/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-11-runtime-objc-msgsend-bd912115db00d354/</guid><description>|更新日期|变更| |:----:|:----:| |20210701|首次发布|</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;本文基于
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;objc4-818.2&lt;/code&gt;源码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iOS14.6 iPhoneX&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;源码版本、系统版本、设备不同的话，真机调试的汇编会和源码有不同。自行调试的时候需要注意&lt;/li&gt;
&lt;li&gt;源码中对于重要部分做了注释，&lt;code&gt;...&lt;/code&gt;表示省略了一些其他架构或者无关紧要的代码&lt;/li&gt;
&lt;li&gt;为了方便阅读，汇编代码中的做了&lt;code&gt;缩进调整&lt;/code&gt;和&lt;code&gt;分块注释&lt;/code&gt;，阅读起来应该会轻松不少&lt;/li&gt;
&lt;li&gt;&lt;code&gt;电脑端&lt;/code&gt;阅读体验更佳哦&lt;/li&gt;
&lt;li&gt;本文看着会比较累，用了个看着比较轻松愉悦的主题。
&lt;ul&gt;
&lt;li&gt;配上一杯 ☕️ 体验更佳&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;objc_msgSend&lt;/code&gt; 相关的内容很多，此文会持续更新、修正
&lt;ul&gt;
&lt;li&gt;具体更新内容可以查看&lt;code&gt;更新记录&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;欢迎收藏、交流、抓虫&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;更新记录&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;更新日期&lt;/th&gt;
&lt;th&gt;变更&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20210701&lt;/td&gt;
&lt;td&gt;首次发布&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;一、平时我们是如何调用方法的？&lt;/h2&gt;
&lt;p&gt;有三种方式进行调用：
&lt;code&gt;OC Method&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[model doSomething]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;NSObject API&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[model isKindOfClass:[NSString class]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;objc API&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class_getSuperclass([RYModel class])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API的层级结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4a033471eea3ffc8.webp&quot; alt=&quot;runtimeLevel.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.1 实验三种方式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;RYModel *obj = [[RYModel alloc] init];
[obj dosomethingA];

[obj performSelector:NSSelectorFromString(@&quot;dosomethingA&quot;)];

// &amp;lt; Xcode 12
//    objc_msgSend((id)obj, sel_registerName(&quot;dosomethingA&quot;));

// Xcode 12 需要转类型，只改Build setting不行
((void (*)(id, SEL))(void *)objc_msgSend)(obj, sel_registerName(&quot;dosomethingA&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#import &amp;lt;objc/message.h&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xcode 12&lt;/code&gt; 以下
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Build setting&lt;/code&gt; -&amp;gt; &lt;code&gt;objc_msgSend&lt;/code&gt; -&amp;gt; &lt;code&gt;Enable Strict Checking of objc_msgSend Calls&lt;/code&gt; -&amp;gt; &lt;code&gt;NO&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xcode 12&lt;/code&gt; 以上
&lt;ul&gt;
&lt;li&gt;需要对 objc_msgSend 转类型，才能正常调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3562e316364d4808ba1145bfee9909b8~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;msgsend01.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;输出：都能够正常调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
ObjcMsgSend[17313:6255181] -[RYModel dosomethingA]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 调用父类方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@interface RYSubModel : RYModel

- (BOOL)isSub;

@end

@implementation RYSubModel

- (BOOL)isSub {
    NSLog(@&quot;%s&quot;,__FUNCTION__);
    [super dosomethingA];
    return  YES;
}

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RYSubModel&lt;/code&gt; 继承 &lt;code&gt;RYModel&lt;/code&gt; ，我们看看C++文件中 &lt;code&gt;isSub&lt;/code&gt; 的情况。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static BOOL _I_RYSubModel_isSub(RYSubModel * self, SEL _cmd) {
    NSLog((NSString *)&amp;amp;__NSConstantStringImpl__var_folders_py_7v1wvf813z5bmw0hr97yplwc0000gn_T_RYModel_c52934_mi_6,__FUNCTION__);
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass(&quot;RYSubModel&quot;))}, sel_registerName(&quot;dosomethingA&quot;));
    return ((bool)1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里 &lt;code&gt;[super dosomethingA]&lt;/code&gt; 转成C++后，发现一个 &lt;code&gt;objc_msgSendSuper&lt;/code&gt; 方法，通过它来调用父类的方法。&lt;/p&gt;
&lt;h3&gt;1.3 objc_msgSendSuper&lt;/h3&gt;
&lt;p&gt;我们试试能不能直接调用 &lt;code&gt;objc_msgSendSuper&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;super
&lt;ul&gt;
&lt;li&gt;objc_super 结构体指针&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;op
&lt;ul&gt;
&lt;li&gt;selector&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &amp;amp;&amp;amp;  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;objc_super&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &amp;amp;&amp;amp;  !__OBJC2__ // 
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;代码&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct objc_super ry_super;
ry_super.receiver = obj;
ry_super.super_class = [RYModel class];
((void (*)(id, SEL))(void *)objc_msgSendSuper)((__bridge id)(&amp;amp;ry_super), sel_registerName(&quot;dosomethingA&quot;));

输出：
ObjcMsgSend[17330:6265149] -[RYModel dosomethingA] // 调用父类方法成功
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;修改父类参数&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我们将父类修改成它自己的类对象试试。 &lt;code&gt; ry_super.super_class = [RYSubModel class];&lt;/code&gt;，输出依然正确。内部有个查找的链条。&lt;/p&gt;
&lt;h3&gt;1.4 源码&lt;/h3&gt;
&lt;p&gt;我们找一下 &lt;code&gt;objc_msgSend&lt;/code&gt; 的源码。&lt;/p&gt;
&lt;p&gt;ARM64版的源码都在 &lt;code&gt;objc-msg-arm64.s&lt;/code&gt; 中，是 &lt;code&gt;汇编&lt;/code&gt; 的。&lt;/p&gt;
&lt;p&gt;通过添加符号断点也可以查看到汇编代码。&lt;/p&gt;
&lt;h2&gt;二、汇编说明&lt;/h2&gt;
&lt;h3&gt;2.1 这里用到的主要的的汇编指令备注&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指令&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cmp&lt;/td&gt;
&lt;td&gt;cmp    x0, #0x0&lt;/td&gt;
&lt;td&gt;作差值计算，结果丢弃，只调整状态寄存器&lt;code&gt;CPSR&lt;/code&gt;标志位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b.le&lt;/td&gt;
&lt;td&gt;b.le 0x1afad2b98;&lt;/td&gt;
&lt;td&gt;意为&lt;code&gt;less than or equal to&lt;/code&gt;，结果&lt;code&gt;&amp;lt;=0&lt;/code&gt;，的时候跳转指令 &lt;code&gt;0x1afad2b98&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b.eq&lt;/td&gt;
&lt;td&gt;b.eq   0x1afad2b68&lt;/td&gt;
&lt;td&gt;意为&lt;code&gt;equal&lt;/code&gt;，结果&lt;code&gt;==0&lt;/code&gt;，时跳转指令&lt;code&gt;0x1afad2b68&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;and&lt;/td&gt;
&lt;td&gt;and    x16, x13&lt;/td&gt;
&lt;td&gt;x16 &amp;amp; x13 与操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ldr&lt;/td&gt;
&lt;td&gt;ldr    x13, [x0]&lt;/td&gt;
&lt;td&gt;将地址x0的储存器的数据，放入x13寄存器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lsr&lt;/td&gt;
&lt;td&gt;lsr #48&lt;/td&gt;
&lt;td&gt;逻辑右移48位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lsl&lt;/td&gt;
&lt;td&gt;lsl #4&lt;/td&gt;
&lt;td&gt;逻辑左移4位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ldp&lt;/td&gt;
&lt;td&gt;ldp    x17, x9, [x12]&lt;/td&gt;
&lt;td&gt;取出x12的值存入x17,x9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b.ne&lt;/td&gt;
&lt;td&gt;b.ne   0x1afad2b54&lt;/td&gt;
&lt;td&gt;不相等，跳转指令&lt;code&gt;0x1afad2b54&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tbnz&lt;/td&gt;
&lt;td&gt;tbnz	p11, #0, LLookupPreopt\Function&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Test and branch Not zero&lt;/code&gt;，即：如果p11的第0位不为0就执行&lt;code&gt;LLookupPreopt\Function&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TBZ&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;为0，和&lt;code&gt;tbnz&lt;/code&gt;类似&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;adrp&lt;/td&gt;
&lt;td&gt;adrp x0, 1&lt;/td&gt;
&lt;td&gt;1. 将1的值,左移12位 &lt;code&gt;1 0000 0000 0000 == 0x1000&lt;/code&gt;;\n 2.将PC寄存器的低12位清零 &lt;code&gt;0x1045228b0  ==&amp;gt; 0x104522000&lt;/code&gt;; 3.将将1 和 2 的结果相加  给 X0 寄存器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eor&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;异或&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2.2 寄存器说明&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/article-01-0a8f9f909a6676b5/&quot;&gt;寄存器&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x0-x7&lt;/code&gt;，是用来传参的。
&lt;ul&gt;
&lt;li&gt;x0：消息接收者&lt;/li&gt;
&lt;li&gt;x1：sel&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;☕️ 休息一下吧，后面开始会有点累&lt;/h2&gt;
&lt;h2&gt;三 _objc_msgSend 源码汇编&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame

// 判断是否存在消息接收者
cmp	p0, #0			// nil check and tagged pointer check 比较P0寄存器和0

#if SUPPORT_TAGGED_POINTERS 
// 支持Taggedpointer ARM64下 SUPPORT_TAGGED_POINTERS = 1，
// 根据状态寄存器结果 p0 - 0 &amp;lt;= 0 跳转执行 LNilOrTagged
b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
// 等于0，即接收者为nil，LReturnZero
b.eq	LReturnZero
#endif

// ISA 存入p13
ldr	p13, [x0]		// p13 = isa

// 从ISA中获取 Class，p16存的是Class的指针
GetClassFromIsa_p16 p13, 1, x0	// p16 = class

// 获取ISA完成后执行：
LGetIsaDone:
// calls imp or objc_msgSend_uncached
// 调用IMP或者查缓存
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

END_ENTRY _objc_msgSend
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 GetClassFromIsa_p16&lt;/h3&gt;
&lt;p&gt;获取Class存入p16&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/********************************************************************
 * GetClassFromIsa_p16 src, needs_auth, auth_address
 * src is a raw isa field. Sets p16 to the corresponding class pointer.
 * The raw isa might be an indexed isa to be decoded, or a
 * packed isa that needs to be masked.
 *
 * On exit:
 *   src is unchanged
 *   p16 is a class pointer
 *   x10 is clobbered
 ********************************************************************/
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
...
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \src
.else // needs_auth == 1
	// 64-bit packed isa
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif

.endmacro
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 ExtractISA 获取ClassISA&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// XS/XS Max/XR A12处理器开始
#if __has_feature(ptrauth_calls)
// JOP
...
.macro ExtractISA
    // ISA($1) &amp;amp; ISA_MASK 存入 p16p16($0)，即 class
	and	$0, $1, #ISA_MASK
#if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_STRIP
	xpacd	$0
#elif ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
	mov	x10, $2
	movk	x10, #ISA_SIGNING_DISCRIMINATOR, LSL #48
	autda	$0, x10
#endif
.endmacro
...

// JOP
#else
// not JOP

...
.macro ExtractISA
    // ISA($1) &amp;amp; ISA_MASK 存入 p16($0)，即 class
	and    $0, $1, #ISA_MASK
.endmacro

// not JOP
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 CacheLookup 查找缓存&lt;/h3&gt;
&lt;p&gt;参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mode：缓存命中模式
&lt;ul&gt;
&lt;li&gt;CacheHit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Function
&lt;ul&gt;
&lt;li&gt;命中缓存后调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MissLabelDynamic
&lt;ul&gt;
&lt;li&gt;未命中调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MissLabelConstant
&lt;ul&gt;
&lt;li&gt;MissLabelConstant is only used for the GETIMP variant&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;官方注释&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;建议查看一下，有助于理解&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/********************************************************************
 *
 * CacheLookup NORMAL|GETIMP|LOOKUP &amp;lt;function&amp;gt; MissLabelDynamic MissLabelConstant
 *
 * MissLabelConstant is only used for the GETIMP variant.
 *
 * Locate the implementation for a selector in a class method cache.
 *
 * When this is used in a function that doesn&apos;t hold the runtime lock,
 * this represents the critical section that may access dead memory.
 * If the kernel causes one of these functions to go down the recovery
 * path, we pretend the lookup failed by jumping the JumpMiss branch.
 *
 * Takes:
 *	 x1 = selector
 *	 x16 = class to be searched
 *
 * Kills:
 * 	 x9,x10,x11,x12,x13,x15,x1
 *
 * Untouched:
 * 	 x14
 *
 * On exit: (found) calls or returns IMP
 *                  with x16 = class, x17 = IMP
 *                  In LOOKUP mode, the two low bits are set to 0x3
 *                  if we hit a constant cache (used in objc_trace)
 *          (not found) jumps to LCacheMiss
 *                  with x15 = class
 *                  For constant caches in LOOKUP mode, the low bit
 *                  of x16 is set to 0x1 to indicate we had to fallback.
 *          In addition, when LCacheMiss is __objc_msgSend_uncached or
 *          __objc_msgLookup_uncached, 0x2 will be set in x16
 *          to remember we took the slowpath.
 *          So the two low bits of x16 on exit mean:
 *            0: dynamic hit
 *            1: fallback to the parent class, when there is a preoptimized cache
 *            2: slowpath
 *            3: preoptimized cache hit
 *
 ********************************************************************/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;源码&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//
	// Restart protocol:
	//
	//   As soon as we&apos;re past the LLookupStart\Function label we may have
	//   loaded an invalid cache pointer or mask.
	//
	//   When task_restartable_ranges_synchronize() is called,
	//   (or when a signal hits us) before we&apos;re past LLookupEnd\Function,
	//   then our PC will be reset to LLookupRecover\Function which forcefully
	//   jumps to the cache-miss codepath which have the following
	//   requirements:
	//
	//   GETIMP:
	//     The cache-miss is just returning NULL (setting x0 to 0)
	//
	//   NORMAL and LOOKUP:
	//   - x0 contains the receiver
	//   - x1 contains the selector
	//   - x16 contains the isa
	//   - other registers are set as per calling conventions
	//

    // 将x16(class)移动到x15
	mov	x15, x16			// stash the original isa

LLookupStart\Function:
...


.endmacro

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;源码较长，我们分部分看&lt;/p&gt;
&lt;h3&gt;3.4 LLookupStart\Function 开始查找&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;获取当前 bucket_t 和 HashIndex，用来查找缓存
&lt;ul&gt;
&lt;li&gt;这里对源码的&lt;code&gt;if-else&lt;/code&gt;缩进进行调整，方便查看&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;LLookupStart\Function:
	// p1 = _cmd, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// ARM64 &amp;amp;&amp;amp; __LP64__: MacOS 或 模拟器
...
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// ARM64 &amp;amp;&amp;amp; __LP64__
    // ClassISA + #CACHE(0x10) 存入 p11，即cache_t(p11)
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
    #if CONFIG_USE_PREOPT_CACHES
        // 真机
        #if __has_feature(ptrauth_calls)
        // &amp;gt; X 的机型
            // 判断 cache_t(p11) 第0位是否不为0，如果为0，是否有共享缓存（first_shared_cache_sel）
            // 没缓存：继续走
            // 有缓存：跳转 LLookupPreopt\Function
            tbnz	p11, #0, LLookupPreopt\Function
            // cache_t(p11) &amp;amp; #0x0000ffffffffffff -&amp;gt; p10(buckets)
            // 取低 48 位
            and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        #else
            and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
            tbnz	p11, #0, LLookupPreopt\Function
        #endif

        // (_cmd ^ _cmd &amp;gt;&amp;gt; 7) -&amp;gt; p12
        eor	p12, p1, p1, LSR #7
        // ((cache_t &amp;amp; p12) &amp;gt;&amp;gt; 48) -&amp;gt; p12(HashIndex)
        and	p12, p12, p11, LSR #48		// x12 = (_cmd(p1) ^ (_cmd(p1) &amp;gt;&amp;gt; 7)) &amp;amp; mask
    #else
        and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
        and	p12, p1, p11, LSR #48		// x12 = _cmd &amp;amp; mask
    #endif // CONFIG_USE_PREOPT_CACHES

#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// ARM64 &amp;amp;&amp;amp; !__LP64__：真机
略
#else
#error Unsupported cache mask storage for ARM64.
#endif
    // ((buckets + HashIndex) &amp;lt;&amp;lt; 1+PTRSHIFT) -&amp;gt; p13(bucket_t)
    // 通过偏移找到 bucket_t 存入p13
    // p12(HashIndex)
    // `buckets`是`buckets`的首地址，HashIndex为Hash表的下标
    // 如：HashIndex为1那就偏移1个`bucket_t`的长度，即：16（sel8+imp8）
    // 左移`(1+PTRSHIFT))`是为了到`HashIndex-1`位置，这里和cache_t插入缓存的逻辑保持一致，否则找不到的。
    // 此时p13存的的是目标bucket_t地址
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd &amp;amp; mask) &amp;lt;&amp;lt; (1+PTRSHIFT))
// #define PTRSHIFT 3  // 1&amp;lt;&amp;lt;PTRSHIFT == PTRSIZE

// 遍历缓存

// 第一次循环结束后

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 循环遍历（上）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;						// do {
// #define BUCKET_SIZE      (2 * __SIZEOF_POINTER__) 即 (sel + imp 指针的大小)
// 取出x13 放入 p17(imp) p9(sel)
// 然后 `*bucket--` 向前移动2个指针的长度
// 即：一个bucket_t的长度
// *bucket--: 先取值，再--
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
    // 对比 p1(传入的sel) 和 p9(p13的sel)
	cmp	p9, p1				//     if (sel != _cmd) {
	b.ne	3f				//         scan more
						//     } else {
// 命中调用 CacheHit \Mode
2:	CacheHit \Mode				// hit:    call or return imp
						//     }
3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) goto Miss;
	cmp	p13, p10			// } while (bucket &amp;gt;= buckets)
    // 未命中 回到 1 继续循环
	b.hs	1b

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.6 CacheHit&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
    // 调用缓存IMP
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don&apos;t ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP
.elseif $0 == LOOKUP
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don&apos;t care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.7 TailCallCachedImp 执行缓存IMP&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#if __has_feature(ptrauth_calls)
// JOP
...

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (addressOfCachedIMP ^ SEL) -&amp;gt; addressOfCachedIMP
	eor	$1, $1, $2	// mix SEL into ptrauth modifier
    // (addressOfCachedIMP ^ isa) -&amp;gt; addressOfCachedIMP
	eor	$1, $1, $3  // mix isa into ptrauth modifier
    // 这个指令还没搞明白
	brab	$0, $1
.endmacro

// JOP
#else
// not JOP
...

.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
    // (cachedIMP ^ isa) -&amp;gt; cachedIMP
	eor	$0, $0, $3
    // 跳转执行 cachedIMP
	br	$0
.endmacro

...

// not JOP
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.8 循环遍历（下）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当循环回到第一个bucket的时候就停止。&lt;/li&gt;
&lt;li&gt;将p13移到 buckets + (mask &amp;lt;&amp;lt; 1+PTRSHIFT)&lt;/li&gt;
&lt;li&gt;继续遍历，直到遍历完&lt;/li&gt;
&lt;li&gt;如果仍然没有命中，_objc_msgSend_uncached&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// wrap-around:
//   p10 = first bucket
//   p11 = mask (and maybe other bits on LP64)
//   p12 = _cmd &amp;amp; mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.

...
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask &amp;lt;&amp;lt; 1+PTRSHIFT)
						// see comment about maskZeroBits
...

	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				// } while (sel != 0 &amp;amp;&amp;amp;
	ccmp	p13, p12, #0, ne		//     bucket &amp;gt; first_probed)
	b.hi	4b
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.9 LLookupPreopt\Function: 查找共享缓存内的数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#if CONFIG_USE_PREOPT_CACHES
...
    LLookupPreopt\Function:
    #if __has_feature(ptrauth_calls)
        // cache_t(p11) &amp;amp; #0x007ffffffffffffe 存入 p10
        and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
        // 验证？没找到这个指令的具体作用
        autdb	x10, x16			// auth as early as possible
    #endif
        // 查找共享缓存内的数据
        // x12 = (_cmd - first_shared_cache_sel)
        adrp	x9, _MagicSelRef@PAGE
        ldr	p9, [x9, _MagicSelRef@PAGEOFF]
        sub	p12, p1, p9

        // w9  = ((_cmd - first_shared_cache_sel) &amp;gt;&amp;gt; hash_shift &amp;amp; hash_mask)
    #if __has_feature(ptrauth_calls)
        // bits 63..60 of x11 are the number of bits in hash_mask
        // bits 59..55 of x11 is hash_shift

        lsr	x17, x11, #55			// w17 = (hash_shift, ...)
        lsr	w9, w12, w17			// &amp;gt;&amp;gt;= shift

        lsr	x17, x11, #60			// w17 = mask_bits
        mov	x11, #0x7fff
        lsr	x11, x11, x17			// p11 = mask (0x7fff &amp;gt;&amp;gt; mask_bits)
        and	x9, x9, x11			// &amp;amp;= mask
    #else
        // bits 63..53 of x11 is hash_mask
        // bits 52..48 of x11 is hash_shift
        lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
        lsr	w9, w12, w17			// &amp;gt;&amp;gt;= shift
        and	x9, x9, x11, LSR #53		// &amp;amp;=  mask
    #endif

        ldr	x17, [x10, x9, LSL #3]		// x17 == sel_offs | (imp_offs &amp;lt;&amp;lt; 32)
        cmp	x12, w17, uxtw

    .if \Mode == GETIMP
    ...
    .else
        b.ne    5f                // cache miss
        sub    x17, x16, x17, LSR #32        // imp = isa - imp_offs

    .if \Mode == NORMAL
        br	x17
    .elseif \Mode == LOOKUP
    ...

    5:	ldursw	x9, [x10, #-8]			// offset -8 is the fallback offset
        add	x16, x16, x9			// compute the fallback isa
        b	LLookupStart\Function		// lookup again with a new isa
    .endif
#endif // CONFIG_USE_PREOPT_CACHES
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.10 GetTaggedClass&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;if SUPPORT_TAGGED_POINTERS&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Look up the class for a tagged pointer in x0, placing it in x16.
.macro GetTaggedClass

	and	x10, x0, #0x7		// x10 = small tag
	asr	x11, x0, #55		// x11 = large tag with 1s filling the top (because bit 63 is 1 on a tagged pointer)
	cmp	x10, #7		// tag == 7?
	csel	x12, x11, x10, eq	// x12 = index in tagged pointer classes array, negative for extended tags.
					// The extended tag array is placed immediately before the basic tag array
					// so this looks into the right place either way. The sign extension done
					// by the asr instruction produces the value extended_tag - 256, which produces
					// the correct index in the extended tagged pointer classes array.

	// x16 = _objc_debug_taggedpointer_classes[x12]
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ldr	x16, [x10, x12, LSL #3]

.endmacro
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.11 LNilOrTagged&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq	LReturnZero		// nil check
GetTaggedClass
b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.12 LReturnZero&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;LReturnZero:
// x0 is already zero
mov	x1, #0
movi	d0, #0
movi	d1, #0
movi	d2, #0
movi	d3, #0
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;☕️ 看到这里，再休息一下吧。真机汇编看着更带劲儿~&lt;/h2&gt;
&lt;h2&gt;四、真机汇编调试&lt;/h2&gt;
&lt;p&gt;个人觉得看符号断点的汇编更直观，但是源码中有注释。&lt;/p&gt;
&lt;p&gt;真机符号断点的作为参考，和源码对照着再看一遍会更加清晰。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;libobjc.A.dylib`objc_msgSend:
/*** Start
判断接收者是否为空 
***/
-&amp;gt;  0x197b05140 &amp;lt;+0&amp;gt;:   cmp    x0, #0x0                  ; =0x0 
    0x197b05144 &amp;lt;+4&amp;gt;:   b.le   0x197b051e8               ; &amp;lt;+168&amp;gt;
/***************************/

/*** Start
将接收者存入 x13 
即：x13 = isa 
***/
    0x197b05148 &amp;lt;+8&amp;gt;:   ldr    x13, [x0]
/***************************/

/*** Start
GetClassFromIsa_p16 -&amp;gt; 
ExtractISA: 
isa &amp;amp; ISA_MASK -&amp;gt; x16
即：x16 = class的isa 
***/
    0x197b0514c &amp;lt;+12&amp;gt;:  and    x16, x13, #0xffffffff8
/***************************/

/*** Start
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
查找缓存
***/
    0x197b05150 &amp;lt;+16&amp;gt;:  mov    x15, x16 // LGetIsaDone

    /*** Start
    LLookupStart\Function:
    ClassISA + #CACHE(0x10) 存入 p11，即cache_t(p11)
    判断 cache_t 第0位是否不为0，如果为0，是否有共享缓存。有就跳转 LLookupPreopt
    ***/
    0x197b05154 &amp;lt;+20&amp;gt;:  ldr    x11, [x16, #0x10]
    0x197b05158 &amp;lt;+24&amp;gt;:  and    x10, x11, #0xfffffffffffe
    0x197b0515c &amp;lt;+28&amp;gt;:  tbnz   w11, #0x0, 0x197b051b0    ; &amp;lt;+112&amp;gt;

        /*** Start
        计算 cache_hash 
        (sel ^ sel &amp;gt;&amp;gt; 7) -&amp;gt; p12
        ((cache_t &amp;amp; p12) &amp;gt;&amp;gt; 48) -&amp;gt; p12(HashIndex)
        ***/
        0x197b05160 &amp;lt;+32&amp;gt;:  eor    x12, x1, x1, lsr #7
        0x197b05164 &amp;lt;+36&amp;gt;:  and    x12, x12, x11, lsr #48
        /***************************/

    /***************************/

    /*** Start
    通过偏移找到 bucket_t 存入x13
    x12(HashIndex)
    `buckets`是`buckets`的首地址，HashIndex为Hash表的下标
    如：HashIndex为1那就偏移1个`bucket_t`的长度，即：16（sel8+imp8）
    左移`(1+PTRSHIFT))`是为了到`HashIndex-1`位置，这里和cache_t插入缓存的逻辑保持一致，否则找不到的。
    (buckets + HashIndex &amp;lt;&amp;lt; 1+PTRSHIFT) -&amp;gt; p13(bucket_t)
    此时x13存的的是目标bucket_t地址
    ***/
    0x197b05168 &amp;lt;+40&amp;gt;:  add    x13, x10, x12, lsl #4
    /***************************/

    /*** Start
    遍历查缓存
    #define BUCKET_SIZE      (2 * __SIZEOF_POINTER__) 即 (sel + imp 指针的大小)
    取出 x13 - 0x10 放入 p17(imp) p9(sel)
    0x10 = 2，即2个单位
    `*bucket--` 向前移动2个指针的长度（sel + imp）
    即：前移一个bucket_t的长度，将x13指向上一个缓存
    对比缓存和_cmd
    ***/
    0x197b0516c &amp;lt;+44&amp;gt;:  ldp    x17, x9, [x13], #-0x10
    0x197b05170 &amp;lt;+48&amp;gt;:  cmp    x9, x1
    0x197b05174 &amp;lt;+52&amp;gt;:  b.ne   0x197b05180               ; &amp;lt;+64&amp;gt;

        /*** Start
        命中缓存 TailCallCachedImp
        (cachedIMP ^ isa) -&amp;gt; cachedIMP
        ***/ 
        0x197b05178 &amp;lt;+56&amp;gt;:  eor    x17, x17, x16
        0x197b0517c &amp;lt;+60&amp;gt;:  br     x17
        /***************************/

    /*** Start
    while 循环条件判断
    if (sel(x9) == 0) goto Miss;
    while (bucket &amp;gt;= buckets) 满足条件回到 +44 指令行继续循环
    ***/
    0x197b05180 &amp;lt;+64&amp;gt;:  cbz    x9, 0x197b054c0           ; _objc_msgSend_uncached
    0x197b05184 &amp;lt;+68&amp;gt;:  cmp    x13, x10// 
    0x197b05188 &amp;lt;+72&amp;gt;:  b.hs   0x197b0516c               ; &amp;lt;+44&amp;gt;
    /***************************/


    /*** Start
    当循环回到第一个bucket的时候就停止循环后：
    
    * 将p13移到 buckets + (mask &amp;lt;&amp;lt; 1+PTRSHIFT)
    * 继续遍历，直到遍历完
    * 如果仍然没有命中，_objc_msgSend_uncached
    ***/
    0x197b0518c &amp;lt;+76&amp;gt;:  add    x13, x10, x11, lsr #44
    0x197b05190 &amp;lt;+80&amp;gt;:  add    x12, x10, x12, lsl #4

        /*** Start
        第二次遍历
        ***/
        0x197b05194 &amp;lt;+84&amp;gt;:  ldp    x17, x9, [x13], #-0x10

            /*** Start
            命中缓存 跳转 56指令行：TailCallCachedImp
            ***/ 
            0x197b05198 &amp;lt;+88&amp;gt;:  cmp    x9, x1
            0x197b0519c &amp;lt;+92&amp;gt;:  b.eq   0x197b05178               ; &amp;lt;+56&amp;gt;
            /***************************/
        
        // 循环条件判断
        0x197b051a0 &amp;lt;+96&amp;gt;:  cmp    x9, #0x0                  ; =0x0 
        0x197b051a4 &amp;lt;+100&amp;gt;: ccmp   x13, x12, #0x0, ne
        //     bucket &amp;gt; first_probed)
        0x197b051a8 &amp;lt;+104&amp;gt;: b.hi   0x197b05194               ; &amp;lt;+84&amp;gt;
        /***************************/

        0x197b051ac &amp;lt;+108&amp;gt;: b      0x197b054c0               ; _objc_msgSend_uncached
    /***************************/

    /*** Start
    LLookupPreopt\Function
    查找共享缓存内的数据
    ***/

    // x12 = (_cmd - first_shared_cache_sel)
    0x197b051b0 &amp;lt;+112&amp;gt;: adrp   x9, 227000
    0x197b051b4 &amp;lt;+116&amp;gt;: add    x9, x9, #0x47b            ; =0x47b 
    0x197b051b8 &amp;lt;+120&amp;gt;: sub    x12, x1, x9

    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    0x197b051bc &amp;lt;+124&amp;gt;: lsr    x17, x11, #48
    0x197b051c0 &amp;lt;+128&amp;gt;: lsr    w9, w12, w17
    0x197b051c4 &amp;lt;+132&amp;gt;: and    x9, x9, x11, lsr #53

    // x17 == sel_offs | (imp_offs &amp;lt;&amp;lt; 32)
    0x197b051c8 &amp;lt;+136&amp;gt;: ldr    x17, [x10, x9, lsl #3]
    0x197b051cc &amp;lt;+140&amp;gt;: cmp    x12, w17, uxtw

    0x197b051d0 &amp;lt;+144&amp;gt;: b.ne   0x197b051dc               ; &amp;lt;+156&amp;gt;
    0x197b051d4 &amp;lt;+148&amp;gt;: sub    x17, x16, x17, lsr #32

    0x197b051d8 &amp;lt;+152&amp;gt;: br     x17

        /*** Start
        5.
        ***/
        // offset -8 is the fallback offse
        0x197b051dc &amp;lt;+156&amp;gt;: ldursw x9, [x10, #-0x8]
        // compute the fallback isa
        0x197b051e0 &amp;lt;+160&amp;gt;: add    x16, x16, x9
        // lookup again with a new isa
        0x197b051e4 &amp;lt;+164&amp;gt;: b      0x197b05154               ; &amp;lt;+20&amp;gt;
        /***************************/

    /***************************/


    /*** Start
    接收者为空时调用 LNilOrTagged
    ***/
    0x197b051e8 &amp;lt;+168&amp;gt;: b.eq   0x197b0520c               ; &amp;lt;+204&amp;gt;

        /*** Start
        GetTaggedClass
        ***/
        0x197b051ec &amp;lt;+172&amp;gt;: and    x10, x0, #0x7
        0x197b051f0 &amp;lt;+176&amp;gt;: asr    x11, x0, #55
        0x197b051f4 &amp;lt;+180&amp;gt;: cmp    x10, #0x7                 ; =0x7 
        0x197b051f8 &amp;lt;+184&amp;gt;: csel   x12, x11, x10, eq
        0x197b051fc &amp;lt;+188&amp;gt;: adrp   x10, 255422
        0x197b05200 &amp;lt;+192&amp;gt;: add    x10, x10, #0xb60          ; =0xb60 
        0x197b05204 &amp;lt;+196&amp;gt;: ldr    x16, [x10, x12, lsl #3]
        /***************************/

    0x197b05208 &amp;lt;+200&amp;gt;: b      0x197b05150               ; &amp;lt;+16&amp;gt; // LGetIsaDone
    /***************************/

    /*** Start
    LReturnZero
    ***/
    0x197b0520c &amp;lt;+204&amp;gt;: mov    x1, #0x0
    0x197b05210 &amp;lt;+208&amp;gt;: movi   d0, #0000000000000000
    0x197b05214 &amp;lt;+212&amp;gt;: movi   d1, #0000000000000000
    0x197b05218 &amp;lt;+216&amp;gt;: movi   d2, #0000000000000000
    0x197b0521c &amp;lt;+220&amp;gt;: movi   d3, #0000000000000000
    0x197b05220 &amp;lt;+224&amp;gt;: ret    
    /***************************/   

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;🎉 感谢能耐心看到这里，不错不错&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;看汇编是真的费劲儿&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;objc_msgSend 汇编流程图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/74e3e61261c74880.webp&quot; alt=&quot;objc_msgSend汇编流程图.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、未命中缓存的处理 __objc_msgSend_uncached&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r10 is the searched class

// r10 is already the class to search
MethodTableLookup NORMAL	// r11 = IMP
jmp	*%r11			// goto *imp

END_ENTRY __objc_msgSend_uncached
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里会调用 &lt;code&gt;MethodTableLookup&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;5.1 查找方法列表 MethodTableLookup&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;.macro MethodTableLookup
	// 创建一个栈，为调用方法做准备，存储寄存器中的值
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3

	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

    // 复原 SAVE_REGS用到的寄存器，出栈
	RESTORE_REGS MSGSEND

.endmacro
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 慢速查找 _lookUpImpOrForward&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;id inst&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;receiver(x0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SEL sel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;selector(x1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Class cls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ClassISA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;int behavior&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;method lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;behavior: &lt;code&gt;LOOKUP_INITIALIZE | LOOKUP_RESOLVER)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;流程图&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1d8ce6207a680dcb.webp&quot; alt=&quot;慢速查找&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;源码解析&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    /**
    发给Class的第一个消息通常是通过了objc_opt_*或各种优化的入口：
    + new
    + alloc
    + self
    但是，此时Class尚未初始化
    */
    if (slowpath(!cls-&amp;gt;isInitialized())) { 
        behavior |= LOOKUP_NOCACHE;
     }

    /** 
    加锁
    保证 method-lookup + cache-fill 的原子性。
    否则
    可能分类添加后会被永久忽略，因为缓存被旧的数据覆盖
    */
    runtimeLock.lock();

    /**
    为防止 CFI攻击
    检查是否是已注册的类
    确保Class编译进了二进制文件
    或者是使用 
    objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair
    合法的注册过的
    */
    checkIsKnownClass(cls);

    // 如果需要的话初始化class
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior &amp;amp; LOOKUP_INITIALIZE);

    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    /**
    死循环。通过goto和break跳出
    */
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass-&amp;gt;cache.isConstantOptimizedCache(/* strict */true)) {
            ...
        } else {
            // curClass method list.
            // 用二分法查找sel
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth-&amp;gt;imp(false);
                goto done;
            }

            // 这里将获取了父类并赋值给 curClass
            if (slowpath((curClass = curClass-&amp;gt;getSuperclass()) == nil)) {
                // 父类都找完了（superClass == nil）也没有就进行消息转发了
                // No implementation found, and method resolver didn&apos;t help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }
        ...

        // Superclass cache. 这里的 curClass 已经是父类了
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 需要进行消息转发
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    ...

 done:
    if (fastpath((behavior &amp;amp; LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
    // iOS真机
        while (cls-&amp;gt;cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls-&amp;gt;cache.preoptFallbackClass();
        }
#endif
        // 插入缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
...
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 二分查找赏析&lt;/h3&gt;
&lt;p&gt;这里的算法比较精妙，值得学习。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
 * search_method_list_inline
 **********************************************************************/
template&amp;lt;class getNameFunc&amp;gt;
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &amp;amp;getName)
{
    ASSERT(list);

    auto first = list-&amp;gt;begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    /**
    count &amp;gt;&amp;gt;= 1，右移一位，即二分一次
    */
    for (count = list-&amp;gt;count; count != 0; count &amp;gt;&amp;gt;= 1) {
        // 居中的下标
        probe = base + (count &amp;gt;&amp;gt; 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            /**
            probe &amp;gt; first
            开始遍历一直把同名的遍历完，因为有分类的存在，所以可能会有同名的。
            会掉分类的。
            */
            while (probe &amp;gt; first &amp;amp;&amp;amp; keyValue == (uintptr_t)getName((probe - 1))) {
                probe--;
            }
            return &amp;amp;*probe;
        }
        
        if (keyValue &amp;gt; probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、动态决议与消息转发&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/posts/ios-13-c7a7f6c73ada5643/&quot;&gt;动态决议与消息转发&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;附：知识点补充&lt;/h2&gt;
&lt;h3&gt;1. 取 buckets 过程的补充说明&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 判断 cache_t(p11) 第0位是否不为0，如果为0，是否有共享缓存（first_shared_cache_sel）
// 没缓存：继续走
// 有缓存：跳转 LLookupPreopt\Function
tbnz	p11, #0, LLookupPreopt\Function
// cache_t(p11) &amp;amp; #0x0000ffffffffffff -&amp;gt; p10(buckets)
// 取低 48 位
and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这里的第0位，是指低位的0位&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;strong&gt;OBJC2&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;我们在进行源码学习的时候时常会见到&lt;code&gt;__OBJC2__&lt;/code&gt;这个宏定义。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;因为&lt;code&gt;runtime&lt;/code&gt;有两个版本&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.0：用于早期系统上，32位的MacOS上&lt;/li&gt;
&lt;li&gt;2.0（现行版本）：&lt;code&gt;iOS&lt;/code&gt;和&lt;code&gt;MacOS X 10.5&lt;/code&gt;以后的64位系统上&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;想要了解更多可以查看下面的官方文档：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048&quot;&gt;Objective-C Runtime Programming Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/objectivec/objective-c_runtime&quot;&gt;Objective-C Runtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. cache_t&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/ios-10-class-cache-t-551f1a8b6b2847f3/&quot;&gt;【Lawliet玩iOS源码】Class: cache_t&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;4. Class的实现（Realize）和初始化（Initialize）&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/ios-12-0254f974d91c51ef/&quot;&gt;类的实现与初始化&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>类的实现与初始化源码解读</title><link>https://blog.lab-lawliet.app/posts/ios-12-0254f974d91c51ef/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-12-0254f974d91c51ef/</guid><description>在上一文中runtime-objc_msgSend中我们有遇到这样一个重要的流程</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在上一文中&lt;a href=&quot;/posts/ios-11-runtime-objc-msgsend-bd912115db00d354/&quot;&gt;runtime-objc_msgSend&lt;/a&gt;中我们有遇到这样一个重要的流程&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior &amp;amp; LOOKUP_INITIALIZE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本文就将对&lt;code&gt;类的实现与初始化&lt;/code&gt;，结合源码作深入探究。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* realizeAndInitializeIfNeeded_locked
如果类未实现就实现，如果未初始化就初始化
inst：是cls或者其子类的一个实例对象，或者nil
cls：需要初始化或者实现的类
initialize：true会初始化class，false跳过初始化
**********************************************************************/
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    if (slowpath(!cls-&amp;gt;isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (slowpath(initialize &amp;amp;&amp;amp; !cls-&amp;gt;isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    return cls;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;objc4-818.2&lt;/code&gt;源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、实现初始化的区别&lt;/h2&gt;
&lt;p&gt;这里你肯定和我一样有疑问：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化我大概知道是啥，实现是个什么鬼？有什么区别？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 isRealized()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;bool isRealized() const {
    return !isStubClass() &amp;amp;&amp;amp; (data()-&amp;gt;flags &amp;amp; RW_REALIZED);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际是判断当前类的&lt;code&gt;class_rw_t *data()&lt;/code&gt;是否存在，及是否有标志位&lt;code&gt;RW_REALIZED&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;1.2 isInitialized()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;bool isInitialized() {
    return getMeta()-&amp;gt;data()-&amp;gt;flags &amp;amp; RW_INITIALIZED;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际是判断metaClass的&lt;code&gt;class_rw_t *data()&lt;/code&gt;是否存在，及是否有标志位&lt;code&gt;RW_INITIALIZED&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;一、实现类：realizeClassMaybeSwiftAndLeaveLocked&lt;/h2&gt;
&lt;h3&gt;流程图&lt;/h3&gt;
&lt;p&gt;搭配源码及注释食用更加&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/81fd4f54ff7a69a3.webp&quot; alt=&quot;iOS底层-类的实现&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.1 实现OC类&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls-&amp;gt;isRealized()) {
        // 已经实现了的话，验证一下没问题就return
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
    auto ro = (const class_ro_t *)cls-&amp;gt;data();
    // 是否是元类
    auto isMeta = ro-&amp;gt;flags &amp;amp; RO_META;
    
    if (ro-&amp;gt;flags &amp;amp; RO_FUTURE) {
        // TODO: 不太知道这个是啥意思
        // This was a future class. rw data is already allocated.
        rw = cls-&amp;gt;data();
        ro = cls-&amp;gt;data()-&amp;gt;ro();
        ASSERT(!isMeta);
        cls-&amp;gt;changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 一般Class。创建rw
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc&amp;lt;class_rw_t&amp;gt;();
        // 设置ro
        rw-&amp;gt;set_ro(ro);
        // 设置标志位
        rw-&amp;gt;flags = RW_REALIZED|RW_REALIZING|isMeta;
        // 设置rw
        cls-&amp;gt;setData(rw);
    }

    // 创建 cache
    cls-&amp;gt;cache.initializeToEmptyOrPreoptimizedInDisguise();

#if FAST_CACHE_META
    if (isMeta) cls-&amp;gt;cache.setBit(FAST_CACHE_META);
#endif

    /**
    选取在Class数组中的下标
    有个全局变量 objc_indexed_classes_count会递增
    全局数组用来保存Class Class objc_indexed_classes[1] = { nil };

    放入 objc_indexed_classes 是在
     */
    cls-&amp;gt;chooseClassArrayIndex();

。。。
    
    // 父类
    supercls = realizeClassWithoutSwift(remapClass(cls-&amp;gt;getSuperclass()), nil);
    // 元类
    metacls = realizeClassWithoutSwift(remapClass(cls-&amp;gt;ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    。。。
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    // 设置父类
    cls-&amp;gt;setSuperclass(supercls);
    // 设置类的ISA为metaClass
    cls-&amp;gt;initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    /**
     存在父类并且不是元类
     调整成员变量偏移量/布局，可能会重新创建 ro，并更新成员变量
     */
    if (supercls  &amp;amp;&amp;amp;  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn&apos;t set already.
    cls-&amp;gt;setInstanceSize(ro-&amp;gt;instanceSize);

    // Copy some flags from ro to rw
    // 将一些标志位从ro复制到rw
    if (ro-&amp;gt;flags &amp;amp; RO_HAS_CXX_STRUCTORS) {
        cls-&amp;gt;setHasCxxDtor();
        if (! (ro-&amp;gt;flags &amp;amp; RO_HAS_CXX_DTOR_ONLY)) {
            cls-&amp;gt;setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro-&amp;gt;flags &amp;amp; RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls &amp;amp;&amp;amp; supercls-&amp;gt;forbidsAssociatedObjects()))
    {
        rw-&amp;gt;flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass&apos;s subclass lists
    if (supercls) {
        // 如果存在父类，添加到其子类列表中
        addSubclass(supercls, cls);
    } else {
        /**
         设置根类：全局变量 _firstRealizedClass
         
         ***********************************************************************
         * _firstRealizedClass
         * The root of all realized classes
         **********************************************************************
         static Class _firstRealizedClass = nil;
         
         */
        addRootClass(cls);
    }

    // Attach categories
    /***********************************************************************
    添加分类
    * Fixes up cls&apos;s method list, protocol list, and property list.
    **********************************************************************/
    methodizeClass(cls, previously);

    return cls;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 实现Swift类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;注意点
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Metaclasses&lt;/code&gt; never have a &lt;code&gt;Swift initializer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Root classes&lt;/code&gt; never have a &lt;code&gt;Swift initializer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;(These two together avoid initialization order problems at the root.)
&lt;ul&gt;
&lt;li&gt;这两条避免了初始化的顺序错乱的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;没有&lt;code&gt;initializer的Swift类&lt;/code&gt;也不存在拥有&lt;code&gt;initializer的父类&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;realizeClassWithoutSwift()&lt;/code&gt;内无须扫描父类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;static Class realizeSwiftClass(Class cls)
{
。。。

    if (auto init = cls-&amp;gt;swiftMetadataInitializer()) {
        // 有Swift元数据初始化方法
        。。。
        Class newcls = init(cls, nil);

        if (cls != newcls) {
            mutex_locker_t lock(runtimeLock);
            addRemappedClass(cls, newcls);
        }
        return newcls;
    }
    else {
        // 没有Swift初始化方法，直接调用OC的
        mutex_locker_t lock(runtimeLock);
        return realizeClassWithoutSwift(cls, nil);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、初始化类：initializeAndLeaveLocked&lt;/h2&gt;
&lt;p&gt;相比Realize的流程，初始化的流程相对简单。我画了个流程图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f088b4be3024c13b.webp&quot; alt=&quot;iOS底层-类的初始化&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Realize
&lt;ul&gt;
&lt;li&gt;主要完成类的基础结构的搭建
&lt;ul&gt;
&lt;li&gt;如&lt;code&gt;ISA链&lt;/code&gt;和&lt;code&gt;继承链&lt;/code&gt;，&lt;code&gt;rw&lt;/code&gt;，&lt;code&gt;ro&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Initialize
&lt;ul&gt;
&lt;li&gt;找到&lt;code&gt;非元类&lt;/code&gt;的Class&lt;/li&gt;
&lt;li&gt;&lt;code&gt;标志位&lt;/code&gt;处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+initialize&lt;/code&gt;方法调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>动态决议与消息转发</title><link>https://blog.lab-lawliet.app/posts/ios-13-c7a7f6c73ada5643/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-13-c7a7f6c73ada5643/</guid><description>在上一文中我们有探究到objc_msgSend相关的一些底层原理。</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在上一文中我们有探究到&lt;code&gt;objc_msgSend&lt;/code&gt;相关的一些底层原理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们发现在慢速查找的过程中有这样
&lt;ul&gt;
&lt;li&gt;forward_imp 消息转发&lt;/li&gt;
&lt;li&gt;resolveMethod_locked 动态决议&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;本文就将对这两个地方做深入探究&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    
    ...

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass-&amp;gt;cache.isConstantOptimizedCache(/* strict */true)) {
            ...
        } else {
            ...
            if (slowpath((curClass = curClass-&amp;gt;getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn&apos;t help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        ...
    }

    // No implementation found. Try method resolver once.
    if (slowpath(behavior &amp;amp; LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

    ...

 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior &amp;amp; LOOKUP_NIL) &amp;amp;&amp;amp; imp == forward_imp)) {
        return nil;
    }
    return imp;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;先附上一张流程图&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;仅从源码分析会很困难，结合代码调试各种情况，和这张流程图可以更好的理解整个流程的细节&lt;/li&gt;
&lt;li&gt;我自己也反复调试了很多次才理的差不多，以前知道这些东西，但没有深入底层去理解细节&lt;/li&gt;
&lt;li&gt;这张图也改了好几次&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ae8b47d200e53251.webp&quot; alt=&quot;动态决议与消息转发&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一、 _objc_msgForward_impcache&lt;/h2&gt;
&lt;p&gt;通过在源码&lt;code&gt;objc-msg-arm64.s&lt;/code&gt;中搜索我们一步步找到核心代码&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;__objc_msgForward_impcache&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b	__objc_msgForward

END_ENTRY __objc_msgForward_impcache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;__objc_msgForward&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ENTRY __objc_msgForward

adrp	x17, __objc_forward_handler@PAGE
ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17

END_ENTRY __objc_msgForward
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;__objc_forward_handler&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们并没有找到 &lt;code&gt;__objc_forward_handler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;怀疑不是汇编代码，去掉&lt;code&gt;_&lt;/code&gt;试试&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;objc_defaultForwardHandler&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;找到了下面的代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    /**
    打印一些信息
    这里就有我们常看到的 unrecognized selector 的相关崩溃新系了
    */ 
    _objc_fatal(&quot;%c[%s %s]: unrecognized selector sent to instance %p &quot;
                &quot;(no message forward handler is installed)&quot;, 
                class_isMetaClass(object_getClass(self)) ? &apos;+&apos; : &apos;-&apos;, 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 resolveMethod_locked&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    ...

    if (! cls-&amp;gt;isMetaClass()) {
        // 对象方法（cls不是元类），理解ISA关系图这里就会比较清楚的
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // 类方法
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、实例方法动态决议&lt;/h2&gt;
&lt;p&gt;这里声明一个实例方法，但不做实现。触发调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)noIMP;

[obj noIMP];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会见到我们常见的一个崩溃信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;libc++abi: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception &apos;NSInvalidArgumentException&apos;, reason: &apos;-[RYModel noIMP]: unrecognized selector sent to instance 0x100677a40&apos;
terminating with uncaught exception of type NSException
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 动态添加Method&lt;/h3&gt;
&lt;p&gt;重写 &lt;code&gt;+ (BOOL)resolveInstanceMethod:(SEL)sel&lt;/code&gt; 添加&lt;code&gt;method&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)callNoIMPSel {
    NSLog(@&quot;调用了没IMP的SEL&quot;);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(noIMP)) {
        IMP safeIMP = class_getMethodImplementation(self, @selector(callNoIMPSel));
        Method method =  class_getInstanceMethod(self, @selector(callNoIMPSel));
        const char *type = method_getTypeEncoding(method);
        NSLog(@&quot;对%@进行动态决议&quot;, NSStringFromSelector(sel));
        return class_addMethod(self, sel, safeIMP, type);
    }
    return [super resolveInstanceMethod:sel];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;对noIMP进行动态决议
调用了没IMP的SEL
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 如果再次调用，还会不会进行动态决议呢？&lt;/h3&gt;
&lt;p&gt;理论分析：第一次添加了Method后，会同步插入到方法缓存中，所以第二次执行就不会再次添加。&lt;/p&gt;
&lt;p&gt;实际运行结果也验证了刚才的分析。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;对noIMP进行动态决议
调用了没IMP的SEL
调用了没IMP的SEL
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 源码解析&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    ...
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    ...

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    // 直接调用 objc_msgSend 调用 resolveInstanceMethod:
    bool resolved = msg(cls, resolve_sel, sel);

    // 动态决议处理后来到这里
    // Cache the result (good or bad) so the resolver doesn&apos;t fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

        if (resolved  &amp;amp;&amp;amp;  PrintResolving) {
            if (imp) {
                ...
            }
            else {
                // Method resolver didn&apos;t add anything?
                ...
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    ...

    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    。。。
#endif
    if (slowpath(imp == NULL)) {
        // 再查找
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior &amp;amp; LOOKUP_NIL) &amp;amp;&amp;amp; imp == (IMP)_objc_msgForward_impcache) {
        // 需要转发
        return nil;
    }
    return imp;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、类方法动态决议&lt;/h2&gt;
&lt;h3&gt;4.1 动态添加Method&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(noClassIMP)) {
        IMP safeIMP = class_getMethodImplementation(self, @selector(callClassNoIMPSel));
        Method method =  class_getClassMethod(self, @selector(callClassNoIMPSel));
        const char *type = method_getTypeEncoding(method);
        NSLog(@&quot;对%@进行动态决议（类方法）&quot;, NSStringFromSelector(sel));
        return class_addMethod(self, sel, safeIMP, type);
    }
    return [super resolveClassMethod:sel];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果你写成上面的样子，你会发现依旧会崩溃。根本没生效。&lt;/li&gt;
&lt;li&gt;为什么呢？&lt;/li&gt;
&lt;li&gt;如果你不知道为什么，你需要首先搞明白 &lt;code&gt;类方法存在哪里&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/ios-05-class-0ee1949a2e6315f6/&quot;&gt;深入探索Class的结构&lt;/a&gt;这篇文章可以带你了解&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正确的实现方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(noClassIMP)) {
        Class meta = objc_getMetaClass(&quot;RYModel&quot;);
        IMP safeIMP = class_getMethodImplementation(meta, @selector(callClassNoIMPSel));
        Method method =  class_getClassMethod(meta, @selector(callClassNoIMPSel));
        const char *type = method_getTypeEncoding(method);
        NSLog(@&quot;对%@进行动态决议（类方法）&quot;, NSStringFromSelector(sel));
        return class_addMethod(meta, sel, safeIMP, type);
    }
    return [super resolveClassMethod:sel];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;对noClassIMP进行动态决议（类方法）
调用了Class没IMP的SEL
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 类方法动态决议的特殊处理&lt;/h3&gt;
&lt;p&gt;在源码中及流程图中可以发现，在 &lt;code&gt;resolveClassMethod&lt;/code&gt; 之后如果缓存中还没有对应的&lt;code&gt;Method&lt;/code&gt;的话就会再去调用 &lt;code&gt;resolveInstanceMethod&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
    resolveInstanceMethod(inst, sel, cls);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;可见苹果工程师也是操碎了心啊，给了好多机会来让我们不要崩溃&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;五、消息转发&lt;/h2&gt;
&lt;p&gt;在 &lt;code&gt;lookUpImpOrForward&lt;/code&gt; 中我们找到 &lt;code&gt;_objc_msgForward_impcache&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.1 寻找切入点&lt;/h3&gt;
&lt;p&gt;发现其实现在 &lt;code&gt;objc-msg-arm64.s&lt;/code&gt; 中&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;__objc_msgForward_impcache&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;STATIC_ENTRY __objc_msgForward_impcache

// No stret specialization.
b	__objc_msgForward

END_ENTRY __objc_msgForward_impcache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;__objc_msgForward&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ENTRY __objc_msgForward

adrp	x17, __objc_forward_handler@PAGE
ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17

END_ENTRY __objc_msgForward
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;_objc_forward_handler&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们有进一步找到了 &lt;code&gt;_objc_forward_handler&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;拥有一个默认的实现： &lt;code&gt;objc_defaultForwardHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;以及一个&lt;code&gt;set&lt;/code&gt;方法：&lt;code&gt;objc_setForwardHandler&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#if !__OBJC2__

...

#else
    // Default forward handler halts the process.
    __attribute__((noreturn, cold)) void
    objc_defaultForwardHandler(id self, SEL sel)
    {
        _objc_fatal(&quot;%c[%s %s]: unrecognized selector sent to instance %p &quot;
                    &quot;(no message forward handler is installed)&quot;, 
                    class_isMetaClass(object_getClass(self)) ? &apos;+&apos; : &apos;-&apos;, 
                    object_getClassName(self), sel_getName(sel), self);
    }
    void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

    #if SUPPORT_STRET (ARM64 = 0)
    ...
    #endif

#endif

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进一步搜索 &lt;code&gt;setForwardHandler&lt;/code&gt; 并未发现相关调用，推测调用处不在OC源码中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/32295ffce8af3488.webp&quot; alt=&quot;消息转发01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分析Log&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;发现调用了 &lt;code&gt;CoreFoundation&lt;/code&gt; 中的 &lt;code&gt;___forwarding___&lt;/code&gt; 方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*** First throw call stack:
(
    ...
	3   CoreFoundation                      0x00007fff203f790b ___forwarding___ + 1448
	4   CoreFoundation                      0x00007fff203f72d8 _CF_forwarding_prep_0 + 120
    ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么怎么去进一步分析呢？&lt;/p&gt;
&lt;h3&gt;5.2 汇编分析 CoreFoundation`&lt;em&gt;&lt;strong&gt;forwarding&lt;/strong&gt;&lt;/em&gt;:&lt;/h3&gt;
&lt;h4&gt;forwardingTargetForSelector:&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;0x7fff203f741b &amp;lt;+184&amp;gt;:  mov    r14, qword ptr [rip + 0x604ed906] ; &quot;forwardingTargetForSelector:&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;通过这条指令我们发现一个 &lt;code&gt;forwardingTargetForSelector:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;我们找下源码，根据命名推测这个方法用来是转发消息给某个对象的。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;我们下个断点：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}

- (id)forwardingTargetForSelector:(SEL)sel {
    return nil;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;发现断点确实走到了这里，输出堆栈信息：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) bt
* thread #1, queue = &apos;com.apple.main-thread&apos;, stop reason = breakpoint 3.1
  * frame #0: 0x00000001003548f2 libobjc.A.dylib`-[NSObject forwardingTargetForSelector:](self=0x000000010132f350, _cmd=&quot;forwardingTargetForSelector:&quot;, sel=&quot;noIMP&quot;) at NSObject.mm:2451:5
    frame #1: 0x00007fff203f7444 CoreFoundation`___forwarding___ + 225
    frame #2: 0x00007fff203f72d8 CoreFoundation`_CF_forwarding_prep_0 + 120
    frame #3: 0x00000001000033d0 KCObjcBuild`main(argc=1, argv=0x00007ffeefbff4a0) at main.m:23:9 [opt]
    frame #4: 0x00007fff20337f5d libdyld.dylib`start + 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;接下来我们试着在这里返回一个对象来接收消息看看效果。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@implementation RYModel
- (id)forwardingTargetForSelector:(SEL)sel {
    return [[RYSubModel alloc] init];
}
@end

@implementation RYSubModel
- (void)noIMP {
    NSLog(@&quot;%s&quot;,__func__);
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行后执行正常，输出了 &lt;code&gt;-[RYSubModel noIMP]&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;5.3 转发对象为空的情况&lt;/h3&gt;
&lt;p&gt;当不实现 &lt;code&gt;forwardingTargetForSelector&lt;/code&gt; 的时候，我们发现，经过一次&lt;code&gt;动态决议&lt;/code&gt;默认返回 &lt;code&gt;nil&lt;/code&gt; 后，又一次进入了 &lt;code&gt;动态决议&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 一次动态决议
+[RYModel resolveInstanceMethod:]--noIMP

// 打印堆栈信息
(lldb) bt
* thread #1, queue = &apos;com.apple.main-thread&apos;, stop reason = breakpoint 6.1
  * frame #0: 0x000000010031682a libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000000000000, sel=&quot;noIMP&quot;, cls=RYModel, behavior=2) at objc-runtime-new.mm:6536:9
    frame #1: 0x00000001002edcb9 libobjc.A.dylib`class_getInstanceMethod(cls=RYModel, sel=&quot;noIMP&quot;) at objc-runtime-new.mm:6192:5
    frame #2: 0x00007fff2040d653 CoreFoundation`__methodDescriptionForSelector + 276
    frame #3: 0x00007fff20426fa0 CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 30
    frame #4: 0x00007fff203f74ef CoreFoundation`___forwarding___ + 396
    frame #5: 0x00007fff203f72d8 CoreFoundation`_CF_forwarding_prep_0 + 120
    frame #6: 0x00000001000034d0 KCObjcBuild`main + 64
    frame #7: 0x00007fff20337f5d libdyld.dylib`start + 1

// 打印变量信息
(lldb) po inst
 nil
(lldb) po sel
&quot;noIMP&quot;

(lldb) po cls
objc[3185]: mutex incorrectly locked
objc[3185]: mutex incorrectly locked
RYModel

(lldb) po behavior
2
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;behavior == 2 == LOOKUP_RESOLVER&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;满足动态决议的条件，再次进行&lt;code&gt;动态决议&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;behavior ^= LOOKUP_RESOLVER&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;behavior&lt;/code&gt;变成&lt;code&gt;0&lt;/code&gt;了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;六、总结&lt;/h2&gt;
&lt;p&gt;这部分的理解还是需要结合调试，单纯看源码很难理清楚，有太多嵌套调用。&lt;/p&gt;
&lt;p&gt;希望我的流程图能帮助理解，有不对的地方也欢迎指出。&lt;/p&gt;
</content:encoded></item><item><title>iOS应用启动（四）：分类的加载</title><link>https://blog.lab-lawliet.app/posts/ios-17-ios-87689077307f3ca6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-17-ios-87689077307f3ca6/</guid><description>我们在12.类的实现与初始化中实现类 realizeClass 的流程最后一步发现是处理分类 methodizeClass。本文就将以此为入口，深入探索 什么是分类。</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;我们在&lt;a href=&quot;/posts/ios-12-0254f974d91c51ef/&quot;&gt;12.类的实现与初始化&lt;/a&gt;中实现类 &lt;code&gt;realizeClass&lt;/code&gt; 的流程最后一步发现是处理分类 &lt;code&gt;methodizeClass&lt;/code&gt;。本文就将以此为入口，深入探索 &lt;em&gt;&lt;strong&gt;什么是分类&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;h2&gt;一、 methodizeClass&lt;/h2&gt;
&lt;p&gt;处理&lt;code&gt;方法列表&lt;/code&gt;、&lt;code&gt;协议列表&lt;/code&gt;、&lt;code&gt;属性列表&lt;/code&gt;，添加&lt;code&gt;分类&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;重要部分见注释：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dc2929213ce3322e.webp&quot; alt=&quot;methodizeClass01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5dd5a0500d742557.webp&quot; alt=&quot;methodizeClass02&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.1 attachToClass 将分类添加到类&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8cf135c7ee140e81.webp&quot; alt=&quot;attachToClass&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;继续调用 &lt;code&gt;attachCategories&lt;/code&gt;，通过初步阅读源码，发现此处为核心步骤，下面进行具体分析&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 attachCategories&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这里对源码中两处重要的注释做翻译：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;添加分类的方法列表、属性、协议到类&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最早加载的分类在最顶部&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有很少的类在运行时会有超过&lt;code&gt;64&lt;/code&gt;个分类&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里使用一个小容量的&lt;code&gt;栈&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类必须按照合适的顺序添加，这里会从前向后读取，从后向前创建本地缓存，并调用 &lt;code&gt;attachLists&lt;/code&gt;。所以最后的顺序是对的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
。。。
    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags &amp;amp; ATTACH_METACLASS);
    // 创建 rwe
    auto rwe = cls-&amp;gt;data()-&amp;gt;extAllocIfNeeded();

    // 循环添加方法、属性、协议
    for (uint32_t i = 0; i &amp;lt; cats_count; i++) {
        auto&amp;amp; entry = cats_list[i];

        method_list_t *mlist = entry.cat-&amp;gt;methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe-&amp;gt;methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi-&amp;gt;isBundle();
        }

        property_list_t *proplist =
            entry.cat-&amp;gt;propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe-&amp;gt;properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat-&amp;gt;protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe-&amp;gt;protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount &amp;gt; 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe-&amp;gt;methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags &amp;amp; ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it&apos;s fine to keep
                return !c-&amp;gt;cache.isConstantOptimizedCache();
            });
        }
    }

    rwe-&amp;gt;properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe-&amp;gt;protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 流程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;extAllocIfNeeded 创建 rwe&lt;/li&gt;
&lt;li&gt;循环添加方法、属性、协议&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 分类加载的时机&lt;/h2&gt;
&lt;p&gt;我们直到类添加分类的核心逻辑是 &lt;code&gt;attachCategories&lt;/code&gt;。我们全局搜索一下 &lt;code&gt;attachCategories&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;attachToClass&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/152773029e870262.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;load_categories_nolock&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/79ee9ac6f02af932.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有上面两处进行了调用。&lt;/p&gt;
&lt;h3&gt;3.1 测试代码准备&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;自定义类&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RYCat : NSObject

@property (nonatomic, copy) NSString *nick;
@property (nonatomic, assign) NSInteger age;

- (void)yeah;
- (void)yeah2;
- (void)yeah3;

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分类&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RYCat (CateA)

- (void)funcA;
- (void)funcA2;
- (void)funcA3;

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;main函数&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RYCat *cat = [RYCat alloc];
[cat funcA];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 添加断点及日志&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0de3459164831301.webp&quot; alt=&quot;attachToClass&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b12107646a93dfb9.webp&quot; alt=&quot;attachCategories&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/35ce7ba80ee2f622.webp&quot; alt=&quot;realizeClassWithoutSwift&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3854b0856e16679a.webp&quot; alt=&quot;load_categories_nolock&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 场景一： 分类有Load✅ 主类有Load✅ （非懒加载）&lt;/h3&gt;
&lt;p&gt;Log如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
load_categories_nolock -- RYCat -- CateA
attachCategories -- RYCat -- CateA
attachCategories -- RYCat -- CateA
+[RYCat load]
+[RYCat(CateA) load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;流程：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_read_images
&lt;ul&gt;
&lt;li&gt;realizeClassWithoutSwift
&lt;ul&gt;
&lt;li&gt;attachToClass
&lt;ul&gt;
&lt;li&gt;load_categories_nolock
&lt;ul&gt;
&lt;li&gt;attachCategories&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 场景二： 分类有Load✅ 主类无Load❌ （非懒加载）&lt;/h3&gt;
&lt;p&gt;Log如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
+[RYCat(CateA) load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;流程：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_read_images
&lt;ul&gt;
&lt;li&gt;realizeClassWithoutSwift
&lt;ul&gt;
&lt;li&gt;attachToClass&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意到：&lt;em&gt;&lt;strong&gt;并没有调用 &lt;code&gt;attachCategories&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;非懒加载&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4a22eaeade3ae91f.webp&quot; alt=&quot;非懒加载&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然主类未实现+load方法，但是分类实现了，那么主类也是非懒加载的。&lt;/p&gt;
&lt;h3&gt;3.4 场景三： 分类无Load❌ 主类有Load✅ （非懒加载）&lt;/h3&gt;
&lt;p&gt;Log如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
+[RYCat load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;流程：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_read_images
&lt;ul&gt;
&lt;li&gt;realizeClassWithoutSwift
&lt;ul&gt;
&lt;li&gt;attachToClass&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意到：和 &lt;em&gt;&lt;strong&gt;分类有Load✅ 主类无Load❌&lt;/strong&gt;&lt;/em&gt; 一样，&lt;em&gt;&lt;strong&gt;没有调用 &lt;code&gt;attachCategories&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.5 场景四： 分类无Load❌ 主类无Load错❌ （懒加载）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5bdf2bf0c63c62cc.webp&quot; alt=&quot;懒加载&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Log如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
Program ended with exit code: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意到：和 &lt;em&gt;&lt;strong&gt;3.3和3.4&lt;/strong&gt;&lt;/em&gt; 一样，&lt;em&gt;&lt;strong&gt;没有调用 &lt;code&gt;attachCategories&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;思考： 为什么没有调用 &lt;code&gt;attachCategories&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;编译器&lt;/code&gt;优化了，直接把分类的数据合并到了主类中。&lt;/p&gt;
&lt;h2&gt;四、 多分类 &lt;code&gt;+load&lt;/code&gt; 执行顺序&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;如果有多个分类，执行的顺序是什么样的呢？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;如图创建两个分类，分别实现 &lt;code&gt;+load&lt;/code&gt; 方法。思考一下执行的顺序是什么样的呢？为什么呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e7d6b5bd6a8c80e.webp&quot; alt=&quot;12&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.1 顺序&lt;/h3&gt;
&lt;p&gt;按顺序输出如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+[RYCat load]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+[RYCat(CateA) load]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+[RYCat(CateB) load]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们先看下编译顺序：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d480ceb4fefd5d5.webp&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调整一下编译顺序看有没有影响：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/39d1081038a03317.webp&quot; alt=&quot;14&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调整编译顺序后输出如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+[RYCat load]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+[RYCat(CateB) load]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+[RYCat(CateA) load]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 结论&lt;/h3&gt;
&lt;p&gt;多分类的情况下，&lt;code&gt;+load&lt;/code&gt; 的执行顺序是和编译的顺序有关的。&lt;/p&gt;
&lt;h2&gt;五、 多分类同名方法执行顺序&lt;/h2&gt;
&lt;p&gt;如果多个分类由同名的方法，又会是怎样的顺序呢？&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;在两个分类中声明实现同名的 &lt;code&gt;funcA&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1c6f61bae5e42d51.webp&quot; alt=&quot;15&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;a href=&quot;#%E5%9B%9B-%E5%A4%9A%E5%88%86%E7%B1%BB-load-%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F&quot;&gt;多分类 &lt;code&gt;+load&lt;/code&gt; 执行顺序&lt;/a&gt; 中我们知道，编译的顺序影响了各个分类的表现，这里我们也进行一下区分&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.1 CateA 比 CateB 先编译&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;+[RYCat load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateA) load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateB) load]&lt;/li&gt;
&lt;li&gt;-[RYCat(CateB) funcA]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 CateB 比 CateA 先编译&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;+[RYCat load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateA) load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateB) load]&lt;/li&gt;
&lt;li&gt;-[RYCat(CateA) funcA]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 在主类中也声明并实现 funcA （CateB 比 CateA 先编译）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;+[RYCat load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateA) load]&lt;/li&gt;
&lt;li&gt;+[RYCat(CateB) load]&lt;/li&gt;
&lt;li&gt;-[RYCat(CateA) funcA]&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 总结&lt;/h3&gt;
&lt;p&gt;可见：多个分类拥有&lt;code&gt;同名方法&lt;/code&gt;时，&lt;code&gt;先编译的分类的方法&lt;/code&gt;会被&lt;code&gt;后编译的分类&lt;/code&gt;覆盖掉。如果主类中也有同名方法也会被覆盖掉。&lt;/p&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;对启动速度的影响&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;em&gt;&lt;strong&gt;3.2&lt;/strong&gt;&lt;/em&gt; 主类分类都有实现 &lt;code&gt;Load方法&lt;/code&gt; 的时候可见整个流程流程会复杂一下，所以减少这种场景的出现，是有助于提升应用启动速度。&lt;/p&gt;
</content:encoded></item><item><title>关联对象源码解析</title><link>https://blog.lab-lawliet.app/posts/ios-18-54d512dbdff8d79c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-18-54d512dbdff8d79c/</guid><description>为分类添加关联对象是我们比较常用的，但是你真的了解关联对象么？</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;为分类添加关联对象是我们比较常用的，但是你真的了解关联对象么？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;objc4-818.2&lt;/code&gt; 源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、 关联对象&lt;/h2&gt;
&lt;p&gt;由于分类的属性不会生产相关的成员变量，所以我们需要自己实现 &lt;code&gt;set/get&lt;/code&gt; 方法。如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// .h

@property (nonatomic, copy) NSString *cateName;


// .m
- (void)setCateName:(NSString *)cateName {
    objc_setAssociatedObject(self, &quot;cateName&quot;, cateName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cateName {
    return  objc_getAssociatedObject(self, &quot;cateName&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么，&lt;code&gt;objc_setAssociatedObject&lt;/code&gt; 和 &lt;code&gt;objc_getAssociatedObject&lt;/code&gt;，究竟做了什么呢？&lt;/p&gt;
&lt;h2&gt;二、 objc_setAssociatedObject&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1dc613e741b75517.webp&quot; alt=&quot;objc_setAssociatedObject&quot; /&gt;&lt;/p&gt;
&lt;p&gt;实际调用了 &lt;code&gt;_object_set_associative_reference&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5d5acc2305304062.webp&quot; alt=&quot;_object_set_associative_reference&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 AssociationsHashMap&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b714387b2690d724.webp&quot; alt=&quot;AssociationsHashMap&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过断点我们发现了一个 &lt;code&gt;AssociationsHashMap&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5f5757f9ee83ba64.webp&quot; alt=&quot;AssociationsManager&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看出它是一个 &lt;code&gt;全局静态&lt;/code&gt; 的 &lt;code&gt;Storage&lt;/code&gt; 类型的 &lt;code&gt;HashMap&lt;/code&gt;。用来保存运行时所有的关联对象的关联关系。&lt;/p&gt;
&lt;h3&gt;2.2 try_emplace&lt;/h3&gt;
&lt;p&gt;如果表中没有的话，就将键值对插入表中。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Inserts key,value pair into the map if the key isn&apos;t already in the map. The value is constructed in-place if the key is not in the map, otherwise it is not moved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/649ddc1fbacc945b.webp&quot; alt=&quot;try_emplace&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;一次 set 两次调用了 try_emplace&lt;/h4&gt;
&lt;p&gt;通过代码可以看出，一次set关联值，会调用两次 &lt;code&gt;try_emplace&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e7f83deadb52b926.webp&quot; alt=&quot;try_emplace&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;只用来检查是否存在关联对象的 &lt;code&gt;key&lt;/code&gt; &lt;code&gt;ObjectAssociationMap{}&lt;/code&gt; 是个空的不做处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auto result = refs.try_emplace(key, std::move(association));&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;这里才是真的设置 &lt;code&gt;value&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;触发set&lt;/h4&gt;
&lt;p&gt;通过调用 &lt;code&gt;LookupBucketFor&lt;/code&gt; 查找表中是否已经存在了对应的关联对象。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;第一次调用set方法&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/74eeb44c32f65e19.webp&quot; alt=&quot;第一次调用set方法&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;第二次调用set方法&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0499dc3b6aa597ff.webp&quot; alt=&quot;第二次调用set方法&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 LookupBucketFor&lt;/h3&gt;
&lt;p&gt;和查找方法缓存的算法基本一致。&lt;/p&gt;
&lt;h3&gt;2.4 InsertIntoBucket&lt;/h3&gt;
&lt;p&gt;插入表中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename KeyArg, typename... ValueArgs&amp;gt;
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &amp;amp;&amp;amp;Key,
                        ValueArgs &amp;amp;&amp;amp;... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);

// first 就是 key
TheBucket-&amp;gt;getFirst() = std::forward&amp;lt;KeyArg&amp;gt;(Key);

// second 是Value
::new (&amp;amp;TheBucket-&amp;gt;getSecond()) ValueT(std::forward&amp;lt;ValueArgs&amp;gt;(Values)...);
return TheBucket;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 关联对象释放&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;思考：在对象 &lt;code&gt;dealloc&lt;/code&gt; 的时候，会自动清理管理那对象么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.1 dealloc&lt;/h3&gt;
&lt;p&gt;我们从 &lt;code&gt;dealloc&lt;/code&gt; 开始找：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dealloc
&lt;ul&gt;
&lt;li&gt;_objc_rootDealloc
&lt;ul&gt;
&lt;li&gt;rootDealloc
&lt;ul&gt;
&lt;li&gt;如果有关联对象&lt;/li&gt;
&lt;li&gt;object_dispose
&lt;ul&gt;
&lt;li&gt;objc_destructInstance
&lt;ul&gt;
&lt;li&gt;_object_remove_assocations
&lt;ul&gt;
&lt;li&gt;来释放关联对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};
    
    {
        AssociationsManager manager;
        AssociationsHashMap &amp;amp;associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i-&amp;gt;second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &amp;amp;ref: refs) {
                    if (ref.second.policy() &amp;amp; OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i-&amp;gt;second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector&amp;lt;ObjcAssociation *, 4&amp;gt; laterRefs;

    // release everything (outside of the lock).
    for (auto &amp;amp;i: refs) {
        if (i.second.policy() &amp;amp; OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don&apos;t get released.
            if (deallocating)
                laterRefs.append(&amp;amp;i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later-&amp;gt;releaseHeldValue();
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>删除链表的节点</title><link>https://blog.lab-lawliet.app/posts/article-18-b22b015a971bf870/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-18-b22b015a971bf870/</guid><description>给定单向链表的头指针和一个要删除的节点的值，定义一个函数删除该节点。</description><pubDate>Thu, 29 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本题较为简单，熟悉链表应该达到随手就能解出的程度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;给定单向链表的头指针和一个要删除的节点的值，定义一个函数删除该节点。&lt;/p&gt;
&lt;p&gt;返回删除后的链表的头节点。&lt;/p&gt;
&lt;p&gt;注意：此题对比原题有改动&lt;/p&gt;
&lt;p&gt;示例 1:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点，那么在调用了你的函数之后，该链表应变为 4 -&amp;gt; 1 -&amp;gt; 9.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点，那么在调用了你的函数之后，该链表应变为 4 -&amp;gt; 5 -&amp;gt; 9.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明：&lt;/p&gt;
&lt;p&gt;题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言，你不需要 free 或 delete 被删除的节点&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;一、 思路&lt;/h2&gt;
&lt;p&gt;本题意在移除一个节点，即：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;1 -&amp;gt; 2 -&amp;gt; 3 -&amp;gt; 4 -&amp;gt; 5&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;如果要移除3节点，就要将2节点的next指向4。所以在遍历链表的时候我们在找到移除目标的时候，需要直到上个节点是什么。&lt;/p&gt;
&lt;p&gt;这里引入一个指针 &lt;code&gt;lastNode&lt;/code&gt; 保存上个节点，遍历时 &lt;code&gt;currentNode&lt;/code&gt; 作为指向当前节点的指针。&lt;/p&gt;
&lt;p&gt;遇到目标节点后，将当前节点的 &lt;code&gt;next&lt;/code&gt; 赋值给 &lt;code&gt;lastNode&lt;/code&gt; 的 &lt;code&gt;next&lt;/code&gt;，返回 &lt;code&gt;head&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;二、 双指针解法&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9970728fb624d525.webp&quot; alt=&quot;LeetCode&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func deleteNode(_ head: ListNode?, _ val: Int) -&amp;gt; ListNode? {
    var lastNode: ListNode?
    var currentNode = head
    
    while let node = currentNode {
        if node.val == val {
            if let last = lastNode {
                last.next = node.next
                return head
            }
            else {
                return node.next
            }
        }
        
        lastNode = node
        currentNode = node.next
    }
    
    return head
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 单指针解法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func deleteNode2(_ head: ListNode?, _ val: Int) -&amp;gt; ListNode? {
    if head?.val == val {
        return head?.next
    }
    var currentNode = head
    while let node = currentNode?.next {
        if node.val == val {
            currentNode?.next = node.next
            return head
        }
        else {
            currentNode = currentNode?.next
        }
    }
    return head
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>最小的k个数</title><link>https://blog.lab-lawliet.app/posts/article-40-k-e5db28f6e845d63d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-40-k-e5db28f6e845d63d/</guid><description>输入整数数组 arr ，找出其中最小的 k 个数。例如，输入4、5、1、6、2、7、3、8这8个数字，则最小的4个数字是1、2、3、4。</description><pubDate>Thu, 29 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;输入整数数组 arr ，找出其中最小的 k 个数。例如，输入4、5、1、6、2、7、3、8这8个数字，则最小的4个数字是1、2、3、4。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：arr = [3,2,1], k = 2
输出：[1,2] 或者 [2,1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：arr = [0,1,2,1], k = 1
输出：[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 &amp;lt;= k &amp;lt;= arr.length &amp;lt;= 10000
0 &amp;lt;= arr[i] &amp;lt;= 10000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;一、 暴力解法&lt;/h2&gt;
&lt;p&gt;先排序，然后直接取。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func getLeastNumbers(_ arr: [Int], _ k: Int) -&amp;gt; [Int] {
    if k == 0 { return [] }
    if arr.count &amp;lt;= k { return arr }
    let rearr: [Int] = arr.sorted { $0 &amp;lt;= $1 }
    return Array(rearr[0...k-1])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0f3509a8af617a89.webp&quot; alt=&quot;暴力解法&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作为优秀的程序员，虽然可以这么解，但是不应该。虽然从结果看，Swift的排序算法效率非常高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、 快速排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func getLeastNumbers(_ arr: [Int], _ k: Int) -&amp;gt; [Int] {
    if k == 0 { return [] }
    if arr.count &amp;lt;= k { return arr }
    let result = quickSort(arr)
    return Array(result[0...k-1])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 辅助空间&lt;/h3&gt;
&lt;p&gt;此法较为容易理解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func quickSort&amp;lt;T: Comparable&amp;gt;(_ arr: [T]) -&amp;gt; [T] {
    guard let base = arr.first, arr.count &amp;gt; 1 else {
        return arr
    }
    
    var left: [T] = []
    var right: [T] = []
    var result: [T] = []

    for index in 1..&amp;lt;arr.count {
        let value = arr[index]
        if value &amp;lt; base {
            left.append(value)
        }
        else {
            right.append(value)
        }
    }
        
    result = quickSort(left)
    result.append(base)
    result.append(contentsOf: quickSort(right))
    
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4e4b0b0bab70a9fa.webp&quot; alt=&quot;辅助空间快速排序&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 原地快排&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func getLeastNumbers(_ arr: [Int], _ k: Int) -&amp;gt; [Int] {
    if k == 0 { return [] }
    if arr.count &amp;lt;= k { return arr }
    var result = arr
    quickSort(&amp;amp;result, 0, arr.count - 1, k)
    return Array(result[0...k-1])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bb1f942727ee9ae4.webp&quot; alt=&quot;原地快排&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;原地快速排序&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func quickSort&amp;lt;T: Comparable&amp;gt;(_ arr: inout [T], _ low: Int, _ high: Int, _ k: Int) {
    let res = partition(&amp;amp;arr, low: low, high: high)
    if res == k {
        return
    }
    else if res &amp;gt; k {
        quickSort2(&amp;amp;arr, low, res-1, k)
    }
    else {
        quickSort2(&amp;amp;arr, res+1, high, k)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;确定基准元素的下标&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func partition&amp;lt;T: Comparable&amp;gt;(_ arr: inout [T], low: Int, high: Int) -&amp;gt; Int {
    let root = arr[high]
    var index = low
    for i in low...high {
        if arr[i] &amp;lt; root {
            if i != index {
                arr.swapAt(i, index)
            }
            index = index+1
        }
    }
    
    if high != index {
        arr.swapAt(high, index)
    }
    return index
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 优先队列&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;什么是优先队列
&lt;ul&gt;
&lt;li&gt;按照一定条件给定的条件&lt;/li&gt;
&lt;li&gt;每次加入元素都会进行排序&lt;/li&gt;
&lt;li&gt;优先级最高的元素上浮到首位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本题我们可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;限制队列元素数量&lt;/li&gt;
&lt;li&gt;更大的元素具有更高优先级&lt;/li&gt;
&lt;li&gt;当队列达到k值后&lt;/li&gt;
&lt;li&gt;移除最高优先级的元素&lt;/li&gt;
&lt;li&gt;最后剩下的就是最小的k个元素了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;首先我们需要实现一下优先队列&lt;/p&gt;
&lt;h4&gt;优先队列实现&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;public struct SwiftPriorityQueue&amp;lt;Element&amp;gt; {
    private let _hasHigherPriority: (Element, Element) -&amp;gt; Bool
    private let _isEqual: (Element, Element) -&amp;gt; Bool

    private var _elements = [Element]()

    public init(hasHigherPriority: @escaping (Element, Element) -&amp;gt; Bool, isEqual: @escaping (Element, Element) -&amp;gt; Bool) {
        _hasHigherPriority = hasHigherPriority
        _isEqual = isEqual
    }

    public mutating func enqueue(_ element: Element) {
        _elements.append(element)
        bubbleToHigherPriority(_elements.count - 1)
    }

    public func peek() -&amp;gt; Element? {
        return _elements.first
    }
    
    public func array() -&amp;gt; [Element] {
        return _elements
    }

    public var isEmpty: Bool {
        return _elements.count == 0
    }
    
    public var count: Int {
        return _elements.count
    }

    public mutating func dequeue() -&amp;gt; Element? {
        guard let front = peek() else {
            return nil
        }

        removeAt(0)

        return front
    }

    public mutating func remove(_ element: Element) {
        for i in 0 ..&amp;lt; _elements.count {
            if _isEqual(_elements[i], element) {
                removeAt(i)
                return
            }
        }
    }

    private mutating func removeAt(_ index: Int) {
        let removingLast = index == _elements.count - 1
        if !removingLast {
            _elements.swapAt(index, _elements.count - 1)
        }

        _ = _elements.popLast()

        if !removingLast {
            bubbleToHigherPriority(index)
            bubbleToLowerPriority(index)
        }
    }

    private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex

        while unbalancedIndex &amp;gt; 0 {
            let parentIndex = (unbalancedIndex - 1) / 2
            guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }
            _elements.swapAt(unbalancedIndex, parentIndex)
            unbalancedIndex = parentIndex
        }
    }

    private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {
        precondition(initialUnbalancedIndex &amp;gt;= 0)
        precondition(initialUnbalancedIndex &amp;lt; _elements.count)

        var unbalancedIndex = initialUnbalancedIndex
        while true {
            let leftChildIndex = unbalancedIndex * 2 + 1
            let rightChildIndex = unbalancedIndex * 2 + 2

            var highestPriorityIndex = unbalancedIndex

            if leftChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = leftChildIndex
            }

            if rightChildIndex &amp;lt; _elements.count &amp;amp;&amp;amp; _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {
                highestPriorityIndex = rightChildIndex
            }

            guard highestPriorityIndex != unbalancedIndex else { break }
            _elements.swapAt(highestPriorityIndex, unbalancedIndex)

            unbalancedIndex = highestPriorityIndex
        }
    }
}

extension SwiftPriorityQueue : CustomDebugStringConvertible {
    public var debugDescription: String {
        return _elements.debugDescription
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;题解&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;func getLeastNumbers(_ arr: [Int], _ k: Int) -&amp;gt; [Int] {
    var stack = SwiftPriorityQueue&amp;lt;Int&amp;gt;(hasHigherPriority: { $0 &amp;gt; $1 }, isEqual: { $0 == $1 })
    
    arr.forEach { item in
        stack.enqueue(item)
        if stack.count &amp;gt; k {
            stack.dequeue()// 最高优先级的出列（最大的出去）
        }
    }
    
    return stack.array()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c6813a5e80349acd.webp&quot; alt=&quot;优先队列&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、 逻辑冒泡&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建固定容量的容器&lt;/li&gt;
&lt;li&gt;遍历元素&lt;/li&gt;
&lt;li&gt;存满后冒泡排序&lt;/li&gt;
&lt;li&gt;继续插入到头&lt;/li&gt;
&lt;li&gt;遍历直到遇到大于等于自己的就停止&lt;/li&gt;
&lt;li&gt;移除最后一个元素&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func getLeastNumbers5(_ arr: [Int], _ k: Int) -&amp;gt; [Int] {
    var result: [Int] = []
    
    guard k &amp;gt; 0 else {
        return result
    }
    
    arr.forEach { item in
        if result.count == k, let min = result.first {
            
            if item &amp;lt;= min {
                result.insert(item, at: 0)
                result.removeLast()
            }
            else {
                result.insert(item, at: 0)
                
                for i in 0..&amp;lt;(result.count - 1) {
                    if result[i] &amp;gt; result[i+1] {
                        result.swapAt(i, i+1)
                    }
                    else {
                        break
                    }
                }
                
                result.removeLast()
            }
        }
        else {
            result.append(item)
            if result.count == k {
                result = result.sorted()
            }
        }
    }
    
    return result
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1cae782bbea61b31.webp&quot; alt=&quot;优先队列&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>基于iCloud构建游戏内排行榜</title><link>https://blog.lab-lawliet.app/posts/article-02-icloud-2f98884d9484a2b8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-02-icloud-2f98884d9484a2b8/</guid><description>昨天晚上打开我的扫雷游戏，发现排行榜出Bug了：无尽榜单显示不出来了。</description><pubDate>Wed, 28 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;昨天晚上打开我的扫雷游戏，发现排行榜出Bug了：无尽榜单显示不出来了。&lt;/p&gt;
&lt;p&gt;虽然很快改好发版了。觉得有必要整理一下我的排行榜的实现思路。以为后续优化提供方向。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;推荐阅读：&lt;a href=&quot;/posts/article-icloud-72e8944d89603a1a/&quot;&gt;基于iCloud构建用户体系&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9a51ad3faa5d8fb1.webp&quot; alt=&quot;Elic&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一、 榜单基础内容&lt;/h2&gt;
&lt;p&gt;排序逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;游戏等级：越高越靠前&lt;/li&gt;
&lt;li&gt;完成时间：越短越靠前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;显示元素：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等级&lt;/li&gt;
&lt;li&gt;时间&lt;/li&gt;
&lt;li&gt;用户昵称&lt;/li&gt;
&lt;li&gt;头像&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 数据流&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/87f3e5ae44eb2ff1.webp&quot; alt=&quot;数据流&quot; /&gt;&lt;/p&gt;
&lt;p&gt;基础数据中除了 &lt;code&gt;游戏等级&lt;/code&gt; &lt;code&gt;完成时间&lt;/code&gt; 是在 &lt;code&gt;Rank record&lt;/code&gt; 中，&lt;code&gt;Nickname&lt;/code&gt; &lt;code&gt;Avatar&lt;/code&gt; 都是要二次查找的。由于不存在服务端，所以这些数据处理都要客户端自行包装。&lt;/p&gt;
&lt;h3&gt;2.1 Rank Table 无尽游戏记录表&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2ee137cfefdd3edd.webp&quot; alt=&quot;ElicRankTable&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.2 User Table 用户表&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/04ef6ad6a4641e04.webp&quot; alt=&quot;ElicUserTable&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 数据逻辑&lt;/h3&gt;
&lt;p&gt;下面描述一下榜单的数据逻辑吧。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;玩家成功完成一场无尽模式游戏
&lt;ul&gt;
&lt;li&gt;前提是 &lt;code&gt;CloudKit&lt;/code&gt; 可用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;Rank table&lt;/code&gt; 中已有该用户创建的记录
&lt;ul&gt;
&lt;li&gt;如果成绩更好：更新最新游戏记录&lt;/li&gt;
&lt;li&gt;否则丢弃本次游戏记录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;Rank table&lt;/code&gt; 中没有该用户创建的记录
&lt;ul&gt;
&lt;li&gt;创建一条记录，保存到 &lt;code&gt;Rank table&lt;/code&gt; 中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用户进入榜单列表拉取数据 &lt;code&gt;Rank record&lt;/code&gt; 数组
&lt;ul&gt;
&lt;li&gt;由于并不存在后端，所以数据关联的逻辑需要自己处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;遍历榜单数据获取用户ID数组
&lt;ul&gt;
&lt;li&gt;遍历用户ID数组，从UserTable中批量查询出对应的 &lt;code&gt;User record&lt;/code&gt; 数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;合并 &lt;code&gt;Rank record&lt;/code&gt; &amp;amp; &lt;code&gt;User record&lt;/code&gt; 为 &lt;code&gt;Rank item data&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Rank item data&lt;/code&gt; 为 &lt;code&gt;Cell&lt;/code&gt; 展示的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cell&lt;/code&gt; 展示数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 用户头像&lt;/h2&gt;
&lt;h3&gt;3.1 Avatar Table&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1327898781fd27d9.webp&quot; alt=&quot;ElicAvatarTable&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 图片资源加载&lt;/h3&gt;
&lt;p&gt;虽然 &lt;code&gt;CloudKit&lt;/code&gt; 提供了保存资源的能力，但我们去请求资源的时候，它就是直接完整的返回整个数据了。&lt;/p&gt;
&lt;p&gt;所以如果将头像数据直接保存在 &lt;code&gt;User record&lt;/code&gt; 中的话，列表的请求速度就很恶心了。（虽然一开始我就是这么做的😂）&lt;/p&gt;
&lt;p&gt;在上一步的图中可以看到，&lt;code&gt;Avatar&lt;/code&gt; 是独立有一张表来存的。这样就为我的 &lt;code&gt;异步加载图片&lt;/code&gt; 创造了可能。&lt;/p&gt;
&lt;p&gt;参考主流图片缓存框架的思路简单封装了一下。在 &lt;code&gt;Cell&lt;/code&gt; 复用的时候异步加载图片资源。&lt;/p&gt;
&lt;p&gt;由于不存在URL这种唯一标识，我采用了 &lt;code&gt;User record id + Avatar modificationDate&lt;/code&gt; 为图片的唯一标识。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1705ab1933d3c2bd.webp&quot; alt=&quot;异步加载图片&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然用图床URL的形式更方便。但是曾经用七牛做博客图床，然后现在图全挂了，还要自己绑域名（虽然我有）。想想算了还是苹果靠谱点（不要钱）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;四、 违规控制&lt;/h2&gt;
&lt;p&gt;由于榜单内的内容包含了用户自定义的部分如：&lt;code&gt;Nickname&lt;/code&gt; &lt;code&gt;Avatar&lt;/code&gt;。而且有人的地方就有人搞事。&lt;/p&gt;
&lt;p&gt;如果有不好的用户用了一些不好的文案或者头像，对于项目来说是有风险的。平时可能还好，你要是审核的时候被看到那不是妥妥要把你拒掉。&lt;/p&gt;
&lt;p&gt;但作为独立开发者（想省钱），我又不可能去接什么审核服务。况且场景本身就把违规的范围控制的很小了，最多两个榜单200条数据。我没事刷一刷看看就行了。&lt;/p&gt;
&lt;h3&gt;4.1 管理员&lt;/h3&gt;
&lt;p&gt;为了能快速获得违规用户的ID，我设置了用户角色的字段 &lt;code&gt;manager&lt;/code&gt;。拥有该权限的用户直接点击榜单 &lt;code&gt;Cell&lt;/code&gt; 就可以复制 &lt;code&gt;User record id&lt;/code&gt;。然后进行下一步处理。&lt;/p&gt;
&lt;h3&gt;4.2 违规内容处理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;昵称违规
&lt;ul&gt;
&lt;li&gt;直接置空&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;头像违规
&lt;ul&gt;
&lt;li&gt;删除头像&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;最开始这里是直接删除 &lt;code&gt;Avatar Table&lt;/code&gt; 中由该用户创建的记录，但是有问题的。思考一下欢迎在评论中回复你想到的问题。其实挺简单的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.3 屏蔽用户&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;User Table&lt;/code&gt; 中有个 &lt;code&gt;blocked&lt;/code&gt; 字段，用以屏蔽用户。&lt;/p&gt;
&lt;p&gt;这种用户可以正常用 &lt;code&gt;App&lt;/code&gt;，游戏记录也会正常记录，但是在榜单列表中会被过滤掉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;到现在为止，只屏蔽过一个用户，用💛图片，我给他删除一次，还继续上传，然后我就把他屏蔽了🤷‍♂️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;五、 优化空间&lt;/h2&gt;
&lt;h3&gt;5.1 违规处理&lt;/h3&gt;
&lt;p&gt;现在违规处理是复制了 &lt;code&gt;User record id&lt;/code&gt; 去后台处理。感觉可以直接把处理的动作直接放在App里，处理起来也更高效。&lt;/p&gt;
&lt;h3&gt;5.2 异步加载图片&lt;/h3&gt;
&lt;p&gt;现在的封装还挺简单，有空了完善一下开源出来，也方便自己后续项目使用。&lt;/p&gt;
&lt;h3&gt;5.3 CloudKit 请求&lt;/h3&gt;
&lt;p&gt;现在在请求方面做了简单的封装，但主要是基于业务的。难以直接套用，还是抽空封装一下把。&lt;/p&gt;
</content:encoded></item><item><title>前缀和</title><link>https://blog.lab-lawliet.app/posts/article-01-b66b75d81eceda84/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-01-b66b75d81eceda84/</guid><pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、什么是前缀和？&lt;/h1&gt;
&lt;h2&gt;1.1 一维前缀和&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个一维数组X，和它的前缀和数组Y。&lt;/li&gt;
&lt;li&gt;那么满足：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Y[0] = X[0]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Y[1] = X[0] + X[1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Y[2] = X[0] + X[1] + X[2]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Y[n] = X[0] + X[1] + ... + X[n]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2 二维前缀和&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个二维数组A，和他的前缀和数组B&lt;/li&gt;
&lt;li&gt;那么满足：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;B[0,0] = A[0,0]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B[0,1] = A[0,0] + A[0,1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B[1,0] = A[0,0] + A[1,0]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B[1,1] = A[0,0] + A[0,1] + A[1,0] + A[1,1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;简单描述就是一片矩形区域内的和&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、计算前缀和&lt;/h1&gt;
&lt;h2&gt;2.1 一维前缀和&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let nums: [Int] = [...]
// 前缀和数组
var pSum: [Int] = Array(repeating: 0, count: nums.count)

for i in 0..&amp;lt;nums.count {
    if i == 0 {
        pSum[0] = nums[0]
    }
    else {
        pSum[i] = pSum[i - 1] + nums[i]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.2 二维前缀和&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;二维前缀和其实就是一个矩阵内值的和，而矩阵又可以由两个列数或行数少一的子矩阵组合后，去除重复的部分，然后再加上右下角的值来构成。&lt;/li&gt;
&lt;li&gt;即
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;B[x,y] = B[x-1,y] + B[x,y-1] - B[x-1, y-1] + A[x,y]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let nums: [[Int]] = [...]
var pSum: [[Int]] = Array(repeating: Array(repeating: 0, count: nums.first?.count ?? 0),
                            count: nums.count)

for y in 0..&amp;lt;nums.count {
    for x in 0..&amp;lt;nums[y].count {
        if x == 0 &amp;amp;&amp;amp; y == 0 {
            pSum[0][0] = nums[0][0]
        }
        else if x == 0 {
            pSum[0][y] = pSum[0][y-1] + nums[x][y]
        }
        else if y == 0 {
            pSum[x][0] = pSum[x-1][0] + nums[x][y]
        }
        else {
            pSum[x][y] = pSum[x-1][y] + pSum[x][y-1] - pSum[x-1][y-1] + nums[x][y]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;三、前缀和的应用&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;前缀和是一种预处理，用来降低数据查询的时间复杂度。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个n长度的数组，进行m次查询，每次随机长度，输出每次的和&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;上面的例子如果暴力解的话，每次都要重头开始到指定下标进行遍历&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果使用前缀和，进行了预处理之后，可以直接查出结果&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假设&lt;code&gt;l&lt;/code&gt; &lt;code&gt;r&lt;/code&gt; 为给定的区间，计算区间内的和&lt;/li&gt;
&lt;li&gt;&lt;code&gt;result = x[r] - x[l-1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;时间复杂度为&lt;code&gt;O(n+m)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;参考&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/107778275&quot;&gt;前缀和技巧&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/117569086&quot;&gt;【朝夕的ACM笔记】算法基础-前缀和&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>同余性质</title><link>https://blog.lab-lawliet.app/posts/article-02-29d7bd95b2591ae4/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-02-29d7bd95b2591ae4/</guid><description>在看题解的时候发现了一个非常简便的解法，用到了「同余性质」</description><pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、题目：&lt;a href=&quot;https://leetcode-cn.com/problems/continuous-subarray-sum&quot;&gt;⭐️⭐️523. 连续的子数组和&lt;/a&gt;&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt; 给你一个整数数组 nums 和一个整数 k ，编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组：
 
 子数组大小 至少为 2 ，且
 子数组元素总和为 k 的倍数。
 如果存在，返回 true ；否则，返回 false 。
 
 如果存在一个整数 n ，令整数 x 符合 x = n * k ，则称 x 是 k 的一个倍数。
 
 示例 1：
 
 输入：nums = [23,2,4,6,7], k = 6
 输出：true
 解释：[2,4] 是一个大小为 2 的子数组，并且和为 6 。
 示例 2：
 
 输入：nums = [23,2,6,4,7], k = 6
 输出：true
 解释：[23, 2, 6, 4, 7] 是大小为 5 的子数组，并且和为 42 。
 42 是 6 的倍数，因为 42 = 7 * 6 且 7 是一个整数。
 示例 3：
 
 输入：nums = [23,2,6,4,7], k = 13
 输出：false
  
 
 提示：
 
 1 &amp;lt;= nums.length &amp;lt;= 10^5
 0 &amp;lt;= nums[i] &amp;lt;= 10^9
 0 &amp;lt;= sum(nums[i]) &amp;lt;= 2^31 - 1
 1 &amp;lt;= k &amp;lt;= 2^31 - 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;二、「同余性质」&lt;/h1&gt;
&lt;p&gt;在看题解的时候发现了一个非常简便的解法，用到了「同余性质」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想要A-B为k的倍数，只需要a、b模k相同即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;证明&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;假设 A = x&lt;em&gt;k + a, B = y&lt;/em&gt;k + b，A-B为k的倍数&lt;/li&gt;
&lt;li&gt;A - B = (x&lt;em&gt;k + a) - (y&lt;/em&gt;k + b) = (x+y)&lt;em&gt;k + (a-b) = z&lt;/em&gt;k&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a-b&lt;/code&gt;也为k的倍数，而&lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt;为&lt;code&gt;A&lt;/code&gt;、&lt;code&gt;B&lt;/code&gt;模&lt;code&gt;k&lt;/code&gt;的值，故两者都在[0,k)区间内&lt;/li&gt;
&lt;li&gt;所以：a=b&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;三、使用同余性质解决上面的题目&lt;/h1&gt;
&lt;h2&gt;3.1 转化&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;连续子数组元素和&lt;/code&gt;转化为求两个&lt;code&gt;下标从0开始连续子数组和的差值&lt;/code&gt;问题。&lt;/p&gt;
&lt;h2&gt;3.2 分析&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;元素依次累加，创建一个数组用来存下标和模k的值&lt;/li&gt;
&lt;li&gt;得到的值模k
&lt;ul&gt;
&lt;li&gt;如果map中不存在该值，根据同余性质，也就是不满足A-B为k的倍数，继续查找&lt;/li&gt;
&lt;li&gt;如果存在，则说明满足条件，可以return了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.3 编码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func checkSubarraySum4(_ nums: [Int], _ k: Int) -&amp;gt; Bool {
    if nums.count &amp;lt; 2 {
        return false
    }
    var moMap: [Int : Int] = [:]
    moMap[0] = -1 // 这样才能正确的吧从0开始复合条件的找出来
    var sum: Int = 0
    for (index, n) in nums.enumerated() {
        sum += n
        let mo = sum % k
        print(&quot;sum\(sum) &amp;gt; \(mo)&quot;)
        if moMap.keys.contains(mo) {
            if let sameMoIndex = moMap[mo], index - sameMoIndex &amp;gt; 1 { // 长度至少为2
                return true
            }
        }
        else {
            moMap[mo] = index
        }
    }
    return false
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>快速幂</title><link>https://blog.lab-lawliet.app/posts/article-03-d8a2dec8aba1197c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-03-d8a2dec8aba1197c/</guid><description>幂运算是我们最常用的运算之一。如果我们要求 x 的 N 次幂，那么想当然的就会写出一个 N 次的循环，然后累乘得到结果。这种幂运算的复杂度是O(N)。</description><pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;幂运算是我们最常用的运算之一。如果我们要求 x 的 N 次幂，那么想当然的就会写出一个 N 次的循环，然后累乘得到结果。这种幂运算的复杂度是O(N)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var res = 1
for _ in 0..&amp;lt;n {
    res *= x
}
return res
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;一、 快速幂&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;那么有没有更快的运算方式呢？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在计算机领域有一种常用的快速幂算法：&lt;code&gt;蒙哥马利幂（Montgomery reduction）算法&lt;/code&gt;。将复杂度从&lt;em&gt;&lt;strong&gt;O(N)&lt;/strong&gt;&lt;/em&gt; 降到了 &lt;em&gt;&lt;strong&gt;O(logN)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;1.1 思路一：递归&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;若 N 为偶数，可以先求 x&amp;lt;sup&amp;gt;N/2&amp;lt;/sup&amp;gt;，然后再平方。&lt;/li&gt;
&lt;li&gt;若 N 为奇数，可以先求 x&amp;lt;sup&amp;gt;(N-1)/2&amp;lt;/sup&amp;gt;，然后再平方，再乘以x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;快速幂的最基础思想就是上面两条了。&lt;/p&gt;
&lt;p&gt;求解 x&amp;lt;sup&amp;gt;N/2&amp;lt;/sup&amp;gt;，x&amp;lt;sup&amp;gt;(N-1)/2&amp;lt;/sup&amp;gt; 时也可以用到上面的思路，每次将指数除以2，每次都可以将复杂度降低一半，知道最后指数为1。&lt;/p&gt;
&lt;h4&gt;Swift实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里不考虑大数，仅作为思路展示&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func quickpow(_ x: Int, _ n: Int) -&amp;gt; Int {
    if n == 0 {// 直到最后指数为0
        return 1
    }
    let res = quickpow(x, n/2)
    if n%2 != 0 {// n 为奇数
        return res * res * x
    }
    // n为偶数
    return res * res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 思路二：二进制拆分&lt;/h3&gt;
&lt;p&gt;我们以 x&amp;lt;sup&amp;gt;14&amp;lt;/sup&amp;gt; 为例进行推导。&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;
&amp;lt;font size = 30&amp;gt;
x&amp;lt;sup&amp;gt;14&amp;lt;sub&amp;gt;(10)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;1110&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;
&amp;lt;/font&amp;gt;
&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;换个方向，更直观：&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;
&amp;lt;font size = 30&amp;gt;
x&amp;lt;sup&amp;gt;1110&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; = x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;
&amp;lt;/font&amp;gt;
&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;推论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设 x 的 N 次幂等于 res
&lt;ul&gt;
&lt;li&gt;1: 在指数 N 的二进制形式下，从最低位开始左移一位，x自乘一次（和指数N左移一位保持一致）&lt;/li&gt;
&lt;li&gt;如果当前位为1
&lt;ul&gt;
&lt;li&gt;当前位a到最低位，中间位全部置0时表示的值b，x&amp;lt;sup&amp;gt;b&amp;lt;/sup&amp;gt;的累乘&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果当前位位0
&lt;ul&gt;
&lt;li&gt;执行1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;循环直到指数N等于0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;当前位&lt;/th&gt;
&lt;th&gt;指数b&lt;/th&gt;
&lt;th&gt;res&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;111&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;0&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;code&gt;1&lt;/code&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;code&gt;1&lt;/code&gt;10&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;110&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;x&amp;lt;sup&amp;gt;10&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;100&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt; * x&amp;lt;sup&amp;gt;1000&amp;lt;sub&amp;gt;(2)&amp;lt;/sub&amp;gt;&amp;lt;/sup&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;Swift实现&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里不考虑大数，仅作为思路展示&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;func quickpow(_ x: Int, _ n: Int) -&amp;gt; Int {
    var res = 1
    var x = x
    var n = n
    
    while n &amp;gt; 0 {
        if n &amp;amp; 1 == 1 { // 如果n的当前末位为1
            res *= x // res乘上当前的x
        }
        x *= x// x自乘，当前指数左移一位
        n &amp;gt;&amp;gt;= 1// n右移一位，想当于 n / 2
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/95902286&quot;&gt;Pecco-算法学习笔记(4)：快速幂&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/112312044&quot;&gt;【朝夕的ACM笔记】杂项-快速幂&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;二、 快速幂取模&lt;/h2&gt;
&lt;h3&gt;2.1 模运算规律&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;(a + b) % p = (a % p + b % p) % p&lt;/li&gt;
&lt;li&gt;(a - b) % p = (a % p - b % p + p) % p&lt;/li&gt;
&lt;li&gt;(a * b) % p = (a % p * b % p) % p&lt;/li&gt;
&lt;li&gt;a&amp;lt;sup&amp;gt;b&amp;lt;/sup&amp;gt; % p = ((a % p)&amp;lt;sup&amp;gt;b&amp;lt;/sup&amp;gt;) % p&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;根据这个规律，可以推导出&lt;a href=&quot;/posts/article-02-29d7bd95b2591ae4/&quot;&gt;同余性质&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.2 常见问题场景&lt;/h3&gt;
&lt;p&gt;我们经常会看到 &lt;code&gt;输出结果对x取模&lt;/code&gt;。这种题目一般考察两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在原算法的基础上，多一个取模运算来考察你对取模运算规律的掌握&lt;/li&gt;
&lt;li&gt;大数据时数据增长太快，64 位甚至 128 位的整形无法表示
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/article-14-ii-390b57350a02bdfb/&quot;&gt;「剑指Offer」14-II.剪绳子&lt;/a&gt;是个典型问题
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/tan-xin-kuai-su-mi-by-happy-austin9mq-6bxz/&quot;&gt;题解&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 利用快速幂，控制数据范围&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;14-II.剪绳子&lt;/code&gt; 中通过快速幂，结合模运算规律，控制了幂运算结果的范围不会超过 &lt;code&gt;1_000_000_007&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func modPow(_ x: Int, _ n: Int) -&amp;gt; Int {
    var res = 1
    var x = x
    var n = n
    
    while n &amp;gt; 0 {
        if n &amp;amp; 1 == 1 {
            res *= x
            res %= 1_000_000_007 // 限制了数据范围
        }
        x *= x
        x %= 1_000_000_007 // 限制了数据范围
        n &amp;gt;&amp;gt;= 1
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>II.剪绳子</title><link>https://blog.lab-lawliet.app/posts/article-14-ii-390b57350a02bdfb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-14-ii-390b57350a02bdfb/</guid><description>给你一根长度为 n 的绳子，请把绳子剪成整数长度的 m 段（m、n都是整数，n&gt;1并且m&gt;1），每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]k[1]...*k[m - 1] 可能的最大乘积是多少？例如，当绳子的长度是8时，我们把它剪成长度分别为2、3、3的三段，此时</description><pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给你一根长度为 n 的绳子，请把绳子剪成整数长度的 m 段（m、n都是整数，n&amp;gt;1并且m&amp;gt;1），每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]&lt;em&gt;k[1]&lt;/em&gt;...*k[m - 1] 可能的最大乘积是多少？例如，当绳子的长度是8时，我们把它剪成长度分别为2、3、3的三段，此时得到的最大乘积是18。&lt;/p&gt;
&lt;p&gt;答案需要取模 1e9+7（1000000007），如计算初始结果为：1000000008，请返回 1。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;2 &amp;lt;= n &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与&lt;a href=&quot;https://leetcode-cn.com/problems/integer-break/&quot;&gt;主站 343 题&lt;/a&gt;相同&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 常规思路与问题&lt;/h2&gt;
&lt;p&gt;咋一看，和&lt;a href=&quot;/posts/article-14-i-d74b8496056611fd/&quot;&gt;14-I.剪绳子&lt;/a&gt;没什么区别嘛～ 不就是结果要取模嘛～&lt;/p&gt;
&lt;h3&gt;2.1 一般思路&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;那你就上当了&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我们用动态规划的解法来试试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 4 {
        return n - 1
    }
    var dp = [Int](repeating: 1, count: n + 1)
    for i in 3...n {
        for j in 2..&amp;lt;i {
            dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
        }
    }
    return dp[n] % 1_000_000_007
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;试试两个测试用例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10 - 36
2 - 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;很正常没问题嘛～🤷‍♂️&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2.2 试试120&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7c06628eee5caf8a.webp&quot; alt=&quot;120&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;居然出错了？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2.3 数据长度问题&lt;/h3&gt;
&lt;p&gt;请注意本题的数据范围 &lt;code&gt;2 &amp;lt;= n &amp;lt;= 1000&lt;/code&gt; 和第一题的 &lt;code&gt;2 &amp;lt;= n &amp;lt;= 58&lt;/code&gt;，差别很大。&lt;/p&gt;
&lt;p&gt;上面报错就是因为数据超出了范围了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;那你可能会想到，那我换 &lt;code&gt;Int64&lt;/code&gt; ？我换 &lt;code&gt;long&lt;/code&gt; ？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然鹅并不行&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;那我每次纯数据都取一下模？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;更不行，中间数据都变了，结果当然更错&lt;/p&gt;
&lt;h4&gt;我们用 &lt;code&gt;Decimal&lt;/code&gt; 来接收看看&lt;code&gt;120&lt;/code&gt;时到底是多大：&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8e455de355c7b78b.webp&quot; alt=&quot;12157665459056928801&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;120&lt;/code&gt; 时就已经 &lt;code&gt;20位&lt;/code&gt; 了，更何况本题的范围最大&lt;code&gt;1000&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们就不要再数据类型的选择上进行纠结了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.4 &lt;code&gt;Decimal&lt;/code&gt; 解法&lt;/h3&gt;
&lt;p&gt;这里将元素使用 &lt;code&gt;Decimal&lt;/code&gt; 进行承载&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let base = NSDecimalNumber(decimal: Decimal(1_000_000_007))

func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 4 {
        return n - 1
    }

    var dp = [Decimal](repeating: 1, count: n + 1)
    for i in 3...n {
        for j in 2..&amp;lt;i {
            dp[i] = max(dp[i], max(Decimal(j) * Decimal(i - j), Decimal(j) * dp[i - j]))
        }
    }
    
    let handler = NSDecimalNumberHandler(roundingMode: .down, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)

    let result = NSDecimalNumber(decimal: dp[n])
    let baseCount = result.dividing(by: base).rounding(accordingToBehavior: handler)

    let tp = result.subtracting(base.multiplying(by: baseCount))
    
    return Int(truncating: tp)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/aaf15e5f16de3ff2.webp&quot; alt=&quot;Decimal&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e2e7fad034f7668.webp&quot; alt=&quot;Decimal&quot; /&gt;&lt;/p&gt;
&lt;p&gt;结果到 255 的时候还是错了，还是丢失了精度。毕竟 &lt;code&gt;NSDecimalNumber&lt;/code&gt; 也是有范围的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/387d1dd1b8546d52.webp&quot; alt=&quot;Decimal&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;感觉想继续用动态规划不太现实了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.5 补充 Java 的 BigInteger 可以动态规划&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.math.BigInteger;
class Solution {
    public int cuttingRope(int n) {
        BigInteger[] dp = new BigInteger[n + 1];
         Arrays.fill(dp, BigInteger.valueOf(1));
        // dp[1] = BigInteger.valueOf(1);
        for(int i = 3; i &amp;lt; n + 1; i++){
            for(int j = 1; j &amp;lt; i; j++){
                dp[i] = dp[i].max(BigInteger.valueOf(j * (i - j))).max(dp[i - j].multiply(BigInteger.valueOf(j)));
            }
        }
        return dp[n].mod(BigInteger.valueOf(1000000007)).intValue();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;链接：&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/jian-dan-li-jie-dong-tai-gui-hua-xun-hua-4g3o/&quot;&gt;作者：ollieq&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里就不再纠结数据类型了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 贪心-循环求余&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;尽可能多的切成长度为3的，乘积最大&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.1 解法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 4 {
        return n - 1
    }
    var res = 1
    var n = n
    while n &amp;gt; 4 {
        res = res * 3 % 1_000_000_007
        n -= 3
    }
    return res * n % 1_000_000_007
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;时间复杂度&lt;/th&gt;
&lt;th&gt;空间复杂度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/50709f759605f910.webp&quot; alt=&quot;真的是快&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;真快&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;3.2 循环求余&lt;/h3&gt;
&lt;p&gt;详细可以移步&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/&quot;&gt;这个题解&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a0550a0d58311ff7.webp&quot; alt=&quot;循环求余&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四、贪心-快速幂&lt;/h2&gt;
&lt;p&gt;核心是通过 &lt;code&gt;快速幂&lt;/code&gt; 算法及模运算规律，控制数据范围。详细了解可以移步&lt;a href=&quot;/posts/article-03-d8a2dec8aba1197c/&quot;&gt;快速幂&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/solution/tan-xin-kuai-su-mi-by-happy-austin9mq-6bxz/&quot;&gt;LeetCode&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.1 解法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 4 {
        return n - 1
    }
    let a = n / 3, b = n % 3
    
    if b == 0 {
        return modPow(3, a) % 1_000_000_007
    }
    else if b == 1 {
        return modPow(3, a - 1) * 4 % 1_000_000_007
    }
    else {
        return modPow(3, a) * 2 % 1_000_000_007
    }
}

func modPow(_ x: Int, _ n: Int) -&amp;gt; Int {
    var res = 1
    var x = x
    var n = n
    
    while n &amp;gt; 0 {
        if n &amp;amp; 1 == 1 {
            res *= x
            res %= 1_000_000_007 // 限制了数据范围
        }
        x *= x
        x %= 1_000_000_007 // 限制了数据范围
        n &amp;gt;&amp;gt;= 1
    }
    
    return res
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0abe49d7b9fb8de1.webp&quot; alt=&quot;快速幂&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>解密被加密的数据库文件</title><link>https://blog.lab-lawliet.app/posts/article-25-0d587a1ec0402ba6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-25-0d587a1ec0402ba6/</guid><description>进来有一个个人项目的灵感，于是找到了有提供类似功能的产品，准备逆向研究一波。拿到了IPA包，也找到了数据库文件。但是在准备看下数据库文件的时候遇到了问题。</description><pubDate>Sat, 24 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;进来有一个个人项目的灵感，于是找到了有提供类似功能的产品，准备逆向研究一波。拿到了IPA包，也找到了数据库文件。但是在准备看下数据库文件的时候遇到了问题。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;数据库加密了🤷‍♂️&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;作为一个&lt;em&gt;&lt;strong&gt;秃头的&lt;/strong&gt;&lt;/em&gt;逆向工程师（并不是），我们怎么能轻言放弃呢？&lt;/p&gt;
&lt;p&gt;确定本次逆向目标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[X] 获取数据库密码&lt;/li&gt;
&lt;li&gt;[X] 清除数据库密码&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;开森～都完成了👌&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、 定位数据库SDK&lt;/h2&gt;
&lt;p&gt;先看看IPA中framework文件夹中有什么吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1bd70adb438ccbab.webp&quot; alt=&quot;Framework&quot; /&gt;&lt;/p&gt;
&lt;p&gt;难道用了 &lt;code&gt;CoreData&lt;/code&gt; ？我们再进一步分析&lt;/p&gt;
&lt;h3&gt;分析 MachO&lt;/h3&gt;
&lt;p&gt;我们使用 &lt;code&gt;Hopper&lt;/code&gt; 进行分析，将 &lt;code&gt;IPA&lt;/code&gt; 中的可执行文件（&lt;code&gt;MachO&lt;/code&gt;）丢进 &lt;code&gt;Hopper&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/414bb0466078ac8e.webp&quot; alt=&quot;Hopper&quot; /&gt;&lt;/p&gt;
&lt;p&gt;哦吼～ 很明显用的 &lt;code&gt;FMDB&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;二、 定位密码函数&lt;/h2&gt;
&lt;p&gt;查看 &lt;code&gt;FMDB&lt;/code&gt; 的 &lt;code&gt;API&lt;/code&gt; 寻找切入点。找到了 &lt;code&gt;- (BOOL)setKey:(NSString*)key;&lt;/code&gt;，准备 &lt;code&gt;Hook&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface FMDatabase : NSObject {
    /** Set encryption key.
 
    @param key The key to be used.

    @return `YES` if success, `NO` on error.

    @see http://www.sqlite-encrypt.com/develop-guide.htm
    
    @warning You need to have purchased the sqlite encryption extensions for this method to work.
    */

    - (BOOL)setKey:(NSString*)key;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 寻找Hook目标函数&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;砸壳、重签名这里就不再赘述，不是本文的重点。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://ryukiedev.gitbook.io/wiki/ni-xiang/23.-ying-yong-za-ke&quot;&gt;应用砸壳（一定要有越狱手机）&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;下面使用 &lt;code&gt;Logos&lt;/code&gt; 简单进行 &lt;code&gt;Hook&lt;/code&gt;。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface FMDatabase : NSObject

@end

%hook FMDatabase
- (void)setKey:(NSString*)key {
    NSLog(@&quot;🔓获取到了数据库密码 %@&quot;, key);
    %orig;
}

%end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;运行后在应用中触发数据库操作，日志输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-07-24 12:26:26.561085+0800 xx[16475:3222446] 🔓拦截到了数据库密码 xxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;成功拦截到了密码👏&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;四、 移除密码&lt;/h2&gt;
&lt;p&gt;我们先将数据库文件拷贝出来一份。&lt;/p&gt;
&lt;p&gt;使用我的 &lt;code&gt;小工具-RemoveDataBasePassword&lt;/code&gt; 即可获取无密码的数据库文件了～&lt;/p&gt;
&lt;p&gt;获取移除密码的数据库文件。就可以正常打开了&lt;/p&gt;
&lt;h3&gt;4.1 RemoveDataBasePassword 使用说明&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/RemoveDataBasePassword&quot;&gt;GitHub-RemoveDataBasePassword&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果有帮助到你，留下一颗小星星吧⭐️～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;将你的数据库文件拖进工程&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;YourDataBasePassWord&lt;/code&gt; 为密码&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;YourDataBaseName&lt;/code&gt; 为你的数据库文件名称&lt;/li&gt;
&lt;li&gt;运行程序，模拟器即可&lt;/li&gt;
&lt;li&gt;当看到屏幕出现 &lt;code&gt;Click&lt;/code&gt; 点击屏幕&lt;/li&gt;
&lt;li&gt;查看 Log，就可以获取路径&lt;/li&gt;
&lt;li&gt;Enjoy it!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.2 Navicat 报错&lt;/h3&gt;
&lt;p&gt;我一直用的 &lt;code&gt;Navicat&lt;/code&gt; 来查看数据库的，但是打开移除密码的数据库时报错了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/34cf7b58aaeef341.webp&quot; alt=&quot;Navicat&quot; /&gt;&lt;/p&gt;
&lt;p&gt;虽然报错了，其实没问题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/931f22770a2d31ee.webp&quot; alt=&quot;Navicat&quot; /&gt;&lt;/p&gt;
&lt;p&gt;双击 main，就可以继续访问数据了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9cb35f7287b66f5c.webp&quot; alt=&quot;Navicat&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、 总结思考&lt;/h2&gt;
&lt;p&gt;整个研究的过程还是比较顺利的，同时也提醒了自己：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果本地数据库信息比较重要的话，仅仅为数据库添加密码时远远不够的&lt;/li&gt;
&lt;li&gt;对于懂一些逆向的玩家来说还是很容易就能获取到的&lt;/li&gt;
&lt;li&gt;感觉也是时候和大家分享一下一些防护手段了&lt;/li&gt;
&lt;li&gt;欢迎点赞、评论、交流&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>I.剪绳子</title><link>https://blog.lab-lawliet.app/posts/article-14-i-d74b8496056611fd/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-14-i-d74b8496056611fd/</guid><description>给你一根长度为 n 的绳子，请把绳子剪成整数长度的 m 段（m、n都是整数，n&gt;1并且m&gt;1），每段绳子的长度记为 k[0],k[1]...k[m-1] 。</description><pubDate>Thu, 22 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 题目&lt;/h2&gt;
&lt;p&gt;给你一根长度为 n 的绳子，请把绳子剪成整数长度的 m 段（m、n都是整数，n&amp;gt;1并且m&amp;gt;1），每段绳子的长度记为 k[0],k[1]...k[m-1] 。&lt;/p&gt;
&lt;p&gt;请问 k[0]&lt;em&gt;k[1]&lt;/em&gt;...*k[m-1] 可能的最大乘积是多少？&lt;/p&gt;
&lt;p&gt;例如，当绳子的长度是8时，我们把它剪成长度分别为2、3、3的三段，此时得到的最大乘积是18。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;2 &amp;lt;= n &amp;lt;= 58&lt;/p&gt;
&lt;p&gt;注意：本题与主站 343 题相同：https://leetcode-cn.com/problems/integer-break/&lt;/p&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-lcof&quot;&gt;力扣（LeetCode）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;二、 动态规划&lt;/h2&gt;
&lt;p&gt;动态规划是面试中的热门话题。&lt;/p&gt;
&lt;h3&gt;2.1 什么样的场景适合动态规划呢？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果题目是：
&lt;ul&gt;
&lt;li&gt;求一个问题的最优解（通常是&lt;code&gt;最大值&lt;/code&gt;或者&lt;code&gt;最小值&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;而且改问题能够分解为若干个子问题&lt;/li&gt;
&lt;li&gt;并且字问题之间还有重叠的更小的子问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;就可以考虑使用动态规划来解决这个问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 本题是否适合动态规划？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;我们把长度为n的绳子剪成若干段后得到的乘积的最大值定义为函数&lt;code&gt;f(n)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;假设我们把第一刀剪在了长度为&lt;code&gt;i（0 &amp;lt; i &amp;lt; n）&lt;/code&gt;的位置，于是把绳子剪成了长度为&lt;code&gt;i&lt;/code&gt;和&lt;code&gt;n-i&lt;/code&gt;的两段。&lt;/li&gt;
&lt;li&gt;我们想要得到整个问题的最优解&lt;code&gt;f(n)&lt;/code&gt;那么要同样用最优化的方法把长度为&lt;code&gt;i&lt;/code&gt;和&lt;code&gt;n-i&lt;/code&gt;的两段分别剪成若干段，使得各自剪出的每段绳子的乘积最大。&lt;/li&gt;
&lt;li&gt;也就是说整体问题的最优解是依赖各个子问题的最优解。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 动态规划的过程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;由于子问题在分解大问题的过程中重复出现，为了避免重复求解子问题，我们可以：
&lt;ul&gt;
&lt;li&gt;从下往上的顺序先计算小问题的最优解并存储下来&lt;/li&gt;
&lt;li&gt;再以此为基础求取大问题的最优解&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这种从上往下分析问题，从下往上解决问题也是动态规划的一大特征。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 应用在本题中&lt;/h3&gt;
&lt;p&gt;在应用动态规划的时候，我们每一步都可能面临若干个选择。&lt;/p&gt;
&lt;p&gt;本题中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一刀有n-1种选择。我们可以剪再 1,2...n-1 的任意位置。&lt;/li&gt;
&lt;li&gt;由于我们事先不知道那个位置是最优解，只好把所有可能的额都尝试一遍，然后比较出最优解。&lt;/li&gt;
&lt;li&gt;用数学表示就是
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;f(n) = max(f(i), f(n-i))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;lt; i &amp;lt; n&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 4 {
        return n - 1
    }
    var dp = [Int](repeating: 1, count: n + 1)
    for i in 3...n {
        for j in 2..&amp;lt;i {
            dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
        }
    }
    return dp[n]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;时间复杂度&lt;/th&gt;
&lt;th&gt;空间复杂度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O(n^2)&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;三、 贪婪算法&lt;/h2&gt;
&lt;p&gt;贪婪算法和动态规划不一样。当我们应用贪婪算法解决问题的时候，每一步都可以做出一个贪婪的选择，基于这个选择，我们确定能够得到最优解。&lt;/p&gt;
&lt;p&gt;至于为什么这样的贪婪选择能够得到最优解。这就需要使用数学方式来证明了。&lt;/p&gt;
&lt;h3&gt;3.1 应用再本题中&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果绳子长度大于5，则每次都剪出一段长度为3的绳子。&lt;/li&gt;
&lt;li&gt;如果剩下的长度仍然大于5，则接着剪出一段长度为3的绳子。&lt;/li&gt;
&lt;li&gt;循环&lt;/li&gt;
&lt;li&gt;直到剩下的绳子长度小于5。
&lt;ul&gt;
&lt;li&gt;当绳子长度为4时，剪成 2x2=4&lt;/li&gt;
&lt;li&gt;当绳子长度为3时，剪成 1x2=2&lt;/li&gt;
&lt;li&gt;当绳子长度为2时，剪成 1x1=1
&lt;ul&gt;
&lt;li&gt;其实不剪最大，但要求至少剪一刀那就剪呗&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;剪出一段长度为3的绳子&lt;/code&gt;就是我们在每一步做出的贪婪选择。&lt;/p&gt;
&lt;h3&gt;3.2 证明上面思路的正确性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当n&amp;gt;=5时
&lt;ul&gt;
&lt;li&gt;2(n-2)&amp;gt;n &amp;amp;&amp;amp; 3(n-3)&amp;gt;n&lt;/li&gt;
&lt;li&gt;也就是说当绳子的长度大于或等于5时，我们可以把它剪成3或2的绳子段&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;另外当n&amp;gt;=5
&lt;ul&gt;
&lt;li&gt;3(n-3)&amp;gt;=2(n-2)&lt;/li&gt;
&lt;li&gt;因此我们应该尽可能多的剪长度为3的绳子&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;上面时原书中的解释，更详细专业的证明&lt;a href=&quot;https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/mian-shi-ti-14-i-jian-sheng-zi-tan-xin-si-xiang-by/&quot;&gt;Krahets&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func cuttingRope(_ n: Int) -&amp;gt; Int {
    if n &amp;lt; 2 {
        return 0
    }
    if n == 2 {
        return 1
    }
    if n == 3 {
        return 2
    }
        
    // 3长度的最大段数
    var threeCount = n / 3
     
    // 当绳子剩下4的时候 不能再剪3长度，更优的方式是剪成长度为2的，因为 2x2 &amp;gt; 3x1
    if n - threeCount * 3 == 1 {
        threeCount -= 1
    }
    let twoCount = (n - threeCount * 3) / 2
    
    return Int(truncating: NSDecimalNumber(decimal: pow(3, threeCount))) * Int(truncating: NSDecimalNumber(decimal: pow(2, twoCount)))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;时间复杂度&lt;/th&gt;
&lt;th&gt;空间复杂度&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>iOS应用启动（一）：dyld与main函数</title><link>https://blog.lab-lawliet.app/posts/ios-14-ios-dyld-start-0da8a5a7f572209b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-14-ios-dyld-start-0da8a5a7f572209b/</guid><description>每个应用程序都会依赖很多的库，每当应用程序启动，都会将MachO中的可执行文件加载到内存中。那么这个过程是怎样的呢？</description><pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;dyld-832.7.3&lt;/code&gt; 与 &lt;code&gt;objc4-818.2&lt;/code&gt; 源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;每个应用程序都会依赖很多的库，每当应用程序启动，都会将MachO中的可执行文件加载到内存中。那么这个过程是怎样的呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3b0b9ba98dbe81ce.webp&quot; alt=&quot;简要加载过程&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;一、 切入点&lt;/h2&gt;
&lt;p&gt;应用程序的入口是 &lt;code&gt;main&lt;/code&gt; 函数，那么 &lt;code&gt;main&lt;/code&gt; 函数之前做了什么呢？&lt;/p&gt;
&lt;p&gt;我们在 &lt;code&gt;main&lt;/code&gt; 函数下个断点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/07018822b36e5b76.webp&quot; alt=&quot;断点&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) bt
* thread #1, queue = &apos;com.apple.main-thread&apos;, stop reason = breakpoint 1.1
* frame #0: 0x00000001001f9ef8 ObjcMsgSend`main(argc=1, argv=0x000000016fc0b878) at main.m:17:50
  frame #1: 0x00000001a2d56140 libdyld.dylib`start + 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现并没有有用的信息。那么在main之前下断点试试吧。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2da177c61a1f8fa2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过分析堆栈信息发现切入点 &lt;code&gt;_dyld_start&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;二、 dyld-动态链接器&lt;/h2&gt;
&lt;h3&gt;2.1 _dyld_start&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;dyld&lt;/code&gt;源码中找到了对应实现，为汇编代码，我们找到一处重要的注释：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/050f858a51aabc02.webp&quot; alt=&quot;call dyldbootstrap::start&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;dyldbootstrap::start&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3a6d2668f1314138.webp&quot; alt=&quot;dyldbootstrap::start&quot; /&gt;&lt;/p&gt;
&lt;p&gt;前面都是进行一些配置，最后一个看名字就很重要的 &lt;code&gt;dyld::_main&lt;/code&gt; 我们进去看看&lt;/p&gt;
&lt;h3&gt;2.2 dyld::_main&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c1c59a73255a698a.webp&quot; alt=&quot;dyld::_main&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一千多行代码，果然很重要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过注释我们也可以看出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里是 &lt;code&gt;dyld&lt;/code&gt; 的入口&lt;/li&gt;
&lt;li&gt;最终返回程序的 &lt;code&gt;main()&lt;/code&gt; 函数&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;因为代码非常长，我对于关键点做了一些注释方便大家对照源码进行查看&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/02ea4fdebea6c3e7.webp&quot; alt=&quot;marks&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;源码流程注释对照&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;只要设置了这两个环境变量参数，在App启动时就会打印相关参数、环境变量信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4190bd3270423ae9.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加载共享缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9a13342532e7eb8b.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为主程序初始化 ImageLoader&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b1d88134ed25fd43.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;现在共享缓存已经加载完毕了，设置版本化的dylib覆盖&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/55820f0ccff50128.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;加载所有插入的库，越狱插件在这里加入&lt;/li&gt;
&lt;li&gt;记录插入的库的数量，以便统一搜索将先查看插入的库，然后是main，然后是其他。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8b84d8219685cd58.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接主程序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9fe66fe71f379549.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;链接所有插入的库。链接主可执行文件后执行此操作，以使插入的dylib（例如libSystem）不在程序使用的dylib的前面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5e466471b0e6cec5.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只有插入的库可以插入。绑定所有插入的库后，注册插入信息，以便链接工作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/02697d44d56c3c2d.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;绑定并通知主要可执行文件，现在插入的已被注册&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;绑定并通知现在已插入的已插入image已被注册&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/75192fc96659ff51.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;执行所有初始化方法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通知任何监视过程此过程即将进入main()&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9df427fdc229dfca.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查找main指针&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/78325bbd35448315.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 sMainExecutable&lt;/h3&gt;
&lt;p&gt;从上面的流程分析中可以看出，最终&lt;code&gt;return&lt;/code&gt;的&lt;code&gt;main&lt;/code&gt;函数指针 来自于 &lt;code&gt;sMainExecutable&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;定位 &lt;code&gt;sMainExecutable&lt;/code&gt; 的初始化&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b1d88134ed25fd43.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.4 initializeMainExecutable 初始化主程序&lt;/h3&gt;
&lt;p&gt;核心源码及流程注释&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void initializeMainExecutable()
{
#pragma mark - Ryukie 记录一下，进入初始化流程了
	// record that we&apos;ve reached this step
	gLinkContext.startedInitializingMainExecutable = true;
#pragma mark - Ryukie 初始化所有插入的库
	// run initialzers for any inserted dylibs
	ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
	initializerTimes[0].count = 0;
	const size_t rootCount = sImageRoots.size();
	if ( rootCount &amp;gt; 1 ) {
		for(size_t i=1; i &amp;lt; rootCount; ++i) {
			sImageRoots[i]-&amp;gt;runInitializers(gLinkContext, initializerTimes[0]);
		}
	}
#pragma mark - Ryukie 运行主要可执行文件的初始化程序及其依赖的
	// run initializers for main executable and everything it brings up 
	sMainExecutable-&amp;gt;runInitializers(gLinkContext, initializerTimes[0]);

    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结合前面我们在 &lt;code&gt;+load&lt;/code&gt; 处断点的堆栈信息可以得到验证：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a747c0b540033de0.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;主线历程我们就分析到这里，细节的流程后面的文章会继续进行分析&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、 总结&amp;amp;流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/53e630dd7162c8c3.webp&quot; alt=&quot;dyld:_main&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序执行从 &lt;code&gt;_dyld_start&lt;/code&gt; 开始&lt;/li&gt;
&lt;li&gt;进入 &lt;code&gt;dyld_main&lt;/code&gt; 函数&lt;/li&gt;
&lt;li&gt;配置环境变量以及 &lt;code&gt;rebase_dyld&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;加载&lt;code&gt;共享缓存&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;系统的动态库&lt;/code&gt;都在这里了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dyld2/dyld(ClosureMode)&lt;/code&gt; 决定以哪种模式继续进行&lt;/li&gt;
&lt;li&gt;实例化主程序&lt;code&gt;ImageLoaderMachO&lt;/code&gt;，加入到&lt;code&gt;allImages&lt;/code&gt;中&lt;/li&gt;
&lt;li&gt;到此&lt;code&gt;共享缓存&lt;/code&gt;加载完毕，设置版本化的&lt;code&gt;dylib&lt;/code&gt;覆盖
&lt;ul&gt;
&lt;li&gt;Now that shared cache is loaded, setup an versioned dylib overrides&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;加载插入的库
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;越狱插件&lt;/code&gt;在这里加入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;记录插入的库的数量，以便统一搜索将先查看插入的库，然后是main，然后是其他。
&lt;ul&gt;
&lt;li&gt;record count of inserted libraries so that a flat search will look at inserted libraries, then main, then others.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;链接主程序
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;绑定符号（非懒加载、弱符号）等&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;链接所有插入的库
&lt;ul&gt;
&lt;li&gt;链接主可执行文件后执行此操作，以使插入的dylib（例如libSystem）不在程序使用的dylib的前面&lt;/li&gt;
&lt;li&gt;只有插入的库可以插入。绑定所有插入的库后，注册插入信息，以便链接工作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;绑定并通知&lt;code&gt;主程序可执行文件&lt;/code&gt;，现在插入的已被注册&lt;/li&gt;
&lt;li&gt;绑定并通知&lt;code&gt;现在已插入的库&lt;/code&gt;已插入的Image已被注册&lt;/li&gt;
&lt;li&gt;执行所有初始化方法：&lt;code&gt;ImageLoader&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;初始化所有插入的库、运行主要可执行文件的初始化程序及其依赖&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageLoader&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runInitializers&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;processInitializers&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ecursiveInitialization&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;notifySingle&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;此函数执行一个回调，此回调是&lt;code&gt;_objc_init&lt;/code&gt;初始化时服饰的一个函数&lt;code&gt;load_images&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;load_images&lt;/code&gt;里执行&lt;code&gt;class_load_metgods&lt;/code&gt;函数
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;class_load_metgods&lt;/code&gt;里调用&lt;code&gt;call_class_loads&lt;/code&gt;函数：循环调用各个类的&lt;code&gt;load&lt;/code&gt;函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doModInitFunction&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;内部会调用全局C++对象的构造函数&lt;code&gt;__attribute__((constructor))&lt;/code&gt;的C函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通知任何监视过程此过程即将进入&lt;code&gt;main()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;得到&lt;code&gt;main()&lt;/code&gt;指针并&lt;code&gt;return&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>iOS应用启动（二）：环境配置与runtime初始化</title><link>https://blog.lab-lawliet.app/posts/ios-15-ios-objc-init-0db94fe16c8a3d28/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-15-ios-objc-init-0db94fe16c8a3d28/</guid><description>结合上一文iOS应用启动（一）：_dyld_start及符号断点我们找到了 _objc_init 的调用栈</description><pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;dyld-832.7.3&lt;/code&gt; 与 &lt;code&gt;objc4-818.2&lt;/code&gt; 源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;结合上一文&lt;a href=&quot;/posts/ios-14-ios-dyld-start-0da8a5a7f572209b/&quot;&gt;iOS应用启动（一）：_dyld_start&lt;/a&gt;及符号断点我们找到了 &lt;code&gt;_objc_init&lt;/code&gt; 的调用栈&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f4c02cac0fe2a4c6.webp&quot; alt=&quot;dyldinit01&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dyld`ImageLoaderMachO::doModInitFunctions&lt;/li&gt;
&lt;li&gt;libSystem.B.dylib`libSystem_initializer&lt;/li&gt;
&lt;li&gt;libdispatch.dylib`libdispatch_init&lt;/li&gt;
&lt;li&gt;libdispatch.dylib`_os_object_init&lt;/li&gt;
&lt;li&gt;libobjc.A.dylib`_objc_init&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一、 _objc_init 做了什么&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;引导初始化，使用 dyld 注册 images。&lt;/li&gt;
&lt;li&gt;在库初始化时间之前由 libSystem 调用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 源码分析&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    /// 初始化环境变量
    environ_init();

    /// 关于线程`key`的绑定：比如线程数据的析构函数
    tls_init();
    
    /// 调用全局静态C++构造函数
    static_init();
    
    /// runtime 运行时环境初始化
    runtime_init();
    
    /// 初始化 `libobjc` 的异常处理系统
    exception_init();

#if __OBJC2__
    /// 缓存条件初始化
    cache_t::init();
#endif
    /// 启动回调机制
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&amp;amp;map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面我们将对一些重要步骤进行说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;environ_init
&lt;ul&gt;
&lt;li&gt;初始化环境变量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;tls_init
&lt;ul&gt;
&lt;li&gt;关于线程&lt;code&gt;key&lt;/code&gt;的绑定：比如线程数据的析构函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;static_init
&lt;ul&gt;
&lt;li&gt;调用全局静态C++构造函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;runtime_init
&lt;ul&gt;
&lt;li&gt;runtime 运行时环境初始化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;exception_init
&lt;ul&gt;
&lt;li&gt;初始化 &lt;code&gt;libobjc&lt;/code&gt; 的异常处理系统&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cache_t::init()
&lt;ul&gt;
&lt;li&gt;缓存条件初始化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_imp_implementationWithBlock_init
&lt;ul&gt;
&lt;li&gt;启动回调机制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_dyld_objc_notify_register
*&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 environ_init&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;初始化环境变量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;修改环境变量能够有效帮助我们进行开发调试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;修改环境变量进行调试&lt;/h3&gt;
&lt;p&gt;如这样一个场景，项目代码较多，引用的SDK也很多，想要知道哪些类实现了 &lt;code&gt;+load&lt;/code&gt; 方法该怎么办？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改一个环境变量即可打印出来
&lt;ul&gt;
&lt;li&gt;打开 &lt;code&gt;Edit Scheme&lt;/code&gt; - &lt;code&gt;Run&lt;/code&gt; - &lt;code&gt;Argument&lt;/code&gt; 添加环境变量 &lt;code&gt;OBJC_PRINT_LOAD_METHODS = YES&lt;/code&gt; 即可打印所有 &lt;code&gt;+load&lt;/code&gt; 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/eff11b3c96f75618.webp&quot; alt=&quot;OBJC_PRINT_LOAD_METHODS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;objc[58337]: LOAD: category &apos;NSObject(NSObject)&apos; scheduled for +load
objc[58337]: LOAD: +[NSObject(NSObject) load]

objc[58337]: LOAD: category &apos;NSObject(NSObject)&apos; scheduled for +load
objc[58337]: LOAD: +[NSObject(NSObject) load]

objc[58337]: LOAD: class &apos;NSColor&apos; scheduled for +load
objc[58337]: LOAD: class &apos;NSApplication&apos; scheduled for +load
objc[58337]: LOAD: class &apos;NSBinder&apos; scheduled for +load
objc[58337]: LOAD: class &apos;NSColorSpaceColor&apos; scheduled for +load
objc[58337]: LOAD: class &apos;NSNextStepFrame&apos; scheduled for +load
objc[58337]: LOAD: +[NSColor load]

objc[58337]: LOAD: +[NSApplication load]

objc[58337]: LOAD: +[NSBinder load]

objc[58337]: LOAD: +[NSColorSpaceColor load]

objc[58337]: LOAD: +[NSNextStepFrame load]

objc[58337]: LOAD: category &apos;NSError(FPAdditions)&apos; scheduled for +load
objc[58337]: LOAD: +[NSError(FPAdditions) load]

objc[58337]: LOAD: class &apos;_DKEventQuery&apos; scheduled for +load
objc[58337]: LOAD: +[_DKEventQuery load]

objc[58337]: LOAD: class &apos;RYModel&apos; scheduled for +load
objc[58337]: LOAD: +[RYModel load]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自定义模型实现了&lt;code&gt;+load&lt;/code&gt;方法被打印了出来，&lt;code&gt;+[RYModel load]&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;获取可用环境变量&lt;/h3&gt;
&lt;p&gt;在任意终端输入指令 &lt;code&gt;export OBJC_HELP=1&lt;/code&gt; 即可获取环境变量列表，选择自己需要的进行使用，可以提高开发调试的效率哦。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  RyukieDevGitBook git:(master) ✗ export OBJC_HELP=1
objc[57485]: Objective-C runtime debugging. Set variable=YES to enable.
objc[57485]: OBJC_HELP: describe available environment variables
objc[57485]: OBJC_PRINT_OPTIONS: list which options are set
objc[57485]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[57485]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[57485]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[57485]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 static_init&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;调用全局静态C++构造函数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;执行全局静态 &lt;code&gt;C++&lt;/code&gt; &lt;code&gt;构造函数&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;libc&lt;/code&gt; &lt;em&gt;&lt;strong&gt;在 &lt;code&gt;dyld&lt;/code&gt; 调用&lt;code&gt;构造函数&lt;/code&gt;之前 调用&lt;/strong&gt;&lt;/em&gt; &lt;code&gt;_objc_init()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;所以我们要自己处理&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/

static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&amp;amp;_mh_dylib_header, &amp;amp;count);
    for (size_t i = 0; i &amp;lt; count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&amp;amp;_mh_dylib_header, &amp;amp;count);
    for (size_t i = 0; i &amp;lt; count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 验证&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;在我的代码中添加一个C++构造函数&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__attribute__((constructor)) void ryFunc() {
    printf(&quot;我的constructor：%s \n&quot;, __func__);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;发现并没有在这一步骤中执行我的C++构造函数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 思考&lt;/h3&gt;
&lt;p&gt;这里其实调用的并非所有C++构造函数，而是在底层objc库中的构造函数。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;在objc源码中添加一个构造函数：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__attribute__((constructor)) void ryFuncInObjc() {
    printf(&quot;我的constructor：%s \n&quot;, __func__);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4d223d87e4555145.webp&quot; alt=&quot;static_init&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;此处正常没有输出日志&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/685b5d6820d766a6.webp&quot; alt=&quot;static_init&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;日志输出：&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;我的constructor：ryFuncInObjc
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;这里调用的C++构造函数特指 &lt;code&gt;objc源码中定义的一系列构造函数&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;因为全局构造函数非常重要，为了保证全局构造函数调用的及时性，所以这里自己进行了调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四、 runtime_init&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;runtime 运行时环境初始化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过查看这里的两个 &lt;code&gt;init&lt;/code&gt; 方法我们发现，这两个是集合类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public:
    template &amp;lt;typename... Ts&amp;gt;
    void init(Ts &amp;amp;&amp;amp;... Args) {
        new (_storage) Type(std::forward&amp;lt;Ts&amp;gt;(Args)...);
    }

    Type &amp;amp;get() {
        return *reinterpret_cast&amp;lt;Type *&amp;gt;(_storage);
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.1 unattachedCategories&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;static UnattachedCategories unattachedCategories;

} // namespace objc


&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 allocatedClasses&lt;/h3&gt;
&lt;p&gt;一个存储所有已经被 &lt;code&gt;allocated&lt;/code&gt; 的类和元类的&lt;code&gt;表&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
namespace objc {
static ExplicitInitDenseSet&amp;lt;Class&amp;gt; allocatedClasses;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、 exception_init&lt;/h2&gt;
&lt;p&gt;初始化 &lt;code&gt;libobjc&lt;/code&gt; 的异常处理系统&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* exception_init
* Initialize libobjc&apos;s exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&amp;amp;_objc_terminate);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;需要的话也可以自定义一异常捕捉回调&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、 _imp_implementationWithBlock_init&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;启动回调机制。通常不会做什么，因为所有的初始化都是懒加载的，但是对于某些进程，会迫不及待的加载 &lt;code&gt;trampolines dylib&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;七、 _dyld_objc_notify_register&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这里进行了镜像文件的读取与加载，后面会单独进行分析&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>iOS应用启动（三）：镜像文件的读取和加载</title><link>https://blog.lab-lawliet.app/posts/ios-16-ios-835227ae425b8ae6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-16-ios-835227ae425b8ae6/</guid><description>在上一文iOS应用启动（二）：环境配置与runtime初始化我们提到了 _dyld_objc_notify_register，本文就将深入对镜像文件的读取map_images与加载loadClass进行解读。</description><pubDate>Wed, 21 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;dyld-832.7.3&lt;/code&gt; 与 &lt;code&gt;objc4-818.2&lt;/code&gt; 源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在上一文&lt;a href=&quot;/posts/ios-15-ios-objc-init-0db94fe16c8a3d28/&quot;&gt;iOS应用启动（二）：环境配置与runtime初始化&lt;/a&gt;我们提到了 &lt;code&gt;_dyld_objc_notify_register&lt;/code&gt;，本文就将深入对镜像文件的读取&lt;code&gt;map_images&lt;/code&gt;与加载&lt;code&gt;loadClass&lt;/code&gt;进行解读。&lt;/p&gt;
&lt;h2&gt;一、 _dyld_objc_notify_register&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;_dyld_objc_notify_register(&amp;amp;map_images, load_images, unmap_image);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/98fa44a40d7cbae7.webp&quot; alt=&quot;_dyld_objc_notify_register&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里有两个重要的函数：
&lt;ul&gt;
&lt;li&gt;map_images
&lt;ul&gt;
&lt;li&gt;管理文件中和动态库中的所有符号：&lt;code&gt;class&lt;/code&gt; &lt;code&gt;protocol&lt;/code&gt; &lt;code&gt;selector&lt;/code&gt; &lt;code&gt;category&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;load_images
&lt;ul&gt;
&lt;li&gt;执行 &lt;code&gt;+load&lt;/code&gt; 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;进行了镜像文件的读取与加载&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;这里有个一个小细节&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我们看到这里调用 &lt;code&gt;&amp;amp;map_images&lt;/code&gt; 这里用了函数的地址，因为这里非常重要，为了保持整个过程的同步。&lt;/p&gt;
&lt;h2&gt;二、 map_images&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 map_images_nolock&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...

    if (hCount &amp;gt; 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    ...
    
    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i &amp;lt; mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 _read_images&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的源码非常长，但是&lt;code&gt;Apple&lt;/code&gt;贴心的加了&lt;code&gt;Log&lt;/code&gt;我们可以从&lt;code&gt;Log&lt;/code&gt;找下线索&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;ts.log(&quot;IMAGE TIMES: fix up selector references&quot;);

ts.log(&quot;IMAGE TIMES: discover classes&quot;);

ts.log(&quot;IMAGE TIMES: remap classes&quot;);

ts.log(&quot;IMAGE TIMES: fix up objc_msgSend_fixup&quot;);

ts.log(&quot;IMAGE TIMES: discover protocols&quot;);

ts.log(&quot;IMAGE TIMES: fix up @protocol references&quot;);

ts.log(&quot;IMAGE TIMES: discover categories&quot;);

ts.log(&quot;IMAGE TIMES: realize non-lazy classes&quot;);

ts.log(&quot;IMAGE TIMES: realize future classes&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;整体流程：
&lt;ul&gt;
&lt;li&gt;条件控制进行第一次加载&lt;/li&gt;
&lt;li&gt;修复预编译阶段的 &lt;code&gt;@selector&lt;/code&gt; 混乱问题&lt;/li&gt;
&lt;li&gt;错误混乱的类处理&lt;/li&gt;
&lt;li&gt;修复重映射一些没有被镜像文件加再进来的类&lt;/li&gt;
&lt;li&gt;修复一些消息&lt;/li&gt;
&lt;li&gt;当类中有协议的时候，&lt;code&gt;readProtocol&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修复没有被加载的协议&lt;/li&gt;
&lt;li&gt;处理分类&lt;/li&gt;
&lt;li&gt;类的加载相关处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;修复&lt;/strong&gt;&lt;/em&gt;：主要是指对地址进行修复，将&lt;code&gt;MachO&lt;/code&gt;中的地址修复为&lt;code&gt;dyld&lt;/code&gt;处理后的地址&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.3 循环调用 readClass 加载类&lt;/h3&gt;
&lt;p&gt;在这里添加Log可以发现，自定义的类是最后加载的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;... // 一系列系统类加载
readClass -- RYModel
readClass -- RYSubModel
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这里如果想过滤系统的可以参考下面的代码：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;const char *myClassName = &quot;RYModel&quot;;
const char *mangledName = cls-&amp;gt;nonlazyMangledName();

if (strcmp(mangledName, myClassName) == 0) {
    printf(&quot;%s -- %s\n&quot;, __func__, myClassName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 readClass 源码中的坑&lt;/h3&gt;
&lt;p&gt;下图中的代码，看似进行了Class的ro rw的操作但实际运行断点的时候根本不会执行，千万不要被源码误导了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8051b50f906c382c.webp&quot; alt=&quot;坑&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如图三个断点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e97476ec75f88058.webp&quot; alt=&quot;坑&quot; /&gt;&lt;/p&gt;
&lt;p&gt;跳过了中间的断点&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c8884dbf1dcbb634.webp&quot; alt=&quot;坑&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.5 寻找类相关的流程&lt;/h3&gt;
&lt;p&gt;在源码中找到了可能的地方，添加断点：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8e35939acaa20b20.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实现 &lt;code&gt;非懒加载类&lt;/code&gt;（实现类&lt;code&gt;+load&lt;/code&gt;方法或者静态实例）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Realize non-lazy classes (for +load methods and static instances)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4d6e8c36743c8a2d.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Realize newly-resolved future classes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;运行后只输出了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Realize non-lazy classes _read_images -- RYModel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而在其中也发现来到了另一个重要的流程：&lt;code&gt;realizeClassWithoutSwift&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关于这部分内容之前在研究msgSend的过程中有顺便进行了探索，可以在&lt;a href=&quot;/posts/ios-12-0254f974d91c51ef/&quot;&gt;类的实现与初始化&lt;/a&gt;一文中进行深入了解。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;补充：懒加载类 &amp;amp; 非懒加载类&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;2.5&lt;/code&gt;中提到了 &lt;code&gt;非懒加载类&lt;/code&gt;，那么什么是 &lt;code&gt;非懒加载类&lt;/code&gt;，什么是 &lt;code&gt;懒加载类&lt;/code&gt;？&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;懒加载类&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;数据加载推迟到第一次接收第一次消息的时候&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;非懒加载类&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;map_image&lt;/code&gt; 的时候加载所有类数据。&lt;/p&gt;
&lt;h2&gt;三、 load_images&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;这里流程就比较简单了
&lt;ul&gt;
&lt;li&gt;加载分类&lt;/li&gt;
&lt;li&gt;查找所有 load 方法&lt;/li&gt;
&lt;li&gt;调用 load 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories &amp;amp;&amp;amp; didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        // 加载分类
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    // 查找所有 load 方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    /// 调用 load 方法
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;流程图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e17c62c470769c2b.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>机器人的运动范围</title><link>https://blog.lab-lawliet.app/posts/article-13-ebff83701b3010f1/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-13-ebff83701b3010f1/</guid><description>地上有一个m行n列的方格，从坐标 [0,0] 到坐标 [m-1,n-1] 。</description><pubDate>Sun, 18 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;地上有一个m行n列的方格，从坐标 [0,0] 到坐标 [m-1,n-1] 。&lt;/p&gt;
&lt;p&gt;一个机器人从坐标 [0, 0] 的格子开始移动，它每次可以向左、右、上、下移动一格（不能移动到方格外），也不能进入行坐标和列坐标的数位之和大于k的格子。&lt;/p&gt;
&lt;p&gt;例如，当k为18时，机器人能够进入方格 [35, 37] ，因为3+5+3+7=18。但它不能进入方格 [35, 38]，因为3+5+3+8=19。请问该机器人能够到达多少个格子？&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：m = 2, n = 3, k = 1
输出：3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：m = 3, n = 1, k = 0
输出：1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 &amp;lt;= n,m &amp;lt;= 100
0 &amp;lt;= k &amp;lt;= 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;一、 分析&lt;/h2&gt;
&lt;p&gt;矩阵搜索问题一般有两种思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过递归实现的&lt;code&gt;深度优先DFS&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;朝一个方向走到底，再回退&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用过队列实现的&lt;code&gt;广度优先BFS&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;按照“平推”的方式向前搜索&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 解法一： 深度优先及优化&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4168d30a4faf66e3.webp&quot; alt=&quot;解法&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func movingCount(_ m: Int, _ n: Int, _ k: Int) -&amp;gt; Int {
    if m &amp;lt;= 0 || n &amp;lt;= 0 || k &amp;lt; 0 {
        return 0
    }
    var flags: [[Bool]] = Array(repeating: Array(repeating: false, count: n), count: m)
    return moveTo(0, 0, k, flags: &amp;amp;flags)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 核心递归逻辑&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func moveTo(_ x: Int, _ y: Int, _ k: Int, flags: inout [[Bool]]) -&amp;gt; Int {
    if (x &amp;lt; 0 || x &amp;gt;= flags.first?.count ?? 0) ||
        (y &amp;lt; 0 || y &amp;gt;= flags.count) ||
        (flags[y][x] == true) ||
        (digitSum(x) + digitSum(y) &amp;gt; k)
    {
        return 0
    }
    
    flags[y][x] = true
    
    let count = 1
        + moveTo(x + 1, y, k, flags: &amp;amp;flags)
        + moveTo(x - 1, y, k, flags: &amp;amp;flags)
        + moveTo(x, y + 1, k, flags: &amp;amp;flags)
        + moveTo(x, y - 1, k, flags: &amp;amp;flags)
    
    return count
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 数位和函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func digitSum(_ num: Int) -&amp;gt; Int {
    var temp = num
    var result = 0
    while temp &amp;gt; 0 {
        result += temp % 10
        temp = Int(floor(Double(temp) / 10.0))
    }
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;时间复杂度
&lt;ul&gt;
&lt;li&gt;最差O(m*n)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;空间复杂度
&lt;ul&gt;
&lt;li&gt;最差O(m*n)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 深度优先算法优化&lt;/h2&gt;
&lt;p&gt;在深度优先的解法中，我们才拆分为了&lt;code&gt;上下左右&lt;/code&gt;四个方向的子问题。&lt;/p&gt;
&lt;p&gt;但实际上本题只需要&lt;code&gt;右下&lt;/code&gt;两个方向就可以，因为上左一定是走过的。&lt;/p&gt;
&lt;p&gt;优化后的核心 &lt;code&gt;moveTo&lt;/code&gt; 代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func moveTo(_ x: Int, _ y: Int, _ k: Int, flags: inout [[Bool]]) -&amp;gt; Int {
    if (x &amp;lt; 0 || x &amp;gt;= flags.first?.count ?? 0) ||
        (y &amp;lt; 0 || y &amp;gt;= flags.count) ||
        (flags[y][x] == true) ||
        (digitSum(x) + digitSum(y) &amp;gt; k)
    {
        return 0
    }
    
    flags[y][x] = true
    
    let count = 1
        + moveTo(x + 1, y, k, flags: &amp;amp;flags)
        + moveTo(x, y + 1, k, flags: &amp;amp;flags)
    
    return count
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;可以发现减少了多余的计算效率提升了不少，这点如果在面试中注意到的话是个很好的加分项哦&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4153380f6fdbe4ee.webp&quot; alt=&quot;13&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;思路回顾&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;设 &lt;code&gt;fn(x, y)&lt;/code&gt; 代表从 &lt;code&gt;(x, y)&lt;/code&gt; 开始能到的格子数&lt;/li&gt;
&lt;li&gt;从左上角开始的那么我们可以转化一下&lt;/li&gt;
&lt;li&gt;即：&lt;code&gt;fn(x, y) = fn(x + 1, y) + fn(x, y + 1)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四、 解法二：广度优先&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;广度优先&lt;/code&gt;解法理解相比&lt;code&gt;深度优先&lt;/code&gt;不够直观&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;func movingCount_bfs(_ m: Int, _ n: Int, _ k: Int) -&amp;gt; Int {
    var visited = Array(repeating: Array(repeating: false, count: n), count: m)
    var result = 0
    var queue:Array = [(0,0,0,0)]

    while !queue.isEmpty {
        let (y,x,si,sj) = queue.removeFirst()
        if y &amp;gt;= m || x &amp;gt;= n || k &amp;lt; si + sj || visited[y][x]{
            continue
        }
        visited[y][x] = true
        result = result + 1
        queue.append((y + 1, x, digitSum(y + 1), sj))
        queue.append((y, x + 1, si, digitSum(x + 1)))
    }
    return result
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5571e2ba58325a1d.webp&quot; alt=&quot;03&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4.1 图解&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;来源&lt;a href=&quot;https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/&quot;&gt;Krahets&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9db3a9443496ade6.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ed01cb0bb12db838.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/bba3e5326ea1f4d1.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b0d688351ea20691.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/763ca5e9914d48da.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c84188afe19e7a67.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f431f6cb9d03bbd9.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dd1631070b171518.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/151dfcf7ae602169.webp&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>链表中倒数第k个节点</title><link>https://blog.lab-lawliet.app/posts/article-22-k-8bc70317d23c2851/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-22-k-8bc70317d23c2851/</guid><description>输入一个链表，输出该链表中倒数第k个节点。为了符合大多数人的习惯，本题从1开始计数，即链表的尾节点是倒数第1个节点。</description><pubDate>Wed, 14 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;输入一个链表，输出该链表中倒数第k个节点。为了符合大多数人的习惯，本题从1开始计数，即链表的尾节点是倒数第1个节点。&lt;/p&gt;
&lt;p&gt;例如，一个链表有 6 个节点，从头节点开始，它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 给定一个链表: 1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;5, 和 k = 2.

 返回链表 4-&amp;gt;5.
 通过次数193,790提交次数246,166
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/RyukiePlayground/blob/main/%E5%89%91%E6%8C%87Offer.playground/Pages/22.%E9%93%BE%E8%A1%A8%E4%B8%AD%E5%80%92%E6%95%B0%E7%AC%ACk%E4%B8%AA%E8%8A%82%E7%82%B9.xcplaygroundpage/Contents.swift&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;解法一：栈&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func getKthFromEnd(_ head: ListNode?, _ k: Int) -&amp;gt; ListNode? {
    var node = head
    
    var temp: [ListNode] = []
    
    while let currentNode = node {
        temp.insert(currentNode, at: 0)
        if temp.count &amp;gt; k {
            temp.removeLast()
        }
        node = currentNode.next
    }
    
    return temp.last
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解法二：差速双指针&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func getKthFromEnd(_ head: ListNode?, _ k: Int) -&amp;gt; ListNode? {
   var right: ListNode? = head, left: ListNode? = head
   
   // 移动到 第 k 个节点
   for _ in 0..&amp;lt;k-1 {
       right = right?.next
   }
   
   while let _ = right?.next {
       right = right?.next
       left = left?.next
   }
   
   return left
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>反转链表</title><link>https://blog.lab-lawliet.app/posts/article-24-7ec7ea5c127ea6be/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-24-7ec7ea5c127ea6be/</guid><description>定义一个函数，输入一个链表的头节点，反转该链表并输出反转后链表的头节点。</description><pubDate>Wed, 14 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;定义一个函数，输入一个链表的头节点，反转该链表并输出反转后链表的头节点。&lt;/p&gt;
&lt;p&gt;示例:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入: 1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;5-&amp;gt;NULL
 输出: 5-&amp;gt;4-&amp;gt;3-&amp;gt;2-&amp;gt;1-&amp;gt;NULL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 0 &amp;lt;= 节点个数 &amp;lt;= 5000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：本题与主站 206 题相同：https://leetcode-cn.com/problems/reverse-linked-list/&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;来源：力扣（LeetCode）&lt;/li&gt;
&lt;li&gt;链接：https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof&lt;/li&gt;
&lt;li&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解法&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/RyukiePlayground/blob/main/%E5%89%91%E6%8C%87Offer.playground/Pages/24.%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8.xcplaygroundpage/Contents.swift&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;func reverseList(_ head: ListNode?) -&amp;gt; ListNode? {
    var node: ListNode?
    var temp: ListNode? = head
    
    while let current = temp {
        let n = ListNode(current.val)
        n.next = node
        node = n
        temp = current.next
    }
    
    return node
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>顺时针打印矩阵</title><link>https://blog.lab-lawliet.app/posts/article-29-ff66c4a6240556e1/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-29-ff66c4a6240556e1/</guid><description>输入一个矩阵，按照从外向里以顺时针的顺序依次打印出每一个数字。</description><pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;输入一个矩阵，按照从外向里以顺时针的顺序依次打印出每一个数字。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出：[1,2,3,6,9,8,7,4,5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出：[1,2,3,4,8,12,11,10,9,5,6,7]
``` 

限制：

```C++
0 &amp;lt;= matrix.length &amp;lt;= 100
0 &amp;lt;= matrix[i].length &amp;lt;= 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：本题与主站 54 题相同：https://leetcode-cn.com/problems/spiral-matrix/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/solution/swift-fei-chang-xing-xiang-hao-li-jie-de-vbhy/&quot;&gt;题解链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/RyukiePlayground/blob/main/%E5%89%91%E6%8C%87Offer.playground/Pages/29.%20%E9%A1%BA%E6%97%B6%E9%92%88%E6%89%93%E5%8D%B0%E7%9F%A9%E9%98%B5.xcplaygroundpage/Contents.swift&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;出现需要变更方向的点的拐向是一定的：
&lt;ul&gt;
&lt;li&gt;当前向➡️扫描，拐点一定向↓&lt;/li&gt;
&lt;li&gt;当前向↓扫描，拐点一定向←&lt;/li&gt;
&lt;li&gt;当前向←扫描，拐点一定向↑&lt;/li&gt;
&lt;li&gt;当前向↑扫描，拐点一定向➡️&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;flags&lt;/code&gt;为标记数组，用来标识已经扫描过的下标&lt;/p&gt;
&lt;p&gt;在一个方向上：&lt;code&gt;扫描到边界&lt;/code&gt; 或者 &lt;code&gt;扫描到已标记的坐标&lt;/code&gt; 则触发转向&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class Solution {
    enum Dirction {
        /// x + 1
        case Right
        /// y + 1
        case Down
        /// x - 1
        case Left
        /// y - 1
        case Up
    }

    func spiralOrder(_ matrix: [[Int]]) -&amp;gt; [Int] {
        var result: [Int] = []
        let height = matrix.count
        
        guard let first = matrix.first, height &amp;gt; 0 else {
            return result
        }
        
        let width = first.count
        var x = 0, y = 0
        var dir: Dirction = .Right
        
        var flags: [[Bool]] = Array(repeating: Array(repeating: false, count: width), count: height)
        
        while x &amp;gt;= 0, y &amp;gt;= 0, x &amp;lt; width, y &amp;lt; height, result.count &amp;lt; width * height {
            flags[y][x] = true
            result.append(matrix[y][x])
            
            switch dir {
            case .Right:
                if x + 1 &amp;lt; width, flags[y][x + 1] == false {
                    x += 1
                }
                else {
                    dir = .Down
                    y += 1
                }
            case .Down:
                if y + 1 &amp;lt; height, flags[y + 1][x] == false {
                    y += 1
                }
                else {
                    dir = .Left
                    x -= 1
                }
            case .Left:
                if x - 1 &amp;gt;= 0, x - 1 &amp;lt; width, flags[y][x - 1] == false {
                    x -= 1
                }
                else {
                    dir = .Up
                    y -= 1
                }
            case .Up:
                if y - 1 &amp;gt;= 0, y - 1 &amp;lt; height, flags[y - 1][x] == false {
                    y -= 1
                }
                else {
                    dir = .Right
                    x += 1
                }
            }
        }
        
        return result
    }
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>一套iOS底层试卷</title><link>https://blog.lab-lawliet.app/posts/article-ios-6f7b84884328c0b1/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-ios-6f7b84884328c0b1/</guid><description>xcrun 或 clang</description><pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6983175020340051976&quot;&gt;一套iOS底层试卷&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、选择题 ⚠️ 有单选有多选哦⚠️&lt;/h2&gt;
&lt;h3&gt;1.1 在LP64下,一个指针的有多少个字节&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 4&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: 8&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 16&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 64&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 一个实例对象的内存结构存在哪些元素&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[X] A:成员变量&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: supClass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] C: cache_t&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] D: bit&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;A&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.3 下面sizeof(struct3) 大小等于&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct Struct1 {
    char b;// 1
    int c; // 4
    double a; // 8
    short d; // 2
}struct1;

struct Struct2 {
    double a;
    int b;
    char c;
    short d;
}struct2;

struct Struct3 {
    double a;
    int b;
    char c;
    struct Struct1 str1;
    short d;
    int e;
    struct Struct2 str2;
}struct3;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 48&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: 56&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 64&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 72&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;!-- * Struct1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0 1-4 7-14 15-16&lt;/li&gt;
&lt;li&gt;总 24&lt;/li&gt;
&lt;li&gt;Struct2
&lt;ul&gt;
&lt;li&gt;0-7 11-14 15 16-17&lt;/li&gt;
&lt;li&gt;总 24&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Struct3
&lt;ul&gt;
&lt;li&gt;0-7 8-11 12 15&lt;/li&gt;
&lt;li&gt;16-19 23-30 31-32&lt;/li&gt;
&lt;li&gt;33-34 35-38&lt;/li&gt;
&lt;li&gt;39-46 47-50 51 53-54&lt;/li&gt;
&lt;li&gt;总 56&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;B --&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;C&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.4 下列代码: re1 re2 re3 re4 re5 re6 re7 re8输出结果&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];     
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];   
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];  
NSLog(@&quot; re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n&quot;,re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];      
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];    
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];     
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];   
NSLog(@&quot; re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n&quot;,re5,re6,re7,re8);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 1011 1111&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] B: 1100 1011&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] C: 1000 1111&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 1101 1111&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.5 (x + 7) &amp;amp; ~7 这个算法是几字节对齐&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 7&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: 8&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 14&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 16&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.6 判断下列数据结构大小&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;union kc_t {
    uintptr_t bits;// 8
    struct {
        int a;// 4
        char b;// 1
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[X] A: 8&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] B: 12&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 13&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 16&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.7 元类的 isa 指向谁, 根元类的父类是谁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 自己 , 根元类&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[B] B: 自己 , NSObject&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 根元类 , 根元类&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] D: 根元类 , NSObject&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.8 查找方法缓存的时候发现是乱序的, 为什么? 哈希冲突怎么解决的&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 哈希函数原因 , 不解决&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: 哈希函数原因 , 再哈希&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] C: 他存他的我也布吉岛 , 再哈希&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 他乱由他乱,清风过山岗 , 不解决&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.9 消息的流程是&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[X] A: 先从缓存快速查找&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] B: 慢速递归查找methodlist (自己的和父类的,直到父类为nil)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] C: 动态方法决议&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] D: 消息转发流程&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.10 类方法动态方法决议为什么在后面还要实现 resolveInstanceMethod&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;[ ] A: 类方法存在元类(以对象方法形式存在), 元类的父类最终是 NSObject 所以我们可以通过resolveInstanceMethod 防止 NSObject 中实现了对象方法!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] B: 因为在oc的底层最终还是对象方法存在&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[X] C: 类方法存在元类以对象方法形式存在.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;[ ] D: 咸吃萝卜,淡操心! 苹果瞎写的 不用管&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;A&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、判断题&lt;/h2&gt;
&lt;h3&gt;2.1 光凭我们的对象地址,无法确认对象是否存在关联对象&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 对&lt;/li&gt;
&lt;li&gt;[X] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 int c[4] = {1,2,3,4}; int *d = c; c[2] = *(d+2)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[X] 对&lt;/li&gt;
&lt;li&gt;[ ] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 @interface LGPerson : NSObject{ UIButton *btn } 其中 btn 是实例变量&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[X] 对&lt;/li&gt;
&lt;li&gt;[ ] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 NSObject 除外 元类的父类 = 父类的元类&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[X] 对&lt;/li&gt;
&lt;li&gt;[ ] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.5 对象的地址就是内存元素的首地址&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] 对&lt;/li&gt;
&lt;li&gt;[X] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;对&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.6 类也是对象&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[X] 对&lt;/li&gt;
&lt;li&gt;[ ] 错&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、简单题&lt;/h2&gt;
&lt;h3&gt;3.1 怎么将上层OC代码还原成 C++代码  分值10分&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;xcrun 或 clang&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;3.2 怎么打开汇编查看流程,有什么好处 ?  分值10分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debug - DebugWorkflow - Always show disassembly&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;查看汇编可以从更深层次了解当前函数的汇编层面的执行，为&lt;code&gt;OBJC&lt;/code&gt;源码分析提供信息，部分源码的执行被LLDB进行了改动，结合会变查看可以避免方向性错误，结合&lt;code&gt;memory read&lt;/code&gt;可以更清楚的看到寄存器之间是如何让互相配合处理配合的。使用汇编查看流程可以再不确定源码出处和执行流程的情况下，跟踪内部代码，并找到出处。同时，结合符号断点的方式能够更清楚的跟踪源码实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 x/4gx 和 p/x 以及 p *$0 代表什么意思  分值10分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;从首地址开始，1️以8字节的形式打印4段地址&lt;/li&gt;
&lt;li&gt;打印地址&lt;/li&gt;
&lt;li&gt;取出&lt;code&gt;$0&lt;/code&gt;地址中的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4 类方法存在哪里? 为什么要这么设计?  分值10分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;实例方法保存在类的方法列表中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;类方法保存在元类的方法列表中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这样设计的好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;底层不用区分类方法和对象方法，本质上都是对象方法。方法调用都可以理解为消息发送，只不过消息的接收者不一样。&lt;/li&gt;
&lt;li&gt;这样设计更加的面向对象，类的一切信息都存储在元类中，对象的一切信息都存储在类中。类是元类实例化出的对象，实例对象是类实例化出的对象。存储也更符合面向对象的特点。&lt;/li&gt;
&lt;li&gt;OC语言早期借鉴了另一种 &lt;code&gt;SmallTalk&lt;/code&gt;的语言，其中也是这么设计的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.5 方法慢速查找过程中的二分查找流程,请用伪代码实现 分值10分&lt;/h3&gt;
&lt;h3&gt;3.6 ISA_MASK = 0x00007ffffffffff8ULL 那么这个 ISA_MASK 的算法意义是什么?  分值10分&lt;/h3&gt;
&lt;p&gt;ISA包含在&lt;code&gt;ISA_BITFIELD&lt;/code&gt;中， &lt;code&gt;&amp;amp; ISA_MASK&lt;/code&gt;是为了去除ISA地址两边的数据（&lt;code&gt;标志位&lt;/code&gt;、&lt;code&gt;引用计数&lt;/code&gt;等），拿到正确的ISA地址。&lt;/p&gt;
&lt;h3&gt;3.7 类的结构里面为什么会有 rw 和 ro 以及 rwe ?  分值10分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;class_ro_t
&lt;ul&gt;
&lt;li&gt;read only&lt;/li&gt;
&lt;li&gt;类名、方法、协议、成员变量等&lt;/li&gt;
&lt;li&gt;Clean memory
&lt;ul&gt;
&lt;li&gt;运行时不会改变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;class_rw_t
&lt;ul&gt;
&lt;li&gt;read write&lt;/li&gt;
&lt;li&gt;使用来读写数据的，在这个数据结构中存储了只有在运行时才会生成的信息。&lt;/li&gt;
&lt;li&gt;可读可写&lt;/li&gt;
&lt;li&gt;Dirty memory
&lt;ul&gt;
&lt;li&gt;运行时会改变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;class_rw_ext_t
&lt;ul&gt;
&lt;li&gt;read write extension&lt;/li&gt;
&lt;li&gt;动态添加的
&lt;ul&gt;
&lt;li&gt;方法&lt;/li&gt;
&lt;li&gt;属性&lt;/li&gt;
&lt;li&gt;协议等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这将 class_rw_t 的体积减少了进 50%。在有需要的时候将 class_rw_ext_t 插入其中，约 90% 的类用不到这些拓展数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Dirty memory 要比 Clean memory 昂贵的多，只要进程在运行，他就一直存在。Clean memory 可以进行移除，以节省空间；如果需要可以再从磁盘中加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;之所以将Class的数据分成两部分，是为了尽可能的节省空间&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.8 cache 在什么时候开始扩容 , 为什么?  分值10分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一般情况
&lt;ul&gt;
&lt;li&gt;3/4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;真机Arm64
&lt;ul&gt;
&lt;li&gt;7/8&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;小缓存特殊情况
&lt;ul&gt;
&lt;li&gt;100%&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么
&lt;ul&gt;
&lt;li&gt;效率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.9 objc_msgSend 为什么用汇编写 , objc_msgSend 是如何递归找到imp?  分值10分&lt;/h3&gt;
&lt;p&gt;效率高&lt;/p&gt;
&lt;h3&gt;3.10 一个类的类方法没有实现为什么可以调用 NSObject 同名对象方法  分值10分&lt;/h3&gt;
&lt;p&gt;ISA&lt;/p&gt;
</content:encoded></item><item><title>矩阵中的路径（回溯法）</title><link>https://blog.lab-lawliet.app/posts/article-12-92c0bde8f0835b01/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-12-92c0bde8f0835b01/</guid><description>回溯法可以看做蛮力法的升级版，它从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案。回溯法非常适合有多个步骤组成的问题，并且每个步骤都有多个选项。当我们在某一步选择了其中一个选项时，就进入下一步，然后有面临新的选择。我们这样重复选择，直至达到最终状态。</description><pubDate>Sat, 10 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、回溯法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;回溯法&lt;/code&gt;可以看做蛮力法的升级版，它从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案。回溯法非常适合&lt;code&gt;有多个步骤组成的问题&lt;/code&gt;，并且&lt;code&gt;每个步骤都有多个选项&lt;/code&gt;。当我们在某一步选择了其中一个选项时，就进入下一步，然后有面临新的选择。我们这样重复选择，直至达到最终状态。&lt;/p&gt;
&lt;p&gt;用&lt;code&gt;回溯法&lt;/code&gt;解决的问题的所有选项可以形象的用&lt;code&gt;树状结构&lt;/code&gt;表示。在某一步有n个可能的选项，那么该步骤可以看成是树状结构的一个&lt;code&gt;节点&lt;/code&gt;，每个选项可以看做树中节点的&lt;code&gt;联线&lt;/code&gt;，经过这些联线到达该节点的n个&lt;code&gt;子节点&lt;/code&gt;。树的叶节点对应着终结状态。&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果在叶节点的状态满足题目的越苏条件时，那么我们找到了一个可行的解决方案。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果在叶节点的状态不满足约束条件，那么只好回溯到它的上一个节点，在尝试其他的选项。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果上一个节点，所有可能的选项多已经试过了，并且不能达到满足条件的最终状态，则再次回溯到上一个节点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果所有节点的所有选项都已经尝试过，仍然不能达到满足约束条件的终结状态，则无解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、典型题目&lt;/h2&gt;
&lt;p&gt;给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中，返回 true ；否则，返回 false 。&lt;/p&gt;
&lt;p&gt;单词必须按照字母顺序，通过相邻的单元格内的字母构成，其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
 
例如，在下面的 3×4 的矩阵中包含单词 &quot;ABCCED&quot;（单词中的字母已标出）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|A|B|C|E|
|S|F|C|S|
|A|D|E|E|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入：board = [[&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;E&quot;],[&quot;S&quot;,&quot;F&quot;,&quot;C&quot;,&quot;S&quot;],[&quot;A&quot;,&quot;D&quot;,&quot;E&quot;,&quot;E&quot;]], word = &quot;ABCCED&quot;
 输出：true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入：board = [[&quot;a&quot;,&quot;b&quot;],[&quot;c&quot;,&quot;d&quot;]], word = &quot;abcd&quot;
 输出：false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 &amp;lt;= board.length &amp;lt;= 200
1 &amp;lt;= board[i].length &amp;lt;= 200
board 和 word 仅由大小写英文字母组成
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：本题与主站 79 题相同：https://leetcode-cn.com/problems/word-search/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;三、解法&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0dcbbd43763610af.webp&quot; alt=&quot;解法&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyukieSama/RyukiePlayground/blob/main/%E5%89%91%E6%8C%87Offer.playground/Pages/%E2%AD%90%EF%B8%8F%E2%AD%90%EF%B8%8F12.%E7%9F%A9%E9%98%B5%E4%B8%AD%E7%9A%84%E8%B7%AF%E5%BE%84%EF%BC%88%E5%9B%9E%E6%BA%AF%E6%B3%95%EF%BC%89.xcplaygroundpage/Contents.swift&quot;&gt;⭐GitHub&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; func exist(_ board: [[Character]], _ word: String) -&amp;gt; Bool {
    let maxY = board.count
    let chars: [Character] = Array(word)
    
    guard let maxX = board.first?.count, maxY &amp;gt; 0, maxX &amp;gt; 0 else {
        return false
    }
    
    // 用于保存访问状态
    var visited: [[Bool]] = Array(repeating: Array(repeating: false, count: maxX), count: maxY)
    
    var pathLength = 0
    
    for y in 0..&amp;lt;maxY {
        for x in 0..&amp;lt;maxX {
            if hasPathCore(board, chars, maxX: maxX, maxY: maxY, pathLength: &amp;amp;pathLength, x: x, y: y, visted: &amp;amp;visited) {
                return true
            }
        }
    }
    
    return false
}

func hasPathCore(_ board: [[Character]], _ chars: [Character], maxX: Int, maxY: Int, pathLength: inout Int, x: Int, y: Int, visted: inout [[Bool]]) -&amp;gt; Bool {
    if pathLength == chars.count {
        return true
    }
    
    var result = false
    if x &amp;gt;= 0, y &amp;gt;= 0,
       x &amp;lt; maxX, y &amp;lt; maxY,
       visted[y][x] == false,
       board[y][x] == chars[pathLength]
    {
        pathLength += 1
        visted[y][x] = true
        
        result =
            hasPathCore(board, chars, maxX: maxX, maxY: maxY, pathLength: &amp;amp;pathLength, x: x - 1, y: y, visted: &amp;amp;visted) ||
            hasPathCore(board, chars, maxX: maxX, maxY: maxY, pathLength: &amp;amp;pathLength, x: x + 1, y: y, visted: &amp;amp;visted) ||
            hasPathCore(board, chars, maxX: maxX, maxY: maxY, pathLength: &amp;amp;pathLength, x: x, y: y - 1, visted: &amp;amp;visted) ||
            hasPathCore(board, chars, maxX: maxX, maxY: maxY, pathLength: &amp;amp;pathLength, x: x, y: y + 1, visted: &amp;amp;visted)
        
        if result == false {
            pathLength -= 1
            visted[y][x] = false
        }
    }
    
    return result
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、本题考点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;考察对回溯法的理解。通常在二维矩阵上找路径都可以使用回溯法解决&lt;/li&gt;
&lt;li&gt;考察对数组的编程能力。我们一般都把矩阵看成是儿歌二维数组。只有对数组的特性充分了解，才能快速、正确地实现回溯法的代码。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>二进制中1的个数（含一个拓展问题）</title><link>https://blog.lab-lawliet.app/posts/article-15-1-652f6b37fedc0e22/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-15-1-652f6b37fedc0e22/</guid><description>编写一个函数，输入是一个无符号整数（以二进制串的形式），返回其二进制表达式中数字位数为 &apos;1&apos; 的个数（也被称为 汉明重量).）。</description><pubDate>Fri, 09 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、题目&lt;/h2&gt;
&lt;p&gt;编写一个函数，输入是一个无符号整数（以二进制串的形式），返回其二进制表达式中数字位数为 &apos;1&apos; 的个数（也被称为 汉明重量).）。&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;请注意，在某些语言（如 Java）中，没有无符号整数类型。在这种情况下，输入和输出都将被指定为有符号整数类型，并且不应影响您的实现，因为无论整数是有符号的还是无符号的，其内部的二进制表示形式都是相同的。
在 Java 中，编译器使用 二进制补码 记法来表示有符号整数。因此，在上面的 示例 3 中，输入表示有符号整数 -3。&lt;/p&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入：n = 11 (控制台输入 00000000000000000000000000001011)
 输出：3
 解释：输入的二进制串 00000000000000000000000000001011 中，共有三位为 &apos;1&apos;。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 2：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入：n = 128 (控制台输入 00000000000000000000000010000000)
 输出：1
 解释：输入的二进制串 00000000000000000000000010000000 中，共有一位为 &apos;1&apos;。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 3：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 输入：n = 4294967293 (控制台输入 11111111111111111111111111111101，部分语言中 n = -3）
 输出：31
 解释：输入的二进制串 11111111111111111111111111111101 中，共有 31 位为 &apos;1&apos;。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;&lt;code&gt; 输入必须是长度为 32 的 二进制串 。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意：本题与主站 191 题相同：https://leetcode-cn.com/problems/number-of-1-bits/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h3&gt;解法&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;一位一位右移，每次判断最后一位&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func hammingWeight(_ n: Int) -&amp;gt; Int {
    var count = 0
    var temp = n
    
    while temp &amp;gt; 0 {
        count += (temp &amp;amp; 1 == 1) ? 1 : 0
        temp &amp;gt;&amp;gt;= 1
    }

    return count
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;思考一&lt;/h3&gt;
&lt;p&gt;这个解法很容易就可以想出来。那么面试官可能会问了，&lt;code&gt;右移一位&lt;/code&gt;和&lt;code&gt;除以2&lt;/code&gt;是在数学上是等价的，那么这里可以换成除以2么？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最好不要。因为除法的效率比位运算效率低得多，在实际编码过程中也尽量使用位运算代替乘除法，尤其是高频调用的代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、拓展问题&lt;/h2&gt;
&lt;p&gt;如果题目中的 &lt;code&gt;无符号整数&lt;/code&gt; 换成 &lt;code&gt;有符号整数&lt;/code&gt;，上面的解法是否还有效？&lt;/p&gt;
&lt;p&gt;0x80000000 是一个负数，右移就不是 0x40000000了，是0xC0000000&lt;/p&gt;
&lt;p&gt;右移会以符号位填补空白，负数补1，正数补0&lt;/p&gt;
&lt;p&gt;如果单存的右移的话就会死循环&lt;/p&gt;
&lt;p&gt;这里换个思路，移动1的位置&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知识补充：
&lt;ul&gt;
&lt;li&gt;负数在计算机中二进制用补码标识&lt;/li&gt;
&lt;li&gt;负数的补码：除符号位其余各位取反，然后+1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func hammingWeight2(_ n: Int) -&amp;gt; Int {
    var count: Int = 0
    var flag: Int = 1
    
    while (flag != 0) {
        count += (n &amp;amp; flag == flag) ? 1 : 0
        if flag &amp;lt; 0 {// 最高位为1
            return count
        }
        flag &amp;lt;&amp;lt;= 1
    }

    return count
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>没钱推广？这忘了这100美元</title><link>https://blog.lab-lawliet.app/posts/article-01-100-5b74bf50350d08d5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-01-100-5b74bf50350d08d5/</guid><description>对于独立开发者而言，推广一般来说是个难事儿。（人脉广的大佬就请忽略了，这里说的是一般情况，比如我自己。）</description><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;对于独立开发者而言，推广一般来说是个难事儿。（人脉广的大佬就请忽略了，这里说的是一般情况，比如我自己。）&lt;/p&gt;
&lt;p&gt;我就以我比较浅薄的见识聊一下我对于推广的了解吧。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一般用户增长有这么几个途径
&lt;ul&gt;
&lt;li&gt;广告平台投放
&lt;ul&gt;
&lt;li&gt;国内环境对于个人开发者不友好，基本都需要公司主体&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;KOL推广
&lt;ul&gt;
&lt;li&gt;要自己联系，定价和效果不一定&lt;/li&gt;
&lt;li&gt;如果你自己就是KOL再好不过了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;个人推广
&lt;ul&gt;
&lt;li&gt;主要是通过个人的朋友圈等自己的社交媒体推广&lt;/li&gt;
&lt;li&gt;效果一般&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用商店浏览
&lt;ul&gt;
&lt;li&gt;自然用户主要来源&lt;/li&gt;
&lt;li&gt;如果能上苹果推荐的话带来的用户量就很可观了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用商店搜索
&lt;ul&gt;
&lt;li&gt;自然用户主要来源&lt;/li&gt;
&lt;li&gt;搜索的权重和你App的销售状况也是有关的，这里就不暂开讲了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过上面的分析，其实留给国内独立开发者的低成本的推广其实选项不多，看起来一般人来说只能靠自然增长了。&lt;/p&gt;
&lt;p&gt;某天，我发现 &lt;code&gt;Apple&lt;/code&gt; 给了开发者一个不大不小的福利：&lt;code&gt;$100&lt;/code&gt; 推广费。（哇！比年费还多1美元呢~）&lt;/p&gt;
&lt;p&gt;这里就教你怎么用这 &lt;code&gt;$100&lt;/code&gt; 推广费，以及我自己的使用体验。&lt;/p&gt;
&lt;h2&gt;二、Apple Search Ads&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://searchads.apple.com/cn/&quot;&gt;SearchAds&lt;/a&gt;是&lt;code&gt;Apple&lt;/code&gt;自己的&lt;code&gt;App Store&lt;/code&gt;广告系统。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/76b0b021eadb1d71.webp&quot; alt=&quot;Ads&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.1 两种广告模式&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e64585badb353eac.webp&quot; alt=&quot;Ads&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://searchads.apple.com/cn/basic/&quot;&gt;Basic🔗&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;最简单的广告&lt;/li&gt;
&lt;li&gt;设置投放地区、预估单价、每月预算即可投放&lt;/li&gt;
&lt;li&gt;这里主要聊到的就是这个模式的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://searchads.apple.com/cn/advanced/&quot;&gt;Advanced🔗&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;更多专业的选项&lt;/li&gt;
&lt;li&gt;我不专业没太多研究&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、Basic的使用&lt;/h2&gt;
&lt;p&gt;我们只需要关注下面三个点即可&lt;/p&gt;
&lt;h3&gt;3.1 投放地区&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不包括&lt;code&gt;中国大陆&lt;/code&gt;地区。很遗憾，大陆政策比较特别。
&lt;ul&gt;
&lt;li&gt;虽然最近有开中国大陆的，看了下，是需要公司主体的。这里就忽略掉。&lt;/li&gt;
&lt;li&gt;港澳台是可以投放的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;所以想要使用&lt;code&gt;SearchAds&lt;/code&gt;达到有效的推广的话，本地化还是简单做下比较好，至少搞个中英吧。&lt;/li&gt;
&lt;li&gt;地区不要全选
&lt;ul&gt;
&lt;li&gt;我最开始投放的时候就全选了&lt;/li&gt;
&lt;li&gt;但是投放一段时间后发现个别地区的单价过高，十几倍的情况也是有的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;我的投放案例&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里是某段时间内投放效果
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;SearchAds&lt;/code&gt;后台可以下载&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;梦见账本&lt;/a&gt;做了中、英、日的多语言&lt;/li&gt;
&lt;li&gt;这次分析之后我只留下了下载量与单价较为合适的几个&lt;/li&gt;
&lt;li&gt;感觉因为地区全选了浪费了好多额度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/432846d3ac8920dd.webp&quot; alt=&quot;Ads&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 最高每次安装费用 (CPI)&lt;/h3&gt;
&lt;p&gt;别怕，实际平均会比这个值低很多，看我上面的记录其实单价超过&lt;code&gt;$1&lt;/code&gt;的不多。这里苹果也会给你一个推荐值，我就用的推荐值&lt;code&gt;$2.20&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;3.3 每月预算&lt;/h3&gt;
&lt;p&gt;很好理解了，超过这个数就暂停投放了，我就可怜的设了个&lt;code&gt;$10&lt;/code&gt;...&lt;/p&gt;
&lt;h2&gt;四、总结&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SearchAds&lt;/code&gt;有局限性，比如国内不能用。但是对于独立开发者而言，却是一个门槛极低的并且可靠的推广途径。&lt;/p&gt;
&lt;p&gt;投放期间的下载量和停止投放对比起来还是挺明显更的，要是觉得效果不错，而且应用本地化做的也OK的话，其实完全可以持续在上面投放的，要绑定信用卡。&lt;/p&gt;
&lt;p&gt;这里&lt;code&gt;Apple&lt;/code&gt;提供了&lt;code&gt;$100&lt;/code&gt;，我早就用完了。现在想想觉得挺可惜的，最早因为乱选地区浪费了很多用户。也希望看到这篇文章的你能够让这&lt;code&gt;$100&lt;/code&gt;花的更有效。&lt;/p&gt;
</content:encoded></item><item><title>调整数组顺序使奇数位于偶数前面</title><link>https://blog.lab-lawliet.app/posts/article-21-52c5c737ef6d4a8a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-21-52c5c737ef6d4a8a/</guid><description>输入一个整数数组，实现一个函数来调整该数组中数字的顺序，使得所有奇数位于数组的前半部分，所有偶数位于数组的后半部分。</description><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;输入一个整数数组，实现一个函数来调整该数组中数字的顺序，使得所有奇数位于数组的前半部分，所有偶数位于数组的后半部分。&lt;/p&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;p&gt;输入：nums = [1,2,3,4]
输出：[1,3,2,4]
注：[3,1,2,4] 也是正确的答案之一。&lt;/p&gt;
&lt;p&gt;提示：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= nums.length &amp;lt;= 50000
1 &amp;lt;= nums[i] &amp;lt;= 10000&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;解法一&lt;/h2&gt;
&lt;p&gt;最容易想到的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func exchange(_ nums: [Int]) -&amp;gt; [Int] {
    var temp: [Int] = []
    nums.forEach {
        if $0 &amp;amp; 1 == 1 {
            temp.insert($0, at: 0)
        }
        else {
            temp.append($0)
        }
    }
    return temp
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解法二：差速双指针&lt;/h2&gt;
&lt;p&gt;配上这个动图食用更佳&lt;a href=&quot;https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/solution/ti-jie-shou-wei-shuang-zhi-zhen-kuai-man-shuang-zh/&quot;&gt;来源&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2fc303009d27b12c.gif&quot; alt=&quot;差速双指针&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func exchange1(_ nums: [Int]) -&amp;gt; [Int] {
    var fastP: Int = 0
    var slowP: Int = 0
    var temp = nums

    while fastP &amp;lt; nums.count {
        if temp[fastP] &amp;amp; 1 == 1 {
            temp.swapAt(fastP, slowP)
            slowP += 1
        }
        fastP += 1
    }

    return temp
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>合并两个排序的链表</title><link>https://blog.lab-lawliet.app/posts/article-25-5d3051f6d4b94f4d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-25-5d3051f6d4b94f4d/</guid><description>输入两个递增排序的链表，合并这两个链表并使新链表中的节点仍然是递增排序的。</description><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;输入两个递增排序的链表，合并这两个链表并使新链表中的节点仍然是递增排序的。&lt;/p&gt;
&lt;p&gt;示例1：&lt;/p&gt;
&lt;p&gt;输入：1-&amp;gt;2-&amp;gt;4, 1-&amp;gt;3-&amp;gt;4
输出：1-&amp;gt;1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;4
限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 链表长度 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 21 题相同：https://leetcode-cn.com/problems/merge-two-sorted-lists/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;/p&gt;
&lt;p&gt;链接：https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;func mergeTwoLists(_ l1: ListNode?, _ l2: ListNode?) -&amp;gt; ListNode? {
    var line1 = l1
    var line2 = l2
    var firstNode: ListNode?
    var lastNode: ListNode?
    
    while let temp1 = line1, let temp2 = line2 {
        let isL1VSmaller = temp1.val &amp;lt; temp2.val
        let node = ListNode(isL1VSmaller ? temp1.val : temp2.val)
        if firstNode == nil {
            firstNode = node
        }
        else {
            lastNode?.next = node
        }
        lastNode = node
        isL1VSmaller ? (line1 = temp1.next) : (line2 = temp2.next)
    }
    
    if line1 == nil {
        lastNode?.next = line2
    }
    else {
        lastNode?.next = line1
    }

    return firstNode != nil ? firstNode : (l1 != nil ? l1 : l2)
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二叉树的镜像</title><link>https://blog.lab-lawliet.app/posts/article-27-613b67dd7dddd4ac/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-27-613b67dd7dddd4ac/</guid><description>请完成一个函数，输入一个二叉树，该函数输出它的镜像。</description><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;p&gt;请完成一个函数，输入一个二叉树，该函数输出它的镜像。&lt;/p&gt;
&lt;p&gt;例如输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      4
    /   \
   2     7
  / \   / \
 1   3 6   9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;镜像输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      4
    /   \
   7     2
  / \   / \
 9   6 3   1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：root = [4,2,7,1,3,6,9]
输出：[4,7,2,9,6,3,1]&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 节点个数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 226 题相同：https://leetcode-cn.com/problems/invert-binary-tree/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;题解一&lt;/h2&gt;
&lt;p&gt;递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mirrorTree(_ root: TreeNode?) -&amp;gt; TreeNode? {
    if root == nil {
        return nil
    }
    let left = root?.left
    let right = root?.right
    root?.left = mirrorTree(right)
    root?.right = mirrorTree(left)
    return root
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;题解二&lt;/h2&gt;
&lt;p&gt;stack&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mirrorTree2(_ root: TreeNode?) -&amp;gt; TreeNode? {
   var stack: [TreeNode?] = [root]
   
   while stack.last != nil {
       let node = stack.removeLast()
       let right = node?.right
       let left = node?.left
       
       if let rn = right {
           stack.append(rn)
       }
       if let ln = left {
           stack.append(ln)
       }
       
       node?.left = right
       node?.right = left
   }
   
   return root
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>对称的二叉树</title><link>https://blog.lab-lawliet.app/posts/article-28-2e35cc4335e6b896/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-28-2e35cc4335e6b896/</guid><description>请实现一个函数，用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样，那么它是对称的。</description><pubDate>Thu, 08 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;题目&lt;/h2&gt;
&lt;p&gt;请实现一个函数，用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样，那么它是对称的。&lt;/p&gt;
&lt;p&gt;例如，二叉树 [1,2,2,3,4,4,3] 是对称的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     1
    / \
   2   2
  / \ / \
 3  4 4  3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     1
    / \
   2   2
    \   \
    3    3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例 1：&lt;/p&gt;
&lt;p&gt;输入：root = [1,2,2,3,4,4,3]
输出：true
示例 2：&lt;/p&gt;
&lt;p&gt;输入：root = [1,2,2,null,3,null,3]
输出：false&lt;/p&gt;
&lt;p&gt;限制：&lt;/p&gt;
&lt;p&gt;0 &amp;lt;= 节点个数 &amp;lt;= 1000&lt;/p&gt;
&lt;p&gt;注意：本题与主站 101 题相同：https://leetcode-cn.com/problems/symmetric-tree/&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;h2&gt;题解&lt;/h2&gt;
&lt;p&gt;递归&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func isSymmetric(_ root: TreeNode?) -&amp;gt; Bool {
    func compare(tree1: TreeNode?, tree2: TreeNode?) -&amp;gt; Bool {
        if tree1 == nil &amp;amp;&amp;amp; tree2 == nil {
            return true
        }
        else if tree1 == nil || tree2 == nil {
            return false
        }
        else if tree1?.val != tree2?.val {
            return false
        }
        return compare(tree1: tree1?.left, tree2: tree2?.right) &amp;amp;&amp;amp; compare(tree1: tree1?.right, tree2: tree2?.left)
    }
    
    return compare(tree1: root?.left, tree2: root?.right)
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>open与public</title><link>https://blog.lab-lawliet.app/posts/swift-open-public-eded526ab33180af/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-open-public-eded526ab33180af/</guid><description>根据提示，将组件库中的 public 改成 open ，就可以了。</description><pubDate>Mon, 05 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;在对自己的项目进行组件化的时候遇到这样一个问题&lt;/h2&gt;
&lt;h3&gt;UI组件中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class LedgerNavigationViewController: UINavigationController, StoryboardInitializable {
    public override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;项目中（报错）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Cannot inherit from non-open class &apos;LedgerNavigationViewController&apos; outside of its defining module&lt;/li&gt;
&lt;li&gt;Overriding non-open instance method outside of its defining module&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MapNavigationController: LedgerNavigationViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    weak var selector: LocationSelector?
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;修改&lt;/h2&gt;
&lt;p&gt;根据提示，将组件库中的 &lt;code&gt;public&lt;/code&gt; 改成 &lt;code&gt;open&lt;/code&gt; ，就可以了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;open class LedgerNavigationViewController: UINavigationController, StoryboardInitializable {
    open override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;平时每台用到 &lt;code&gt;open&lt;/code&gt;，这里遇到问题才很清楚get到他俩的不同，主要区别在继承的控制上。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;public
&lt;ul&gt;
&lt;li&gt;只能在模块内部被继承&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;open
&lt;ul&gt;
&lt;li&gt;可以再其他模块被继承&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>isKindOfClass的底层实现</title><link>https://blog.lab-lawliet.app/posts/ios-08-iskindofclass-0e09da4ca524a3f5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-08-iskindofclass-0e09da4ca524a3f5/</guid><description>思考一下，会输出什么？</description><pubDate>Sun, 27 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;BOOL result1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL result2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL result3 = [[RYModel class] isKindOfClass:[RYModel class]];
BOOL result4 = [[RYModel class] isMemberOfClass:[RYModel class]];

NSLog(@&quot;Class\n %hhd \n %hhd \n %hhd \n %hhd&quot;, result1, result2, result3, result4);

BOOL result5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL result6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL result7 = [[RYModel alloc] isKindOfClass:[RYModel class]];
BOOL result8 = [[RYModel alloc] isMemberOfClass:[RYModel class]];

NSLog(@&quot;Instacne\n %hhd \n %hhd \n %hhd \n %hhd&quot;, result5, result6, result7, result8);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;思考一下，会输出什么？&lt;/p&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-06-27 08:29:10.567002+0800 IsKindOf[62355:691675] Class
 1 
 0 
 0 
 0
2021-06-27 08:29:10.567836+0800 IsKindOf[62355:691675] Instacne
 1 
 1 
 1 
 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果仅从字面上去理解代码，那么你的答案应该会是不一样的的。我们从源码层面来理解一下吧，源码不长，很容易理解。&lt;/p&gt;
&lt;h2&gt;一、isKindOfClass&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self-&amp;gt;ISA(); tcls; tcls = tcls-&amp;gt;getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls-&amp;gt;getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 流程图-Class&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5534f27775f8d692.webp&quot; alt=&quot;Class-isKindOfClass&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[NSObject class] 即 &lt;code&gt;RootClass&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RootClass&lt;/code&gt; -(ISA)&amp;gt; &lt;code&gt;RootMetaClass&lt;/code&gt; -(superClass)&amp;gt; &lt;code&gt;RootClass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[RYModel class]
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[RYModel class]&lt;/code&gt; -(ISA)&amp;gt; &lt;code&gt;RYModelMetaClass&lt;/code&gt; -(superClass)&amp;gt; &lt;code&gt;RootMetaClass&lt;/code&gt; -(superClass)&amp;gt; &lt;code&gt;RootClass&lt;/code&gt; -(superClass)&amp;gt; &lt;code&gt;nil&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;false&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 流程图-Instance&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/518707fdb2556ba5.webp&quot; alt=&quot;Instance-isKindeOfClass&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NSObjectInstance
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NSObjectInstance&lt;/code&gt; -(class)&amp;gt; &lt;code&gt;RootClass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RYModelInstance
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RYModelInstance&lt;/code&gt; -(class)&amp;gt; &lt;code&gt;RYModelClass&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、isMemberOfClass&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;+ (BOOL)isMemberOfClass:(Class)cls {
    return self-&amp;gt;ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里就很简单了，就不展开说了。&lt;/p&gt;
&lt;h2&gt;三、你以为这就完了？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你看到这里觉得没啥问题？那就有问题了。。。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们运行下OC源码，两个断点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/47fa6cc858996526.webp&quot; alt=&quot;IsKindofClass01&quot; /&gt;&lt;/p&gt;
&lt;p&gt;运行之后发现 &lt;code&gt;+/- (Bool)isKindOfClass:(Class)cls&lt;/code&gt; 居然根本没有调用。&lt;/p&gt;
&lt;h3&gt;3.1 汇编&lt;/h3&gt;
&lt;p&gt;通过断点汇编，我们在调用 &lt;code&gt;isKindOfClass&lt;/code&gt; 的地方发现了这样的指令 &lt;code&gt;objc_opt_isKindOfClass&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;  0x10000381c &amp;lt;+332&amp;gt;: mov    rdi, qword ptr [rip + 0x4f7d] ; (void *)0x0000000100008808: RYModel
    0x100003823 &amp;lt;+339&amp;gt;: call   0x100003b42               ; symbol stub for: objc_opt_class
    0x100003828 &amp;lt;+344&amp;gt;: mov    rbx, rax
    0x10000382b &amp;lt;+347&amp;gt;: mov    rdi, qword ptr [rip + 0x4f6e] ; (void *)0x0000000100008808: RYModel
    0x100003832 &amp;lt;+354&amp;gt;: call   0x100003b42               ; symbol stub for: objc_opt_class
    0x100003837 &amp;lt;+359&amp;gt;: mov    rdi, rbx
    0x10000383a &amp;lt;+362&amp;gt;: mov    rsi, rax
    0x10000383d &amp;lt;+365&amp;gt;: call   0x100003b48               ; symbol stub for: objc_opt_isKindOfClass
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 objc_opt_isKindOfClass&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    // 判空
    if (slowpath(!obj)) return NO;
    Class cls = obj-&amp;gt;getIsa();
    if (fastpath(!cls-&amp;gt;hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls-&amp;gt;getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    // 这里可以忽略
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里下断点，我们发现的确进到了这里。并且 &lt;code&gt;Class&lt;/code&gt; 和 &lt;code&gt;Instance&lt;/code&gt; 的方法都走到了这里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1e4d4ec0d1627e26.webp&quot; alt=&quot;IsKindOfClass02&quot; /&gt;&lt;/p&gt;
&lt;p&gt;编译器对这里进行的优化，用更高效的 &lt;code&gt;objc_opt_isKindOfClass&lt;/code&gt; 代替了原有的方法。&lt;/p&gt;
&lt;h2&gt;四、参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/posts/ios-09-slowpath-fastpath-64c58977bed6ef8d/&quot;&gt;slowpath &amp;amp; fastpath&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>slowpath和fastpath</title><link>https://blog.lab-lawliet.app/posts/ios-09-slowpath-fastpath-64c58977bed6ef8d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-09-slowpath-fastpath-64c58977bed6ef8d/</guid><description>在日常阅读OC源码的过程中经常会遇到这两个宏定义。它是什么意思呢？</description><pubDate>Sun, 27 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;slowpath &amp;amp; fastpath&lt;/h1&gt;
&lt;p&gt;在日常阅读OC源码的过程中经常会遇到这两个宏定义。它是什么意思呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//x很可能为真， fastpath 可以简称为 真值判断
#define fastpath(x) (__builtin_expect(bool(x), 1)) 
//x很可能为假，slowpath 可以简称为 假值判断
#define slowpath(x) (__builtin_expect(bool(x), 0)) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中的 &lt;code&gt;__builtin_expect&lt;/code&gt; 指令是由 &lt;code&gt;gcc&lt;/code&gt; 引入的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目的：编译器可以对代码进行优化，以减少指令跳转带来的性能下降。即性能优化&lt;/li&gt;
&lt;li&gt;作用：允许程序员将最有可能执行的分支告诉编译器。&lt;/li&gt;
&lt;li&gt;指令的写法为：&lt;code&gt;__builtin_expect(EXP, N)&lt;/code&gt; 。表示 EXP==N的概率很大。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fastpath&lt;/code&gt; 定义中 &lt;code&gt;__builtin_expect((x),1)&lt;/code&gt; 表示 x 的值为真的可能性更大；即 执行if 里面语句的机会更大&lt;/li&gt;
&lt;li&gt;&lt;code&gt;slowpath&lt;/code&gt; 定义中的 &lt;code&gt;__builtin_expect((x),0)&lt;/code&gt; 表示 x 的值为假的可能性更大。即执行 else 里面语句的机会更大&lt;/li&gt;
&lt;li&gt;在日常的开发中，也可以通过设置来优化编译器，达到性能优化的目的，设置的路径为：&lt;code&gt;Build Setting&lt;/code&gt; --&amp;gt; &lt;code&gt;Optimization Level&lt;/code&gt; --&amp;gt; &lt;code&gt;Debug&lt;/code&gt; --&amp;gt; &lt;code&gt;将None 改为 fastest 或者 smallest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>深入探索Class的结构</title><link>https://blog.lab-lawliet.app/posts/ios-05-class-0ee1949a2e6315f6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-05-class-0ee1949a2e6315f6/</guid><description>我们先通过下面的例子了解一下内存平移</description><pubDate>Sat, 26 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、内存平移&lt;/h2&gt;
&lt;p&gt;我们先通过下面的例子了解一下&lt;code&gt;内存平移&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int nums[4] = {1, 2, 3, 4};

NSLog(@&quot;数组nums的地址：%p\n&quot;, &amp;amp;nums);

for (int i = 0; i &amp;lt; 4; i++) {
    NSLog(@&quot;第%d个元素是%d地址是%p&quot;, i + 1, nums[i], &amp;amp;nums[i]);
}

NSLog(@&quot;\n&quot;);

int *numsPointer = nums;
NSLog(@&quot;数组nums的指针：%p\n&quot;, numsPointer);

for (int i = 0; i &amp;lt; 4; i++) {
    NSLog(@&quot;偏移%d，取值%d，地址是%p&quot;, i, *(numsPointer + i), (numsPointer + i));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 23:27:02.344397+0800 Class[15488:5517067] 数组nums的地址：0x16f00d3a0

2021-06-22 23:27:02.344734+0800 Class[15488:5517067] 第1个元素是11地址是0x16f00d3a0
2021-06-22 23:27:02.344910+0800 Class[15488:5517067] 第2个元素是12地址是0x16f00d3a4
2021-06-22 23:27:02.345776+0800 Class[15488:5517067] 第3个元素是13地址是0x16f00d3a8
2021-06-22 23:27:02.346025+0800 Class[15488:5517067] 第4个元素是14地址是0x16f00d3ac
2021-06-22 23:27:02.346173+0800 Class[15488:5517067] 
2021-06-22 23:27:02.346328+0800 Class[15488:5517067] 数组nums的指针：0x16f00d3a0

2021-06-22 23:27:02.347933+0800 Class[15488:5517067] 偏移0，取值11，地址是0x16f00d3a0
2021-06-22 23:27:02.348115+0800 Class[15488:5517067] 偏移1，取值12，地址是0x16f00d3a4 // 4字节
2021-06-22 23:27:02.348425+0800 Class[15488:5517067] 偏移2，取值13，地址是0x16f00d3a8 // 4字节
2021-06-22 23:27:02.349788+0800 Class[15488:5517067] 偏移3，取值14，地址是0x16f00d3ac // 4字节
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4c71681c122151c5.webp&quot; alt=&quot;内存平移&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nums&lt;/code&gt;一个元素4字节，指针偏移1次4字节。所以这里可以通过从数组首地址开始偏移的方式进行取值操作。&lt;/p&gt;
&lt;p&gt;同理对&lt;code&gt;Class&lt;/code&gt;的内存结构我们是否也能进行&lt;code&gt;内存平移&lt;/code&gt;来取值呢？&lt;/p&gt;
&lt;h2&gt;二、Class的结构内存计算&lt;/h2&gt;
&lt;p&gt;在Class的源码中我们发现核心的数据结构是这样的，暂时忽略其中的方法，因为不影响Class的内存结构。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方法都存在方法区&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;struct objc_class : objc_object {
    ...

    // Class ISA; objc_object 中的 ISA
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;ISA&lt;/th&gt;
&lt;th&gt;superClass&lt;/th&gt;
&lt;th&gt;cache&lt;/th&gt;
&lt;th&gt;bits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;说明&lt;/td&gt;
&lt;td&gt;ISA（结构体指针）&lt;/td&gt;
&lt;td&gt;父类（结构体指针）&lt;/td&gt;
&lt;td&gt;方法缓存&lt;/td&gt;
&lt;td&gt;类的具体信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;三、objc_class: cache_t 的内存结构&lt;/h2&gt;
&lt;p&gt;我们忽略掉方法，剩下的核心数据结构如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct cache_t {
private:
    explicit_atomic&amp;lt;uintptr_t&amp;gt; _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic&amp;lt;mask_t&amp;gt;    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic&amp;lt;preopt_cache_t *&amp;gt; _originalPreoptCache;
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 小补充：LP64数据模型&lt;/h3&gt;
&lt;p&gt;在里面我们可以看到 &lt;code&gt;__LP64__&lt;/code&gt;，这里代表的是&lt;code&gt;LP64数据模型&lt;/code&gt;。现今所有64位的类Unix平台均使用LP64数据模型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TYPE&lt;/th&gt;
&lt;th&gt;LP32&lt;/th&gt;
&lt;th&gt;ILP32&lt;/th&gt;
&lt;th&gt;LP64&lt;/th&gt;
&lt;th&gt;ILP64&lt;/th&gt;
&lt;th&gt;LLP64&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;含义&lt;/td&gt;
&lt;td&gt;指long和pointer是32位的&lt;/td&gt;
&lt;td&gt;指int，long和pointer是32位的&lt;/td&gt;
&lt;td&gt;指long和pointer是64位&lt;/td&gt;
&lt;td&gt;指int，long，pointer是64位&lt;/td&gt;
&lt;td&gt;指long long和pointer是64位的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CHAR&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHORT&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LONG&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LONG LONG&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POINTER&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3.2 内部联合体的内存结构&lt;/h3&gt;
&lt;p&gt;联合体包含了两部分：结构体、指针。我们已知arm64下指针占用8字节。我们来看下结构体需要占用多少&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;_maybeMask&lt;/th&gt;
&lt;th&gt;_flags&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;uint32_t&lt;/td&gt;
&lt;td&gt;uint16_t&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;结构体大小 8 字节，联合体大小为8字节&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.3 结构总结&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;_bucketsAndMaybeMask&lt;/th&gt;
&lt;th&gt;union&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;说明&lt;/td&gt;
&lt;td&gt;指针(做什么的还需要研究)&lt;/td&gt;
&lt;td&gt;联合体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;long类型 8&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;cache_t大小为16字节&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;四、objc_class : class_data_bits_t 的内存结构&lt;/h2&gt;
&lt;p&gt;核心数据结构如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct class_data_bits_t {
  ...
  // Values are the FAST_ flags above.
  uintptr_t bits;
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现内部只有一个指针&lt;code&gt;bits&lt;/code&gt;，那么如何通过它获取到类的详细信息呢？&lt;/p&gt;
&lt;p&gt;我们发现这样一个函数，通过&lt;code&gt;bits&lt;/code&gt;与上&lt;code&gt;FAST_DATA_MASK&lt;/code&gt;可以获得&lt;code&gt;class_rw_t&lt;/code&gt;。和我们找ISA的过程比较类似。&lt;/p&gt;
&lt;p&gt;那么它是做什么的呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class_rw_t* data() const {
    return (class_rw_t *)(bits &amp;amp; FAST_DATA_MASK);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;FAST_DATA_MASK在&lt;code&gt;LP64&lt;/code&gt;下的定义&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#if __LP64__
...
// data pointer
#define FAST_DATA_MASK          0x00007ffffffffff8UL
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、class_data_bits_t : class_rw_t&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;rw&lt;/code&gt;: read-write&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.1 class_rw_t的内存结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct class_rw_t {
  // Be warned that Symbolication knows the layout of this structure.
  // 标志位，如是否是元类，是否实现了等
  uint32_t flags;
  uint16_t witness;

  explicit_atomic&amp;lt;uintptr_t&amp;gt; ro_or_rw_ext; //

  Class firstSubclass;
  Class nextSiblingClass;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 一些重要函数&lt;/h3&gt;
&lt;p&gt;这里我们会看到一些眼熟的函数，在&lt;code&gt;methods()&lt;/code&gt; &lt;code&gt;properties()&lt;/code&gt; &lt;code&gt;protocols()&lt;/code&gt;中我们发现了另一个重要的类型&lt;code&gt;class_rw_ext_t&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class_rw_ext_t *deepCopy(const class_ro_t *ro) {
    return extAlloc(ro, true);
}

const method_array_t methods() const {
    auto v = get_ro_or_rwe();
    if (v.is&amp;lt;class_rw_ext_t *&amp;gt;()) {
        return v.get&amp;lt;class_rw_ext_t *&amp;gt;(&amp;amp;ro_or_rw_ext)-&amp;gt;methods;
    } else {
        return method_array_t{v.get&amp;lt;const class_ro_t *&amp;gt;(&amp;amp;ro_or_rw_ext)-&amp;gt;baseMethods()};
    }
}

const property_array_t properties() const {
    ...
}

const protocol_array_t protocols() const {
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、class_data_bits_t : class_rw_t : class_rw_ext_t&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr&amp;lt;const class_ro_t&amp;gt; ro;// 成员变量
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.1 list_array_tt&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;method_array_t&lt;/code&gt;/&lt;code&gt;property_array_t&lt;/code&gt;/&lt;code&gt;protocol_array_t&lt;/code&gt; 都是继承于 &lt;code&gt;list_array_tt&lt;/code&gt; 的，通过泛型来完成不同的定义。&lt;/p&gt;
&lt;p&gt;源码核心部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename Element, typename List, template&amp;lt;typename&amp;gt; class Ptr&amp;gt;
class list_array_tt {
  ...
  private:
    union {
        Ptr&amp;lt;List&amp;gt; list;
        uintptr_t arrayAndFlag;
    };
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;method_array_t&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class method_array_t : 
    public list_array_tt&amp;lt;method_t, method_list_t, method_list_t_authed_ptr&amp;gt; {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;property_array_t&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class property_array_t : 
    public list_array_tt&amp;lt;property_t, property_list_t, RawPtr&amp;gt; {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;protocol_array_t&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;class protocol_array_t : 
    public list_array_tt&amp;lt;protocol_ref_t, protocol_list_t, RawPtr&amp;gt; {...}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6.2 class_ro_t&lt;/h3&gt;
&lt;p&gt;源码核心部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct class_ro_t {
  ...
  const ivar_list_t * ivars; // 成员变量
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;七、LLDB调试Class内存结构&lt;/h2&gt;
&lt;h3&gt;结构图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/aebeaf392282b18b.webp&quot; alt=&quot;Class的内存结构&quot; /&gt;&lt;/p&gt;
&lt;p&gt;有这样的一个类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RYModel : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) float height;
@property (nonatomic, strong) RYModel *father;
@property (nonatomic, assign) BOOL isBoy;

- (void)dosomething;

- (void)dosomethingWith:(NSString *)title;

+ (void)classDoSomething;

- (NSString *)sayMyName;

@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.1 成员变量&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;获取&lt;code&gt;class_data_bits_t&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里通过内存偏移的方式找到&lt;code&gt;class_data_bits_t&lt;/code&gt;，如前文所述：&lt;code&gt;bits&lt;/code&gt;所在位置的偏移为8+8+16=32字节。&lt;/p&gt;
&lt;p&gt;那么我们理论上可以通过&lt;code&gt;对象地址+0x20&lt;/code&gt;计算出它的位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x RYModel.class
(Class) $0 = 0x00000001000083e0 RYModel

(lldb) x/4gx 0x00000001000083e0
0x1000083e0: 0x00000001000083b8 0x000000010036a140
0x1000083f0: 0x000000010073c2d0 0x0004803400000007

// 计算bits
(lldb) p/x 0x1000083e0 + 0x20 
(long) $1 = 0x0000000100008400

// 这里转换一下类型便于后面继续调试
(lldb) p (class_data_bits_t *)0x0000000100008400 
(class_data_bits_t *) $2 = 0x0000000100008400
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过 &lt;code&gt;data()&lt;/code&gt; 方法获取 &lt;code&gt;class_rw_t&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p (class_data_bits_t *)0x0000000100008400
(class_data_bits_t *) $4 = 0x0000000100008400

(lldb) p $4-&amp;gt;data()
(class_rw_t *) $5 = 0x000000010073c040
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过 &lt;code&gt;properties&lt;/code&gt; 获取&lt;code&gt;属性列表容器&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $5-&amp;gt;properties
(const property_array_t) $6 = {
  list_array_tt&amp;lt;property_t, property_list_t, RawPtr&amp;gt; = {
     = {
      list = {
        ptr = 0x0000000100008280
      }
      arrayAndFlag = 4295000704
    }
  }
}
  Fix-it applied, fixed expression was: 
    $5-&amp;gt;properties()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;list&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $6.list
(const RawPtr&amp;lt;property_list_t&amp;gt;) $7 = {
  ptr = 0x0000000100008280
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $7.ptr
(property_list_t *const) $8 = 0x0000000100008280
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt; 内的值&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p *$8
(property_list_t) $9 = {
  entsize_list_tt&amp;lt;property_t, property_list_t, 0, PointerModifierNop&amp;gt; = (entsizeAndFlags = 16, count = 5)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过下标取出&lt;code&gt;属性&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $9.get(0)
(property_t) $10 = (name = &quot;name&quot;, attributes = &quot;T@\&quot;NSString\&quot;,C,N,V_name&quot;)
(lldb) p $9.get(1)
(property_t) $11 = (name = &quot;age&quot;, attributes = &quot;Tq,N,V_age&quot;)
(lldb) p $9.get(2)
(property_t) $12 = (name = &quot;height&quot;, attributes = &quot;Tf,N,V_height&quot;)
(lldb) p $9.get(3)
(property_t) $13 = (name = &quot;father&quot;, attributes = &quot;T@\&quot;RYModel\&quot;,&amp;amp;,N,V_father&quot;)
(lldb) p $9.get(4)
(property_t) $14 = (name = &quot;isBoy&quot;, attributes = &quot;Tc,N,V_isBoy&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 实例方法列表&lt;/h3&gt;
&lt;p&gt;前面一些相似的步骤我们省略掉&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从 &lt;code&gt;class_rw_t -&amp;gt; methods&lt;/code&gt; 获取 &lt;code&gt;方法列表容器&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $5-&amp;gt;methods
(const method_array_t) $6 = {
  list_array_tt&amp;lt;method_t, method_list_t, method_list_t_authed_ptr&amp;gt; = {
     = {
      list = {
        ptr = 0x00000001000080e8
      }
      arrayAndFlag = 4295000296
    }
  }
}
  Fix-it applied, fixed expression was: 
    $5-&amp;gt;methods()
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;list&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $6.list
(const method_list_t_authed_ptr&amp;lt;method_list_t&amp;gt;) $14 = {
  ptr = 0x00000001000080e8
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $14.ptr
(method_list_t *const) $15 = 0x00000001000080e8
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt; 中的数据 &lt;code&gt;method_list_t&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p *$15
(method_list_t) $16 = {
  entsize_list_tt&amp;lt;method_t, method_list_t, 4294901763, method_t::pointer_modifier&amp;gt; = (entsizeAndFlags = 27, count = 14)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;获取方法数量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $16.count
(uint32_t) $29 = 14
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过c++函数&lt;code&gt;get()&lt;/code&gt;与&lt;code&gt;big()&lt;/code&gt;单个获取类的实例方法&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里除了我们自己定义的一些实例方法，还看到了一些列属性自动生成的&lt;code&gt;set、get&lt;/code&gt;方法，以及析构函数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $16.get(0).big()
(method_t::big) $19 = {
  name = &quot;setFather:&quot;
  types = 0x0000000100003f60 &quot;v24@0:8@16&quot;
  imp = 0x0000000100003cc0 (ObjcBuild`-[RYModel setFather:])
}
(lldb) p $16.get(1).big()
(method_t::big) $20 = {
  name = &quot;father&quot;
  types = 0x0000000100003f6b &quot;@16@0:8&quot;
  imp = 0x0000000100003ca0 (ObjcBuild`-[RYModel father])
}

... // 省略一些Set、get

(lldb) p $16.get(3).big()
(method_t::big) $22 = {
  name = &quot;dosomething&quot;
  types = 0x0000000100003f3b &quot;v16@0:8&quot;
  imp = 0x0000000100003b30 (ObjcBuild`-[RYModel dosomething])
}
(lldb) p $16.get(4).big()
(method_t::big) $23 = {
  name = &quot;dosomethingWith:&quot;
  types = 0x0000000100003f60 &quot;v24@0:8@16&quot;
  imp = 0x0000000100003b40 (ObjcBuild`-[RYModel dosomethingWith:])
}

...

(lldb) p $16.get(8).big()
(method_t::big) $27 = {
  name = &quot;.cxx_destruct&quot;
  types = 0x0000000100003f3b &quot;v16@0:8&quot;
  imp = 0x0000000100003d30 (ObjcBuild`-[RYModel .cxx_destruct])
}

... // 省略一些Set、get

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.3 协议列表&lt;/h3&gt;
&lt;p&gt;前面一些相似的步骤我们省略掉&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从 &lt;code&gt;class_rw_t -&amp;gt; protocols&lt;/code&gt; 获取 &lt;code&gt;协议列表容器&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $2-&amp;gt;protocols()
(const protocol_array_t) $3 = {
  list_array_tt&amp;lt;unsigned long, protocol_list_t, RawPtr&amp;gt; = {
     = {
      list = {
        ptr = 0x00000001000083b8
      }
      arrayAndFlag = 4295001016
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;list&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $3.list
(const RawPtr&amp;lt;protocol_list_t&amp;gt;) $4 = {
  ptr = 0x00000001000083b8
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $4.ptr
(protocol_list_t *const) $5 = 0x00000001000083b8
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;取出 &lt;code&gt;ptr&lt;/code&gt; 中的数据 &lt;code&gt;protocol_list_t&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p *$5
(protocol_list_t) $6 = (count = 1, list = protocol_ref_t [] @ 0x00007f9a62cb8778)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里可以发现，里面的list中存了一个指针，并不是完整的一个数据结构。我们找到了这样的定义：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;获取 &lt;code&gt;protocol_t *&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p (protocol_t *)$6.list[0]
(protocol_t *) $7 = 0x00000001000088a0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;读取数据&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p *$7
(protocol_t) $8 = {
  objc_object = {
    isa = {
      bits = 4298547400
      cls = Protocol
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 537318425
        magic = 0
        weakly_referenced = 0
        unused = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100003e71 &quot;RYProtocol&quot;
  protocols = 0x0000000100008310
  instanceMethods = 0x0000000100008328
  classMethods = nil
  optionalInstanceMethods = nil
  optionalClassMethods = nil
  instanceProperties = nil
  size = 96
  flags = 0
  _extendedMethodTypes = 0x0000000100008348
  _demangledName = 0x0000000000000000
  _classProperties = nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.4 成员变量&lt;/h3&gt;
&lt;p&gt;前面一些相似的步骤我们省略掉&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从 &lt;code&gt;class_rw_t -&amp;gt; ro()&lt;/code&gt; 获取 &lt;code&gt;成员变量列表容器&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $4-&amp;gt;ro()
(const class_ro_t *) $18 = 0x00000001000083d0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;class_ro_t -&amp;gt; ivars&lt;/code&gt; 获取成员变量列表&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $18-&amp;gt;ivars
(const ivar_list_t *const) $19 = 0x0000000100008570

(lldb) p *$19
(const ivar_list_t) $20 = {
  entsize_list_tt&amp;lt;ivar_t, ivar_list_t, 0, PointerModifierNop&amp;gt; = (entsizeAndFlags = 32, count = 5)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;从成员变量列表中获取成员变量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;
(lldb) p $20.count
(const uint32_t) $21 = 5

(lldb) p $20.get(0)
(ivar_t) $22 = {
  offset = 0x0000000100008770
  name = 0x0000000100003de7 &quot;_isBoy&quot;
  type = 0x0000000100003f47 &quot;c&quot;
  alignment_raw = 0
  size = 1
}
(lldb) p $20.get(1)
(ivar_t) $23 = {
  offset = 0x0000000100008778
  name = 0x0000000100003dee &quot;_height&quot;
  type = 0x0000000100003f49 &quot;f&quot;
  alignment_raw = 2
  size = 4
}
(lldb) p $20.get(2)
(ivar_t) $24 = {
  offset = 0x0000000100008780
  name = 0x0000000100003df6 &quot;_name&quot;
  type = 0x0000000100003f4b &quot;@\&quot;NSString\&quot;&quot;
  alignment_raw = 3
  size = 8
}
(lldb) p $20.get(3)
(ivar_t) $25 = {
  offset = 0x0000000100008788
  name = 0x0000000100003dfc &quot;_age&quot;
  type = 0x0000000100003f57 &quot;q&quot;
  alignment_raw = 3
  size = 8
}
(lldb) p $20.get(4)
(ivar_t) $26 = {
  offset = 0x0000000100008790
  name = 0x0000000100003e01 &quot;_father&quot;
  type = 0x0000000100003f59 &quot;@\&quot;RYModel\&quot;&quot;
  alignment_raw = 3
  size = 8
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.5 类方法&lt;/h3&gt;
&lt;p&gt;类方法我们需要到元类中进行查找&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;获取元类类信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x RYModel.class
(Class) $67 = 0x00000001000087c0 RYModel
(lldb) x/4gx 0x00000001000087c0
0x1000087c0: 0x0000000100008798 0x000000010036a140
0x1000087d0: 0x00000001010695c0 0x0004803400000007
(lldb) p/x 0x0000000100008798 &amp;amp; 0x00007ffffffffff8ULL
(unsigned long long) $68 = 0x0000000100008798
(lldb) p 0x0000000100008798 + 0x20
(long) $70 = 4295002040
(lldb) p/x 0x0000000100008798 + 0x20
(long) $71 = 0x00000001000087b8
(lldb) p (class_data_bits_t *)0x00000001000087b8
(class_data_bits_t *) $72 = 0x00000001000087b8

(lldb) p $72-&amp;gt;data()
(class_rw_t *) $73 = 0x0000000101069510
(lldb) p $73-&amp;gt;methods()
(const method_array_t) $74 = {
  list_array_tt&amp;lt;method_t, method_list_t, method_list_t_authed_ptr&amp;gt; = {
     = {
      list = {
        ptr = 0x0000000100008398
      }
      arrayAndFlag = 4295000984
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;获取元类方法列表（类方法）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $74.list
(const method_list_t_authed_ptr&amp;lt;method_list_t&amp;gt;) $75 = {
  ptr = 0x0000000100008398
}
(lldb) p $75.ptr
(method_list_t *const) $76 = 0x0000000100008398
(lldb) p *$76
(method_list_t) $77 = {
  entsize_list_tt&amp;lt;method_t, method_list_t, 4294901763, method_t::pointer_modifier&amp;gt; = (entsizeAndFlags = 27, count = 1)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;输出方法信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p $77.get(0).big()
(method_t::big) $78 = {
  name = &quot;classDoSomething&quot;
  types = 0x0000000100003f3f &quot;v16@0:8&quot;
  imp = 0x0000000100003920 (ObjcBuild`+[RYModel classDoSomething])
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>WWDC20-runtime优化</title><link>https://blog.lab-lawliet.app/posts/ios-06-wwdc20-runtime-0f3a73b9c44b6d75/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-06-wwdc20-runtime-0f3a73b9c44b6d75/</guid><description>其中主要提到了三点优化</description><pubDate>Sat, 26 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;官网链接&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2020/10163/&quot;&gt;WWDC20-Advancements in the Objective-C runtime&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其中主要提到了三点优化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Class数据结构的变化&lt;/li&gt;
&lt;li&gt;方法列表的变化&lt;/li&gt;
&lt;li&gt;Tagged pointer 格式变化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一、Class数据结构与运行时&lt;/h2&gt;
&lt;h3&gt;1.1 class_ro_t&lt;/h3&gt;
&lt;p&gt;在二进制文件中，Class的结构是这样的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/eef6b8e275368e1f.webp&quot; alt=&quot;Class&quot; /&gt;&lt;/p&gt;
&lt;p&gt;首先Class对象包含了最常用的信息：指向元类、父类、方法缓存的指针&lt;/p&gt;
&lt;p&gt;他还有一个指向更多数据的指针，存储这些额外信息的地方是：&lt;code&gt;class_ro_t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/83f9c3065b241abb.webp&quot; alt=&quot;class_ro_t&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RO&lt;/code&gt; 代表 &lt;code&gt;Read only&lt;/code&gt;， 它包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;类名&lt;/li&gt;
&lt;li&gt;方法&lt;/li&gt;
&lt;li&gt;协议&lt;/li&gt;
&lt;li&gt;实例变量信息等&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Swift Class&lt;/code&gt; 和 &lt;code&gt;OC Class&lt;/code&gt; 共享这一数据结构&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;当类第一次从磁盘加载到内存中时，他的结构是这样的,但是一经使用，他就会发生变化&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;1.2 Clean memory &amp;amp; Dirty memory&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Clean memory
&lt;ul&gt;
&lt;li&gt;是指加载后不会发生改变的内存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;class_ro_t&lt;/code&gt; 就是这种&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dirty memory
&lt;ul&gt;
&lt;li&gt;是指进程运行时会发生改变的内存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Class对象&lt;/code&gt; 就是这种，应为它在运行时会发生变化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4dde3a11e1e67dcf.webp&quot; alt=&quot;CD&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Dirty memory&lt;/code&gt; 要比 &lt;code&gt;Clean memory&lt;/code&gt; 昂贵的多，只要进程在运行，他就一直存在。&lt;code&gt;Clean memory&lt;/code&gt; 可以进行移除，以节省空间；如果需要可以再从磁盘中加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;之所以将Class的数据分成两部分，是为了尽可能的节省空间&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.3 class_ro_t&lt;/h3&gt;
&lt;p&gt;运行时需要追踪每个类的更多信息，当一个类首次被使用时，运行时会为他分配额外的存储空间。 &lt;code&gt;class_rw_t&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/64e29c3174abe93d.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class_rw_t&lt;/code&gt; 使用来读写数据的，在这个数据结构中存储了只有在运行时才会生成的信息。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/77605c424fc6cf7f.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;方法、属性、协议也在 &lt;code&gt;class_rw_t&lt;/code&gt; 中，是因为运行时可以通过 &lt;code&gt;Category&lt;/code&gt; 动态添加。因为 &lt;code&gt;class_ro_t&lt;/code&gt; 是只读的所以我们就在 &lt;code&gt;class_rw_t&lt;/code&gt; 管理这些。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3339e5f59caf0cb0.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是这样会占用很多的内存。那么 &lt;code&gt;如何缩小这部分的体积呢？&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;二、优化 class_rw_t&lt;/h2&gt;
&lt;p&gt;虽然每个类有这样的数据结构，但是真正用到这部分读写功能的却只占很少一部分。&lt;/p&gt;
&lt;p&gt;只有 &lt;code&gt;Swift&lt;/code&gt; 有 &lt;code&gt;Demangled name&lt;/code&gt;，并且只有需要访问  &lt;code&gt;Swift类&lt;/code&gt;  的 &lt;code&gt;Objective-c&lt;/code&gt; 名称时才会用到它。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e979f0da26178e92.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们将 &lt;code&gt;class_rw_t&lt;/code&gt; 分为了两部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;class_rw_t
&lt;ul&gt;
&lt;li&gt;常用的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;class_rw_ext_t
&lt;ul&gt;
&lt;li&gt;不常用的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4e61591ecde8f38a.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这将 &lt;code&gt;class_rw_t&lt;/code&gt; 的体积减少了进 &lt;code&gt;50%&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在有需要的时候将 &lt;code&gt;class_rw_ext_t&lt;/code&gt; 插入其中，约 &lt;code&gt;90%&lt;/code&gt; 的类用不到这些拓展数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a184e2dc1315f2a7.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三、方法列表&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2c29abbe7400a6db.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.1 方法列表元素的结构&lt;/h3&gt;
&lt;h4&gt;selector 方法名&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/10bc28c1ec9b0e7d.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Type Encodings 类型编码&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/239dbc5b2f2ffeb1.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这是一个表示参数类型的字符串。&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;指向方法实现的指针&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/80ab41c3a0465664.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 方法列表元素的大小&lt;/h3&gt;
&lt;p&gt;在64位系统下，占用24字节。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/945bf9bd798f9587.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.3 寻址&lt;/h3&gt;
&lt;p&gt;下面的图模拟了内存的分布&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/706fb2616033af64.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;方法元素的三个指针指向了App内存空间的相对位置，并在加载时修正为实际的位置。由于在内存中，这三个元素的内存地址是连续的。所以我们可以对寻址进行优化。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6c8f6503b5f1b806.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.4 优化：Relative method lists&lt;/h3&gt;
&lt;p&gt;2020年runtime做了优化，通过&lt;code&gt;32位内存偏移&lt;/code&gt;来进行寻址，简化了寻址操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ce417f364a050329.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;偏移量始终是相同的
&lt;ul&gt;
&lt;li&gt;无论image从哪里加载到内存中，它们从磁盘加载到内存后无需进行修正&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将方法列表存入只读的空间，安全节省内存&lt;/li&gt;
&lt;li&gt;使用32位来存储，节省了一半的空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.5 Swizzling relative method lists&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/755a5482c3889ea3.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;现在的imp不能引用完整的地址空间，但如果你Swizzle一个方法，他就可以在任何地方实现。&lt;/p&gt;
&lt;p&gt;由于方法列表放入了只读的空间，我们并不能直接修改方法列表。这里提供了一个&lt;code&gt;全局表&lt;/code&gt;。这个全局表将方法映射到他们被Swizzle的实现上。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/639e04dc8f1708ae.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在实际情况下，只会有少量的Swizzle所以这个表不会很大。&lt;/p&gt;
&lt;h2&gt;四、兼容性&lt;/h2&gt;
&lt;h3&gt;4.1 系统要求&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;支持版本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS Big Sur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS 14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tvOS 14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;watchOS 7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;Xcode会对支持更低版本的包按老的方式进行处理。兼容Swift。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.2 一些SDK&lt;/h3&gt;
&lt;p&gt;如果我们使用了在其他地方编译好的SDK，但是他又没有兼容 &lt;code&gt;Related method lists&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在老的OS下运行，如果它通过地址去访问方法，它会将两个段32位数据作为一个64位去进行读取，两个指针粘连在一起，那么就会发生崩溃。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/410535222408511e.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为保证不会出现这样的问题，请使用系统API。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/14a053c2a9579cdb.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、Tagged pointer format changes (ARM64)&lt;/h2&gt;
&lt;h3&gt;5.1 Tagged pointer&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/62159c9f807d2d93.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以前一个对象的指针是这样的结构。我们只用到了中间的部分。&lt;/p&gt;
&lt;p&gt;由于低位对齐的原则低三位为0，由于地址空间有限，高位为0。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Tagged pointer&lt;/code&gt;，将第一位设为了1。这是区别它和原始指针的最简单的方式。以 NSNumber 为例：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f790a28fcf4e3fcc.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.2 Tagged pointer On Intel&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ec262f8294ad6646.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最低位1
&lt;ul&gt;
&lt;li&gt;代表它是 &lt;code&gt;Tagged pointer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;接下来3位是：标签位
&lt;ul&gt;
&lt;li&gt;表示了指针的类型，例如3代表它是 &lt;code&gt;NSNumber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这里有3位，这里最多能有8种类型&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;剩下的是 &lt;code&gt;Payload&lt;/code&gt; 是特定类型可以随意使用的数据
&lt;ul&gt;
&lt;li&gt;对于NSNumber来说，这里就是实际的数字&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;对于 &lt;code&gt;标签7&lt;/code&gt; 有特殊情况&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/14162f6b3820c198.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它表示一个拓展标签&lt;/li&gt;
&lt;li&gt;使用接下来的8位来编码类型&lt;/li&gt;
&lt;li&gt;这允许多出256个标签类型&lt;/li&gt;
&lt;li&gt;但是代价是减少了 &lt;code&gt;Payload&lt;/code&gt; 的可用区域&lt;/li&gt;
&lt;li&gt;这使得我们可以将 &lt;code&gt;Tagged pointer&lt;/code&gt; 用于更多类型，只要 &lt;code&gt;Payload&lt;/code&gt; 能够装入。
&lt;ul&gt;
&lt;li&gt;如：&lt;code&gt;UIColor&lt;/code&gt; 、 &lt;code&gt;NSIndexSet&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;注意&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;开发者无法添加 &lt;code&gt;Tagged pointer&lt;/code&gt; 类型。（OC）&lt;/li&gt;
&lt;li&gt;Swift中带有关联值的枚举就是一个类似 &lt;code&gt;Tagged pointer&lt;/code&gt; 的类。
&lt;ul&gt;
&lt;li&gt;Swift运行时将枚举判别器存储在关联值的 &lt;code&gt;Payload&lt;/code&gt; 的备用位中。&lt;/li&gt;
&lt;li&gt;而且Swift值类型的运用，使得 &lt;code&gt;Tagged pointer&lt;/code&gt; 没有那么重要了。&lt;/li&gt;
&lt;li&gt;If you&apos;ve ever used an enum with an associated value that&apos;s a class, that&apos;s like a tagged pointer. The Swift runtime stores the enum discriminator in the spare bits of the associated value payload.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.3 Tagged pointer On ARM&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9a5dda9dafeabbab.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ARM&lt;/code&gt; 下，结构使反过来的。为什么不合Intel上保持一致呢？&lt;/p&gt;
&lt;h4&gt;优化 objc_msgSend&lt;/h4&gt;
&lt;p&gt;目的是加快 &lt;code&gt;objc_msgSend&lt;/code&gt; 最常见的分支路径。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ba7a4369c3a9f896.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;只用通过最高位是否为1，就可以区分同时检查它是 &lt;code&gt;（nil || 非Tagged pointer）&lt;/code&gt;，节省了条件分支。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;优化： iOS 14&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c69cf35a268b04fd.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在新版系统中，将标签为移到了后3位。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/709ee9e16e064fe9.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;ARM64&lt;/code&gt;下，会忽略指针的前8位
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Top byte ignore&lt;/code&gt; 的 ARM特性&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：以前这样的判断是OK的，但是在iOS14后就不行了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a25e54728d6348ff.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;老老实实使用系统API，它会帮你处理一切。不要为了看起来牛逼而写一些这种危机四伏的代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6172d482a19b63c1.webp&quot; alt=&quot;RW&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>ISA与Class</title><link>https://blog.lab-lawliet.app/posts/ios-04-isa-class-ad79a9f5c5077687/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-04-isa-class-ad79a9f5c5077687/</guid><description>在上一文中我们初步探究了，obj和isa.</description><pubDate>Wed, 23 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;ISA与Class的继承&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本文基于 &lt;code&gt;objc4-818.2&lt;/code&gt; 源码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;本文较长，建议吃饱喝足再进行阅读。由于很多内容关联性很强，本来准备拆分一下的。但还是决定放在一起，逐步深入。避免发生 &lt;code&gt;这里一个知识点在另一篇文章，那我晚点再看（根本不会再看）&lt;/code&gt; 的情况。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;其中 &lt;code&gt;协议列表&lt;/code&gt; 的探究过程中遇到了一些问题，无法进行下去，有解决办法的欢迎交流~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、类对象&lt;/h2&gt;
&lt;p&gt;在上一文中我们初步探究了，obj和isa.&lt;/p&gt;
&lt;p&gt;通过下面的步骤我们可以找到类对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x obj1
(RYObject *) $8 = 0x000000028026c340
(lldb) x/4gx 0x000000028026c340
0x28026c340: 0x000001a1042194f1 0x0000000000000000
0x28026c350: 0x0000b6106e0ac350 0x000000028356012e
(lldb) p/x 0x000001a1042194f1 &amp;amp; 0x0000000ffffffff8ULL
(unsigned long long) $14 = 0x00000001042194f0
(lldb) po 0x000001a1042194f1 &amp;amp; 0x0000000ffffffff8ULL
RYObject
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 类对象ISA&lt;/h3&gt;
&lt;p&gt;Class其实也是一个对象，那么我很好奇。类对象是不是也有个ISA呢？&lt;/p&gt;
&lt;p&gt;我们以同样的步骤进行调试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x 0x000001a1042194f1 &amp;amp; 0x0000000ffffffff8ULL
(unsigned long long) $11 = 0x00000001042194f0
(lldb) x/4gx 0x00000001042194f0
0x1042194f0: 0x00000001042194c8 0x00000001fc942218
0x104219500: 0x00030002815641c0 0x0003801000000000
(lldb) po 0x00000001042194c8
RYObject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;居然也有一个&lt;code&gt;RYObject&lt;/code&gt;，并且内存地址和第一步生成的&lt;code&gt;0x00000001042194f0&lt;/code&gt; 不一样，是&lt;code&gt;0x00000001042194c8&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这里的&lt;code&gt;0x00000001042194c8&lt;/code&gt;其实就是&lt;code&gt;元类&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;二、元类&lt;/h2&gt;
&lt;p&gt;我们将刚才编译过的&lt;code&gt;MachO&lt;/code&gt;丢进&lt;code&gt;MachoView&lt;/code&gt;中解析。&lt;/p&gt;
&lt;p&gt;打开符号表，搜索&lt;code&gt;RYObject&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6e89aebc078af8c8.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;_&lt;em&gt;OBJC_METACLASS_RO&lt;/em&gt;$_RYObject
&lt;ul&gt;
&lt;li&gt;元类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_&lt;em&gt;OBJC_CLASS_RO&lt;/em&gt;$_RYObject
&lt;ul&gt;
&lt;li&gt;类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、ISA与Class的关系&lt;/h2&gt;
&lt;p&gt;我们使用下面的代码进行验证，其中&lt;code&gt;RYSubObject&lt;/code&gt;继承&lt;code&gt;RYObject&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    RYObject *rySubObject = [[RYSubObject alloc] init];
    NSLog(@&quot;实例%@ 的地址是 %p&quot;, rySubObject, rySubObject);
    Class rySubObjectClass = object_getClass(rySubObject);
    NSLog(@&quot;实例%@ 的类对象是 %@ 地址是 %p&quot;, rySubObject, rySubObjectClass, rySubObjectClass);
    Class rySubObjectMetaClass = object_getClass(rySubObjectClass);
    NSLog(@&quot;实例%@ 的元类是 %@ 地址是 %p&quot;, rySubObject, rySubObjectMetaClass, rySubObjectMetaClass);
    Class rySubObjectRootMetaClass = object_getClass(rySubObjectMetaClass);
    NSLog(@&quot;实例%@ 的根元类是 %@ 地址是 %p&quot;, rySubObject, rySubObjectRootMetaClass, rySubObjectRootMetaClass);
    
    NSLog(@&quot;\n&quot;);
    
    RYObject *ryObject = [[RYObject alloc] init];
    NSLog(@&quot;实例%@ 的地址是 %p&quot;, ryObject, ryObject);
    Class ryObjectClass = object_getClass(ryObject);
    NSLog(@&quot;实例%@ 的类对象是 %@ 地址是 %p&quot;, ryObject, ryObjectClass, ryObjectClass);
    Class ryObjectMetaClass = object_getClass(ryObjectClass);
    NSLog(@&quot;实例%@ 的元类是 %@ 地址是 %p&quot;, ryObject, ryObjectMetaClass, ryObjectMetaClass);
    Class ryObjectRootMetaClass = object_getClass(ryObjectMetaClass);
    NSLog(@&quot;实例%@ 的根元类是 %@ 地址是 %p&quot;, ryObject, ryObjectRootMetaClass, ryObjectRootMetaClass);

    NSLog(@&quot;\n&quot;);
    
    NSObject *nsObject = [[NSObject alloc] init];
    NSLog(@&quot;实例%@ 的地址是 %p&quot;, nsObject, nsObject);
    Class nsObjectClass = object_getClass(nsObject);
    NSLog(@&quot;实例%@ 的类对象是 %@ 地址是 %p&quot;, nsObject, nsObjectClass, nsObjectClass);
    Class nsObjectMetaClass = object_getClass(nsObjectClass);
    NSLog(@&quot;实例%@ 的元类是 %@ 地址是 %p&quot;, nsObject, nsObjectMetaClass, nsObjectMetaClass);
    Class nsObjectRootMetaClass = object_getClass(nsObjectMetaClass);
    NSLog(@&quot;实例%@ 的根元类是 %@ 地址是 %p&quot;, nsObject, nsObjectRootMetaClass, nsObjectRootMetaClass);
    
    NSLog(@&quot;\n&quot;);
    
    Class rySubSuperClass = class_getSuperclass(rySubObjectClass);
    NSLog(@&quot;子类%@（地址%p）的父类是%@ 地址是%p&quot;,rySubObjectClass,rySubObjectClass, rySubSuperClass, rySubSuperClass);
    Class rySuperClass = class_getSuperclass(ryObjectClass);
    NSLog(@&quot;父类%@（地址%p）的父类是%@ 地址是%p&quot;,ryObjectClass,ryObjectClass, rySuperClass, rySuperClass);
    Class nsSuperClass = class_getSuperclass(nsObjectClass);
    NSLog(@&quot;根类%@（地址%p）的父类是%@ 地址是%p&quot;,nsObjectClass,nsObjectClass, nsSuperClass, nsSuperClass);
    
    NSLog(@&quot;\n&quot;);
    
    Class rySubMetaSuperClass = class_getSuperclass(rySubObjectMetaClass);
    NSLog(@&quot;元类%@（地址%p）的父类是%@ 地址是%p&quot;,rySubObjectMetaClass,rySubObjectMetaClass, rySubMetaSuperClass, rySubMetaSuperClass);
    Class ryMetaSuperClass = class_getSuperclass(ryObjectMetaClass);
    NSLog(@&quot;元类%@（地址%p）的父类是%@ 地址是%p&quot;,ryObjectMetaClass,ryObjectMetaClass, ryMetaSuperClass, ryMetaSuperClass);
    Class nsMetaSuperClass = class_getSuperclass(nsObjectMetaClass);
    NSLog(@&quot;根元类%@（地址%p）的父类是%@ 地址是%p&quot;,nsObjectMetaClass,nsObjectMetaClass, nsMetaSuperClass, nsMetaSuperClass);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依次得到输出如下&lt;/p&gt;
&lt;h3&gt;3.1 子类ISA&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 21:21:53.685253+0800 Class[15467:5499656] 实例&amp;lt;RYSubObject: 0x282af0700&amp;gt; 的地址是 0x282af0700
2021-06-22 21:21:53.685426+0800 Class[15467:5499656] 实例&amp;lt;RYSubObject: 0x282af0700&amp;gt; 的类对象是 RYSubObject 地址是 0x10227d648
2021-06-22 21:21:53.685717+0800 Class[15467:5499656] 实例&amp;lt;RYSubObject: 0x282af0700&amp;gt; 的元类是 RYSubObject 地址是 0x10227d620
2021-06-22 21:21:53.685782+0800 Class[15467:5499656] 实例&amp;lt;RYSubObject: 0x282af0700&amp;gt; 的根元类是 NSObject 地址是 0x1fc9421f0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/06da1f94bd2b3709.webp&quot; alt=&quot;RYSubObject&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;子类实例对象 &lt;code&gt;-isa-&lt;/code&gt; 子类 &lt;code&gt;-isa-&lt;/code&gt; 子类元类 &lt;code&gt;-isa-&lt;/code&gt; 根元类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.2 父类ISA&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 21:21:53.685876+0800 Class[15467:5499656] 实例&amp;lt;RYObject: 0x282afc490&amp;gt; 的地址是 0x282afc490
2021-06-22 21:21:53.685929+0800 Class[15467:5499656] 实例&amp;lt;RYObject: 0x282afc490&amp;gt; 的类对象是 RYObject 地址是 0x10227d5a8
2021-06-22 21:21:53.686703+0800 Class[15467:5499656] 实例&amp;lt;RYObject: 0x282afc490&amp;gt; 的元类是 RYObject 地址是 0x10227d580
2021-06-22 21:21:53.686760+0800 Class[15467:5499656] 实例&amp;lt;RYObject: 0x282afc490&amp;gt; 的根元类是 NSObject 地址是 0x1fc9421f0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e75275038d485d0f.webp&quot; alt=&quot;RYObject&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实例对象 &lt;code&gt;-isa-&lt;/code&gt; 类 &lt;code&gt;-isa-&lt;/code&gt; 元类 &lt;code&gt;-isa-&lt;/code&gt; 根元类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.3 根类ISA&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 21:21:53.687198+0800 Class[15467:5499656] 实例&amp;lt;NSObject: 0x282afdae0&amp;gt; 的地址是 0x282afdae0
2021-06-22 21:21:53.687245+0800 Class[15467:5499656] 实例&amp;lt;NSObject: 0x282afdae0&amp;gt; 的类对象是 NSObject 地址是 0x1fc942218
2021-06-22 21:21:53.687291+0800 Class[15467:5499656] 实例&amp;lt;NSObject: 0x282afdae0&amp;gt; 的元类是 NSObject 地址是 0x1fc9421f0
2021-06-22 21:21:53.687384+0800 Class[15467:5499656] 实例&amp;lt;NSObject: 0x282afdae0&amp;gt; 的根元类是 NSObject 地址是 0x1fc9421f0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/89589c189618085e.webp&quot; alt=&quot;NSObject&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根类实例对象 &lt;code&gt;-isa-&lt;/code&gt; 根类 &lt;code&gt;-isa-&lt;/code&gt; 根元类 &lt;code&gt;-isa-&lt;/code&gt; 根元类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.4 ISA关系图&lt;/h3&gt;
&lt;p&gt;我们将上面的三组关系整合一下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7c4396f74107433d.webp&quot; alt=&quot;ISA&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.5 类继承关系&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 21:21:53.687691+0800 Class[15467:5499656] 子类RYSubObject（地址0x10227d648）的父类是RYObject 地址是0x10227d5a8
2021-06-22 21:21:53.687822+0800 Class[15467:5499656] 父类RYObject（地址0x10227d5a8）的父类是NSObject 地址是0x1fc942218
2021-06-22 21:21:53.688283+0800 Class[15467:5499656] 根类NSObject（地址0x1fc942218）的父类是(null) 地址是0x0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;子类 &lt;code&gt;-继承-&lt;/code&gt; 父类 &lt;code&gt;-继承-&lt;/code&gt; 根类 &lt;code&gt;-继承-&lt;/code&gt; 空&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.6 元类继承关系&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-06-22 21:21:53.688982+0800 Class[15467:5499656] 元类RYSubObject（地址0x10227d620）的父类是RYObject 地址是0x10227d580
2021-06-22 21:21:53.689029+0800 Class[15467:5499656] 元类RYObject（地址0x10227d580）的父类是NSObject 地址是0x1fc9421f0
2021-06-22 21:21:53.689075+0800 Class[15467:5499656] 根元类NSObject（地址0x1fc9421f0）的父类是NSObject 地址是0x1fc942218
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里发现 &lt;em&gt;&lt;strong&gt;根元类NSObject（地址0x1fc9421f0）&lt;/strong&gt;&lt;/em&gt; 的父类是 &lt;em&gt;&lt;strong&gt;根类（地址0x1fc942218）&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;子类的元类 &lt;code&gt;-继承-&lt;/code&gt; 父类的元类 &lt;code&gt;-继承-&lt;/code&gt; 根元类 &lt;code&gt;-继承-&lt;/code&gt; 根类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.7 ISA与Class的继承关系图梳理&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7c9105c16db43654.webp&quot; alt=&quot;ISA与Class&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Swift轻量级网络封装：SwiftyServiceProtocol（适用于混编或纯Swift项目）</title><link>https://blog.lab-lawliet.app/posts/swift-swift-swiftyserviceprotocol-swift-7c91dd235594041a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swift-swiftyserviceprotocol-swift-7c91dd235594041a/</guid><description>随着Swift越来越成熟，越来越多的项目也开始使用Swift了。很多朋友应该都有遇到和OC混编的情况。</description><pubDate>Wed, 23 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;随着Swift越来越成熟，越来越多的项目也开始使用Swift了。很多朋友应该都有遇到和OC混编的情况。&lt;/p&gt;
&lt;p&gt;我自己的话，纯Swift和混编的都做过。在爽过纯Swift后，在混编时经常会有被困住手脚的感觉。&lt;/p&gt;
&lt;p&gt;尤其这两年的公司项目，很多老代码和必须依赖的基础库都是OC。特别是网络库（基于AFN），又不能不用公司的。&lt;/p&gt;
&lt;p&gt;但是在使用过Swift的&lt;a href=&quot;https://github.com/Moya/Moya&quot;&gt;Moya&lt;/a&gt;框架后，对于在混编的Swift中不能写出很Swift的网络请求这件事尤为难受。&lt;/p&gt;
&lt;p&gt;于是有了想解决这点痛苦的想法。就有了 &lt;code&gt;SwiftyServiceProtocol&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对&lt;code&gt;Swift&lt;/code&gt; 的 &lt;code&gt;协议&lt;/code&gt; &lt;code&gt;枚举&lt;/code&gt; &lt;code&gt;泛型&lt;/code&gt;等有一定了解的话会很便于理解&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、封装目标&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;不依赖任何三方网络库&lt;/li&gt;
&lt;li&gt;便于更换底层网络库（AFN? Alamofire? 自研网络库？ 无痛切换。极端一点你甚至可以不同模块使用不同网络库)&lt;/li&gt;
&lt;li&gt;用起来很Moya很Swifty&lt;/li&gt;
&lt;li&gt;兼容多模块多域名&lt;/li&gt;
&lt;li&gt;轻量&lt;/li&gt;
&lt;li&gt;用于纯Swift项目或者混编项目（不考虑OC调用。原因：单纯的不喜欢）&lt;/li&gt;
&lt;li&gt;便于模块化处理&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、SwiftyServiceProtocol&lt;/h2&gt;
&lt;p&gt;源码在此：&lt;a href=&quot;https://github.com/RyukieSama/Swifty/blob/master/Swifty/Protocol/Service/Service.swift&quot;&gt;GitHub_SwiftyServiceProtocol_喜欢的话点个小星星吧⭐️&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;这个协议十分轻量，不算注释也就12行代码。&lt;/p&gt;
&lt;h3&gt;3.1 在项目中对 SwiftyServiceProtocol 进行拓展&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public protocol MyServiceProtocol: SwiftyServiceProtocol where SwiftyServiceCallBackType == 你的网络库的回调 {

}

extension MyServiceProtocol {
    /// 如果你的项目只有一个域名，在这里写死就好。如果不同模块有不同的域名，那么在对应的Service中实现这个get就行，测试环境生产环境的逻辑也可以放在这里
    var base: String {
        return &quot;你的域名&quot;
    }
    
    var urlString: String {
        return base + &quot;/&quot; + path
    }
    
    func request(completion: SwiftyServiceCallBackType?) {
        switch method {
        case .GET:
            get(completion: completion)
        case .POST:
            post(completion: completion)
        }
    }
    
    // MARK: - Private
    private func post(completion: 你的网络库的回调?) {
        // 你的POST实现
    }
    
    private func get(completion: 你的网络库的回调?) {
        // 你的GET实现
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 实际使用&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这里以用户模块的一些请求为例&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;定义相关Service&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;enum YourUserService { // 定义你需要的接口
    /// 获取用户信息
    case Info(uid: Int64)
    /// 关注某人
    case Follow(uid: Int64)
    /// 取消关注某人
    case UnFollow(uid: Int64)
}

extension YourUserService: MyServiceProtocol {
    // var base: String {
    //     return &quot;如果这个模块有不同模块有不同的域名，在这里实现这个get就行&quot;
    // }

    /// 接口参数
    var parameters: [AnyHashable : Any]? {
        switch self {
        case let .Info(uid):
            return [&quot;uid&quot; : uid]
        case .Follow(let uid):
            return [&quot;id&quot; : uid]
        case .UnFollow(let uid):
            return [&quot;id&quot; : uid]
        }
    }
    
    /// 接口路径
    var path: String {
        switch self {
        case .Info:
            return &quot;info的path&quot;
        case .Follow:
            return &quot;follow的path&quot;
        case .UnFollow:
            return &quot;unfollow的path&quot;
        }
    }
    
    /// 请求方法选择
    var method: SwiftyServiceMethod {
        switch self {
        case .Info:
            return .GET
        default:
            return .POST
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;调用场景&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UserService
    .Info(yourUser.uid)
    .request { (response) in
        // 处理网络回调
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、总结&lt;/h2&gt;
&lt;p&gt;这个协议我自己已经在两个混编项目中使用了，如果你也喜欢的话，欢迎来点个小星星⭐️&lt;a href=&quot;https://github.com/RyukieSama/Swifty/blob/master/Swifty/Protocol/Service/Service.swift&quot;&gt;GitHub_SwiftyServiceProtocol&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;参考：
&lt;a href=&quot;https://github.com/Moya/Moya&quot;&gt;Moya&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>对象本质探究与isa</title><link>https://blog.lab-lawliet.app/posts/ios-03-isa-60c1a1b7a018e93e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-03-isa-60c1a1b7a018e93e/</guid><description>我们知道，OC底层是C++，我们先将下面的代码还原成C++代码再进行下一步。</description><pubDate>Mon, 14 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、结构分析&lt;/h2&gt;
&lt;p&gt;我们知道，OC底层是C++，我们先将下面的代码还原成C++代码再进行下一步。&lt;/p&gt;
&lt;p&gt;新建一个空项目在&lt;code&gt;main&lt;/code&gt;函数中写入测试代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface RKObject : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

@implementation RKObject

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@&quot;Hello, World!&quot;);
    }
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 还原成C++&lt;/h3&gt;
&lt;p&gt;执行&lt;code&gt;clang -rewrite-objc main.m -o main.cpp&lt;/code&gt;，就能得到C++文件。&lt;/p&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 模拟器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
// 真机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们找到和我们定义的对象相关的地方：&lt;/p&gt;
&lt;h3&gt;1.2 一些关键内容&lt;/h3&gt;
&lt;h4&gt;a. 定义&lt;/h4&gt;
&lt;p&gt;这里是一些类型定义&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ifndef _REWRITER_typedef_RKObject
#define _REWRITER_typedef_RKObject
typedef struct objc_object RKObject;
typedef struct {} _objc_exc_RKObject;
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b. 结构体&lt;/h4&gt;
&lt;p&gt;这里我们可以看到，是一个结构体。&lt;/p&gt;
&lt;p&gt;而我们知道，结构体是不能继承的。这里通过一个&lt;code&gt;NSObject_IVARS&lt;/code&gt;，实现了OC的&lt;code&gt;继承&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;同样也可以看到我们定义的属性相对应的成员变量。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extern &quot;C&quot; unsigned long OBJC_IVAR_$_RKObject$_name;
extern &quot;C&quot; unsigned long OBJC_IVAR_$_RKObject$_age;
struct RKObject_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	NSString *_name;
};


// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, assign) int age;

/* @end */
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c. 函数&lt;/h4&gt;
&lt;p&gt;这里是成员变量相关的&lt;code&gt;set/get&lt;/code&gt;方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// @implementation RKObject


static NSString * _I_RKObject_name(RKObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RKObject$_name)); }
extern &quot;C&quot; __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_RKObject_setName_(RKObject * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct RKObject, _name), (id)name, 0, 1); }

static int _I_RKObject_age(RKObject * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_RKObject$_age)); }
static void _I_RKObject_setAge_(RKObject * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_RKObject$_age)) = age; }
// @end

&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;静态的实例方法&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;我们发现它是&lt;code&gt;static&lt;/code&gt;的，他不是实例方法么？&lt;/li&gt;
&lt;li&gt;我们再看，它包含了两个参数，这两个参数在我们使用的时候是隐含的：
&lt;ul&gt;
&lt;li&gt;对象&lt;/li&gt;
&lt;li&gt;Selector&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这样我们知道，实例方法本身也是静态的，只不过有两个隐藏的参数罢了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;get方法如何获取成员变量的&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;name&lt;/code&gt;的get方法的实现中看到
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;return (*(NSString **)((char *)self + OBJC_IVAR_$_RKObject$_name)); &lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;实例首地址 + 成员变量的offset = 成员变量的指针&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;d. isa&lt;/h4&gt;
&lt;p&gt;在上面我们知道，&lt;code&gt;struct NSObject_IMPL NSObject_IVARS&lt;/code&gt;，是OC继承关系的核心。&lt;/p&gt;
&lt;p&gt;那么&lt;code&gt;NSObject_IMPL&lt;/code&gt;是什么呢？&lt;/p&gt;
&lt;p&gt;我们很快就能在文件中找到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct NSObject_IMPL {
	Class isa;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它就是&lt;code&gt;isa&lt;/code&gt;！&lt;/p&gt;
&lt;h4&gt;e. Class&lt;/h4&gt;
&lt;p&gt;在文件中我们找到，&lt;code&gt;Class&lt;/code&gt;的定义。本质是一个&lt;code&gt;objc_class&lt;/code&gt;结构体指针的别名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;f. id类型&lt;/h4&gt;
&lt;p&gt;再往下我们还能发现一个，它也是一个&lt;code&gt;objc_object *&lt;/code&gt;，结构体指针。&lt;/p&gt;
&lt;p&gt;这也解答了，为什么我们平时使用&lt;code&gt;id&lt;/code&gt;类型的时候没有&lt;code&gt;*&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct objc_object *id;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、isa&lt;/h2&gt;
&lt;p&gt;在&lt;a href=&quot;/posts/ios-01-alloc-b9640e29b7eb5e2c/&quot;&gt;alloc与字节对齐&lt;/a&gt;一文中我们有聊到&lt;code&gt;initInstanceIsa&lt;/code&gt;，将isa和class进行绑定。&lt;/p&gt;
&lt;p&gt;我们从这里开始继续研究isa&lt;/p&gt;
&lt;h3&gt;2.1 源码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls-&amp;gt;instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls-&amp;gt;classArrayIndex() &amp;gt; 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls-&amp;gt;classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don&apos;t want to hurt instantiation
    isa = newisa;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 isa的本质 isa_t&lt;/h3&gt;
&lt;p&gt;从源码中我们看到isa的类型为&lt;code&gt;isa_t&lt;/code&gt;，我们找到&lt;code&gt;isa_t&lt;/code&gt;的源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;union isa_t {
    // 构造方法
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 &amp;amp;&amp;amp; has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;破案了，他是个联合体！&lt;/p&gt;
&lt;h3&gt;2.3 ISA_BITFIELD isa的位域&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;arm64&lt;/code&gt;下的&lt;code&gt;ISA_BITFIELD&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;nonpointer
&lt;ul&gt;
&lt;li&gt;是否是纯isa指针
&lt;ul&gt;
&lt;li&gt;0：纯isa指针，只包含类对象地址&lt;/li&gt;
&lt;li&gt;1：还包含了类的信息、对象的引用计数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;has_assoc
&lt;ul&gt;
&lt;li&gt;是否存在关联对象的标志位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;has_cxx_dtor
&lt;ul&gt;
&lt;li&gt;该对象是否有C++或者Objc的析构器
&lt;ul&gt;
&lt;li&gt;如果有析构函数，则需要做析构逻辑&lt;/li&gt;
&lt;li&gt;如果没有，就可以快速的释放对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;shiftcls
&lt;ul&gt;
&lt;li&gt;类指针的值
&lt;ul&gt;
&lt;li&gt;开启指针优化的情况下，arm64中用33位来存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;magic
&lt;ul&gt;
&lt;li&gt;用于判断当前对象是已经初始化的对象&lt;/li&gt;
&lt;li&gt;还是没有初始化的空间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;weakly_referenced
&lt;ul&gt;
&lt;li&gt;是否被弱引用&lt;/li&gt;
&lt;li&gt;没有弱引用的对象可以更快的释放&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;unused
*&lt;/li&gt;
&lt;li&gt;has_sidetable_rc
&lt;ul&gt;
&lt;li&gt;当引用计数大于10时，需要借用该变量存储进位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;extra_rc
&lt;ul&gt;
&lt;li&gt;引用计数&lt;/li&gt;
&lt;li&gt;实际的值是&lt;code&gt;引用计数-1&lt;/code&gt;，即：
&lt;ul&gt;
&lt;li&gt;引用计数为10，&lt;code&gt;extra_rc = 9&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 获取class对象: getClass&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren&apos;t security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &amp;amp;= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &amp;amp;= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &amp;amp;= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &amp;amp;= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析源码实现，我们知道了。是通过isa的&lt;code&gt;bits&lt;/code&gt;再与上一个掩码&lt;code&gt;ISA_MASK&lt;/code&gt;来获取到类对象的地址的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ARM64&lt;/code&gt;: &lt;code&gt;define ISA_MASK  0x0000000ffffffff8ULL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__x86_64__&lt;/code&gt;: &lt;code&gt;define ISA_MASK        0x00007ffffffffff8ULL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;a.验证&lt;/h4&gt;
&lt;p&gt;用我们上面的demo进行验证，注意是Mac环境，不是arm64。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2ac064668d6d61ed.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x RKObject.class
(Class) $0 = 0x00000001000081f8 RKObject
(lldb) x/4gx obj
0x1007576a0: 0x011d8001000081fd 0x0000000000000000
0x1007576b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000081fd &amp;amp; 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000081f8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们发现计算的结果和直接获取的结果一致。&lt;/p&gt;
&lt;h4&gt;b. 不使用掩码，通过位运算计算出Class&lt;/h4&gt;
&lt;p&gt;以Mac下为例，isa指针的结构是一定的。在一个isa指针的空间内，class的位置是相对固定的。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结构如下： B + Class + A（小端模式，从右向左）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A：1+1+1 = 3&lt;/li&gt;
&lt;li&gt;Class：44&lt;/li&gt;
&lt;li&gt;B：6+1+1+1+8 = 17&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) x/4gx obj
0x1007576a0: 0x011d8001000081fd 0x0000000000000000
0x1007576b0: 0x0000000000000000 0x0000000000000000

(lldb) p/t 0x011d8001000081fd
(long) $11 = 0b00000001000111011 00000000000000100000000000000001000000111111 101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;左移3位，使A从右边溢出，左边多的位会补0
&lt;ul&gt;
&lt;li&gt;现在的结构：&lt;code&gt;000 + B + Class&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x 0x011d8001000081fd &amp;gt;&amp;gt; 3
(long) $7 = 0x0023b0002000103f

(lldb) p/t 0x0023b0002000103f
(long) $12 = 0b000 00000001000111011 00000000000000100000000000000001000000111111
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;现在需要去掉B，右移&lt;code&gt;3+17=20&lt;/code&gt;位
&lt;ul&gt;
&lt;li&gt;现在的结构：&lt;code&gt;Class + 20个0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x 0x0023b0002000103f &amp;lt;&amp;lt; 20
(long) $8 = 0x0002000103f00000

(lldb) p/t 0x0002000103f00000
(long) $13 = 0b00000000000000100000000000000001000000111111 00000000000000000000
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;回到原位，右移17位：
&lt;ul&gt;
&lt;li&gt;现在的结构：17个0 + Class + 3个0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) p/x 0x0002000103f00000 &amp;gt;&amp;gt; 17
(long) $9 = 0x00000001000081f8

(lldb) p/t 0x00000001000081f8
(long) $14 = 0b00000000000000000 00000000000000100000000000000001000000111111 000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、isa和类对象的绑定&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这里是&lt;code&gt;objc_object::initIsa&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) { // 如果是纯isa就直接setClass
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls-&amp;gt;instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls-&amp;gt;classArrayIndex() &amp;gt; 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls-&amp;gt;classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don&apos;t want to hurt instantiation
    isa = newisa;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 class绑定流程图&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fcb1179500a1cd58.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 中间有个判断 Nonpointer isa&lt;/h3&gt;
&lt;p&gt;下面我们具体研究下&lt;/p&gt;
&lt;h2&gt;四、Nonpointer isa&lt;/h2&gt;
&lt;p&gt;在上面我们有多次看到&lt;code&gt;Nonpointer isa&lt;/code&gt;出现。现在我们深入的研究一下&lt;/p&gt;
&lt;h3&gt;4.1 isa_t&lt;/h3&gt;
&lt;p&gt;在上面我们知道&lt;code&gt;isa&lt;/code&gt;是一个联合体，而联合体成员是互斥的。&lt;/p&gt;
&lt;p&gt;这个就是&lt;code&gt;Nonpointer isa&lt;/code&gt;区别于纯&lt;code&gt;isa&lt;/code&gt;的核心！&lt;/p&gt;
&lt;h3&gt;4.2 isa_t::setClass&lt;/h3&gt;
&lt;p&gt;这里是设置Class的核心实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Set the class field in an isa. Takes both the class to set and
// a pointer to the object where the isa will ultimately be used.
// This is necessary to get the pointer signing right.
//
// Note: this method does not support setting an indexed isa. When
// indexed isas are in use, it can only be used to set the class of a
// raw isa.
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // No signing, just use the raw pointer.
    uintptr_t signedCls = (uintptr_t)newCls;

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // We&apos;re only signing Swift classes. Non-Swift classes just use
    // the raw pointer
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls-&amp;gt;isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // We&apos;re signing everything
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif

    shiftcls_and_sig = signedCls &amp;gt;&amp;gt; 3;

#elif SUPPORT_INDEXED_ISA
    // Indexed isa only uses this method to set a raw pointer class.
    // Setting an indexed class is handled separately.
    cls = newCls;

#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls &amp;gt;&amp;gt; 3;
#endif
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 Nonpointer isa 和 纯isa的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Nonpointer isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;类对象会被赋值到&lt;code&gt;isa_t&lt;/code&gt;的&lt;code&gt;BITFIELD&lt;/code&gt;中的&lt;code&gt;shiftcls&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;纯&lt;code&gt;isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;会被直接赋值到&lt;code&gt;isa_t&lt;/code&gt;的&lt;code&gt;cls&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;两者互斥&lt;/li&gt;
&lt;li&gt;默认创建的对象都是&lt;code&gt;Nonpointer isa&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;可通过修改Xcode环境变量改成使用纯isa&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>基于iCloud构建用户体系</title><link>https://blog.lab-lawliet.app/posts/article-icloud-72e8944d89603a1a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-icloud-72e8944d89603a1a/</guid><description>这个md文档一月份就创建了，一直没填坑。</description><pubDate>Fri, 11 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;这个md文档一月份就创建了，一直没填坑。&lt;/p&gt;
&lt;p&gt;虽然应用本身不是什么成功的应用，但是自己感觉在做的过程中还是有挺多收获的。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/em&gt; 本文不会非常详细的说明具体类的使用方法，代码具体怎么写，这些官方文档比任何人都说的清楚。&lt;/p&gt;
&lt;p&gt;目的在于对于个人开发者提供一个思路。&lt;/p&gt;
&lt;p&gt;大多个人开发者，比如我自己，资源时间精力有限，但是想法很多。&lt;/p&gt;
&lt;p&gt;而iCloud是我遇到最合适用来构建起用户体系，以及拓展功能需求的工具了。&lt;/p&gt;
&lt;p&gt;自己已经独立做了两款应用，都是基于iCloud建立起来的，欢迎体验反馈，要是再来个五星好评是对我最大的鼓励~&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;扫雷Elic 无尽天梯&lt;/th&gt;
&lt;th&gt;梦见账本&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;下载&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1488204246&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://apps.apple.com/cn/app/id1498426607&quot;&gt;AppStore&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;哎，没有设计师自己整UI终究还是不太好看啊~TAT~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;下面会以扫雷项目中的例子来做一些说明&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、独立项目用户体系技术方案选型&lt;/h2&gt;
&lt;h3&gt;2.1 自己开发&lt;/h3&gt;
&lt;p&gt;也不是没考虑过，但不是自己所擅长的。而且需要考虑的问题太多，技术、运维、安全都是需要考虑的。&lt;/p&gt;
&lt;p&gt;当然如果有大佬这些都能搞定的话，这是最好的方案了。&lt;/p&gt;
&lt;h3&gt;2.2 LeanCloud&lt;/h3&gt;
&lt;p&gt;早期有用这家的服务写过Demo，所以选型的时候也有考虑他家的服务，毕竟后期拓展到不同的平台没有障碍。&lt;/p&gt;
&lt;p&gt;但是收费限制多，具体可以自行去查看。Pass&lt;/p&gt;
&lt;h3&gt;2.3 GameCenter&lt;/h3&gt;
&lt;p&gt;很早以前有研究过一下，就如其名苹果系统内部的一套游戏用户体系。&lt;/p&gt;
&lt;p&gt;包含了用户、排行榜、成就点之类的比较基础的功能。&lt;/p&gt;
&lt;p&gt;虽然很多游戏都有接入，但是存在感实在不高。&lt;/p&gt;
&lt;p&gt;就我对GameCenter浅薄的了解来说，要拓展功能业务的话，只依靠CameCenter的话是很难支撑起来的，依旧需要另一套东西来承载。&lt;/p&gt;
&lt;p&gt;所以Pass掉了。&lt;/p&gt;
&lt;h3&gt;2.4 iCloud&lt;/h3&gt;
&lt;p&gt;大家初步对iCloud的认识可能停留在可以备份同步照片文件的基础上。&lt;/p&gt;
&lt;p&gt;这里建立用户体系依靠的是&lt;code&gt;CloudKit&lt;/code&gt;，建议直接阅读官方文档，想知道的都有。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;官方文档：&lt;a href=&quot;https://developer.apple.com/documentation/cloudkit&quot;&gt;CloudKit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;三、什么是CloudKit&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Store structured app and user data in iCloud containers that all users of your app can share.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The CloudKit framework provides interfaces for moving data between your app and your iCloud containers. You use CloudKit to store your app’s existing data in the cloud so that the user can access it on multiple devices. You can also store data in a public area where all users can access it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上面是官方文档中对&lt;code&gt;CloudKit&lt;/code&gt;的说明，比较重要的一句 &lt;code&gt;You can also store data in a public area where all users can access it.&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;数据&lt;code&gt;可共享&lt;/code&gt;是建立起用户体系的核心。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;推荐苹果的官方文章，根据自身场景，选择合适的接入方案&lt;a href=&quot;https://developer.apple.com/documentation/cloudkit/determining_if_cloudkit_is_right_for_your_app&quot;&gt;「Determining If CloudKit Is Right for Your App」&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;四、CloudKit存储数据的方式&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://icloud.developer.apple.com/dashboard/&quot;&gt;CloudKit控制面板&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2455dd1598c76840.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进入DataBase，对核心的几块内容做一下说明：&lt;/p&gt;
&lt;h3&gt;4.1 基础数据单元：CKRecord&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/939e5d93a5fead61.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;所有数据都是由&lt;code&gt;CKRecord&lt;/code&gt;承载，存储在容器中的。&lt;/p&gt;
&lt;p&gt;值得注意的是，只要用户开启啦iCloud及iCloudDrive服务。&lt;/p&gt;
&lt;p&gt;支持的数据类型如下，这是官方注释里面的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// Acceptable value object classes are:
/// - `String`
/// - `Date`
/// - `Data`
/// - `Bool`
/// - `Int`
/// - `UInt`
/// - `Float`
/// - `Double`
/// - `[U]Int8 et al`
/// - `CKReference / CKRecord.Reference`
/// - `CKAsset`
/// - `CLLocation`
/// - `NSData`
/// - `NSDate`
/// - `NSNumber`
/// - `NSString`
/// - `Array` and/or `NSArray` containing objects of any of the types above
///
/// Any other classes will result in an exception with name `NSInvalidArgumentException`.
///
/// Field keys starting with `_` are reserved. Attempting to set a key prefixed with a `_` will result in an error.
///
/// Key names roughly match C variable name restrictions. They must begin with an ASCII letter and can contain ASCII letters and numbers and the underscore character.
/// The maximum key length is 255 characters.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CKRecord&lt;/code&gt;本身包含了一些基础字段比如&lt;code&gt;创建者id&lt;/code&gt;、&lt;code&gt;创建时间&lt;/code&gt;、&lt;code&gt;修改时间&lt;/code&gt;、&lt;code&gt;记录id&lt;/code&gt;等。&lt;/p&gt;
&lt;p&gt;当然我们也可以自定义字段，读写起来就和使用字典类似。具体可以点进&lt;code&gt;CKRecord&lt;/code&gt;文件查看，这里就不再多说了。&lt;/p&gt;
&lt;h3&gt;4.2 数据存储空间：Zone&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/30731bab2941be7f.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里就是三个不同访问权限的数据库，通过&lt;code&gt;CKContainer&lt;/code&gt;访问。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ab6f3af96c535c64.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;a.Public&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;默认所有用户可读，所有者可修改&lt;/li&gt;
&lt;li&gt;可设置权限，只能在后台进行调整，不可通过API修改&lt;/li&gt;
&lt;li&gt;开发者可以通过后台查看&lt;/li&gt;
&lt;li&gt;不占用所有者的iCloud储存空间&lt;/li&gt;
&lt;li&gt;占用App的iCloud存储空间&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;扫雷&lt;/code&gt;用的是这个数据库，因为包含了排行榜，需要所有用户都可以访问。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面是相关的两段官方说明：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; *  Records in a public database
 *  - By default are world readable, owner writable.
 *  - Can be locked down by Roles, a process done in the Developer Portal, a web interface.  Roles are not present in the client API.
 *  - Are visible to the application developer via the Developer Portal.
 *  - Do not contribute to the owner&apos;s iCloud account storage quota.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This database is available regardless of whether the user’s device has an iCloud account. The contents of the public database are readable by all users of the app, and users have write access to the records, and other objects, they create. The public database’s contents are visible in the developer portal, where you can assign roles to users and restrict access as necessary.
Data in the public database counts toward your app’s iCloud storage quota.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;b.Private&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;默认只有所有者可读写&lt;/li&gt;
&lt;li&gt;开发者在后台也无法访问&lt;/li&gt;
&lt;li&gt;占用所有者的iCloud存储空间&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这个适合做一些私密性较强的应用，比如密码管理应用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt; *  Records in a private database
 *  - By default are only owner readable and owner writable.
 *  - Are not visible to the application developer via the Developer Portal.
 *  - Are counted towards the owner&apos;s iCloud account storage quota.
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c.Shared&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;只对通过CKShare授权的合作者可见&lt;/li&gt;
&lt;li&gt;开发者在后台也无法访问&lt;/li&gt;
&lt;li&gt;占用组织者的iCloud储存空间&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这个没具体用过，先不深入探讨。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt; *  Records in a shared database
 *  - Are available to share participants based on the permissions of the enclosing CKShare
 *  - Are not visible to the application developer via the Developer Portal.
 *  - Are counted towards the originating owner&apos;s iCloud account storage quota.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 数据表结构：RecordTypes&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b40969c66d851173.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;User&lt;/code&gt;是默认就存在的一张表，无须自己创建。如果用户安装了你的应用，你去获取用户对象的时候&lt;code&gt;CloudKit&lt;/code&gt;后台会自动生成一条记录。是不是很方便？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;根据&lt;code&gt;扫雷Elic&lt;/code&gt;的需求场景，我创建了这些表&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/499317a9b114d6c2.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User(default)：用户&lt;/li&gt;
&lt;li&gt;AvatarSpace：头像&lt;/li&gt;
&lt;li&gt;EndLessRank：无尽模式排行榜&lt;/li&gt;
&lt;li&gt;GameHistory：游戏记录&lt;/li&gt;
&lt;li&gt;AppConfig：应用配置&lt;/li&gt;
&lt;li&gt;ZenRank：“禅”模式排行榜&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;添加自定义字段&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8ccb5865b49728a9.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;点进对应Type后就可以看到表结构了，上面一半是默认的数据结构。在下方点&lt;code&gt;+&lt;/code&gt;可以添加自定义的字段，并选择数据类型。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;需要注意的是：在测试环境下，新增的字段可以删除掉。&lt;em&gt;&lt;strong&gt;一旦发布线上后，就不能删除了&lt;/strong&gt;&lt;/em&gt;。只能新增。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4.4 数据索引：Indexs&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fc05eabba4e8a8f6.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QUERYABLE
&lt;ul&gt;
&lt;li&gt;可查询&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SORTABLE
&lt;ul&gt;
&lt;li&gt;可排序&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SEARCHABLE
&lt;ul&gt;
&lt;li&gt;可搜索&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;根据需求进行设置，不设置的话会读不到数据的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;比如无尽模式排行榜，需要根据时间和等级进行的排序，就把&lt;code&gt;level&lt;/code&gt; &amp;amp; &lt;code&gt;duration&lt;/code&gt; 设置了&lt;code&gt;SORTABLE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9ef63adf55cdc665.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9a51ad3faa5d8fb1.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;五、QA&lt;/h2&gt;
&lt;h3&gt;5.1 存在iCloud安全么？&lt;/h3&gt;
&lt;p&gt;虽然没有绝对的安全，但对比市面上的云存储服务。苹果的还是相对让我放心的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;关键是便宜啊！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.2 要是用户抓包改数据呢？&lt;/h3&gt;
&lt;p&gt;我试过抓包，直接连不上容器了。感兴趣的可以尝试一下。&lt;/p&gt;
&lt;h3&gt;5.3 破解？&lt;/h3&gt;
&lt;p&gt;写到这里了我掏出了我的越狱机。。。说干就干，砸一个出来重签。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;想了解砸壳请移步：&lt;a href=&quot;/posts/article-23-34ee7481103d23e6/&quot;&gt;应用砸壳&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;好家伙，直接崩了，挺安全的了吧？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然我并没有深究能不能跳过系统这一步的验证，等萨尔有下了再来研究下。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;我也还没做防Hook，有兴趣破解一下的也可以找我交流哈，十分欢迎。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/862a8aaf188ad4b6.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5.4 访问速度&lt;/h3&gt;
&lt;p&gt;说不上非常快，但是够用。主要还是要合理设计表结构，要是在列表的数据结构里直接包含资源文件的话，那请求速度和用户体验就惨不忍睹了。&lt;/p&gt;
&lt;h3&gt;5.5 运维成本&lt;/h3&gt;
&lt;p&gt;几乎谈不上运维，咱也做不了什么。作为开发者能做的就是把应用逻辑和数据结构设计得合理一些。&lt;/p&gt;
&lt;p&gt;一些基础的运维数据在后台也是可以看到的。&lt;/p&gt;
&lt;h3&gt;5.6 跨平台&lt;/h3&gt;
&lt;p&gt;只做iOS平台还好，要是以后想拓展到不同平台的话，是不是就用不成了？难道还要单独搞一套，甚至迁移用户？&lt;/p&gt;
&lt;p&gt;放心，苹果贴心的提供了API&lt;a href=&quot;https://developer.apple.com/documentation/cloudkitjs&quot;&gt;CloudKit JS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;具体没研究，看起来是可以做跨平台的。&lt;/p&gt;
&lt;h3&gt;5.7 容量&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;1PB&lt;/strong&gt;&lt;/em&gt;够用不？&lt;/p&gt;
&lt;p&gt;抱歉，我刚看到这个单位的第一时间没反应过来。&lt;/p&gt;
&lt;p&gt;就是&lt;code&gt;1024TB&lt;/code&gt;，真没想到。&lt;/p&gt;
&lt;p&gt;不是一下子就有1P，随着你用户的量级来的。&lt;/p&gt;
&lt;p&gt;开发者需要关心的只有&lt;code&gt;Public database&lt;/code&gt;所占用的大小。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/icloud/cloudkit/&quot;&gt;Build Apps Using CloudKit&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.8 资源存储 CKAsset&lt;/h3&gt;
&lt;p&gt;CloudKit提供了CKAsset这个数据类型用来保存一些资源文件，可以放在CKRecord中进行保存。&lt;/p&gt;
&lt;p&gt;看起来挺方便。&lt;/p&gt;
&lt;h4&gt;问题&lt;/h4&gt;
&lt;p&gt;如扫雷的排行榜功能，是需要显示头像的。&lt;/p&gt;
&lt;p&gt;这里如果将头像直接添加为User表中，那么实际拉取用户的时候会很慢，因为这里会把图片资源一起带下来。而不是我们平时的Url的形式。&lt;/p&gt;
&lt;p&gt;如果将图片传到图床上然后添加图片Url的字段也是ok的，但近年来感觉图床限制越来越多了，不方便使用。&lt;/p&gt;
&lt;p&gt;这里我后面单独键了张表用来存头像资源，然后封装了一个加载、缓存CKAsset图片资源的工具，方便在排行版中使用。&lt;/p&gt;
&lt;h2&gt;七、总结&lt;/h2&gt;
&lt;p&gt;iCloud对于个人开发者来说是个好东西，可以低成本的构建用户体系。&lt;/p&gt;
&lt;p&gt;只要发挥想象力，很多功能都是可以可以做的。&lt;/p&gt;
&lt;p&gt;这里我用到的只是CloudKit的一部分，还有很多没用过。也许还存在很多的发掘空间。&lt;/p&gt;
&lt;h2&gt;八、参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/cloudkit&quot;&gt;CloudKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/icloud/cloudkit/designing/&quot;&gt;Designing apps using CloudKit.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/icloud/cloudkit/&quot;&gt;Build Apps Using CloudKit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/cloudkitjs&quot;&gt;CloudKit JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/cloudkit/determining_if_cloudkit_is_right_for_your_app&quot;&gt;「Determining If CloudKit Is Right for Your App」&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>结构体内存对齐</title><link>https://blog.lab-lawliet.app/posts/ios-02-82824caea63dcd16/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-02-82824caea63dcd16/</guid><description>1</description><pubDate>Wed, 09 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、各类型所占内存对照表&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/c2f21c579814aaba.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;二、结构体内存大小&lt;/h1&gt;
&lt;h2&gt;2.1 创建调试用结构体&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;struct Girl {
    double height;
    char firstChar;
    int age;
    short hairCount;
}Misa;

struct Boy {
    double height;
    int age;
    char firstChar;
    short hairCount;
}Light;

struct Kira {
    double height;
    int age;
    char firstChar;
    short hairCount;
    struct Girl misa;
    struct Boy light;
}Together;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.2 调试输出结构体大小&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po sizeof(Misa)
24

(lldb) po sizeof(Light)
16

(lldb) po sizeof(Together)
56
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.3 思考&lt;/h2&gt;
&lt;p&gt;奇怪了，&lt;code&gt;Misa&lt;/code&gt;和&lt;code&gt;Light&lt;/code&gt;，明明内存成员相同，怎么一个16，一个24呢？下面我们先了解一个东西：&lt;code&gt;内存对齐&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;三、内存对齐规则&lt;/h1&gt;
&lt;h2&gt;3.1 数据成员对齐规则&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;第一个数据成员从偏移量为0的地方开始，占用该成员大小的字节。
&lt;ul&gt;
&lt;li&gt;如Misa，以&lt;code&gt;double&lt;/code&gt;开始，那么就是&lt;code&gt;0-7&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;以后每个成员的存储起始位置都要从&lt;code&gt;该成员大小&lt;/code&gt;或者&lt;code&gt;成员的子成员&lt;/code&gt;大小的&lt;code&gt;整数倍&lt;/code&gt;开始
&lt;ul&gt;
&lt;li&gt;子成员，如：
&lt;ul&gt;
&lt;li&gt;数组、结构体等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.2 结构体作为成员对齐规则&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;如果一个&lt;code&gt;结构体A&lt;/code&gt;内，有&lt;code&gt;结构体B&lt;/code&gt;作为成员。则该成员要从其内部&lt;code&gt;最大元素大小&lt;/code&gt;的&lt;code&gt;整数倍地址&lt;/code&gt;开始存储.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.3 总大小对齐&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;结构体总大小，必须是其内部最大成员的整数倍，不足要补齐。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;四、还原内存结构&lt;/h1&gt;
&lt;h2&gt;4.1 Misa的结构&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;double&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;int&lt;/th&gt;
&lt;th&gt;short&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用空间&lt;/td&gt;
&lt;td&gt;0.1.2.3.4.5.6.7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;12.13.14.15&lt;/td&gt;
&lt;td&gt;16.17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;double
&lt;ul&gt;
&lt;li&gt;8字节&lt;/li&gt;
&lt;li&gt;从0开始，占用0-7&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;char
&lt;ul&gt;
&lt;li&gt;1字节&lt;/li&gt;
&lt;li&gt;从1的整数倍开始，8满足条件，占用8&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;int
&lt;ul&gt;
&lt;li&gt;4字节&lt;/li&gt;
&lt;li&gt;从4的整数倍开始，上一个末位为8，那么先后找，就找从12开始，占用12-15&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;short
&lt;ul&gt;
&lt;li&gt;2字节&lt;/li&gt;
&lt;li&gt;从2的整数倍开始，上一个末位为15，那就从16开始，暂用16-17&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;总大小
&lt;ul&gt;
&lt;li&gt;这些成员暂用了0-16共17个字节&lt;/li&gt;
&lt;li&gt;按照第三条对齐规则，向上取最大成员8的倍数，结果就是0-23，即24字节。和实际输出相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.2 Light的结构&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;double&lt;/th&gt;
&lt;th&gt;int&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;short&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用空间&lt;/td&gt;
&lt;td&gt;0.1.2.3.4.5.6.7&lt;/td&gt;
&lt;td&gt;8.9.10.11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;14.15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;double
&lt;ul&gt;
&lt;li&gt;8字节&lt;/li&gt;
&lt;li&gt;从0开始，占用0-7&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;int
&lt;ul&gt;
&lt;li&gt;4字节&lt;/li&gt;
&lt;li&gt;从4的倍数开始，合适的是8，暂用8-11&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;char
&lt;ul&gt;
&lt;li&gt;1字节&lt;/li&gt;
&lt;li&gt;从1的倍数开始，合适的是12，占用12&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;short
&lt;ul&gt;
&lt;li&gt;2字节&lt;/li&gt;
&lt;li&gt;从2的倍数开始，合适的是14，占用14-15&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;总大小
&lt;ul&gt;
&lt;li&gt;成员占用了0-15&lt;/li&gt;
&lt;li&gt;按照原则3，需要是最大成员8的倍数，即16字节。和实际输出相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.3 对比Light和Misa&lt;/h2&gt;
&lt;p&gt;如此我们可以发现，对于结构体来说，内部成员的顺序直接影响了结构体占用的空间。&lt;/p&gt;
&lt;h1&gt;五、嵌套结构体Together的内存结构&lt;/h1&gt;
&lt;h2&gt;5.1 Together的外层结构&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;double&lt;/th&gt;
&lt;th&gt;int&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;short&lt;/th&gt;
&lt;th&gt;Girl&lt;/th&gt;
&lt;th&gt;Boy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;成员最大8&lt;/td&gt;
&lt;td&gt;成员最大8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用空间&lt;/td&gt;
&lt;td&gt;0.1.2.3.4.5.6.7&lt;/td&gt;
&lt;td&gt;8.9.10.11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;14.15&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;由于比较长，Girl和Boy我们拿出来做&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;5.2 Girl&lt;/h2&gt;
&lt;p&gt;从8的整数倍开始，即16&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;double&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;int&lt;/th&gt;
&lt;th&gt;short&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用空间&lt;/td&gt;
&lt;td&gt;16.17.18.19.20.21.22.23&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;28.29.30.31&lt;/td&gt;
&lt;td&gt;32.33&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;5.3 Boy&lt;/h2&gt;
&lt;p&gt;从8的整数倍开始，即40&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;double&lt;/th&gt;
&lt;th&gt;int&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;short&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大小（字节）&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用空间&lt;/td&gt;
&lt;td&gt;40.41.42.43.44.45.46.47&lt;/td&gt;
&lt;td&gt;48.49.50.51&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;54.55&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;5.4 总size&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;占用0-55&lt;/li&gt;
&lt;li&gt;最大成员8，则Together总大小为56。与输出一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;六、思考&lt;/h1&gt;
&lt;h2&gt;6.1 顺序影响大小，我们知道OC对象的本质就是结构体,那么是不是我们在开发的时候要算出最优解，再来排列成员呢？&lt;/h2&gt;
&lt;p&gt;并不用，这里内部做了优化了的。后面我们会单独对这里进行研究。&lt;/p&gt;
&lt;h2&gt;6.2 做内存对齐的意义何在呢？&lt;/h2&gt;
&lt;h3&gt;a.平台因素&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不是所有硬件平台都能访问任意地址上的任意数据的&lt;/li&gt;
&lt;li&gt;某些硬件平台只能在某些地址处理某些特定的数据类型，否则会抛出硬件异常&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;b.性能因素&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据结构，尤其是栈，应该尽可能的在自然边界上对齐&lt;/li&gt;
&lt;li&gt;因为未来访问未对齐的内存，处理器需要做两次内存访问；而对其的内存访问只需要一次&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;c.总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;内存对齐是一个&lt;code&gt;以空间换时间&lt;/code&gt;的过程，为了使CPU更高效的读写数据，提升性能&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;七、联合体&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;结构体
&lt;ul&gt;
&lt;li&gt;优点：所有成员可共存，全面&lt;/li&gt;
&lt;li&gt;缺点：内存分配存在浪费，不管用不用，全部分配&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;联合体
&lt;ul&gt;
&lt;li&gt;优点：内存使用更加精细灵活，节省空间&lt;/li&gt;
&lt;li&gt;缺点：成员互斥&lt;/li&gt;
&lt;li&gt;同时只有一个成员可以得到这块内存的使用权(对该内存的读写)，各变量共用一个内存首地址&lt;/li&gt;
&lt;li&gt;一个union变量的总长度至少能容纳最大的成员变量，而且要满足是所有成员变量类型大小的整数倍&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;举个例子：游戏中的控制角色移动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;union Dirction {
  bool up;
  bool down;
  bool left;
  bool right;
} move;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>alloc与字节对齐</title><link>https://blog.lab-lawliet.app/posts/ios-01-alloc-b9640e29b7eb5e2c/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-01-alloc-b9640e29b7eb5e2c/</guid><description>看下面的例子，输出会是什么样的呢？</description><pubDate>Sun, 06 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、探索alloc&lt;/h1&gt;
&lt;p&gt;看下面的例子，输出会是什么样的呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ryukie *obj1 = [Ryukie alloc];
Ryukie *obj2 = [obj1 init];
Ryukie *obj3 = [obj1 init];

NSLog(@&quot;Objc1：%p，他的指针：%p&quot;, obj1, &amp;amp;obj1);
NSLog(@&quot;Objc2：%p，他的指针：%p&quot;, obj2, &amp;amp;obj2);
NSLog(@&quot;Objc3：%p，他的指针：%p&quot;, obj3, &amp;amp;obj3);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-06-06 19:56:22.045515+0800 OCObject[10944:3914848] Objc1：0x28220bc60，他的指针：0x16d421bf8
2021-06-06 19:56:22.045611+0800 OCObject[10944:3914848] Objc2：0x28220bc60，他的指针：0x16d421bf0
2021-06-06 19:56:22.045659+0800 OCObject[10944:3914848] Objc3：0x28220bc60，他的指针：0x16d421be8
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;不同的指针，指向了同样的内存空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、通过源码了解alloc实现&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://opensource.apple.com/tarballs/objc4/&quot;&gt;Source Browser&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://opensource.apple.com&quot;&gt;Apple Open Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;NSObject.mm&lt;/code&gt;这里就是&lt;code&gt;NSobject&lt;/code&gt;的核心实现代码&lt;/p&gt;
&lt;h2&gt;2.1 可运行调试的源码&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/LGCooci/objc4_debug&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;将项目Clone下来，即可运行调试。更加直观的学习底层实现。&lt;/p&gt;
&lt;h2&gt;2.2 调用流程探究&lt;/h2&gt;
&lt;h3&gt;a. 实验代码&lt;/h3&gt;
&lt;p&gt;我们在main中写下如下代码，分别在&amp;lt;-处添加断点&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@&quot;Hello, World!&quot;);
        RYModel *father = [RYModel alloc];&amp;lt;-
        
        RYModel *objc = [RYModel alloc];&amp;lt;-    
    }
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;b. alloc&lt;/h3&gt;
&lt;p&gt;现在是&lt;code&gt;RYModel&lt;/code&gt;第一次调用alloc。字面实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (id)alloc {
    return _objc_rootAlloc(self);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照代码下一步应该会调用&lt;code&gt;_objc_rootAlloc&lt;/code&gt;，真的吗？&lt;/p&gt;
&lt;h3&gt;c. objc_alloc&lt;/h3&gt;
&lt;p&gt;我们发现断点走到了这里，正如注释里面所说：&lt;code&gt;Calls [cls alloc].&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们推断，这里应该是alloc的实现IMP被修改了。继续下一步&lt;/p&gt;
&lt;h3&gt;d. callAlloc&lt;/h3&gt;
&lt;p&gt;断点直接来到了 &lt;code&gt;objc_msgSend&lt;/code&gt; 处，又继续调用了 &lt;code&gt;alloc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil &amp;amp;&amp;amp; !cls)) return nil;
    if (fastpath(!cls-&amp;gt;ISA()-&amp;gt;hasCustomAWZ())) {
        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));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;e. _objc_rootAlloc&lt;/h3&gt;
&lt;p&gt;这次断点才来到了，&lt;code&gt;_objc_rootAlloc&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/); &amp;lt;-
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;f. _objc_rootAllocWithZone&lt;/h3&gt;
&lt;p&gt;又来到了&lt;code&gt;callAlloc&lt;/code&gt;方法中，这次断点来到了&lt;code&gt;_objc_rootAllocWithZone&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
#if __OBJC2__
    ...
    if (fastpath(!cls-&amp;gt;ISA()-&amp;gt;hasCustomAWZ())) { // 这里会判断类对象的isa指针是否有自定义的AllocWithZone的实现
        return _objc_rootAllocWithZone(cls, nil); &amp;lt;-
    }
#endif
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_objc_rootAllocWithZone&lt;/code&gt;的实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们继续下一步&lt;/p&gt;
&lt;h3&gt;g. _class_createInstanceFromZone 创建实例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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-&amp;gt;isRealized());

    // Read class&apos;s info bits all at once for performance
    bool hasCxxCtor = cxxConstruct &amp;amp;&amp;amp; cls-&amp;gt;hasCxxCtor();
    bool hasCxxDtor = cls-&amp;gt;hasCxxDtor();
    bool fast = cls-&amp;gt;canAllocNonpointer();
    size_t size;

    size = cls-&amp;gt;instanceSize(extraBytes);// 1. 计算所需大小
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size); // 2. 开辟空间
    }
    if (slowpath(!obj)) {
        if (construct_flags &amp;amp; OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone &amp;amp;&amp;amp; fast) {
        obj-&amp;gt;initInstanceIsa(cls, hasCxxDtor); // 3. 实例化isa
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj-&amp;gt;initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;g.1 instanceSize 大小&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这里会去缓存中查找&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CoreFoundation&lt;/code&gt;规定最小16个字节
&lt;ul&gt;
&lt;li&gt;有&lt;code&gt;isa&lt;/code&gt;指针的8字节&lt;/li&gt;
&lt;li&gt;还有8字节预留&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;// Class&apos;s ivar size rounded up to a pointer-size boundary.&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;成员变量的大小向上取整（一个指针的大小8字节）&lt;/li&gt;
&lt;li&gt;即：成员变量的字节对齐&lt;/li&gt;
&lt;li&gt;即：不足8字节就按8字节&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;// May be unaligned depending on class&apos;s ivars.&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;大小取决于成员变量的大小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;inline 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 &amp;lt; 16) size = 16;
    return size;
}

// Class&apos;s ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

// May be unaligned depending on class&apos;s ivars.
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()-&amp;gt;ro()-&amp;gt;instanceSize;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;g.2 calloc 开空间&lt;/h4&gt;
&lt;p&gt;这里分配的还空间内还有数据，因为内存只覆写，不删除。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);// OBJC2 zone已经不用了，不走这里
} else {
    obj = (id)calloc(1, size);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;g.3 initInstanceIsa 初始化实例isa&lt;/h4&gt;
&lt;p&gt;这里会将刚创建的obj和isa进行绑定，完成这一步后，可以发现，obj就会先变成&lt;code&gt;RYModel&lt;/code&gt;了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls-&amp;gt;instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls-&amp;gt;hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;h. 非第一次alloc&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;发现在&lt;em&gt;&lt;strong&gt;d. callAlloc&lt;/strong&gt;&lt;/em&gt;这一步中有所不同：&lt;/li&gt;
&lt;li&gt;没有走&lt;code&gt;return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;而是直接走了&lt;code&gt;_objc_rootAllocWithZone&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这里是LLVM做了处理，对alloc的IMP做了调整，在类第一次调用alloc时进行了一些处理，然后再调用原有的alloc实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;i. 流程图&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;图片较大较长，建议下载到本地&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/cc9a961ccbc68ece.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;三、字节对齐&lt;/h1&gt;
&lt;h2&gt;3.1 算法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Class&apos;s ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) &amp;amp; ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) &amp;amp; ~WORD_MASK;
}
static inline size_t align16(size_t x) {
    return (x + size_t(15)) &amp;amp; ~size_t(15);
}

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.2 先思考一个问题：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6c8e61a9cc5b9919.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显然第二种效率更高，但是牺牲了一定的空间。
&lt;ul&gt;
&lt;li&gt;这就是典型的&lt;code&gt;以空间，换时间&lt;/code&gt;，对于CPU来说处理速度非常重要&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.3 内存调试&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;我们在第一个demo中下个断点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e2b2424a97cfd3fc.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) x obj1
0x282d86140: 0d 57 41 04 a1 61 00 00 28 00 41 04 01 00 00 00  .WA..a..(.A.....
0x282d86150: 42 00 00 00 00 00 00 00 40 61 d8 82 02 00 00 00  B.......@a......
(lldb) p 0x000061a10441570d // 小端、反着看
(long) $3 = 107344189019917
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(long) $1 = 80361110149439809&lt;/code&gt;这里其实是isa指针，但是打印出来确实long类型？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.4 isa 与 MASK&lt;/h2&gt;
&lt;h3&gt;a.获取isa指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// Get the class pointer out of an isa. When ptrauth is supported,
// this operation is optionally authenticated. Many code paths don&apos;t
// need the authentication, so it can be skipped in those cases for
// better performance.
//
// Note: this method does not support retrieving indexed isas. When
// indexed isas are in use, it can only be used to retrieve the class
// of a raw isa.
#if SUPPORT_INDEXED_ISA || (ISA_SIGNING_AUTH_MODE != ISA_SIGNING_AUTH)
#define MAYBE_UNUSED_AUTHENTICATED_PARAM __attribute__((unused))
#else
#define MAYBE_UNUSED_AUTHENTICATED_PARAM UNUSED_WITHOUT_PTRAUTH
#endif

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren&apos;t security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &amp;amp;= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &amp;amp;= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &amp;amp;= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &amp;amp;= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里要拿到isa需要&amp;amp;一个mask，在isa.h中可以找到对应架构的定义&lt;/p&gt;
&lt;h3&gt;b.获取MASk&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;下面是&lt;code&gt;ARM64&lt;/code&gt;的，其他架构的可以自行在头文件中查找，很简单的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#     define ISA_MASK        0x0000000ffffffff8ULL 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;c.复原出isa&lt;/h3&gt;
&lt;p&gt;再次输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po 0x000061a10441570d &amp;amp; 0x0000000ffffffff8
Ryukie
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功输出了Class: Ryukie&lt;/p&gt;
&lt;h2&gt;3.5 查看真实的内存排列方式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@interface Ryukie : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) BOOL isboy;
@property (nonatomic, copy) NSString *desc;
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;a.一个内存对齐的场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先我们格式化打印分析 x/4gx&lt;/li&gt;
&lt;li&gt;这种方式更加直观，不用我们自己去按小端的方式反着看了。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;4&lt;/code&gt;代表打印4个单位的，可以按需要调整。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) x/4gx obj1
0x28150e2e0: 0x000041a100c0577d 0x0000001200000001
0x28150e2f0: 0x0000000100c00008 0x0000000100c00028
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4ef2dd557e662f3b.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里发现 &lt;code&gt;po 0x0000001200000001&lt;/code&gt; 结果很怪异 &lt;code&gt;77309411329&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这里我们还剩 &lt;code&gt;age&lt;/code&gt; &amp;amp; &lt;code&gt;isboy&lt;/code&gt; 两个成员变量没有输出&lt;/li&gt;
&lt;li&gt;将这里的16位分开看下
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;po 0x00000012&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;18&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;po 0x00000001&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;这里Bool只占一个字节但是拉伸为了8个字节&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;这里就是比较明显的内存对齐的一个场景&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;b.思考&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果我们将上面的&lt;code&gt;@property (nonatomic, assign) int age;&lt;/code&gt;改为&lt;code&gt;@property (nonatomic, assign) NSInteger age;&lt;/code&gt; 会是怎样呢？&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>实战人人视频破解</title><link>https://blog.lab-lawliet.app/posts/article-24-6b88faa3e5473a91/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-24-6b88faa3e5473a91/</guid><description>使用class-dump导出头文件，便于我们定位目标。</description><pubDate>Sun, 23 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、class-dump&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;提前准备好&lt;a href=&quot;/posts/article-23-34ee7481103d23e6/&quot;&gt;砸壳🔗&lt;/a&gt;的应用（人人视频5.5.1）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用class-dump导出头文件，便于我们定位目标。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;class-dump -H MachO文件名 -o 输出的文件夹/&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;二、确定逆向目标&lt;/h1&gt;
&lt;p&gt;这里我想要实现非VIP用户可以查看VIP视频。&lt;/p&gt;
&lt;p&gt;一般情况下我们会通过类似&lt;code&gt;isVIP&lt;/code&gt;这样的字眼去进行相关的判断。&lt;/p&gt;
&lt;p&gt;Cycript链接手机进行分析&lt;/p&gt;
&lt;h1&gt;三、登录态&lt;/h1&gt;
&lt;p&gt;通过在Headers中搜索&lt;code&gt;isLogin&lt;/code&gt;看到一个很可能是用来判断登录态的东西：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d5ea60fac9674c99.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;3.1 hook登录态&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@interface UserInfoConfig

+ (bool)isLogined;

- (bool)isLogined;

@end

%hook UserInfoConfig

+ (bool)isLogined {
    return YES;
}

- (bool)isLogined {
    return YES;
}

%end

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.2 检验登录态&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;原版&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d86639200950ed88.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Hook版&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;通过！这里的&lt;code&gt;立即登录&lt;/code&gt;已经变成&lt;code&gt;立即开通&lt;/code&gt;了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6bb6f59eb5073341.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里因为是直接改写了登录状态的返回值，所以这里有了大量的错误提示：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;2021-05-23 14:52:09.592113+0800 PUClient[6981:2390642] You should call login api before calling any im related api&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3.3 找到VIP态&lt;/h2&gt;
&lt;p&gt;从UI层开始分析&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4763651ce7a79f3c.webp&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;a.找到容器: &lt;code&gt;RRVipUserInfoView&lt;/code&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;看看它内部有没有做数据逻辑&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/56eff7d4f2c40f80.webp&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看起来&lt;code&gt;resetStatus&lt;/code&gt;&amp;amp;&lt;code&gt;reloadUIData&lt;/code&gt;是有做些请求的，我们先记录一下。继续分析。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;b.父视图：&lt;code&gt;RRVipBannerTopCell&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f6254533adec359b.webp&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里看到一个&lt;code&gt;@property(retain, nonatomic) RRTopBannerModel *bannerModel;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;但是没有和用户相关的模型属性，推测&lt;code&gt;VIP&lt;/code&gt;的判断是在&lt;code&gt;RRVipUserInfoView&lt;/code&gt;内部！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;c.VIP相关类&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6e05f2ca61e54e19.webp&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过Headers查找VIP关键字，逐个查看，发现&lt;code&gt;RRIMManager&lt;/code&gt;疑似。而且还是个单例。我们Hook看下有什么效果。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;下面又找到几个，可能的文件&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;f.RrmjUser&lt;/h3&gt;
&lt;p&gt;其中有这样一个属性：&lt;code&gt;@property(retain, nonatomic) RRMedalModel *vipMedal;&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;g.RRMedalModel&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;@interface RRMedalModel : NSObject &amp;lt;NSCoding&amp;gt;
{
    _Bool _isExpired;
    long long _medalId;
    NSString *_imgUrl;
    NSString *_name;
    NSString *_endTime;
}

+ (id)modelCustomPropertyMapper;
+ (id)replacedKeyFromPropertyName;
- (void).cxx_destruct;
@property(nonatomic) _Bool isExpired; // @synthesize isExpired=_isExpired;
@property(copy, nonatomic) NSString *endTime; // @synthesize endTime=_endTime;
@property(copy, nonatomic) NSString *name; // @synthesize name=_name;
@property(copy, nonatomic) NSString *imgUrl; // @synthesize imgUrl=_imgUrl;
@property(nonatomic) long long medalId; // @synthesize medalId=_medalId;
- (id)endTimeTextString;
- (id)endTimeString;
- (_Bool)isPermanent;
- (id)initWithCoder:(id)arg1;
- (void)encodeWithCoder:(id)arg1;

@end

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;查看Header后发现疑似VIP相关字段&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;在第二步的&lt;code&gt;UserInfoConfig&lt;/code&gt;中发现有&lt;code&gt;RrmjUser&lt;/code&gt;属性存在，这里准备在运行时set一个我们自己创建的对象，重写会员是否过期的方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Logos代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma mark - RRMedalModel

@interface RRMedalModel: NSObject

- (bool)isPermanent;
- (bool)isExpired;

@end

%hook RRMedalModel

- (bool)isPermanent {
    return YES;
}

- (bool)isExpired {
    return NO;
}

%end

#pragma mark - RrmjUser

@interface RrmjUser: NSObject

@end

%hook RrmjUser

- (id)vipMedal {
    id vip = [[%c(RRMedalModel) alloc] init];
    return vip;
}

%end

#pragma mark - UserInfoConfig-这里Hook了登录态

@interface UserInfoConfig

+ (bool)isLogined;

- (bool)isLogined;

- (id)userInfo;

@end

%hook UserInfoConfig

+ (bool)isLogined {
    return YES;
}

- (bool)isLogined {
    return YES;
}

- (id)userInfo {
    id tempUser = [[%c(RrmjUser) alloc] init];
    return tempUser;
}

%end

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;结果&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/5ff5fc87e83e9ae4.webp&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;之前&lt;code&gt;开通会员&lt;/code&gt;的入口都变成了续费了。哈哈哈哈&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;但是观看会员视频还是只能试看5分钟&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;下面的目标就是解除5分钟的限制了！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;四：无限制调广告&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;试着直接把广告加载的方法Return了，但是直接不能加载视频了。于是改成不用等就能点跳过。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#pragma mark - RRPlayerControlAdPlay
@interface RRPlayerControlAdPlay: NSObject

- (bool)canJump;

@end

%hook RRPlayerControlAdPlay

- (bool)canJump {
    return YES;
}

%end
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;五：改变视频权限&lt;/h1&gt;
&lt;h2&gt;5.1结合正常显示的剧集数据对比非会员和会员的视频区别&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/986e03171499c324.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;5.2 调试找出关键字段的区别&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d31ce8ceb5d7348e.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;5.3 Hook&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#pragma mark - MovieEpisode
@interface MovieEpisode: NSObject
/// 2 VIP nil free
- (long long)feeModeType;
- (NSString *)feeMode;

@end

%hook MovieEpisode

- (NSString *)feeMode {
    return @&quot;free&quot;;
}

- (long long)feeModeType {
    return 0;
}

%end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5.4 结果&lt;/h2&gt;
&lt;p&gt;列表页显示成功，没有了VIp标签&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d6efc0bb12e7ddc2.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是点击对应集数观看的时候就报错了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/01b78a5b3e3980db.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;六：视频流&lt;/h1&gt;
&lt;p&gt;试了很多数据模型进行改写都不行&lt;/p&gt;
&lt;p&gt;免费的视频流，在浏览器即可访问&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po ((M3u8Model *)0x2828f1300).dibblingTotalDuration
0

(lldb) po ((M3u8Model *)0x2828f1300).url
https://tx-cdn-local.rr.tv/2a66f13043434abb9114720c3cc4f19c/d6372e9485f84ba38a4b82ab0f31bad2-0e37a229c0ddec2ae7b75fbcac9f6f55-ld.mp4?auth_key=1622396264-900b321e0b42e34f95ab888009a63e84-0-39b17c20f3adcd88504a446cd2496c25&amp;amp;clientType=ios_rrsp_jzsp&amp;amp;clientVersion=5.5.1&amp;amp;parseUsage=PLAY&amp;amp;uid=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VIP视频免费5min看的视频流，调整参数也不能访问完整视频。从域名看的话免费的放在腾讯云收费的放在阿里云。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) po ((M3u8Model *)0x2828d9280).url
https://ali-preview.rr.tv/da81107fbb414227bad8faafa68ba9e6/eb37b38567b44e34817a36d9a8b09c3f-74a18a8c38dc4be51298ea5519a1815c-ld.mp4?auth_key=1622396977-56748ddc75ea4acf87fe91146aecae62-0-fb83047d57857ce0deded4705ee8a6be&amp;amp;end=300

(lldb) po ((M3u8Model *)0x2828d9280).dibblingTotalDuration
0

(lldb) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6.1 暂时的结论&lt;/h2&gt;
&lt;p&gt;基本可以确定，VIP视频的逻辑是有在视频流做处理的，不能简单通过客户端调整进行破解操作。&lt;/p&gt;
&lt;p&gt;人人视频破解暂时告一段落。&lt;/p&gt;
&lt;p&gt;不过也算挺有收货的&lt;/p&gt;
</content:encoded></item><item><title>LLDB</title><link>https://blog.lab-lawliet.app/posts/article-16-lldb-72a547b27022e142/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-16-lldb-72a547b27022e142/</guid><description>breakpoint set -n funcname</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一：前言&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;平时我们进行开发的时候可以通过Xcode进行断点设置。&lt;/li&gt;
&lt;li&gt;但我们在进行逆向研究的时候，并没有源码不能直接进行断点调试。&lt;/li&gt;
&lt;li&gt;这时候我们就需要使用LLDB进行断点设置&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、符号断点相关指令&lt;/h1&gt;
&lt;h2&gt;2.1 单个符号断点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint set -n funcname&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a4fa37b9f0e5956c.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2.2 一组符号断点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint set -n funcname -n funcname -n funcname -n funcname&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这种方便对一个流程进行断点，同时开启和禁用一组断点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(lldb) breakpoint set -n &quot;[ViewController saceAction:]&quot; -n &quot;[ViewController stopAction:]&quot; -n &quot;[ViewController goonAction:]&quot;
Breakpoint 3: 3 locations.

---

3: names = {&apos;[ViewController saceAction:]&apos;, &apos;[ViewController saceAction:]&apos;, &apos;[ViewController saceAction:]&apos;, &apos;[ViewController stopAction:]&apos;, &apos;[ViewController stopAction:]&apos;, &apos;[ViewController stopAction:]&apos;, &apos;[ViewController goonAction:]&apos;, &apos;[ViewController goonAction:]&apos;, &apos;[ViewController goonAction:]&apos;}, locations = 3, resolved = 3, hit count = 0
  3.1: where = LLDB调试`-[ViewController saceAction:] + 60 at ViewController.m:32:5, address = 0x0000000100c99ecc, resolved, hit count = 0 
  3.2: where = LLDB调试`-[ViewController stopAction:] + 60 at ViewController.m:36:5, address = 0x0000000100c99f24, resolved, hit count = 0 
  3.3: where = LLDB调试`-[ViewController goonAction:] + 60 at ViewController.m:40:5, address = 0x0000000100c99f7c, resolved, hit count = 0 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.3 c&lt;/h2&gt;
&lt;p&gt;Continue&lt;/p&gt;
&lt;h2&gt;2.4 查看断点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;breakpoint list
&lt;ul&gt;
&lt;li&gt;查看断点列表
&lt;ul&gt;
&lt;li&gt;第一个数字为断点id&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.5 删除&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;delete
&lt;ul&gt;
&lt;li&gt;删除全部&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;delete id
&lt;ul&gt;
&lt;li&gt;删除指定id的断点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.6 禁用&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint disable 加id&lt;/code&gt; 禁用一组或一个&lt;/p&gt;
&lt;p&gt;&lt;code&gt;breakpoint disable&lt;/code&gt; 禁用全部&lt;/p&gt;
&lt;h2&gt;2.7 激活&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint enable&lt;/code&gt; 启用全部
加id启用单个&lt;/p&gt;
&lt;h2&gt;2.8 包含指令&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint set -r touchesBegan:withEvent:&lt;/code&gt;
会对所有方法名包含这个的&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可简写：b -r xxx&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2.9 selector&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint set --selector touchesBegan:withEvent:&lt;/code&gt;
整个项目中的同名selector&lt;/p&gt;
&lt;h2&gt;2.10 指定文件selector&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;breakpoint set --file ViewController.m --selector stopAction:&lt;/code&gt;
一般逆向不知道用不上。&lt;/p&gt;
&lt;h2&gt;2.11 下一步&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;n&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;2.- 产看更多指令&lt;/h2&gt;
&lt;p&gt;help breakpoint&lt;/p&gt;
&lt;h1&gt;三、执行代码&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;expression
&lt;ul&gt;
&lt;li&gt;就是常用的&lt;code&gt;p&lt;/code&gt;，可以用来执行代码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;四、堆栈&lt;/h1&gt;
&lt;h2&gt;4.1 bt&lt;/h2&gt;
&lt;p&gt;查看堆栈信息&lt;/p&gt;
&lt;h2&gt;4.2 查看当前作用域内的变量信息&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;frame variable&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;4.3 直接返回不执行后面的代码，提前返回&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;thread return&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以在调试阶段，绕过检测。在逆向调试时很有用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;五、内存断点&lt;/h1&gt;
&lt;h2&gt;5.1 对象set方法断点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;watchpoint set varible 对象-&amp;gt;属性&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;5.2 地址断点&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;watchpoint set expression 0x...&lt;/code&gt;
当该地址被访问的时候就会触发&lt;/p&gt;
&lt;h2&gt;其他用法和符号断点类似&lt;/h2&gt;
&lt;h1&gt;六、target stop-hook&lt;/h1&gt;
&lt;h2&gt;6.1 target stop-hook add&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在断点内添加一段指令，每当断点触发就会调用这段指令&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) target stop-hook add -o &quot;frame variable&quot;
Stop hook #1 added.
(lldb) n
2021-05-22 11:31:12.733658+0800 LLDB调试[5728:2271351] 1
(ViewController *) self = 0x000000012dd0f590
(SEL) _cmd = &quot;touchesBegan:withEvent:&quot;
(__NSSetM *) touches = 0x00000002816feae0 1 element
(UITouchesEvent *) event = 0x00000002823caf40

(lldb) n
2021-05-22 11:31:15.528090+0800 LLDB调试[5728:2271351] funcA
(ViewController *) self = 0x000000012dd0f590
(SEL) _cmd = &quot;touchesBegan:withEvent:&quot;
(__NSSetM *) touches = 0x00000002816feae0 1 element
(UITouchesEvent *) event = 0x00000002823caf40
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6.2 target stop-hook list&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(lldb) target stop-hook list
Hook: 1
  State: enabled
  Commands: 
      frame variable
(lldb) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6.3 target stop-hook delete&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(lldb) target stop-hook list
Hook: 1
  State: enabled
  Commands: 
      frame variable
(lldb) target stop-hook delete 1
(lldb) target stop-hook list
No stop hooks.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6.4 undisplay&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;和delete类似&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;(lldb) target stop-hook add -o &quot;frame variable&quot;
Stop hook #2 added.
(lldb) target stop-hook list
Hook: 2
  State: enabled
  Commands: 
      frame variable
(lldb) undisplay 2
(lldb) target stop-hook list
No stop hooks.
(lldb) 
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;七、LLDB初始化配置&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;cd到根目录下
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cd ~&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;修改&lt;code&gt;.lldbinit&lt;/code&gt;文件
&lt;ul&gt;
&lt;li&gt;没有就创建&lt;/li&gt;
&lt;li&gt;添加指令
&lt;ul&gt;
&lt;li&gt;如：&lt;code&gt;target stop-hook add -o &quot;frame variable&quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;保存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;就不用每次都自己去加了&lt;/li&gt;
&lt;li&gt;不要的话去文件中删掉即可&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>虚拟内存</title><link>https://blog.lab-lawliet.app/posts/article-17-d7ab7cc29ef0af2a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-17-d7ab7cc29ef0af2a/</guid><description>早期计算机系统的内存地址都是物理地址。</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、物理内存&lt;/h2&gt;
&lt;p&gt;早期计算机系统的内存地址都是物理地址。&lt;/p&gt;
&lt;h3&gt;1.1 内存不够用&lt;/h3&gt;
&lt;p&gt;应用程序会被完整加载到内存中，大量浪费空间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b930a3ebc5f73b29.webp&quot; alt=&quot;虚拟内存-应用程序完整加载&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.2 物理地址不安全&lt;/h3&gt;
&lt;p&gt;可以直接访问内存地址实现跨进程，访问其他应用程序的进程。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/251cf7ee36318439.webp&quot; alt=&quot;虚拟内存-直接访问物理地址&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;物理内存中的数据不会清除，只覆写&lt;/strong&gt;&lt;/em&gt;：这个过程是系统控制的，新的需要存进物理内存的数据，会被写入最不活跃的区域中。应用在后台可能被kill掉的原因之一就是被覆写了。&lt;/p&gt;
&lt;h2&gt;二、虚拟内存&lt;/h2&gt;
&lt;h3&gt;2.1 虚拟页表&lt;/h3&gt;
&lt;p&gt;应用程序进程内拿到的地址并不是真实的地址，会通过&lt;code&gt;虚拟页表&lt;/code&gt;映射到物理内存。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;iOS&lt;/code&gt; 一页数据 &lt;code&gt;16kb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Mac&lt;/code&gt; &lt;code&gt;4K&lt;/code&gt; ，可以在终端中使用 &lt;code&gt;PAGESIZE&lt;/code&gt;，命令获取&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.2 内存优化&lt;/h3&gt;
&lt;p&gt;应用程序不会完整的加载，当用到某一页数据的时候，才会被加载到物理内存中。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ce36ccc0d98761b5.webp&quot; alt=&quot;虚拟内存&quot; /&gt;&lt;/p&gt;
&lt;p&gt;流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统和CPU配合进行地址翻译
&lt;ul&gt;
&lt;li&gt;硬件中MMU（内存管理单元）进行地址翻译
&lt;ul&gt;
&lt;li&gt;非常快&lt;/li&gt;
&lt;li&gt;高速缓存
&lt;ul&gt;
&lt;li&gt;映射过的会被缓存不会重复翻译&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;找到物理地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将数据加载进物理内存或读取&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 ASLR&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ASLR&lt;/code&gt; 每次启动为 &lt;code&gt;虚拟页表&lt;/code&gt; 添加随机的偏移值，已获得更高的安全性。&lt;/p&gt;
&lt;h2&gt;三、 缺页中断&lt;/h2&gt;
&lt;p&gt;由于 &lt;code&gt;虚拟页表&lt;/code&gt; 的存在，在我们代码访问的数据 &lt;code&gt;page&lt;/code&gt; 没有被加载进内存的时候，就会出现 &lt;code&gt;PageFault&lt;/code&gt; &lt;code&gt;缺页中断&lt;/code&gt;。而大量的缺页中断会影响应用的启动速度。&lt;/p&gt;
&lt;p&gt;而我们可以通过 &lt;code&gt;二进制重排&lt;/code&gt; 对数据在 &lt;code&gt;MachO&lt;/code&gt; 中的分页布局进行优化，使启动时相关的数据尽量在连续的 &lt;code&gt;page&lt;/code&gt; 中，来减少 &lt;code&gt;Page Fault&lt;/code&gt; 的次数。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里我有进行一次相关的实战：&lt;a href=&quot;/posts/ios-03-fe6ede4cd6882a9c/&quot;&gt;二进制重排实践&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Chisel工具</title><link>https://blog.lab-lawliet.app/posts/article-18-chisel-76e80e7d54b2ac66/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-18-chisel-76e80e7d54b2ac66/</guid><description>Put a border around views with an ambiguous layout. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/chisel&quot;&gt;chisel&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Chisel is a collection of LLDB commands to assist in the debugging of iOS apps.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;说明&lt;/h1&gt;
&lt;h2&gt;alamborder&lt;/h2&gt;
&lt;p&gt;Put a border around views with an ambiguous layout. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: alamborder
Put a border around views with an ambiguous layout&lt;/p&gt;
&lt;p&gt;Options:
--color/-c &amp;lt;color&amp;gt;; Type: string; A color name such as &apos;red&apos;, &apos;green&apos;, &apos;magenta&apos;, etc.
--width/-w &amp;lt;width&amp;gt;; Type: CGFloat; Desired width of border.&lt;/p&gt;
&lt;p&gt;Syntax: alamborder [--color=color] [--width=width]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBAutolayoutBorderAmbiguous in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAutoLayoutCommands.py.&lt;/p&gt;
&lt;h2&gt;alamunborder&lt;/h2&gt;
&lt;p&gt;Removes the border around views with an ambiguous layout. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: alamunborder
Removes the border around views with an ambiguous layout&lt;/p&gt;
&lt;p&gt;Syntax: alamunborder&lt;/p&gt;
&lt;p&gt;This command is implemented as FBAutolayoutUnborderAmbiguous in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAutoLayoutCommands.py.&lt;/p&gt;
&lt;h2&gt;binside&lt;/h2&gt;
&lt;p&gt;Set a breakpoint for a relative address within the framework/library that&apos;s currently running. This does the work of finding the offset for the framework/library and sliding your address accordingly. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: binside
Set a breakpoint for a relative address within the framework/library that&apos;s currently running. This does the work of finding the offset for the framework/library and sliding your address accordingly.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;address&amp;gt;; Type: string; Address within the currently running framework to set a breakpoint on.&lt;/p&gt;
&lt;p&gt;Syntax: binside &amp;lt;address&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBFrameworkAddressBreakpointCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDebugCommands.py.&lt;/p&gt;
&lt;h2&gt;bmessage&lt;/h2&gt;
&lt;p&gt;Set a breakpoint for a selector on a class, even if the class itself doesn&apos;t override that selector. It walks the hierarchy until it finds a class that does implement the selector and sets a conditional breakpoint there. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: bmessage
Set a breakpoint for a selector on a class, even if the class itself doesn&apos;t override that selector. It walks the hierarchy until it finds a class that does implement the selector and sets a conditional breakpoint there.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;expression&amp;gt;; Type: string; Expression to set a breakpoint on, e.g. &quot;-[MyView setFrame:]&quot;, &quot;+[MyView awesomeClassMethod]&quot; or &quot;-[0xabcd1234 setFrame:]&quot;&lt;/p&gt;
&lt;p&gt;Syntax: bmessage &amp;lt;expression&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBMethodBreakpointCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDebugCommands.py.&lt;/p&gt;
&lt;h2&gt;border&lt;/h2&gt;
&lt;p&gt;Draws a border around &amp;lt;viewOrLayer&amp;gt;. Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: border
Draws a border around &amp;lt;viewOrLayer&amp;gt;. Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView/CALayer *; The view/layer to border.
NSViews must be layer-backed.&lt;/p&gt;
&lt;p&gt;Options:
--color/-c &amp;lt;color&amp;gt;; Type: string; A color name such as &apos;red&apos;, &apos;green&apos;, &apos;magenta&apos;, etc.
--width/-w &amp;lt;width&amp;gt;; Type: CGFloat; Desired width of border.
--depth/-d &amp;lt;depth&amp;gt;; Type: int; Number of levels of subviews to border. Each level gets a different color beginning with the provided or default color&lt;/p&gt;
&lt;p&gt;Syntax: border [--color=color] [--width=width] [--depth=depth] &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBDrawBorderCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;caflush&lt;/h2&gt;
&lt;p&gt;Force Core Animation to flush. This will &apos;repaint&apos; the UI but also may mess with ongoing animations. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: caflush
Force Core Animation to flush. This will &apos;repaint&apos; the UI but also may mess with ongoing animations.&lt;/p&gt;
&lt;p&gt;Syntax: caflush&lt;/p&gt;
&lt;p&gt;This command is implemented as FBCoreAnimationFlushCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;dcomponents&lt;/h2&gt;
&lt;p&gt;Set debugging options for components. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: dcomponents
Set debugging options for components.&lt;/p&gt;
&lt;p&gt;Options:
--set/-s ; Set debug mode for components
--unset/-u ; Unset debug mode for components&lt;/p&gt;
&lt;p&gt;Syntax: dcomponents [--set] [--unset]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBComponentsDebugCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBComponentCommands.py.&lt;/p&gt;
&lt;h2&gt;dismiss&lt;/h2&gt;
&lt;p&gt;Dismiss a presented view controller. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: dismiss
Dismiss a presented view controller.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewController&amp;gt;; Type: UIViewController *; The view controller to dismiss.&lt;/p&gt;
&lt;p&gt;Syntax: dismiss &amp;lt;viewController&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBDismissViewControllerCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;fa11y&lt;/h2&gt;
&lt;p&gt;Find the views whose accessibility labels match labelRegex and puts the address of the first result on the clipboard. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: fa11y
Find the views whose accessibility labels match labelRegex and puts the address of the first result on the clipboard.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;labelRegex&amp;gt;; Type: string; The accessibility label regex to search the view hierarchy for.&lt;/p&gt;
&lt;p&gt;Syntax: fa11y &amp;lt;labelRegex&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBFindViewByAccessibilityLabelCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAccessibilityCommands.py.&lt;/p&gt;
&lt;h2&gt;flicker&lt;/h2&gt;
&lt;p&gt;Quickly show and hide a view to quickly help visualize where it is. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: flicker
Quickly show and hide a view to quickly help visualize where it is.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView*; The view to flicker.&lt;/p&gt;
&lt;p&gt;Syntax: flicker &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBFlickerViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBFlickerCommands.py.&lt;/p&gt;
&lt;h2&gt;fv&lt;/h2&gt;
&lt;p&gt;Find the views whose class names match classNameRegex and puts the address of first on the clipboard. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: fv
Find the views whose class names match classNameRegex and puts the address of first on the clipboard.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;classNameRegex&amp;gt;; Type: string; The view-class regex to search the view hierarchy for.&lt;/p&gt;
&lt;p&gt;Syntax: fv &amp;lt;classNameRegex&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBFindViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBFindCommands.py.&lt;/p&gt;
&lt;h2&gt;fvc&lt;/h2&gt;
&lt;p&gt;Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: fvc
Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard.&lt;/p&gt;
&lt;p&gt;Options:
--name/-n &amp;lt;classNameRegex&amp;gt;; Type: string; The view-controller-class regex to search the view controller hierarchy for.
--view/-v &amp;lt;view&amp;gt;; Type: UIView; This function will print the View Controller that owns this view.&lt;/p&gt;
&lt;p&gt;Syntax: fvc [--name=classNameRegex] [--view=view]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBFindViewControllerCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBFindCommands.py.&lt;/p&gt;
&lt;h2&gt;hide&lt;/h2&gt;
&lt;p&gt;Hide a view or layer. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: hide
Hide a view or layer.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView/CALayer *; The view/layer to hide.&lt;/p&gt;
&lt;p&gt;Syntax: hide &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBHideViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;mask&lt;/h2&gt;
&lt;p&gt;Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer&apos;s bounds  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: mask
Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer&apos;s bounds&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView/CALayer *; The view/layer to mask.&lt;/p&gt;
&lt;p&gt;Options:
--color/-c &amp;lt;color&amp;gt;; Type: string; A color name such as &apos;red&apos;, &apos;green&apos;, &apos;magenta&apos;, etc.
--alpha/-a &amp;lt;alpha&amp;gt;; Type: CGFloat; Desired alpha of mask.&lt;/p&gt;
&lt;p&gt;Syntax: mask [--color=color] [--alpha=alpha] &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBMaskViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;mwarning&lt;/h2&gt;
&lt;p&gt;simulate a memory warning  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: mwarning
simulate a memory warning&lt;/p&gt;
&lt;p&gt;Syntax: mwarning&lt;/p&gt;
&lt;p&gt;This command is implemented as FBMemoryWarningCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDebugCommands.py.&lt;/p&gt;
&lt;h2&gt;pa11y&lt;/h2&gt;
&lt;p&gt;Print accessibility labels of all views in hierarchy of &amp;lt;aView&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pa11y
Print accessibility labels of all views in hierarchy of &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aView&amp;gt;; Type: UIView*; The view to print the hierarchy of.&lt;/p&gt;
&lt;p&gt;Syntax: pa11y &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintAccessibilityLabels in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAccessibilityCommands.py.&lt;/p&gt;
&lt;h2&gt;pa11yi&lt;/h2&gt;
&lt;p&gt;Print accessibility identifiers of all views in hierarchy of &amp;lt;aView&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pa11yi
Print accessibility identifiers of all views in hierarchy of &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aView&amp;gt;; Type: UIView*; The view to print the hierarchy of.&lt;/p&gt;
&lt;p&gt;Syntax: pa11yi &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintAccessibilityIdentifiers in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAccessibilityCommands.py.&lt;/p&gt;
&lt;h2&gt;pactions&lt;/h2&gt;
&lt;p&gt;Print the actions and targets of a control. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pactions
Print the actions and targets of a control.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;control&amp;gt;; Type: UIControl *; The control to inspect the actions of.&lt;/p&gt;
&lt;p&gt;Syntax: pactions &amp;lt;control&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintTargetActions in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;paltrace&lt;/h2&gt;
&lt;p&gt;Print the Auto Layout trace for the given view. Defaults to the key window. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: paltrace
Print the Auto Layout trace for the given view. Defaults to the key window.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;view&amp;gt;; Type: UIView *; The view to print the Auto Layout trace for.&lt;/p&gt;
&lt;p&gt;Syntax: paltrace &amp;lt;view&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintAutolayoutTrace in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBAutoLayoutCommands.py.&lt;/p&gt;
&lt;h2&gt;panim&lt;/h2&gt;
&lt;p&gt;Prints if the code is currently execution with a UIView animation block. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: panim
Prints if the code is currently execution with a UIView animation block.&lt;/p&gt;
&lt;p&gt;Syntax: panim&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintIsExecutingInAnimationBlockCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pbcopy&lt;/h2&gt;
&lt;p&gt;Print object and copy output to clipboard  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pbcopy
Print object and copy output to clipboard&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; The object to print&lt;/p&gt;
&lt;p&gt;Syntax: pbcopy &amp;lt;object&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintToClipboard in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pblock&lt;/h2&gt;
&lt;p&gt;Print the block`s implementation address and signature. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pblock
Print the block`s implementation address and signature&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;block&amp;gt;; Type: ; The block object you want to print&lt;/p&gt;
&lt;p&gt;Syntax: pblock &amp;lt;block&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintBlock in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBClassDump.py.&lt;/p&gt;
&lt;h2&gt;pbundlepath&lt;/h2&gt;
&lt;p&gt;Print application&apos;s bundle directory path. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pbundlepath
Print application&apos;s bundle directory path.&lt;/p&gt;
&lt;p&gt;Options:
--open/-o ; open in Finder&lt;/p&gt;
&lt;p&gt;Syntax: pbundlepath [--open]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintApplicationBundlePath in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pca&lt;/h2&gt;
&lt;p&gt;Print layer tree from the perspective of the render server. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pca
Print layer tree from the perspective of the render server.&lt;/p&gt;
&lt;p&gt;Syntax: pca&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintCoreAnimationTree in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pcells&lt;/h2&gt;
&lt;p&gt;Print the visible cells of the highest table view in the hierarchy. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pcells
Print the visible cells of the highest table view in the hierarchy.&lt;/p&gt;
&lt;p&gt;Syntax: pcells&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintOnscreenTableViewCells in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pclass&lt;/h2&gt;
&lt;p&gt;Print the inheritance starting from an instance of any class. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pclass
Print the inheritance starting from an instance of any class.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; The instance to examine.&lt;/p&gt;
&lt;p&gt;Syntax: pclass &amp;lt;object&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintInheritanceHierarchy in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pcomponents&lt;/h2&gt;
&lt;p&gt;Print a recursive description of components found starting from &amp;lt;aView&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pcomponents
Print a recursive description of components found starting from &amp;lt;aView&amp;gt;.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aView&amp;gt;; Type: UIView*; The view to from which the search for components begins.&lt;/p&gt;
&lt;p&gt;Options:
--up/-u ; Print only the component hierarchy found on the first superview that has them, carrying the search up to its window.
--show-views/-v &amp;lt;showViews&amp;gt;; Type: BOOL; Prints the component hierarchy and does not print the views if the supplied argument is &apos;NO&apos;. Supply either a &apos;YES&apos; or a &apos;NO&apos;. The default is to show views.&lt;/p&gt;
&lt;p&gt;Syntax: pcomponents [--up] [--show-views=showViews] &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBComponentsPrintCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBComponentCommands.py.&lt;/p&gt;
&lt;h2&gt;pcurl&lt;/h2&gt;
&lt;p&gt;Print the NSURLRequest (HTTP) as curl command. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pcurl
Print the NSURLRequest (HTTP) as curl command.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;request&amp;gt;; Type: NSURLRequest*/NSMutableURLRequest*; The request to convert to the curl command.&lt;/p&gt;
&lt;p&gt;Options:
--embed-data/-e ; Embed request data as base64.&lt;/p&gt;
&lt;p&gt;Syntax: pcurl [--embed-data] &amp;lt;request&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintAsCurl in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pdata&lt;/h2&gt;
&lt;p&gt;Print the contents of NSData object as string. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pdata
Print the contents of NSData object as string.
Supported encodings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ascii,&lt;/li&gt;
&lt;li&gt;utf8,&lt;/li&gt;
&lt;li&gt;utf16, unicode,&lt;/li&gt;
&lt;li&gt;utf16l (Little endian),&lt;/li&gt;
&lt;li&gt;utf16b (Big endian),&lt;/li&gt;
&lt;li&gt;utf32,&lt;/li&gt;
&lt;li&gt;utf32l (Little endian),&lt;/li&gt;
&lt;li&gt;utf32b (Big endian),&lt;/li&gt;
&lt;li&gt;latin1, iso88591 (88591),&lt;/li&gt;
&lt;li&gt;latin2, iso88592 (88592),&lt;/li&gt;
&lt;li&gt;cp1251 (1251),&lt;/li&gt;
&lt;li&gt;cp1252 (1252),&lt;/li&gt;
&lt;li&gt;cp1253 (1253),&lt;/li&gt;
&lt;li&gt;cp1254 (1254),&lt;/li&gt;
&lt;li&gt;cp1250 (1250),&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Arguments:
&amp;lt;data&amp;gt;; Type: NSData *; NSData object.&lt;/p&gt;
&lt;p&gt;Options:
--encoding/-e &amp;lt;encoding&amp;gt;; Type: string; Used encoding (default utf-8).&lt;/p&gt;
&lt;p&gt;Syntax: pdata [--encoding=encoding] &amp;lt;data&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintData in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pdocspath&lt;/h2&gt;
&lt;p&gt;Print application&apos;s &apos;Documents&apos; directory path. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pdocspath
Print application&apos;s &apos;Documents&apos; directory path.&lt;/p&gt;
&lt;p&gt;Options:
--open/-o ; open in Finder&lt;/p&gt;
&lt;p&gt;Syntax: pdocspath [--open]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintApplicationDocumentsPath in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pinternals&lt;/h2&gt;
&lt;p&gt;Show the internals of an object by dereferencing it as a pointer. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pinternals
Show the internals of an object by dereferencing it as a pointer.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; Object expression to be evaluated.&lt;/p&gt;
&lt;p&gt;Syntax: pinternals &amp;lt;object&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintInternals in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pinvocation&lt;/h2&gt;
&lt;p&gt;Print the stack frame, receiver, and arguments of the current invocation. It will fail to print all arguments if any arguments are variadic (varargs). Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pinvocation
Print the stack frame, receiver, and arguments of the current invocation. It will fail to print all arguments if any arguments are variadic (varargs).&lt;/p&gt;
&lt;p&gt;NOTE: Sadly this is currently only implemented on x86.&lt;/p&gt;
&lt;p&gt;Options:
--all/-a ; Specify to print the entire stack instead of just the current frame.&lt;/p&gt;
&lt;p&gt;Syntax: pinvocation [--all]&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintInvocation in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBInvocationCommands.py.&lt;/p&gt;
&lt;h2&gt;pivar&lt;/h2&gt;
&lt;p&gt;Print the value of an object&apos;s named instance variable. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pivar
Print the value of an object&apos;s named instance variable.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; Object expression to be evaluated.
&amp;lt;ivarName&amp;gt;; Type: ; Name of instance variable to print.&lt;/p&gt;
&lt;p&gt;Syntax: pivar &amp;lt;object&amp;gt; &amp;lt;ivarName&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintInstanceVariable in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pjson&lt;/h2&gt;
&lt;p&gt;Print JSON representation of NSDictionary or NSArray object  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pjson
Print JSON representation of NSDictionary or NSArray object&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; The NSDictionary or NSArray object to print&lt;/p&gt;
&lt;p&gt;Options:
--plain/-p ; Plain JSON&lt;/p&gt;
&lt;p&gt;Syntax: pjson [--plain] &amp;lt;object&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintJSON in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pkp&lt;/h2&gt;
&lt;p&gt;Print out the value of the key path expression using -valueForKeyPath:. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pkp
Print out the value of the key path expression using -valueForKeyPath:&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;keypath&amp;gt;; Type: NSString *; The keypath to print&lt;/p&gt;
&lt;p&gt;Syntax: pkp &amp;lt;keypath&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintKeyPath in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pmethods&lt;/h2&gt;
&lt;p&gt;Print the class and instance methods of a class. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pmethods
Print the class and instance methods of a class.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;instance or class&amp;gt;; Type: instance or Class; an Objective-C Class.&lt;/p&gt;
&lt;p&gt;Options:
--address/-a ; Print the implementation address of the method
--instance/-i ; Print the instance methods
--class/-c ; Print the class methods
--name/-n ; Take the argument as class name&lt;/p&gt;
&lt;p&gt;Syntax: pmethods [--address] [--instance] [--class] [--name] &amp;lt;instance or class&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintMethods in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBClassDump.py.&lt;/p&gt;
&lt;h2&gt;poobjc&lt;/h2&gt;
&lt;p&gt;Print the expression result, with the expression run in an ObjC++ context. (Shortcut for &quot;expression -O -l ObjC++ -- &quot; )  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: poobjc
Print the expression result, with the expression run in an ObjC++ context. (Shortcut for &quot;expression -O -l ObjC++ -- &quot; )&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;expression&amp;gt;; Type: ; ObjC expression to evaluate and print.&lt;/p&gt;
&lt;p&gt;Syntax: poobjc &amp;lt;expression&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintObjectInObjc in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pproperties&lt;/h2&gt;
&lt;p&gt;Print the properties of an instance or Class  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pproperties
Print the properties of an instance or Class&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;instance or class&amp;gt;; Type: instance or Class; an Objective-C Class.&lt;/p&gt;
&lt;p&gt;Options:
--name/-n ; Take the argument as class name&lt;/p&gt;
&lt;p&gt;Syntax: pproperties [--name] &amp;lt;instance or class&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintProperties in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBClassDump.py.&lt;/p&gt;
&lt;h2&gt;present&lt;/h2&gt;
&lt;p&gt;Present a view controller. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: present
Present a view controller.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewController&amp;gt;; Type: UIViewController *; The view controller to present.&lt;/p&gt;
&lt;p&gt;Syntax: present &amp;lt;viewController&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPresentViewControllerCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;presponder&lt;/h2&gt;
&lt;p&gt;Print the responder chain starting from a specific responder. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: presponder
Print the responder chain starting from a specific responder.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;startResponder&amp;gt;; Type: UIResponder *; The responder to use to start walking the chain.&lt;/p&gt;
&lt;p&gt;Syntax: presponder &amp;lt;startResponder&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintUpwardResponderChain in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;ptv&lt;/h2&gt;
&lt;p&gt;Print the highest table view in the hierarchy. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: ptv
Print the highest table view in the hierarchy.&lt;/p&gt;
&lt;p&gt;Syntax: ptv&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintOnscreenTableView in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pvc&lt;/h2&gt;
&lt;p&gt;Print the recursion description of &amp;lt;aViewController&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pvc
Print the recursion description of &amp;lt;aViewController&amp;gt;.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aViewController&amp;gt;; Type: UIViewController*; The view controller to print the description of.&lt;/p&gt;
&lt;p&gt;Syntax: pvc &amp;lt;aViewController&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintViewControllerHierarchyCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;pviews&lt;/h2&gt;
&lt;p&gt;Print the recursion description of &amp;lt;aView&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: pviews
Print the recursion description of &amp;lt;aView&amp;gt;.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aView&amp;gt;; Type: UIView*/NSView*; The view to print the description of.&lt;/p&gt;
&lt;p&gt;Options:
--up/-u ; Print only the hierarchy directly above the view, up to its window.
--depth/-d &amp;lt;depth&amp;gt;; Type: int; Print only to a given depth. 0 indicates infinite depth.&lt;/p&gt;
&lt;p&gt;Syntax: pviews [--up] [--depth=depth] &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBPrintViewHierarchyCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBPrintCommands.py.&lt;/p&gt;
&lt;h2&gt;rcomponents&lt;/h2&gt;
&lt;p&gt;Synchronously reflow and update root components found starting from &amp;lt;aView&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: rcomponents
Synchronously reflow and update root components found starting from &amp;lt;aView&amp;gt;.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;aView&amp;gt;; Type: UIView*; The view to from which the search for the root components begins.&lt;/p&gt;
&lt;p&gt;Options:
--up/-u ; Reflow only the root components found on the first superview that has them, carrying the search up to its window.&lt;/p&gt;
&lt;p&gt;Syntax: rcomponents [--up] &amp;lt;aView&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBComponentsReflowCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBComponentCommands.py.&lt;/p&gt;
&lt;h2&gt;setinput&lt;/h2&gt;
&lt;p&gt;Input text into text field or text view that is first responder. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: setinput
Input text into text field or text view that is first responder.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;inputText&amp;gt;; Type: string; The text to input.&lt;/p&gt;
&lt;p&gt;Syntax: setinput &amp;lt;inputText&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBInputTexToFirstResponderCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBTextInputCommands.py.&lt;/p&gt;
&lt;h2&gt;settext&lt;/h2&gt;
&lt;p&gt;Set text on text on a view by accessibility id. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: settext
Set text on text on a view by accessibility id.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;accessibilityId&amp;gt;; Type: string; The accessibility ID of the input view.
&amp;lt;replacementText&amp;gt;; Type: string; The text to set.&lt;/p&gt;
&lt;p&gt;Syntax: settext &amp;lt;accessibilityId&amp;gt; &amp;lt;replacementText&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBInputTexByAccessibilityIdCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBTextInputCommands.py.&lt;/p&gt;
&lt;h2&gt;show&lt;/h2&gt;
&lt;p&gt;Show a view or layer. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: show
Show a view or layer.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView/CALayer *; The view/layer to show.&lt;/p&gt;
&lt;p&gt;Syntax: show &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBShowViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;slowanim&lt;/h2&gt;
&lt;p&gt;Slows down animations. Works on the iOS Simulator and a device. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: slowanim
Slows down animations. Works on the iOS Simulator and a device.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;speed&amp;gt;; Type: float; Animation speed (default 0.1).&lt;/p&gt;
&lt;p&gt;Syntax: slowanim &amp;lt;speed&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBSlowAnimationCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;taplog&lt;/h2&gt;
&lt;p&gt;Log tapped view to the console. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: taplog
Log tapped view to the console.&lt;/p&gt;
&lt;p&gt;Syntax: taplog&lt;/p&gt;
&lt;p&gt;This command is implemented as FBTapLoggerCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBFindCommands.py.&lt;/p&gt;
&lt;h2&gt;unborder&lt;/h2&gt;
&lt;p&gt;Removes border around &amp;lt;viewOrLayer&amp;gt;. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: unborder
Removes border around &amp;lt;viewOrLayer&amp;gt;.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/NSView/CALayer *; The view/layer to unborder.&lt;/p&gt;
&lt;p&gt;Options:
--depth/-d &amp;lt;depth&amp;gt;; Type: int; Number of levels of subviews to unborder.&lt;/p&gt;
&lt;p&gt;Syntax: unborder [--depth=depth] &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBRemoveBorderCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;unmask&lt;/h2&gt;
&lt;p&gt;Remove mask from a view or layer  Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: unmask
Remove mask from a view or layer&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;viewOrLayer&amp;gt;; Type: UIView/CALayer *; The view/layer to mask.&lt;/p&gt;
&lt;p&gt;Syntax: unmask &amp;lt;viewOrLayer&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBUnmaskViewCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;unslowanim&lt;/h2&gt;
&lt;p&gt;Turn off slow animations. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: unslowanim
Turn off slow animations.&lt;/p&gt;
&lt;p&gt;Syntax: unslowanim&lt;/p&gt;
&lt;p&gt;This command is implemented as FBUnslowAnimationCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDisplayCommands.py.&lt;/p&gt;
&lt;h2&gt;visualize&lt;/h2&gt;
&lt;p&gt;Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: visualize
Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;target&amp;gt;; Type: (id); The object to visualize.&lt;/p&gt;
&lt;p&gt;Syntax: visualize &amp;lt;target&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBVisualizeCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBVisualizationCommands.py.&lt;/p&gt;
&lt;h2&gt;vs&lt;/h2&gt;
&lt;p&gt;Interactively search for a view by walking the hierarchy. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: vs
Interactively search for a view by walking the hierarchy.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;view&amp;gt;; Type: UIView*; The view to border.&lt;/p&gt;
&lt;p&gt;Syntax: vs &amp;lt;view&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBViewSearchCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBFlickerCommands.py.&lt;/p&gt;
&lt;h2&gt;wivar&lt;/h2&gt;
&lt;p&gt;Set a watchpoint for an object&apos;s instance variable. Expects &apos;raw&apos; input (see &apos;help raw-input&apos;.)&lt;/p&gt;
&lt;p&gt;Syntax: wivar
Set a watchpoint for an object&apos;s instance variable.&lt;/p&gt;
&lt;p&gt;Arguments:
&amp;lt;object&amp;gt;; Type: id; Object expression to be evaluated.
&amp;lt;ivarName&amp;gt;; Type: ; Name of the instance variable to watch.&lt;/p&gt;
&lt;p&gt;Syntax: wivar &amp;lt;object&amp;gt; &amp;lt;ivarName&amp;gt;&lt;/p&gt;
&lt;p&gt;This command is implemented as FBWatchInstanceVariableCommand in
/usr/local/Cellar/chisel/1.5.0/libexec/commands/FBDebugCommands.py.&lt;/p&gt;
</content:encoded></item><item><title>DS.LLDB工具</title><link>https://blog.lab-lawliet.app/posts/article-19-dslldb-105f26fe4f76020d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-19-dslldb-105f26fe4f76020d/</guid><description>可以用来找对象，拿到地址来进行逆向分析。</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/DerekSelander/LLDB&quot;&gt;DerekSelander/LLDB&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;可以用来找对象，拿到地址来进行逆向分析。&lt;/p&gt;
</content:encoded></item><item><title>Cycript工具</title><link>https://blog.lab-lawliet.app/posts/article-20-cycript-be1b282ccb380d98/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-20-cycript-be1b282ccb380d98/</guid><description>找到当前的ruby的如：libruby.3.0.dylib文件，赋值一份到Cycript.lib下，改名为libruby.2.0.0.dylib。</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cycript.org&quot;&gt;cycript&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;一、执行可执行文件报错&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到当前的ruby的如：&lt;code&gt;libruby.3.0.dylib&lt;/code&gt;文件，赋值一份到&lt;code&gt;Cycript.lib&lt;/code&gt;下，改名为&lt;code&gt;libruby.2.0.0.dylib&lt;/code&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;control + d 退出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;二、环境配置&lt;/h1&gt;
&lt;p&gt;如果配置了MonkeyDev的环境变量的话，因为自带了cycript，所以在任意路径下&lt;code&gt;cycript&lt;/code&gt;都是有效的。&lt;/p&gt;
&lt;h1&gt;三、玩法&lt;/h1&gt;
&lt;p&gt;可以通过端口进入当前进程，访问内存环境。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;运行App&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方便起见使用Monkey项目运行&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;获取设备ip&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;通过命令行链接&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cycript -r 192.168.0.102:6666
cy#
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;进入了cycript状态，就可以直接输入代码进行调试了&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Cycrupt用法</title><link>https://blog.lab-lawliet.app/posts/article-21-cycrupt-2fa7807445010fb3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-21-cycrupt-2fa7807445010fb3/</guid><description>1</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、动态调试&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ee6a760993dd4797.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我通过Cycript修改了微信语言选择按钮的文案。&lt;/p&gt;
&lt;h2&gt;1.1 choose&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cy# choose(UILabel)
[#&quot;&amp;lt;UIButtonLabel: 0x111f48ca0; frame = (70 12.6667; 37 22); text = &apos;\xe7\x99\xbb\xe5\xbd\x95&apos;; opaque = NO; userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280c9b700&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;UIButtonLabel: 0x111f674f0; frame = (70 12.6667; 37 22); text = &apos;\xe6\xb3\xa8\xe5\x86\x8c&apos;; opaque = NO; userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280c8a530&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;UIButtonLabel: 0x111f78250; frame = (15 16; 58 17); text = &apos;\xe7\xae\x80\xe4\xbd\x93\xe4\xb8\xad\xe6\x96\x87&apos;; opaque = NO; userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280c8aa30&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;MMUILabel: 0x111f89350; baseClass = UILabel; frame = (0 0; 0 36); userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280cf1ef0&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;MMUILabel: 0x111f8c750; baseClass = UILabel; frame = (0 0; 0 36); userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280cf2ad0&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;UILabel: 0x111f8e430; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280cf3890&amp;gt;&amp;gt;&quot;,#&quot;&amp;lt;UILabel: 0x111c36770; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = &amp;lt;_UILabelLayer: 0x280cc9720&amp;gt;&amp;gt;&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.2 设置值&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cy# #0x111f78250.text = @&quot;Ryukie&quot;
@&quot;Ryukie&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;二、高级用法&lt;/h1&gt;
&lt;h2&gt;2.1 cy封装&lt;/h2&gt;
&lt;h3&gt;APPID&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cy# APPID
@&quot;cn.Ryukie.Sama.Hanoi&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;pvcs&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cy# pvcs()
&quot;&amp;lt;MMUINavigationController 0x1120d5a00&amp;gt;, state: appeared, view: &amp;lt;UILayoutContainerView 0x111c0e060&amp;gt;\n   | &amp;lt;WCAccountLoginFirstViewController 0x11292d400&amp;gt;, state: appeared, view: &amp;lt;UIView 0x111c1c680&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这些其实都是封装好的一些.cy文件。再拷贝进包中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2.2 自定义cy&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;➜  Cy touch Ryukie.cy
➜  Cy vim Ryukie.cy
➜  Cy cat Ryukie.cy
sum = function(a,b) {
	return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;a. 添加到工程中&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ff645f26ab4bb266.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;b. 添加Copy&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ee2883cac1077fa7.webp&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;c. 运行工程，链接cy&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;➜  Cy sh cyWifiLogin.sh // 这里是封装了链接的脚本
cy# @import Ryukie
{}
cy# sum(1,3)
4
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Logos</title><link>https://blog.lab-lawliet.app/posts/article-22-logos-b577ff96b17fdac3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-22-logos-b577ff96b17fdac3/</guid><description>Wiki</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://iphonedev.wiki/index.php/Logos&quot; alt=&quot;Wiki&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>应用砸壳</title><link>https://blog.lab-lawliet.app/posts/article-23-34ee7481103d23e6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-23-34ee7481103d23e6/</guid><description>这里为了方便起见提供了几个好用的脚本：</description><pubDate>Sat, 22 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、frida-ios-dump &lt;a href=&quot;https://github.com/AloneMonkey/frida-ios-dump&quot;&gt;🔗&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;1.1 电脑上安装frida&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Clone到本地先&lt;/li&gt;
&lt;li&gt;cd进目录&lt;/li&gt;
&lt;li&gt;执行&lt;code&gt;sudo pip install -r requirements.txt --upgrade&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2 手机上安装frida&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;添加源&lt;code&gt;build.frida.re&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;搜索并安装frida
&lt;ul&gt;
&lt;li&gt;两端版本要一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.3 手机连上电脑进行端口映射&lt;/h2&gt;
&lt;p&gt;这里为了方便起见提供了几个好用的脚本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sh iPhoneUSBProxy.sh&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/content-assets/619a65ade4ad2d44.sh&quot;&gt;🔗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;进行端口映射（22-2222）&lt;/li&gt;
&lt;li&gt;2222是frida默认的&lt;/li&gt;
&lt;li&gt;22是系统默认的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sh iPhoneLocalLogin.sh&lt;/code&gt;(另开一个终端页)
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/content-assets/0cc4b955b2bdc68d.sh&quot;&gt;🔗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;USB链接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/archive/?category=%E9%80%86%E5%90%91&quot;&gt;ptython-client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意电脑上只能插一个手机。血的教训啊。有一天本地登录一直报错，搞了一个小时各种搜索，头要炸了，拔掉手机去上个厕所。拔下数据线的一瞬间感觉世界都亮了，我才意识到插了两个手机。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.4 查看可砸壳应用&lt;/h2&gt;
&lt;p&gt;进入Clone下来的&lt;code&gt;frida-ios-dump&lt;/code&gt;目录下&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./dump.py -l&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  frida-ios-dump git:(master) ./dump.py -l
 PID  Name           Identifier
----  -------------  -----------------------------
5853  Cydia          com.saurik.Cydia
6661  人人视频           com.rrds.rrdianshi
6609  设置             com.apple.Preferences
   -  App Store      com.apple.AppStore
   -  Azizi          com.inke.yaamar
   -  DobbyDemo      cn.Ryukie.Sama.Loviary
   -  Elic           com.ryukie.sama.minesweeper
   -  FaceTime通话     com.apple.facetime
   -  Facebook       com.facebook.Facebook
   -  Google Maps    com.google.Maps
   -  LogiDemo       cn.Ryukie.Sama.LogiDemo
   -  Safari浏览器      com.apple.mobilesafari
   -  Shadowrocket   com.liguangming.Shadowrocket
   -  Substitute     com.ex.substitute.settings
   -  Twitter        com.atebits.Tweetie2
   -  Watch          com.apple.Bridge
   -  iTunes Store   com.apple.MobileStore
   -  unc0verSobani  com.ryukie.sama.Sobani
   -  信息             com.apple.MobileSMS
   -  健康             com.apple.Health
   -  图书             com.apple.iBooks
   -  地图             com.apple.Maps
   -  备忘录            com.apple.mobilenotes
   -  天气             com.apple.weather
   -  家庭             com.apple.Home
   -  微信             cn.Ryukie.Sama.Hanoi
   -  快捷指令           com.apple.shortcuts
   -  指南针            com.apple.compass
   -  提示             com.apple.tips
   -  提醒事项           com.apple.reminders
   -  播客             com.apple.podcasts
   -  文件             com.apple.DocumentsApp
   -  日历             com.apple.mobilecal
   -  时钟             com.apple.mobiletimer
   -  查找             com.apple.findmy
   -  梦见账本           com.ryukie.sama.ledger.Ledger
   -  测距仪            com.apple.measure
   -  照片             com.apple.mobileslideshow
   -  电话             com.apple.mobilephone
   -  百度地图           com.baidu.map
   -  相机             com.apple.camera
   -  股市             com.apple.stocks
   -  腾讯地图           com.tencent.sosomap
   -  视频             com.apple.tv
   -  计算器            com.apple.calculator
   -  语音备忘录          com.apple.VoiceMemos
   -  通讯录            com.apple.MobileAddressBook
   -  邮件             com.apple.mobilemail
   -  钉钉             com.laiwang.DingTalk
   -  钱包             com.apple.Passbook
   -  音乐             com.apple.Music
   -  高德地图           com.autonavi.amap
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1.4 执行砸壳脚本&lt;/h2&gt;
&lt;p&gt;Run &lt;code&gt;./dump.py&lt;/code&gt; &lt;code&gt;Display name&lt;/code&gt; or &lt;code&gt;Bundle identifier&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;成功后就可以在&lt;code&gt;frida-ios-dump&lt;/code&gt;中找到砸壳的包了！&lt;/p&gt;
&lt;h1&gt;二、Python版本问题&lt;/h1&gt;
&lt;p&gt;升级系统为MacOS11.4后就一直dump不出来了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  frida-ios-dump git:(master) ./dump.py -l
/Library/Python/2.7/site-packages/paramiko/transport.py:33: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
  from cryptography.hazmat.backends import default_backend
Traceback (most recent call last):
  File &quot;./dump.py&quot;, line 20, in &amp;lt;module&amp;gt;
    import paramiko
  File &quot;/Library/Python/2.7/site-packages/paramiko/__init__.py&quot;, line 22, in &amp;lt;module&amp;gt;
    from paramiko.transport import SecurityOptions, Transport
  File &quot;/Library/Python/2.7/site-packages/paramiko/transport.py&quot;, line 129, in &amp;lt;module&amp;gt;
    class Transport(threading.Thread, ClosingContextManager):
  File &quot;/Library/Python/2.7/site-packages/paramiko/transport.py&quot;, line 190, in Transport
    if KexCurve25519.is_available():
  File &quot;/Library/Python/2.7/site-packages/paramiko/kex_curve25519.py&quot;, line 30, in is_available
    X25519PrivateKey.generate()
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/primitives/asymmetric/x25519.py&quot;, line 39, in generate
    from cryptography.hazmat.backends.openssl.backend import backend
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/backends/openssl/__init__.py&quot;, line 7, in &amp;lt;module&amp;gt;
    from cryptography.hazmat.backends.openssl.backend import backend
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/backends/openssl/backend.py&quot;, line 117, in &amp;lt;module&amp;gt;
    from cryptography.hazmat.bindings.openssl import binding
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/bindings/openssl/binding.py&quot;, line 14, in &amp;lt;module&amp;gt;
    from cryptography.hazmat.bindings._openssl import ffi, lib
ImportError: dlopen(/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/bindings/_openssl.so, 2): Symbol not found: _DTLS_client_method
  Referenced from: /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/bindings/_openssl.so
  Expected in: flat namespace
 in /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/cryptography/hazmat/bindings/_openssl.so
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py&quot;, line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File &quot;/Library/Python/2.7/site-packages/paramiko/transport.py&quot;, line 120, in _join_lingering_threads
    for thr in _active_threads:
TypeError: &apos;NoneType&apos; object is not iterable
Error in sys.exitfunc:
Traceback (most recent call last):
  File &quot;/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py&quot;, line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File &quot;/Library/Python/2.7/site-packages/paramiko/transport.py&quot;, line 120, in _join_lingering_threads
    for thr in _active_threads:
TypeError: &apos;NoneType&apos; object is not iterable
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;解决&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;前提是安装了Python3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;切换用Python3环境&lt;/p&gt;
&lt;p&gt;&lt;code&gt;➜  frida-ios-dump git:(master) sudo pip3 install -r requirements.txt --upgrade&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>Inlinehook:Dobby</title><link>https://blog.lab-lawliet.app/posts/article-15-inlinehook-dobby-9c3d93c148997051/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-15-inlinehook-dobby-9c3d93c148997051/</guid><description>GitHub:Dobby</description><pubDate>Fri, 21 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、Dobby&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jmpews/Dobby&quot;&gt;GitHub:Dobby&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;按文档生成一个Xcode工程。获取&lt;code&gt;DobbyX.framework&lt;/code&gt;。一个跨平台的用于Hook静态语言的框架。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd Dobby &amp;amp;&amp;amp; mkdir build_for_ios_arm64 &amp;amp;&amp;amp; cd build_for_ios_arm64
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS=&quot;arm64&quot; -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、将framework集成进工程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将上面获取到的&lt;code&gt;DobbyX.framework&lt;/code&gt;，拖进项目。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Build Phases&lt;/code&gt; 中添加 &lt;code&gt;Copy file&lt;/code&gt;，添加&lt;code&gt;DobbyX.framework&lt;/code&gt;，类型为&lt;code&gt;framework&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、使用&lt;/h2&gt;
&lt;h3&gt;3.1 int DobbyHook(void *address, void *replace_call, void **origin_call);&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;address
&lt;ul&gt;
&lt;li&gt;需要hook的函数地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;replace_call
&lt;ul&gt;
&lt;li&gt;新函数地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;origin_call
&lt;ul&gt;
&lt;li&gt;原始函数指针的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;由于静态语言没有符号，直接通过地址来进行hook&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;3.2 试验一下&lt;/h3&gt;
&lt;p&gt;下面就是简单的使用方式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;ViewController.h&quot;
#import &quot;DobbyX.framework/Headers/dobby.h&quot;

@interface ViewController ()

@end

@implementation ViewController

int test_sum(int a, int b) {
    return a + b;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    
    DobbyHook(test_sum, new_sum, (void *)&amp;amp;old_sum_p);
}

/// 原始函数指针
static int (*old_sum_p)(int a, int b);
// 新函数地址
int new_sum(int a, int b) {
    int right = old_sum_p(a, b);
    NSLog(@&quot;Hook了！正确的结果是：%d&quot;,right);
    return right + 1;
}

- (void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event {
    NSLog(@&quot;结果：%d&quot;,test_sum(1, 1));
}

@end

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;控制台输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag &quot;-DDOBBY_DEBUG=OFF&quot;


[*] [DobbyHook] Initialize at 0x104d31d90
[*] [trampoline] Generate trampoline buffer 0x104d31d90 -&amp;gt; 0x104d31e74
[*] [insn relocate] origin 0x104d31d90 - 12
[*] [insn relocate] relocated 0x10d2c0000 - 32
[*] [intercept routing] Active patch 0x104d31d90
2021-05-19 08:33:00.081796+0800 DobbyDemo[4207:1785806] Hook了！正确的结果是：2
2021-05-19 08:33:00.081972+0800 DobbyDemo[4207:1785806] 结果：3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、流程分析&lt;/h2&gt;
&lt;h3&gt;4.1 汇编分析 test_sum&lt;/h3&gt;
&lt;h4&gt;a. 原始函数的汇编&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DobbyDemo`test_sum:
    0x104a71dc4 &amp;lt;+0&amp;gt;:  sub    sp, sp, #0x10             ; =0x10 
    0x104a71dc8 &amp;lt;+4&amp;gt;:  str    w0, [sp, #0xc]
    0x104a71dcc &amp;lt;+8&amp;gt;:  str    w1, [sp, #0x8]
-&amp;gt;  0x104a71dd0 &amp;lt;+12&amp;gt;: ldr    w8, [sp, #0xc]
    0x104a71dd4 &amp;lt;+16&amp;gt;: ldr    w9, [sp, #0x8]
    0x104a71dd8 &amp;lt;+20&amp;gt;: add    w0, w8, w9
    0x104a71ddc &amp;lt;+24&amp;gt;: add    sp, sp, #0x10             ; =0x10 
    0x104a71de0 &amp;lt;+28&amp;gt;: ret    
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b. Hook后的汇编&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DobbyDemo`test_sum:
    0x104c7dd90 &amp;lt;+0&amp;gt;:  adrp   x17, 0
    0x104c7dd94 &amp;lt;+4&amp;gt;:  add    x17, x17, #0xe74          ; =0xe74 
    0x104c7dd98 &amp;lt;+8&amp;gt;:  br     x17
-&amp;gt;  0x104c7dd9c &amp;lt;+12&amp;gt;: ldr    w8, [sp, #0xc]
    0x104c7dda0 &amp;lt;+16&amp;gt;: ldr    w9, [sp, #0x8]
    0x104c7dda4 &amp;lt;+20&amp;gt;: add    w0, w8, w9
    0x104c7dda8 &amp;lt;+24&amp;gt;: add    sp, sp, #0x10             ; =0x10 
    0x104c7ddac &amp;lt;+28&amp;gt;: ret    
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c. 分析&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;我们可以发现，前面三行指令不一样了。下面我们从调用方法断点开始调试&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;touchbegin&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
    0x102b7df1c &amp;lt;+76&amp;gt;:  bl     0x102b7e554               ; symbol stub for: objc_storeStrong
    0x102b7df20 &amp;lt;+80&amp;gt;:  mov    w10, #0x1
    0x102b7df24 &amp;lt;+84&amp;gt;:  mov    x0, x10
    0x102b7df28 &amp;lt;+88&amp;gt;:  mov    x1, x10
-&amp;gt;  0x102b7df2c &amp;lt;+92&amp;gt;:  bl     0x102b7dd90               ; test_sum at ViewController.m:17
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;test_sum&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DobbyDemo`test_sum:
    0x102b7dd90 &amp;lt;+0&amp;gt;:  adrp   x17, 0
    0x102b7dd94 &amp;lt;+4&amp;gt;:  add    x17, x17, #0xe74          ; =0xe74 
-&amp;gt;  0x102b7dd98 &amp;lt;+8&amp;gt;:  br     x17
    0x102b7dd9c &amp;lt;+12&amp;gt;: ldr    w8, [sp, #0xc]
    0x102b7dda0 &amp;lt;+16&amp;gt;: ldr    w9, [sp, #0x8]
    0x102b7dda4 &amp;lt;+20&amp;gt;: add    w0, w8, w9
    0x102b7dda8 &amp;lt;+24&amp;gt;: add    sp, sp, #0x10             ; =0x10 
    0x102b7ddac &amp;lt;+28&amp;gt;: ret    
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(lldb) register read x17
     x17 = 0x0000000102b7de74  DobbyDemo`new_sum at ViewController.m:31
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这里发现x17已经编程了&lt;code&gt;new_sum&lt;/code&gt;了！跳到&lt;code&gt;new_sum&lt;/code&gt;中了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;在调用原函数指针的时候才会继续执行&lt;code&gt;-&amp;gt;&lt;/code&gt;下面的指令&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;d. 原理&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;实际上是对一段Text进行了替换（内存中的）！&lt;/li&gt;
&lt;li&gt;Text段无法修改！&lt;/li&gt;
&lt;li&gt;原始MachO无变化！&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五、将符号替换成地址&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在实战的时候，更多情况下我们并不能像上面的Demo中一样简单的拿到方法进行替换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;所以我们需要将符号替换城地址！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5.1 定位函数地址&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;  0x1025f1f3c &amp;lt;+84&amp;gt;:  mov    x0, x10
    0x1025f1f40 &amp;lt;+88&amp;gt;:  mov    x1, x10
    0x1025f1f44 &amp;lt;+92&amp;gt;:  bl     0x1025f1dc4               ; test_sum at ViewController.m:17
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;a.函数地址&lt;/h4&gt;
&lt;p&gt;通过汇编断点我们看到真实的函数地址为：&lt;code&gt;0x1025f1dc4&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;b.MachO中的偏移量&lt;/h4&gt;
&lt;p&gt;我们可以通过函数地址定位到它在&lt;code&gt;MachO&lt;/code&gt;中的偏移量&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;ASLR&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) image list
[  0] 6B1471FE-409A-37F0-93ED-86FDFDEE421E 0x00000001025ec000 /Users/RyukieW/Library/Developer/Xcode/DerivedData/DobbyDemo-fyyzoosecpnxmcakyfrtrxocpawl/Build/Products/Debug-iphoneos/DobbyDemo.app/DobbyDemo 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;offset&lt;/strong&gt;&lt;/em&gt;
0x1025f1dc4 - 0x00000001025ec000 = 0x5DC4&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f358e530cc729fbb.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;MachOView&lt;/code&gt;验证我们的计算没有问题。&lt;/p&gt;
&lt;h3&gt;5.2 代码实现上面的逆向流程&lt;/h3&gt;
&lt;h4&gt;a.通过代码获取ASLR&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;uintptr_t aslr = _dyld_get_image_vmaddr_slide(0);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;你可能会遇到下面的问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Implicit declaration of function &apos;_dyld_get_image_vmaddr_slide&apos; is invalid in C99&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;#import &amp;lt;mach-o/dyld.h&amp;gt;&lt;/strong&gt;&lt;/em&gt; 即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;b.获得函数在MachO中的Offset&lt;/h4&gt;
&lt;p&gt;通过断点调试计算出偏移量，记录下来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static uintptr_t addrSum = 0x100005D40;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c. 通过地址进行hook&lt;/h4&gt;
&lt;p&gt;完整HOOK代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static uintptr_t addrSum = 0x100005D40;
uintptr_t aslr = _dyld_get_image_vmaddr_slide(0);
addrSum += aslr;
DobbyHook((void *)addrSum, new_sum, (void *)&amp;amp;old_sum_p);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;d. 结果&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;2021-05-21 11:30:34.318180+0800 DobbyDemo[5521:2140764] Hook了！正确的结果是：2
2021-05-21 11:30:34.318525+0800 DobbyDemo[5521:2140764] 结果：3
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.3 注意点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;对于自己的工程，每次修改代码，都会导致MachO内的偏移量改变，所以调试的时候需要注意
&lt;ul&gt;
&lt;li&gt;调整变量内容不会变&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对于IPA包，同一个IPA包是不会变的，但是不同版本的会不一样
&lt;ul&gt;
&lt;li&gt;所以插件的话只能针对某个特定版本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.4 参考&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzU5OTcyMzc5MQ==&amp;amp;mid=2247486601&amp;amp;idx=1&amp;amp;sn=62ccc070f715b3c13e30becb081f998a&amp;amp;chksm=feb1d6e9c9c65fff7cf5f5290299b4e5abbcd2b4f6d53d605601d79d9d800ce82e5e0c98e48b&amp;amp;scene=21#wechat_redirect&quot;&gt;利用纯地址进行HOOK&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>fishhook原理</title><link>https://blog.lab-lawliet.app/posts/article-11-fishhook-c591ed58e9fe4e72/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-11-fishhook-c591ed58e9fe4e72/</guid><description>GitHub-Fishhook</description><pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;参考：&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/fishhook&quot;&gt;GitHub-Fishhook&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;为源码添加注释&lt;/h1&gt;
&lt;p&gt;源码代码量不大，建议直接对照源码查看。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name Facebook nor the names of its contributors may be used to
//     endorse or promote products derived from this software without specific
//     prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot;
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include &quot;fishhook.h&quot;

#include &amp;lt;dlfcn.h&amp;gt;
#include &amp;lt;stdbool.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;
#include &amp;lt;mach/mach.h&amp;gt;
#include &amp;lt;mach/vm_map.h&amp;gt;
#include &amp;lt;mach/vm_region.h&amp;gt;
#include &amp;lt;mach-o/dyld.h&amp;gt;
#include &amp;lt;mach-o/loader.h&amp;gt;
#include &amp;lt;mach-o/nlist.h&amp;gt;

#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif

#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST  &quot;__DATA_CONST&quot;
#endif

struct rebindings_entry {
  struct rebinding *rebindings;
  size_t rebindings_nel;
  struct rebindings_entry *next;
};

static struct rebindings_entry *_rebindings_head;

static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel) {
  struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
  if (!new_entry) {
    return -1;
  }
  new_entry-&amp;gt;rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
  if (!new_entry-&amp;gt;rebindings) {
    free(new_entry);
    return -1;
  }
  memcpy(new_entry-&amp;gt;rebindings, rebindings, sizeof(struct rebinding) * nel);
  new_entry-&amp;gt;rebindings_nel = nel;
  new_entry-&amp;gt;next = *rebindings_head;
  *rebindings_head = new_entry;
  return 0;
}

static vm_prot_t get_protection(void *sectionStart) {
  mach_port_t task = mach_task_self();
  vm_size_t size = 0;
  vm_address_t address = (vm_address_t)sectionStart;
  memory_object_name_t object;
#if __LP64__
  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
  vm_region_basic_info_data_64_t info;
  kern_return_t info_ret = vm_region_64(
      task, &amp;amp;address, &amp;amp;size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&amp;amp;info, &amp;amp;count, &amp;amp;object);
#else
  mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;
  vm_region_basic_info_data_t info;
  kern_return_t info_ret = vm_region(task, &amp;amp;address, &amp;amp;size, VM_REGION_BASIC_INFO, (vm_region_info_t)&amp;amp;info, &amp;amp;count, &amp;amp;object);
#endif
  if (info_ret == KERN_SUCCESS) {
    return info.protection;
  } else {
    return VM_PROT_READ;
  }
}


/// 终点：执行重绑定
/// @param rebindings 链表头
/// @param section -
/// @param slide aslr
/// @param symtab 符号表
/// @param strtab String表
/// @param indirect_symtab 间接符号表
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                           section_t *section,
                                           intptr_t slide,
                                           nlist_t *symtab,
                                           char *strtab,
                                           uint32_t *indirect_symtab) {
  const bool isDataConst = strcmp(section-&amp;gt;segname, SEG_DATA_CONST) == 0;
    
    /**
     nl_symbol_ptr 和 la_symbol_ptresction 中的 reserved1 字段指明对应的 indirect symbol table 起始的 index
     */
  uint32_t *indirect_symbol_indices = indirect_symtab + section-&amp;gt;reserved1;
    
    /**
     slide + section-&amp;gt;addr 就是符号对应的存放函数的数组，相应的 __nl_symbol_ptr 和 __la_symbol_ptr 相应的函数指针都在这里面了，所以可以去寻找到函数的地址
     */
  void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section-&amp;gt;addr);
  vm_prot_t oldProtection = VM_PROT_READ;
  if (isDataConst) {
    oldProtection = get_protection(rebindings);
    mprotect(indirect_symbol_bindings, section-&amp;gt;size, PROT_READ | PROT_WRITE);
  }
    // 遍历section里面的每一个符号
  for (uint i = 0; i &amp;lt; section-&amp;gt;size / sizeof(void *); i++) {
      // 找到符号在Indirect symbol table 表中的值
      // 读取 indirect table 中的数据
    uint32_t symtab_index = indirect_symbol_indices[i];
    if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
        symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {
      continue;
    }
      // 以 symtab_index 作为下标，访问 symbol table
    uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
      // 获取到 symbol_name
    char *symbol_name = strtab + strtab_offset;
      // 判断是否函数的名称是否有两个字符，因为函数前面有个&quot;_&quot;，所以方法名至少两个
    bool symbol_name_longer_than_1 = symbol_name[0] &amp;amp;&amp;amp; symbol_name[1];
      // 遍历最初的链表，来进行hook
    struct rebindings_entry *cur = rebindings;
    while (cur) {
      for (uint j = 0; j &amp;lt; cur-&amp;gt;rebindings_nel; j++) {
          
          // 判断 symbol_name[1]开始，两个函数的名字是否一致
        if (symbol_name_longer_than_1 &amp;amp;&amp;amp;
            strcmp(&amp;amp;symbol_name[1], cur-&amp;gt;rebindings[j].name) == 0) {
            
            // 判断replaced的地址不为null以及我方法的实现和rebindings[j].replacement 的方法不一致
          if (cur-&amp;gt;rebindings[j].replaced != NULL &amp;amp;&amp;amp;
              indirect_symbol_bindings[i] != cur-&amp;gt;rebindings[j].replacement) {
              // 让 rebindings[j].replaced 保存 indirect_symbol_bindings[i] 的函数地址
            *(cur-&amp;gt;rebindings[j].replaced) = indirect_symbol_bindings[i];
          }
            // 将替换后的方法给原先的方法，也就是替换内容为自定义函数地址
          indirect_symbol_bindings[i] = cur-&amp;gt;rebindings[j].replacement;
          goto symbol_loop;
        }
      }
      cur = cur-&amp;gt;next;
    }
  symbol_loop:;
  }
  if (isDataConst) {
    int protection = 0;
    if (oldProtection &amp;amp; VM_PROT_READ) {
      protection |= PROT_READ;
    }
    if (oldProtection &amp;amp; VM_PROT_WRITE) {
      protection |= PROT_WRITE;
    }
    if (oldProtection &amp;amp; VM_PROT_EXECUTE) {
      protection |= PROT_EXEC;
    }
    mprotect(indirect_symbol_bindings, section-&amp;gt;size, protection);
  }
}


/// 绑定的核心函数
/// @param rebindings 链表头
/// @param header image的头
/// @param slide ASLR
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
    /**
     dladdr() 这个函数就是在程序里面找header
     
     可确定指定的address，是否位于构成进程的地址空间的其中一个加载模块（可执行文件或共享库）内。
     
     如果某个地址，位于在其上面映射加载模块的基址，和为该加载模块映射的最高虚拟地址之间（包括两端），则认为改地址在加载模块的范围内。
     
     如果某个加载模块复合这个条件，则会搜索其动态符号表，以查找指定的address最接近的符号。
     最接近符号是指其值等于，或最为接近但小于指定address的符号
     
     如果指定的address不再其中一个模块的加载范围内的话，返回0，且不修改Dl_info结构的内容。否则将返回一个非零值，同时设置Dl_info结构的字段。
     
     如果在包含address的加载模块内，找不到其值小于或等于address的符号，则dlisname、dli_saddr和dli_size字段将设置为0，dli_bind字段设置为STB_LOCAL，dli_typs字段设置为STT_NOTYPE。
     
     */
  Dl_info info;
  if (dladdr(header, &amp;amp;info) == 0) {
    return;
  }
// 准备从macho里面去找
  segment_command_t *cur_seg_cmd;
  segment_command_t *linkedit_segment = NULL;
  struct symtab_command* symtab_cmd = NULL;
  struct dysymtab_command* dysymtab_cmd = NULL;
// 跳过header的大小，找到loadCommand
  uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
  for (uint i = 0; i &amp;lt; header-&amp;gt;ncmds; i++, cur += cur_seg_cmd-&amp;gt;cmdsize) {
    cur_seg_cmd = (segment_command_t *)cur;
    if (cur_seg_cmd-&amp;gt;cmd == LC_SEGMENT_ARCH_DEPENDENT) {
      if (strcmp(cur_seg_cmd-&amp;gt;segname, SEG_LINKEDIT) == 0) {
        linkedit_segment = cur_seg_cmd;
      }
    } else if (cur_seg_cmd-&amp;gt;cmd == LC_SYMTAB) {
      symtab_cmd = (struct symtab_command*)cur_seg_cmd;
    } else if (cur_seg_cmd-&amp;gt;cmd == LC_DYSYMTAB) {
      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
    }
  }
// 如果刚才获取的，有一项为空就直接返回
  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
      !dysymtab_cmd-&amp;gt;nindirectsyms) {
    return;
  }

    // 链接时程序的基址 = __LINKEDEDIT.VM_Address - __LINKEDIT.File_Offset + silde
  // Find base symbol/string table addresses
  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment-&amp;gt;vmaddr - linkedit_segment-&amp;gt;fileoff;
  nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd-&amp;gt;symoff);
  char *strtab = (char *)(linkedit_base + symtab_cmd-&amp;gt;stroff);

    // 间接（动态）符号表的地址 = 基址 + 符号表偏移量
  // Get indirect symbol table (array of uint32_t indices into symbol table)
  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd-&amp;gt;indirectsymoff);

  cur = (uintptr_t)header + sizeof(mach_header_t);
  for (uint i = 0; i &amp;lt; header-&amp;gt;ncmds; i++, cur += cur_seg_cmd-&amp;gt;cmdsize) {
    cur_seg_cmd = (segment_command_t *)cur;
    if (cur_seg_cmd-&amp;gt;cmd == LC_SEGMENT_ARCH_DEPENDENT) {
        // 找到Data段
      if (strcmp(cur_seg_cmd-&amp;gt;segname, SEG_DATA) != 0 &amp;amp;&amp;amp;
          strcmp(cur_seg_cmd-&amp;gt;segname, SEG_DATA_CONST) != 0) {
        continue;
      }
      for (uint j = 0; j &amp;lt; cur_seg_cmd-&amp;gt;nsects; j++) {
        section_t *sect =
          (section_t *)(cur + sizeof(segment_command_t)) + j;
          // 找懒加载表
        if ((sect-&amp;gt;flags &amp;amp; SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
          // 非懒加载表
        if ((sect-&amp;gt;flags &amp;amp; SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
      }
    }
  }
}

static void _rebind_symbols_for_image(const struct mach_header *header,
                                      intptr_t slide) {
    rebind_symbols_for_image(_rebindings_head, header, slide);
}

// 指定image进行hook
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel) {
    struct rebindings_entry *rebindings_head = NULL;
    int retval = prepend_rebindings(&amp;amp;rebindings_head, rebindings, rebindings_nel);
    // 这里就少了遍历所有image的操作了
    rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
    if (rebindings_head) {
      free(rebindings_head-&amp;gt;rebindings);
    }
    free(rebindings_head);
    return retval;
}

/// 未指定image进行hook
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
    // prepend_rebindings 的函数会将整个 rebings 数组添加到 _rebindings_head 这个链表的头部
    // fishhook 采用链表的方式来存储每一次调用 rebind_sysbols传入的参数，每次调用，就会在链表的头部插入一个节点，链表的头部是：_rebindings_head
  int retval = prepend_rebindings(&amp;amp;_rebindings_head, rebindings, rebindings_nel);
    // 根据上面的 prepend_rebindings 来做判断，如果小于0的话，直接返回一个错误码回去
  if (retval &amp;lt; 0) {
    return retval;
  }
  // If this was the first call, register callback for image additions (which is also invoked for
  // existing images, otherwise, just run on existing images
    // 判断 _rebindings_head -&amp;gt; next是否为空，来判断是不是第一次调用
  if (!_rebindings_head-&amp;gt;next) {
      // 第一次调用的话调用 _dyld_register_func_for_add_image 注册监听方法
      // 已经被 dyld加载的image会立即加入这个回调
      // 之后的image会在dyld装在的时候触发回调
    _dyld_register_func_for_add_image(_rebind_symbols_for_image);
  } else {
      // 遍历已经加载的image，进行hook
    uint32_t c = _dyld_image_count();
    for (uint32_t i = 0; i &amp;lt; c; i++) {
      _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
    }
  }
  return retval;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;rebind_symbols
&lt;ul&gt;
&lt;li&gt;rebindings数组添加到链表&lt;/li&gt;
&lt;li&gt;根据链表判断是否第一次调用，这么做的目的是保证注册方法只会调用一次。两种情况都是为了回调&lt;code&gt;_rebind_symbols_for_image&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;第一次
&lt;ul&gt;
&lt;li&gt;利用&lt;code&gt;_dyld_register_func_for_add_image&lt;/code&gt;注册监听方法：_rebind_symbols_for_image&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不是第一次
&lt;ul&gt;
&lt;li&gt;循环遍历已经加载的iamge，进行 &lt;code&gt;_rebind_symbols_for_image&lt;/code&gt; 回调&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_rebind_symbols_for_image
&lt;ul&gt;
&lt;li&gt;第一步
&lt;ul&gt;
&lt;li&gt;拿到三张表在内存中的地址
&lt;ul&gt;
&lt;li&gt;符号表地址：&lt;code&gt;symtab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;字符串表地址：&lt;code&gt;strtab&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态（间接）符号表地址：&lt;code&gt;indirect_symtab&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第二步
&lt;ul&gt;
&lt;li&gt;找懒加载和非懒加载表&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第三部
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;perform_rebinding_with_section&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;perform_rebinding_with_section&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;1.得到&lt;code&gt;indirect_symn=bol_bindings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.遍历间接符号表，找到符号&lt;/li&gt;
&lt;li&gt;3.判断是否是需要hook的&lt;/li&gt;
&lt;li&gt;4.保存函数指针，然后替换懒加载符号表中的函数地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;完成Hook。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>反HOOK防护（一）：基于Fishhook</title><link>https://blog.lab-lawliet.app/posts/article-13-hook-fishhook-0607b5969858f5c5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-13-hook-fishhook-0607b5969858f5c5/</guid><description>效果：</description><pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、用Fishhook进行防护&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;我们需要防护别人的Hook，但是保留自己的Hook。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.1 在哪里防护&lt;/h2&gt;
&lt;h3&gt;a.Framework里&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;工程中的&lt;code&gt;Load&lt;/code&gt;在&lt;code&gt;Framework&lt;/code&gt;里的&lt;code&gt;Load&lt;/code&gt;之后调用&lt;/li&gt;
&lt;li&gt;别人注入的Hook代码的&lt;code&gt;Load&lt;/code&gt;在&lt;code&gt;Framework&lt;/code&gt;之后，工程之前
&lt;ul&gt;
&lt;li&gt;有点疑问，这里的&lt;code&gt;Framework&lt;/code&gt;是动态库么？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2 操作一下&lt;/h2&gt;
&lt;h3&gt;a. 创建一个Framework&lt;/h3&gt;
&lt;h3&gt;b. 引入fishhook、objc/message.h&lt;/h3&gt;
&lt;h3&gt;c. 在Framework中创建一个类，防护 &lt;code&gt;method_exchangeImplementations&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (void)load {
    struct rebinding exchange;
    
    exchange.name = &quot;method_exchangeImplementations&quot;;
    exchange.replacement = my_axchange;
    exchange.replaced = (void *)&amp;amp;sysExchangePoint;
    
    struct rebinding bds[] = { exchange };
    
    rebind_symbols(bds, 1);
}

// 保存原函数的指针，这个可以暴露给自己使用
void (*sysExchangePoint)(Method _Nonnull methA, Method _Nonnull methB);

void my_axchange(Method _Nonnull methA, Method _Nonnull methB) {
    NSLog(@&quot;⚠️检测到了Hook！&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;d. 注入&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/posts/article-04-xcode-b0b94e7dcd79440f/&quot;&gt;04.利用Xcode进行重签名与调试&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/posts/article-05-dylib-6c01a01c74a67e1a/&quot;&gt;05.dylib注入&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;效果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/fc0f306b68d3c046.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.3 缺点&lt;/h2&gt;
&lt;h3&gt;a. 字符串&lt;/h3&gt;
&lt;p&gt;因为我们用到了&lt;code&gt;method_exchangeImplementations&lt;/code&gt;这样一个字符串，一旦被逆向工程师找到，很容易定位到防护代码。&lt;/p&gt;
&lt;h3&gt;b. 时间&lt;/h3&gt;
&lt;p&gt;这个防护方案是基于调用时间来进行防护的，如果找到&lt;code&gt;fishhook&lt;/code&gt;的相关调用，进行相关操作，会被破解掉。&lt;/p&gt;
&lt;h3&gt;c. get set IMP 没处理&lt;/h3&gt;
</content:encoded></item><item><title>反HOOK防护（二）：Monkey</title><link>https://blog.lab-lawliet.app/posts/article-14-hook-monkey-ba14c1300625875a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-14-hook-monkey-ba14c1300625875a/</guid><description>安装完成后，重启Xcode，就可以看到有个MonkeyDev</description><pubDate>Mon, 17 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、安装：MonkeyDev&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/AloneMonkey/MonkeyDev&quot;&gt;GitHub-MonkeyDev&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装完成后，重启Xcode，就可以看到有个&lt;code&gt;MonkeyDev&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d69ace9a895c0364.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;二、重签名&lt;/h1&gt;
&lt;p&gt;创建一个&lt;code&gt;MonkeyApp&lt;/code&gt;，我们看到目录下有个&lt;code&gt;TargetApp&lt;/code&gt;，将需要重签名的&lt;code&gt;ipa包&lt;/code&gt;或者&lt;code&gt;app文件&lt;/code&gt;放进去。
并将Demo给为自己的一个可用的BundleID，运行即可！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f184214957a90d7d.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;三、Hook：Logos语法&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;http://iphonedevwiki.net/index.php/Logos&quot;&gt;Logos&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e363a7cd4256a52.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到这个文件，没有高亮。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d10ab46ee70991b.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在这里改一下类型为&lt;code&gt;ObjectiveC++Source&lt;/code&gt;，切换一下文件就可以了。&lt;/p&gt;
&lt;h2&gt;3.1 试着Hook上一篇中我们做了防护的项目&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这里我们准备Hook&lt;code&gt;ViewController&lt;/code&gt;的&lt;code&gt;actionA&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;a. 将编译出的app文件放进TargetApp目录下&lt;/h3&gt;
&lt;h3&gt;b. 用Logos语法写Hook代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;UIKit/UIKit.h&amp;gt;

%hook ViewController

- (void)actionA:(id)sender {
    NSLog(@&quot;action-A-(Hooked!)&quot;);
}

%end

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就这么简单！&lt;/p&gt;
&lt;h3&gt;c. 运行查看Log&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2021-05-16 23:10:24.635341+0800 AntiHook[3859:1526960] ⚠️检测到了Hook！
2021-05-16 23:10:24.635477+0800 AntiHook[3859:1526960] ⚠️检测到了Hook！
2021-05-16 23:10:24.635519+0800 AntiHook[3859:1526960] [AntiAntiDebug Init]
               🎉!!！congratulations!!！🎉
👍----------------insert dylib success----------------👍
[MethodTrace] 
📚--------------------OCMethodTrace(Usage)-------------------📚
https://github.com/omxcodec/OCMethodTrace/blob/master/README.md
📚--------------------OCMethodTrace(Usage)-------------------📚
[MethodTrace] logLevel: 0: logWhen: 0 traceFlag: 2 traceObject: 0(未指定类)
[MethodTrace] Method Trace is disabled

Download cycript(https://cydia.saurik.com/api/latest/3) then run: ./cycript -r 192.168.0.102:6666

2021-05-16 23:10:24.915464+0800 AntiHook[3859:1526960] result: &amp;lt;UIApplication: 0x1017100d0&amp;gt;
2021-05-16 23:10:24.996174+0800 AntiHook[3859:1526960]  INFO: Reveal Server started (Protocol Version 43).
2021-05-16 23:10:33.341774+0800 AntiHook[3859:1526960] action-A-(Hooked!)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然我们的防护代码检测到了Hook，但是依然成功了。&lt;/p&gt;
&lt;h3&gt;d. 思考&lt;/h3&gt;
&lt;p&gt;这里我们使用&lt;a href=&quot;/posts/article-13-hook-fishhook-0607b5969858f5c5/&quot;&gt;反HOOK防护（一）&lt;/a&gt;中的技术去进行防护，并没有成功防护到&lt;code&gt;Monkey&lt;/code&gt;的Hook。我们大概可以推测出：它使用的是Set和Get进行的Hook！&lt;/p&gt;
&lt;p&gt;下面我们针对Set Get进行一下防护验证我们的想法！&lt;/p&gt;
&lt;h1&gt;四、Set/Get Hook防护&lt;/h1&gt;
&lt;p&gt;这里我们简单修改下防护代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (void)load {
    struct rebinding exchange;
    exchange.name = &quot;method_exchangeImplementations&quot;;
    exchange.replacement = my_exchange;
    exchange.replaced = (void *)&amp;amp;sysExchangePoint;
    
    struct rebinding get;
    get.name = &quot;method_getImplementation&quot;;
    get.replacement = my_get;
    get.replaced = (void *)&amp;amp;sysExchangePoint;

    struct rebinding set;
    set.name = &quot;method_setImplementation&quot;;
    set.replacement = my_set;
    set.replaced = (void *)&amp;amp;sysExchangePoint;
    
    struct rebinding bds[] = { exchange, get, set };
    
    rebind_symbols(bds, 3);
}

// 保存原函数的指针，这个可以暴露给自己使用
void (*sysExchangePoint)(Method _Nonnull methA, Method _Nonnull methB);

void my_exchange(Method _Nonnull methA, Method _Nonnull methB) {
    NSLog(@&quot;⚠️检测到了Hook！-method_exchangeImplementations&quot;);
}

void my_get(Method _Nonnull methA, Method _Nonnull methB) {
    NSLog(@&quot;⚠️检测到了Hook！-method_getImplementation&quot;);
}

void my_set(Method _Nonnull methA, Method _Nonnull methB) {
    NSLog(@&quot;⚠️检测到了Hook！-method_setImplementation&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.1 防护成果&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;2021-05-17 23:25:17.892661+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_getImplementation
2021-05-17 23:25:17.892798+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_exchangeImplementations
2021-05-17 23:25:17.892840+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_getImplementation
2021-05-17 23:25:17.892874+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_exchangeImplementations
2021-05-17 23:25:17.892912+0800 AntiHook[3999:1630972] [AntiAntiDebug Init]
               🎉!!！congratulations!!！🎉
👍----------------insert dylib success----------------👍
2021-05-17 23:25:17.914757+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_getImplementation
2021-05-17 23:25:17.914913+0800 AntiHook[3999:1630972] ⚠️检测到了Hook！-method_setImplementation
[MethodTrace] 
...
2021-05-17 23:25:26.987849+0800 AntiHook[3999:1630972] action-A
2021-05-17 23:25:27.952512+0800 AntiHook[3999:1630972] action-A
2021-05-17 23:25:28.803100+0800 AntiHook[3999:1630972] action-B
2021-05-17 23:25:29.170666+0800 AntiHook[3999:1630972] action-B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到我们的&lt;code&gt;set&lt;/code&gt;/&lt;code&gt;get&lt;/code&gt;防护代码被触发了，并且输出了点击按钮输出了正确的Log！
防护成功！也验证了我们的想法：&lt;em&gt;&lt;strong&gt;Monkey的Hook是基于Get/Set的&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>去符号与恢复符号</title><link>https://blog.lab-lawliet.app/posts/article-12-0f17c7609b8d5619/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-12-0f17c7609b8d5619/</guid><description>我们在做逆向的时候，App的符号都被去掉了，所以我们需要对符号进行符号恢复。 去符号会使应用包更小。</description><pubDate>Sun, 16 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、去符号&lt;/h1&gt;
&lt;p&gt;我们在做逆向的时候，App的符号都被去掉了，所以我们需要对符号进行符号恢复。
去符号会使应用包更小。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;动态库：保留全局符号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.1 设置去符号的类型&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/73b517c84b2eacbc.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Deployment Postprocessing&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;YES: 编译也去符号&lt;/li&gt;
&lt;li&gt;NO: 只在打包时去符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Strip Style&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;All Symbols&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;所有符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Non-Global Symbols&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;除了全局符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Debugging Symbols&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;调试符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.2 去符号前后的MachO&lt;/h2&gt;
&lt;h3&gt;a.去符号之前&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b217ed75c5b94ff8.webp&quot; alt=&quot;0&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;b.去符号之后&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a01cd940580e25be.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d700fbeb907f5c8e.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;只剩下&lt;code&gt;间接符号&lt;/code&gt;和&lt;code&gt;一个全局&lt;/code&gt;、&lt;code&gt;一个本地&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.3 函数的实现地址&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/948b5af2d61561c8.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这里是函数的实现地址
&lt;ul&gt;
&lt;li&gt;间接符号的地址这里全是0，调用的时候会先去找桩&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/article-08-hook-fab18ddbc5a66cd9/&quot;&gt;详情参考&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;去符号之前可以看到全局符和本地符号的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、符号类型&lt;/h1&gt;
&lt;h2&gt;3.1 全局符号&lt;/h2&gt;
&lt;p&gt;我们定义的基本都是全集符号&lt;/p&gt;
&lt;h2&gt;3.2 本地符号&lt;/h2&gt;
&lt;p&gt;static 是本地符号&lt;/p&gt;
&lt;h2&gt;3.3 命令查看类型&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f6b120f838289890.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;objdump --macho -t 你的macho文件名&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SymbolBind:
SYMBOL TABLE:
0000000100005f18 l     F __TEXT,__text -[ViewController viewDidLoad]
0000000100005f88 l     F __TEXT,__text -[ViewController test]
0000000100005fd0 l     F __TEXT,__text -[AppDelegate application:didFinishLaunchingWithOptions:]
000000010000604c l     F __TEXT,__text -[AppDelegate application:configurationForConnectingSceneSession:options:]
000000010000614c l     F __TEXT,__text -[AppDelegate application:didDiscardSceneSessions:]
000000010000625c l     F __TEXT,__text -[SceneDelegate scene:willConnectToSession:options:]
00000001000062f8 l     F __TEXT,__text -[SceneDelegate sceneDidDisconnect:]
0000000100006344 l     F __TEXT,__text -[SceneDelegate sceneDidBecomeActive:]
0000000100006390 l     F __TEXT,__text -[SceneDelegate sceneWillResignActive:]
00000001000063dc l     F __TEXT,__text -[SceneDelegate sceneWillEnterForeground:]
0000000100006428 l     F __TEXT,__text -[SceneDelegate sceneDidEnterBackground:]
0000000100006474 l     F __TEXT,__text -[SceneDelegate window]
0000000100006498 l     F __TEXT,__text -[SceneDelegate setWindow:]
00000001000064d4 l     F __TEXT,__text -[SceneDelegate .cxx_destruct]
00000001000080c0  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_NSObject
00000001000080c8  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UIApplicationDelegate
00000001000080d0  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UISceneDelegate
00000001000080d8  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UIWindowSceneDelegate
000000010000c068 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_ViewController
000000010000c0b0 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_ViewController
000000010000c0e8 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_ViewController
000000010000c130 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000c300 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000c320 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_NSObject
000000010000c368 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000c408 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UIApplicationDelegate
000000010000c420 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIApplicationDelegate
000000010000c938 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_UIApplicationDelegate
000000010000c950 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UIApplicationDelegate
000000010000cb00 l     O __DATA,__objc_const __OBJC_CLASS_PROTOCOLS_$_AppDelegate
000000010000cb18 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_AppDelegate
000000010000cb60 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_AppDelegate
000000010000cbb0 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_AppDelegate
000000010000cc08 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_AppDelegate
000000010000cc50 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000ce20 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000ce40 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_NSObject
000000010000ce88 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000cf28 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UISceneDelegate
000000010000cf40 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UISceneDelegate
000000010000d068 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UISceneDelegate
000000010000d0c8 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UIWindowSceneDelegate
000000010000d0e0 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWindowSceneDelegate
000000010000d160 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_UIWindowSceneDelegate
000000010000d178 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UIWindowSceneDelegate
000000010000d1a0 l     O __DATA,__objc_const __OBJC_CLASS_PROTOCOLS_$_SceneDelegate
000000010000d1b8 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_SceneDelegate
000000010000d200 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_SceneDelegate
000000010000d2e0 l     O __DATA,__objc_const __OBJC_$_INSTANCE_VARIABLES_SceneDelegate
000000010000d308 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_SceneDelegate
000000010000d360 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_SceneDelegate
000000010000d3e0 l     O __DATA,__objc_ivar _OBJC_IVAR_$_SceneDelegate._window
000000010000d4d8 l     O __DATA,__data __dyld_private
000000010000d4e0  w    O __DATA,__data __OBJC_PROTOCOL_$_NSObject
000000010000d540  w    O __DATA,__data __OBJC_PROTOCOL_$_UIApplicationDelegate
000000010000d5a0  w    O __DATA,__data __OBJC_PROTOCOL_$_UISceneDelegate
000000010000d600  w    O __DATA,__data __OBJC_PROTOCOL_$_UIWindowSceneDelegate
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* ViewController.m
0000000060a0b161      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/ViewController.o
0000000100005f18      d  *UND*
0000000100005f18      d  *UND* -[ViewController viewDidLoad]
0000000000000070      d  *UND*
0000000000000070      d  *UND*
0000000100005f88      d  *UND*
0000000100005f88      d  *UND* -[ViewController test]
000000000000002c      d  *UND*
000000000000002c      d  *UND*
0000000100005fb4      d  *UND*
0000000100005fb4      d  *UND* _testLocalFunction
000000000000001c      d  *UND*
000000000000001c      d  *UND*
000000010000c068      d  *UND* __OBJC_METACLASS_RO_$_ViewController
000000010000c0b0      d  *UND* __OBJC_$_INSTANCE_METHODS_ViewController
000000010000c0e8      d  *UND* __OBJC_CLASS_RO_$_ViewController
0000000000000000      d  *UND* _OBJC_CLASS_$_ViewController
0000000000000000      d  *UND* _OBJC_METACLASS_$_ViewController
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* AppDelegate.m
0000000060a0b161      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/AppDelegate.o
0000000100005fd0      d  *UND*
0000000100005fd0      d  *UND* -[AppDelegate application:didFinishLaunchingWithOptions:]
000000000000007c      d  *UND*
000000000000007c      d  *UND*
000000010000604c      d  *UND*
000000010000604c      d  *UND* -[AppDelegate application:configurationForConnectingSceneSession:options:]
0000000000000100      d  *UND*
0000000000000100      d  *UND*
000000010000614c      d  *UND*
000000010000614c      d  *UND* -[AppDelegate application:didDiscardSceneSessions:]
0000000000000074      d  *UND*
0000000000000074      d  *UND*
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_NSObject
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UIApplicationDelegate
000000010000c130      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000c300      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000c320      d  *UND* __OBJC_$_PROP_LIST_NSObject
000000010000c368      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000c408      d  *UND* __OBJC_$_PROTOCOL_REFS_UIApplicationDelegate
000000010000c420      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIApplicationDelegate
000000010000c938      d  *UND* __OBJC_$_PROP_LIST_UIApplicationDelegate
000000010000c950      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UIApplicationDelegate
000000010000cb00      d  *UND* __OBJC_CLASS_PROTOCOLS_$_AppDelegate
000000010000cb18      d  *UND* __OBJC_METACLASS_RO_$_AppDelegate
000000010000cb60      d  *UND* __OBJC_$_INSTANCE_METHODS_AppDelegate
000000010000cbb0      d  *UND* __OBJC_$_PROP_LIST_AppDelegate
000000010000cc08      d  *UND* __OBJC_CLASS_RO_$_AppDelegate
0000000000000000      d  *UND* _OBJC_METACLASS_$_AppDelegate
0000000000000000      d  *UND* _OBJC_CLASS_$_AppDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_NSObject
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UIApplicationDelegate
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* main.m
0000000060a0b161      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/main.o
00000001000061c0      d  *UND*
00000001000061c0      d  *UND* _main
000000000000009c      d  *UND*
000000000000009c      d  *UND*
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* SceneDelegate.m
0000000060a0b161      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/SceneDelegate.o
000000010000625c      d  *UND*
000000010000625c      d  *UND* -[SceneDelegate scene:willConnectToSession:options:]
000000000000009c      d  *UND*
000000000000009c      d  *UND*
00000001000062f8      d  *UND*
00000001000062f8      d  *UND* -[SceneDelegate sceneDidDisconnect:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006344      d  *UND*
0000000100006344      d  *UND* -[SceneDelegate sceneDidBecomeActive:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006390      d  *UND*
0000000100006390      d  *UND* -[SceneDelegate sceneWillResignActive:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
00000001000063dc      d  *UND*
00000001000063dc      d  *UND* -[SceneDelegate sceneWillEnterForeground:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006428      d  *UND*
0000000100006428      d  *UND* -[SceneDelegate sceneDidEnterBackground:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006474      d  *UND*
0000000100006474      d  *UND* -[SceneDelegate window]
0000000000000024      d  *UND*
0000000000000024      d  *UND*
0000000100006498      d  *UND*
0000000100006498      d  *UND* -[SceneDelegate setWindow:]
000000000000003c      d  *UND*
000000000000003c      d  *UND*
00000001000064d4      d  *UND*
00000001000064d4      d  *UND* -[SceneDelegate .cxx_destruct]
000000000000003c      d  *UND*
000000000000003c      d  *UND*
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UISceneDelegate
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UIWindowSceneDelegate
000000010000cc50      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000ce20      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000ce40      d  *UND* __OBJC_$_PROP_LIST_NSObject
000000010000ce88      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000cf28      d  *UND* __OBJC_$_PROTOCOL_REFS_UISceneDelegate
000000010000cf40      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UISceneDelegate
000000010000d068      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UISceneDelegate
000000010000d0c8      d  *UND* __OBJC_$_PROTOCOL_REFS_UIWindowSceneDelegate
000000010000d0e0      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWindowSceneDelegate
000000010000d160      d  *UND* __OBJC_$_PROP_LIST_UIWindowSceneDelegate
000000010000d178      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UIWindowSceneDelegate
000000010000d1a0      d  *UND* __OBJC_CLASS_PROTOCOLS_$_SceneDelegate
000000010000d1b8      d  *UND* __OBJC_METACLASS_RO_$_SceneDelegate
000000010000d200      d  *UND* __OBJC_$_INSTANCE_METHODS_SceneDelegate
000000010000d2e0      d  *UND* __OBJC_$_INSTANCE_VARIABLES_SceneDelegate
000000010000d308      d  *UND* __OBJC_$_PROP_LIST_SceneDelegate
000000010000d360      d  *UND* __OBJC_CLASS_RO_$_SceneDelegate
0000000000000000      d  *UND* _OBJC_IVAR_$_SceneDelegate._window
0000000000000000      d  *UND* _OBJC_METACLASS_$_SceneDelegate
0000000000000000      d  *UND* _OBJC_CLASS_$_SceneDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UISceneDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UIWindowSceneDelegate
0000000000000000      d  *UND*
000000010000d460 g     O __DATA,__objc_data _OBJC_CLASS_$_AppDelegate
000000010000d4b0 g     O __DATA,__objc_data _OBJC_CLASS_$_SceneDelegate
000000010000d3e8 g     O __DATA,__objc_data _OBJC_CLASS_$_ViewController
000000010000d438 g     O __DATA,__objc_data _OBJC_METACLASS_$_AppDelegate
000000010000d488 g     O __DATA,__objc_data _OBJC_METACLASS_$_SceneDelegate
000000010000d410 g     O __DATA,__objc_data _OBJC_METACLASS_$_ViewController
0000000100000000 g     F __TEXT,__text __mh_execute_header
00000001000061c0 g     F __TEXT,__text _main
0000000100005fb4 g     F __TEXT,__text _testLocalFunction
0000000000000000         *UND* _NSLog
0000000000000000         *UND* _NSStringFromClass
0000000000000000         *UND* _OBJC_CLASS_$_UIResponder
0000000000000000         *UND* _OBJC_CLASS_$_UISceneConfiguration
0000000000000000         *UND* _OBJC_CLASS_$_UIViewController
0000000000000000         *UND* _OBJC_METACLASS_$_NSObject
0000000000000000         *UND* _OBJC_METACLASS_$_UIResponder
0000000000000000         *UND* _OBJC_METACLASS_$_UIViewController
0000000000000000         *UND* _UIApplicationMain
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* __objc_empty_cache
0000000000000000         *UND* _objc_alloc
0000000000000000         *UND* _objc_autoreleasePoolPop
0000000000000000         *UND* _objc_autoreleasePoolPush
0000000000000000         *UND* _objc_autoreleaseReturnValue
0000000000000000         *UND* _objc_msgSend
0000000000000000         *UND* _objc_msgSendSuper2
0000000000000000         *UND* _objc_opt_class
0000000000000000         *UND* _objc_release
0000000000000000         *UND* _objc_retainAutoreleasedReturnValue
0000000000000000         *UND* _objc_storeStrong
0000000000000000         *UND* dyld_stub_binder
➜  11 clear
➜  11 objdump --macho -t SymbolBind
SymbolBind:
SYMBOL TABLE:
0000000100005f10 l     F __TEXT,__text -[ViewController viewDidLoad]
0000000100005f80 l     F __TEXT,__text -[ViewController test]
0000000100005fc8 l     F __TEXT,__text -[AppDelegate application:didFinishLaunchingWithOptions:]
0000000100006044 l     F __TEXT,__text -[AppDelegate application:configurationForConnectingSceneSession:options:]
0000000100006144 l     F __TEXT,__text -[AppDelegate application:didDiscardSceneSessions:]
0000000100006254 l     F __TEXT,__text -[SceneDelegate scene:willConnectToSession:options:]
00000001000062f0 l     F __TEXT,__text -[SceneDelegate sceneDidDisconnect:]
000000010000633c l     F __TEXT,__text -[SceneDelegate sceneDidBecomeActive:]
0000000100006388 l     F __TEXT,__text -[SceneDelegate sceneWillResignActive:]
00000001000063d4 l     F __TEXT,__text -[SceneDelegate sceneWillEnterForeground:]
0000000100006420 l     F __TEXT,__text -[SceneDelegate sceneDidEnterBackground:]
000000010000646c l     F __TEXT,__text -[SceneDelegate window]
0000000100006490 l     F __TEXT,__text -[SceneDelegate setWindow:]
00000001000064cc l     F __TEXT,__text -[SceneDelegate .cxx_destruct]
00000001000080c0  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_NSObject
00000001000080c8  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UIApplicationDelegate
00000001000080d0  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UISceneDelegate
00000001000080d8  w    O __DATA_CONST,__objc_protolist __OBJC_LABEL_PROTOCOL_$_UIWindowSceneDelegate
000000010000c068 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_ViewController
000000010000c0b0 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_ViewController
000000010000c0e8 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_ViewController
000000010000c130 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000c300 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000c320 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_NSObject
000000010000c368 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000c408 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UIApplicationDelegate
000000010000c420 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIApplicationDelegate
000000010000c938 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_UIApplicationDelegate
000000010000c950 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UIApplicationDelegate
000000010000cb00 l     O __DATA,__objc_const __OBJC_CLASS_PROTOCOLS_$_AppDelegate
000000010000cb18 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_AppDelegate
000000010000cb60 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_AppDelegate
000000010000cbb0 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_AppDelegate
000000010000cc08 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_AppDelegate
000000010000cc50 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000ce20 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000ce40 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_NSObject
000000010000ce88 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000cf28 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UISceneDelegate
000000010000cf40 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UISceneDelegate
000000010000d068 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UISceneDelegate
000000010000d0c8 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_REFS_UIWindowSceneDelegate
000000010000d0e0 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWindowSceneDelegate
000000010000d160 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_UIWindowSceneDelegate
000000010000d178 l     O __DATA,__objc_const __OBJC_$_PROTOCOL_METHOD_TYPES_UIWindowSceneDelegate
000000010000d1a0 l     O __DATA,__objc_const __OBJC_CLASS_PROTOCOLS_$_SceneDelegate
000000010000d1b8 l     O __DATA,__objc_const __OBJC_METACLASS_RO_$_SceneDelegate
000000010000d200 l     O __DATA,__objc_const __OBJC_$_INSTANCE_METHODS_SceneDelegate
000000010000d2e0 l     O __DATA,__objc_const __OBJC_$_INSTANCE_VARIABLES_SceneDelegate
000000010000d308 l     O __DATA,__objc_const __OBJC_$_PROP_LIST_SceneDelegate
000000010000d360 l     O __DATA,__objc_const __OBJC_CLASS_RO_$_SceneDelegate
000000010000d3e0 l     O __DATA,__objc_ivar _OBJC_IVAR_$_SceneDelegate._window
000000010000d4d8 l     O __DATA,__data __dyld_private
000000010000d4e0  w    O __DATA,__data __OBJC_PROTOCOL_$_NSObject
000000010000d540  w    O __DATA,__data __OBJC_PROTOCOL_$_UIApplicationDelegate
000000010000d5a0  w    O __DATA,__data __OBJC_PROTOCOL_$_UISceneDelegate
000000010000d600  w    O __DATA,__data __OBJC_PROTOCOL_$_UIWindowSceneDelegate
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* ViewController.m
0000000060a0b259      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/ViewController.o
0000000100005f10      d  *UND*
0000000100005f10      d  *UND* -[ViewController viewDidLoad]
0000000000000070      d  *UND*
0000000000000070      d  *UND*
0000000100005f80      d  *UND*
0000000100005f80      d  *UND* -[ViewController test]
000000000000002c      d  *UND*
000000000000002c      d  *UND*
0000000100005fac      d  *UND*
0000000100005fac      d  *UND* _testGlobalFunction
000000000000001c      d  *UND*
000000000000001c      d  *UND*
000000010000c068      d  *UND* __OBJC_METACLASS_RO_$_ViewController
000000010000c0b0      d  *UND* __OBJC_$_INSTANCE_METHODS_ViewController
000000010000c0e8      d  *UND* __OBJC_CLASS_RO_$_ViewController
0000000000000000      d  *UND* _OBJC_CLASS_$_ViewController
0000000000000000      d  *UND* _OBJC_METACLASS_$_ViewController
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* AppDelegate.m
0000000060a0b259      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/AppDelegate.o
0000000100005fc8      d  *UND*
0000000100005fc8      d  *UND* -[AppDelegate application:didFinishLaunchingWithOptions:]
000000000000007c      d  *UND*
000000000000007c      d  *UND*
0000000100006044      d  *UND*
0000000100006044      d  *UND* -[AppDelegate application:configurationForConnectingSceneSession:options:]
0000000000000100      d  *UND*
0000000000000100      d  *UND*
0000000100006144      d  *UND*
0000000100006144      d  *UND* -[AppDelegate application:didDiscardSceneSessions:]
0000000000000074      d  *UND*
0000000000000074      d  *UND*
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_NSObject
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UIApplicationDelegate
000000010000c130      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000c300      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000c320      d  *UND* __OBJC_$_PROP_LIST_NSObject
000000010000c368      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000c408      d  *UND* __OBJC_$_PROTOCOL_REFS_UIApplicationDelegate
000000010000c420      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIApplicationDelegate
000000010000c938      d  *UND* __OBJC_$_PROP_LIST_UIApplicationDelegate
000000010000c950      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UIApplicationDelegate
000000010000cb00      d  *UND* __OBJC_CLASS_PROTOCOLS_$_AppDelegate
000000010000cb18      d  *UND* __OBJC_METACLASS_RO_$_AppDelegate
000000010000cb60      d  *UND* __OBJC_$_INSTANCE_METHODS_AppDelegate
000000010000cbb0      d  *UND* __OBJC_$_PROP_LIST_AppDelegate
000000010000cc08      d  *UND* __OBJC_CLASS_RO_$_AppDelegate
0000000000000000      d  *UND* _OBJC_METACLASS_$_AppDelegate
0000000000000000      d  *UND* _OBJC_CLASS_$_AppDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_NSObject
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UIApplicationDelegate
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* main.m
0000000060a0b259      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/main.o
00000001000061b8      d  *UND*
00000001000061b8      d  *UND* _main
000000000000009c      d  *UND*
000000000000009c      d  *UND*
0000000000000000      d  *UND*
0000000000000000      d  *UND* /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/SymbolBind/SymbolBind/
0000000000000000      d  *UND* SceneDelegate.m
0000000060a0b259      d  *UND* /Users/RyukieW/Library/Developer/Xcode/DerivedData/SymbolBind-bhluvjpsukewfxgsouyiwqlmcntk/Build/Intermediates.noindex/SymbolBind.build/Debug-iphoneos/SymbolBind.build/Objects-normal/arm64/SceneDelegate.o
0000000100006254      d  *UND*
0000000100006254      d  *UND* -[SceneDelegate scene:willConnectToSession:options:]
000000000000009c      d  *UND*
000000000000009c      d  *UND*
00000001000062f0      d  *UND*
00000001000062f0      d  *UND* -[SceneDelegate sceneDidDisconnect:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
000000010000633c      d  *UND*
000000010000633c      d  *UND* -[SceneDelegate sceneDidBecomeActive:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006388      d  *UND*
0000000100006388      d  *UND* -[SceneDelegate sceneWillResignActive:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
00000001000063d4      d  *UND*
00000001000063d4      d  *UND* -[SceneDelegate sceneWillEnterForeground:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
0000000100006420      d  *UND*
0000000100006420      d  *UND* -[SceneDelegate sceneDidEnterBackground:]
000000000000004c      d  *UND*
000000000000004c      d  *UND*
000000010000646c      d  *UND*
000000010000646c      d  *UND* -[SceneDelegate window]
0000000000000024      d  *UND*
0000000000000024      d  *UND*
0000000100006490      d  *UND*
0000000100006490      d  *UND* -[SceneDelegate setWindow:]
000000000000003c      d  *UND*
000000000000003c      d  *UND*
00000001000064cc      d  *UND*
00000001000064cc      d  *UND* -[SceneDelegate .cxx_destruct]
000000000000003c      d  *UND*
000000000000003c      d  *UND*
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UISceneDelegate
0000000000000000      d  *UND* __OBJC_LABEL_PROTOCOL_$_UIWindowSceneDelegate
000000010000cc50      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_NSObject
000000010000ce20      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_NSObject
000000010000ce40      d  *UND* __OBJC_$_PROP_LIST_NSObject
000000010000ce88      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_NSObject
000000010000cf28      d  *UND* __OBJC_$_PROTOCOL_REFS_UISceneDelegate
000000010000cf40      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UISceneDelegate
000000010000d068      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UISceneDelegate
000000010000d0c8      d  *UND* __OBJC_$_PROTOCOL_REFS_UIWindowSceneDelegate
000000010000d0e0      d  *UND* __OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_UIWindowSceneDelegate
000000010000d160      d  *UND* __OBJC_$_PROP_LIST_UIWindowSceneDelegate
000000010000d178      d  *UND* __OBJC_$_PROTOCOL_METHOD_TYPES_UIWindowSceneDelegate
000000010000d1a0      d  *UND* __OBJC_CLASS_PROTOCOLS_$_SceneDelegate
000000010000d1b8      d  *UND* __OBJC_METACLASS_RO_$_SceneDelegate
000000010000d200      d  *UND* __OBJC_$_INSTANCE_METHODS_SceneDelegate
000000010000d2e0      d  *UND* __OBJC_$_INSTANCE_VARIABLES_SceneDelegate
000000010000d308      d  *UND* __OBJC_$_PROP_LIST_SceneDelegate
000000010000d360      d  *UND* __OBJC_CLASS_RO_$_SceneDelegate
0000000000000000      d  *UND* _OBJC_IVAR_$_SceneDelegate._window
0000000000000000      d  *UND* _OBJC_METACLASS_$_SceneDelegate
0000000000000000      d  *UND* _OBJC_CLASS_$_SceneDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UISceneDelegate
0000000000000000      d  *UND* __OBJC_PROTOCOL_$_UIWindowSceneDelegate
0000000000000000      d  *UND*
000000010000d460 g     O __DATA,__objc_data _OBJC_CLASS_$_AppDelegate
000000010000d4b0 g     O __DATA,__objc_data _OBJC_CLASS_$_SceneDelegate
000000010000d3e8 g     O __DATA,__objc_data _OBJC_CLASS_$_ViewController
000000010000d438 g     O __DATA,__objc_data _OBJC_METACLASS_$_AppDelegate
000000010000d488 g     O __DATA,__objc_data _OBJC_METACLASS_$_SceneDelegate
000000010000d410 g     O __DATA,__objc_data _OBJC_METACLASS_$_ViewController
0000000100000000 g     F __TEXT,__text __mh_execute_header
00000001000061b8 g     F __TEXT,__text _main
0000000100005fac g     F __TEXT,__text _testGlobalFunction
0000000000000000         *UND* _NSLog
0000000000000000         *UND* _NSStringFromClass
0000000000000000         *UND* _OBJC_CLASS_$_UIResponder
0000000000000000         *UND* _OBJC_CLASS_$_UISceneConfiguration
0000000000000000         *UND* _OBJC_CLASS_$_UIViewController
0000000000000000         *UND* _OBJC_METACLASS_$_NSObject
0000000000000000         *UND* _OBJC_METACLASS_$_UIResponder
0000000000000000         *UND* _OBJC_METACLASS_$_UIViewController
0000000000000000         *UND* _UIApplicationMain
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* __objc_empty_cache
0000000000000000         *UND* _objc_alloc
0000000000000000         *UND* _objc_autoreleasePoolPop
0000000000000000         *UND* _objc_autoreleasePoolPush
0000000000000000         *UND* _objc_autoreleaseReturnValue
0000000000000000         *UND* _objc_msgSend
0000000000000000         *UND* _objc_msgSendSuper2
0000000000000000         *UND* _objc_opt_class
0000000000000000         *UND* _objc_release
0000000000000000         *UND* _objc_retainAutoreleasedReturnValue
0000000000000000         *UND* _objc_storeStrong
0000000000000000         *UND* dyld_stub_binder
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;l&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;本地符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;g&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;全局符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;三、恢复符号表的线索&lt;/h1&gt;
&lt;p&gt;虽然符号表中没有了符号，但是在MachO中的类的方法列表中还是有的！&lt;/p&gt;
&lt;h2&gt;3.1 类名&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;TEXT&lt;/code&gt;段&lt;code&gt;__objc_classname&lt;/code&gt;中：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/10d42c46eae34be7.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;3.2 方法名&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;TEXT&lt;/code&gt;段&lt;code&gt;__objc_methname&lt;/code&gt;中：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a00c38687fd261cb.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;四、符号表恢复&lt;/h1&gt;
&lt;h2&gt;4.1 脚本&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/tobefuturer/restore-symbol&quot;&gt;GitHub: restore-symbol&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将MachO文件放入目录中&lt;/li&gt;
&lt;li&gt;执行脚本&lt;code&gt;./restore-symbol MachO文件名 -o MachO输出文件名&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;=========== Start =============
2021-05-16 15:11:40.712 restore-symbol[97858:6671327] Unknown load command: 0x00000032
Scan OC method in mach-o-file.
Scan OC method finish.
=========== Finish ============
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在用MachOView查看符号表，发现在后面已经恢复了！
&lt;ul&gt;
&lt;li&gt;严谨的说是添加了符号&lt;/li&gt;
&lt;li&gt;由于是基于Runtime的，动态派发的可以恢复。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;对比发现文件大小也增大了&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/6af766c4c43b11ca.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>深入理解动态库与静态库</title><link>https://blog.lab-lawliet.app/posts/article-09-aa5444f4d5727d58/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-09-aa5444f4d5727d58/</guid><description>静态和动态是相对编译时和运行时进行区分的</description><pubDate>Sun, 09 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、简单的区别&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;静态&lt;/code&gt;和&lt;code&gt;动态&lt;/code&gt;是相对&lt;code&gt;编译时&lt;/code&gt;和&lt;code&gt;运行时&lt;/code&gt;进行区分的&lt;/p&gt;
&lt;h2&gt;1.1 静态库&lt;/h2&gt;
&lt;p&gt;静态库在编译时会被链接进MachO中&lt;/p&gt;
&lt;p&gt;后缀一般为&lt;code&gt;.a&lt;/code&gt;/&lt;code&gt;.framework&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1.2 动态库&lt;/h2&gt;
&lt;p&gt;在程序编译时不会被链接到MachO中，而是在程序启动的时候加载动态库到内存中。其中动态库分&lt;code&gt;动态链接库&lt;/code&gt;和&lt;code&gt;动态加载库&lt;/code&gt;两种&lt;/p&gt;
&lt;p&gt;动态库文件一般为&lt;code&gt;.tbd(以前是.dylib)&lt;/code&gt;/&lt;code&gt;.framework&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;动态链接库&lt;/h3&gt;
&lt;p&gt;在编译阶段确定了依赖关系的动态库。当运行可执行文件时，如果尚未加载，则会将加载这些动态库。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;常见的动态库都是这种&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;动态加载库&lt;/h3&gt;
&lt;p&gt;编译阶段不需要指定app需要依赖哪些动态库。当运行过程中需要加载某个动态库时，就会用&lt;code&gt;dlopen&lt;/code&gt;函数动态的把库加载到内存中使用。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这种玩玩可以，上架就别想了。比通过网络如下载一个动态库，然后加载调用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.3 体积&lt;/h2&gt;
&lt;p&gt;一般来说，静态库直接拷贝进可执行文件，相较动态库对包体积的影响会大一些。&lt;/p&gt;
&lt;p&gt;静态库被多个可执行文件依赖就会被拷贝多次。&lt;/p&gt;
&lt;h2&gt;1.4 启动速度&lt;/h2&gt;
&lt;p&gt;因为动态库在应用启动的时候需要加载，所以对应用的启动速度是有一定的影响的。&lt;/p&gt;
&lt;p&gt;严谨的说，对于已经加载过的动态库已经存在于共享缓存中，那么对启动速度的影响就很小了。&lt;/p&gt;
&lt;h2&gt;1.5 各自优缺点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态库
&lt;ul&gt;
&lt;li&gt;模块化，分工合作，提高了代码的复用及核心技术的保密程度&lt;/li&gt;
&lt;li&gt;避免少量改动经常导致大量的重复编译连接&lt;/li&gt;
&lt;li&gt;也可以重用，注意不是共享使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;动态库
&lt;ul&gt;
&lt;li&gt;可以将最终可执行文件体积缩小，将整个应用程序分模块，团队合作，进行分工，影响比较小&lt;/li&gt;
&lt;li&gt;多个应用程序共享内存中得同一份库文件，节省资源&lt;/li&gt;
&lt;li&gt;可以不重新编译连接可执行程序的前提下，更新动态库文件达到更新应用程序的目的&lt;/li&gt;
&lt;li&gt;应用插件化&lt;/li&gt;
&lt;li&gt;软件版本实时模块升级&lt;/li&gt;
&lt;li&gt;在其它大部分平台上，动态库都可以用于不同应用间共享， 共享可执行文件，这就大大节省了内存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在 &lt;code&gt;iOS8&lt;/code&gt; 之前，苹果不允许第三方框架使用动态方式加载，从 iOS8 开始允许开发者有条件地创建和使用动态框架，这种框架叫做 &lt;code&gt;Cocoa Touch Framework&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;虽然同样是动态框架，但是和系统 &lt;code&gt;framework&lt;/code&gt; 不同，苹果系统专属的 &lt;code&gt;framework&lt;/code&gt; 是共享的（如 &lt;code&gt;UIKit&lt;/code&gt;），使用 &lt;code&gt;Cocoa Touch Framework&lt;/code&gt; 制作的动态库在打包和提交 app 时会被放到 &lt;code&gt;app main bundle&lt;/code&gt; 的根目录中，运行在&lt;code&gt;沙盒&lt;/code&gt;里，而不是系统中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;也就是说，不同的 app 就算使用了同样的 framework，但还是会有多份的框架被分别签名、打包和加载。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;不过 iOS8 上开放了 &lt;code&gt;App Extension&lt;/code&gt; 功能，可以为一个应用创建插件，这样主 app 和插件之间共享动态库还是可行的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;二、什么是framework&lt;/h1&gt;
&lt;p&gt;Framework 是 Cocoa/Cocoa Touch 程序中使用的一种资源打包方式，可以将代码文件、头文件、资源文件、说明文档等集中在一起，方便开发者使用。一般如果是静态 Framework 的话，资源打包进 Framework 是读取不了的。静态 Framework 和 .a 文件都是编译进可执行文件里面的。只有动态 Framework 能在 .app 下面的 Framework 文件夹下看到，并读取 .framework 里的资源文件。&lt;/p&gt;
&lt;p&gt;Cocoa/Cocoa Touch 开发框架本身提供了大量的 Framework，比如 Foundation.framework/UIKit.framework 等。需要注意的是，这些 Framework 无一例外都是动态库。&lt;/p&gt;
&lt;p&gt;平时用的第三方 SDK 的 Framework 都是静态库，真正的动态库是上不了 AppStore（iOS8 之后能上 AppStore，因为 App Extension，需要动态库支持)。&lt;/p&gt;
&lt;h2&gt;2.1 结构&lt;/h2&gt;
&lt;h3&gt;Headers&lt;/h3&gt;
&lt;p&gt;表示暴露的头文件，一般都会有一个和 Framework 同名的 .h 文件，在创建 Framework 的时候文件夹里也会默认生成这样一个文件。有这个和 Framework 同名的 .h 文件 @import 导入库的时候编译器才能找到这个库。&lt;/p&gt;
&lt;h3&gt;info.plist&lt;/h3&gt;
&lt;p&gt;主要就是这个 Framework 的一些配置信息。&lt;/p&gt;
&lt;h3&gt;Modules&lt;/h3&gt;
&lt;p&gt;这个文件夹里有个 module.modulemap 文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;framework module DynamicFramework {
  umbrella header &quot;DynamicFramework.h&quot;

  export *

  module * { export * }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里面有这样一句 umbrella header &quot;DynamicFramework.h&quot;，umbrella 有保护伞、庇护的意思。&lt;/p&gt;
&lt;p&gt;也就是说 Headers 中暴露的 DynamicFramework.h 文件被放在 umbrella 雨伞下保护起来了，所以我们需要将其他的所有需要暴露的 .h 文件放到 DynamicFramework.h 文件中保护起来，不然会出现警告。&lt;/p&gt;
&lt;p&gt;@import 的时候也只能找到 umbrella 雨伞下保护起来的 .h 文件。&lt;/p&gt;
&lt;h3&gt;二进制文件（Unix 可执行文件）&lt;/h3&gt;
&lt;p&gt;这个就是你源码编译而成的二进制文件，主要的执行代码就在这个里面。&lt;/p&gt;
&lt;h3&gt;.bundle 文件&lt;/h3&gt;
&lt;p&gt;如果我们在 Build Phases -&amp;gt; Copy Bundle Resources 里加入 .bundle 文件，那么创建出来的 .Framework 里就会有这个 .bundle 的资源文件夹。&lt;/p&gt;
&lt;h2&gt;2.2 资源文件&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CocoaPods&lt;/code&gt; 如何生成 &lt;code&gt;Framework&lt;/code&gt; 的资源文件？&lt;/p&gt;
&lt;p&gt;我们能看到用 &lt;code&gt;cocoapods&lt;/code&gt; 创建 &lt;code&gt;Framework&lt;/code&gt; &lt;code&gt;的时候，Framework&lt;/code&gt; 里面有一个 &lt;code&gt;.bundle&lt;/code&gt; 文件，跟 &lt;code&gt;Framework&lt;/code&gt; 同级目录里也有一个 &lt;code&gt;.bundle&lt;/code&gt;文件。这两个文件其实是一样的。&lt;/p&gt;
&lt;p&gt;那这两个 &lt;code&gt;.bundle&lt;/code&gt; 是怎么来的呢？我们能看到用 &lt;code&gt;use_frameworks!&lt;/code&gt; 生成的 pod 里面，pods 这个 PROJECT 下面会为每一个 &lt;code&gt;pod&lt;/code&gt; 生成一个 &lt;code&gt;target。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/b754ba00f8f505ef.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么如果这个 &lt;code&gt;pod&lt;/code&gt; 有资源文件的话，就会有一个叫 &lt;code&gt;xxx-bundleName&lt;/code&gt; 的 &lt;code&gt;target&lt;/code&gt;，最后这个 &lt;code&gt;target&lt;/code&gt; 生成的就是 &lt;code&gt;bundleName.bundle&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/a2e3274698a01552.webp&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 xxx 的 target 的&lt;code&gt;Build Phases -&amp;gt; Copy Bundle Resources&lt;/code&gt;里加入这个 .bundle，在 Framework 里面就会生成这样一个 bundle。&lt;/p&gt;
&lt;p&gt;在 xxx 的 target 的&lt;code&gt;Build Phases -&amp;gt; Target Dependencies&lt;/code&gt;里加入这个&lt;code&gt; target：xxx-bundleName&lt;/code&gt;，就会在 Framework 的同级目录里生成这样一个 bundle。&lt;/p&gt;
&lt;p&gt;静态 Framework 里不需要加入资源文件。一般资源打包进静态 Framework 是读取不了的。&lt;/p&gt;
&lt;p&gt;静态 Framework 和 .a 文件都是编译进可执行文件里面的。只有动态 Framework 能在 .app 的 Framework 文件夹下看到，并读取 .framework 里的资源文件。&lt;/p&gt;
&lt;p&gt;你可以用 &lt;code&gt;NSBundle * bundle = [[NSBundle mainBundle] bundlePath];&lt;/code&gt; 得到 &lt;code&gt;.app&lt;/code&gt; 目录，如果是动态库你能在 &lt;code&gt;Framework&lt;/code&gt; 目录下看到这个动态库以及动态库里面资源文件。&lt;/p&gt;
&lt;p&gt;然后你只要用 &lt;code&gt;NSBundle * bundle = [NSBundle bundleForClass:&amp;lt;#ClassFromFramework#&amp;gt;];&lt;/code&gt; 得到这个动态库的路径就能读取到里面的资源了。但是如果是静态库的话，因为编译进了可执行文件里面，你也就没办法读到这个静态库了，你能看到 .app 下的 Framework 目录为空。&lt;/p&gt;
&lt;h2&gt;2.3 Embedded Framework&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;报错 Reason: image not found&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果直接在工程里使用创建的动态库时候会出现此错误，需要在工程的 General 里的 Embedded Binaries 添加这个动态库才能使用。&lt;/p&gt;
&lt;p&gt;因为创建的这个动态库其实也不能给其他程序使用的，而你的 App Extension 和 APP 之间是需要使用这个动态库的。这个动态库可以 App Extension 和 APP 之间共用一份（App 和 Extension 的 Bundle 是共享的），因此苹果又把这种 Framework 称为 Embedded Framework。&lt;/p&gt;
&lt;h1&gt;三、CocoaPods&lt;/h1&gt;
&lt;p&gt;在使用 &lt;code&gt;CocoaPods&lt;/code&gt; 的时候在 Podfile 里加入 &lt;code&gt;use_frameworks!&lt;/code&gt;，那么你在编译的时候就会默认帮你生成动态库，我们能看到每个源码 Pod 都会在 Pods 工程下面生成一个对应的动态库 &lt;code&gt;Framework&lt;/code&gt; 的 &lt;code&gt;target，我们能在这个&lt;/code&gt; &lt;code&gt;target&lt;/code&gt; 的 &lt;code&gt;Build Settings -&amp;gt; Mach-O Type&lt;/code&gt; 看到默认设置是 &lt;code&gt;Dynamic Library&lt;/code&gt;，也就是会生成一个动态 &lt;code&gt;Framework&lt;/code&gt;，我们能在 &lt;code&gt;Products&lt;/code&gt; 下面看到每一个 Pod 对应生成的动态库。&lt;/p&gt;
&lt;p&gt;这些生成的动态库将链接到主项目给主工程使用，但是我们上面说过动态库需要在主工程 &lt;code&gt;target&lt;/code&gt; 的 &lt;code&gt;General -&amp;gt; Embedded Binaries&lt;/code&gt; 中添加才能使用，而我们并没有在 &lt;code&gt;Embedded Binaries&lt;/code&gt; 中看到这些动态库。那这是怎么回事呢，其实是 &lt;code&gt;cocoapods&lt;/code&gt; 已经执行了脚本把这些动态库嵌入到了 &lt;code&gt;.app&lt;/code&gt; 的 &lt;code&gt;Framework&lt;/code&gt; 目录下，相当于在 &lt;code&gt;Embedded Binaries&lt;/code&gt; 加入了这些动态库。我们能在主工程 &lt;code&gt;target&lt;/code&gt; 的 &lt;code&gt;Build Phase -&amp;gt; Embed Pods Frameworks&lt;/code&gt; 里看到执行的脚本。&lt;/p&gt;
&lt;h1&gt;参考&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/dins/p/ios-jing-tai-ku-he-dong-tai-ku.html&quot;&gt;iOS 静态库和动态库&lt;/a&gt;
&lt;a href=&quot;https://freelf.me/%E5%88%9D%E8%AF%86iOS%E4%B8%AD%E7%9A%84%E5%8A%A8%E6%80%81%E5%BA%93%E5%92%8C%E9%9D%99%E6%80%81%E5%BA%93&quot;&gt;初识iOS中的动态库和静态库&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>dyld</title><link>https://blog.lab-lawliet.app/posts/article-07-dyld-daf3648a657e8fae/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-07-dyld-daf3648a657e8fae/</guid><description>我们新建一个项目，在main函数起始处下个断点。</description><pubDate>Wed, 05 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://opensource.apple.com/tarballs/dyld/&quot;&gt;DYLD源码&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;一、验证&lt;/h1&gt;
&lt;h2&gt;1.1 在main函数处下断点&lt;/h2&gt;
&lt;p&gt;我们新建一个项目，在main函数起始处下个断点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/abe05db2b911823d.webp&quot; alt=&quot;dyld&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看堆栈信息&lt;code&gt;bt&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) bt
* thread #1, queue = &apos;com.apple.main-thread&apos;, stop reason = breakpoint 1.1
  * frame #0: 0x0000000100d9a190 DyleDemo`main(argc=1, argv=0x000000016f06b8e0) at main.m:12:16
    frame #1: 0x00000001afbb48f0 libdyld.dylib`start + 4
(lldb) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很明显，这里的堆栈信息不足以供我们进行分析。我们需要在更早的地方下断点。&lt;/p&gt;
&lt;h2&gt;1.2 在+load下断点&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/dbdcdbc8b8f07619.webp&quot; alt=&quot;dyld&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) bt
* thread #1, queue = &apos;com.apple.main-thread&apos;, stop reason = breakpoint 2.1
  * frame #0: 0x0000000100911e78 DyleDemo`+[ViewController load](self=ViewController, _cmd=&quot;load&quot;) at ViewController.m:18:1
    frame #1: 0x00000001afadf35c libobjc.A.dylib`load_images + 984
    frame #2: 0x0000000100aca190 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 448
    frame #3: 0x0000000100ada0d8 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&amp;amp;, unsigned int, char const*, ImageLoader::InitializerTimingList&amp;amp;, ImageLoader::UninitedUpwards&amp;amp;) + 512
    frame #4: 0x0000000100ad8520 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&amp;amp;, unsigned int, ImageLoader::InitializerTimingList&amp;amp;, ImageLoader::UninitedUpwards&amp;amp;) + 184
    frame #5: 0x0000000100ad85e8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&amp;amp;, ImageLoader::InitializerTimingList&amp;amp;) + 92
    frame #6: 0x0000000100aca658 dyld`dyld::initializeMainExecutable() + 216
    frame #7: 0x0000000100aceeb0 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4400
    frame #8: 0x0000000100ac9208 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
    frame #9: 0x0000000100ac9038 dyld`_dyld_start + 56
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为load方法在main之前就已经执行了，在这里我们可以看到更丰富的堆栈信息。&lt;/p&gt;
&lt;h2&gt;1.3 一些关键流程&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;跟具体的可以结合&lt;code&gt;dyld&lt;/code&gt;源码进行了解&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.3.1 dyld start&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;rebaseDyld&lt;/code&gt; 重定位
调用 &lt;code&gt;main&lt;/code&gt; dyld的核心逻辑&lt;/p&gt;
&lt;h3&gt;1.3.2 dyld main&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Return address of main() in target program which __dyld_start jumps to&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;a. 加载动态库共享缓存&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;何为共享缓存？UIKit、ARKit等系统库就是在共享缓存中的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;三种情况的加载&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仅加载到当前进程（设置不放入共享缓存）&lt;/li&gt;
&lt;li&gt;已经加载了共享缓存&lt;/li&gt;
&lt;li&gt;第一次加载共享缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;b. dyld3 ClosureMode(闭包模式)&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;iOS11 之后引入了dyld3和闭包模式
闭包模式加载速度更快效率更高。
iOS13 之后Framework和三方库也会通过闭包模式加载&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;先去缓存中找&lt;code&gt;mainClosure&lt;/code&gt;
没找到就创建一个&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;launchWithClosure
&lt;ul&gt;
&lt;li&gt;使用上面的&lt;code&gt;mainClosure&lt;/code&gt;启动&lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;并返回&lt;code&gt;main&lt;/code&gt;函数地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、流程总结&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;程序执行从_dyld_start开始&lt;/li&gt;
&lt;li&gt;进入dyld_main函数&lt;/li&gt;
&lt;li&gt;配置环境变量以及rebase_dyld&lt;/li&gt;
&lt;li&gt;加载共享缓存
&lt;ul&gt;
&lt;li&gt;系统的动态库都在这里了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dyld2/dyld(ClosureMode)&lt;code&gt;决定以哪种模式继续进行&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;实例化主程序&lt;code&gt;ImageLoaderMachO&lt;/code&gt;，加入到&lt;code&gt;allImages&lt;/code&gt;中&lt;/li&gt;
&lt;li&gt;到此共享缓存加载完毕，设置版本化的dylib覆盖
&lt;ul&gt;
&lt;li&gt;Now that shared cache is loaded, setup an versioned dylib overrides&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;加载插入的动态库
&lt;ul&gt;
&lt;li&gt;越狱插件在这里加入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;记录插入的库的数量，以便统一搜索将先查看插入的库，然后是main，然后是其他。
&lt;ul&gt;
&lt;li&gt;record count of inserted libraries so that a flat search will look at inserted libraries, then main, then others.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;链接主程序
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;绑定符号（非懒加载、弱符号）等&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;链接所有插入的库
&lt;ul&gt;
&lt;li&gt;链接主可执行文件后执行此操作，以使插入的dylib（例如libSystem）不在程序使用的dylib的前面&lt;/li&gt;
&lt;li&gt;只有插入的库可以插入。绑定所有插入的库后，注册插入信息，以便链接工作&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;绑定并通知&lt;code&gt;主程序可执行文件&lt;/code&gt;，现在插入的已被注册&lt;/li&gt;
&lt;li&gt;绑定并通知&lt;code&gt;现在已插入的库&lt;/code&gt;已插入的Image已被注册&lt;/li&gt;
&lt;li&gt;执行所有初始化方法：&lt;code&gt;ImageLoader&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;初始化所有插入的库、运行主要可执行文件的初始化程序及其依赖&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageLoader&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runInitializers&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;processInitializers&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ecursiveInitialization&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;notifySingle&lt;/code&gt;:
&lt;ul&gt;
&lt;li&gt;此函数执行一个回调，此回调是&lt;code&gt;_objc_init&lt;/code&gt;初始化时服饰的一个函数&lt;code&gt;load_images&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;load_images&lt;/code&gt;里执行&lt;code&gt;class_load_metgods&lt;/code&gt;函数
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;class_load_metgods&lt;/code&gt;里调用&lt;code&gt;call_class_loads&lt;/code&gt;函数：循环调用各个类的&lt;code&gt;load&lt;/code&gt;函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;doModInitFunction&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;内部会调用全局C++对象的构造函数&lt;code&gt;__attribute__((constructor))&lt;/code&gt;的C函数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通知任何监视过程此过程即将进入main()&lt;/li&gt;
&lt;li&gt;查找主要可执行文件的指针并&lt;code&gt;return&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;三、主程序中的Load先执行还是framework中的先执行？分类呢？子类呢？&lt;/h1&gt;
&lt;h2&gt;3.1 创建一个Demo，创建两个Framework。&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/d697e60a5cebd3ec.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main函数内添加Log&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, char * argv[]) {
    printf(&quot;Main函数&quot;);
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;添加C++构造函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;__attribute__((constructor)) void funcA() {
    printf(__FUNCTION__);
    printf(&quot;\n&quot;);
}

__attribute__((constructor)) void funcB() {
    printf(__FUNCTION__);
    printf(&quot;\n&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Frameworks中添加Log&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1948d0bc52d5b12c.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运行看Log&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 19:59:09.301905+0800 DyleDemo[34074:1711187] TestSecond-TestClassSecond-Load
2021-05-09 19:59:09.302965+0800 DyleDemo[34074:1711187] TestF-TestClass-Load
2021-05-09 19:59:09.303008+0800 DyleDemo[34074:1711187] ViewController-Load
2021-05-09 19:59:09.303027+0800 DyleDemo[34074:1711187] SomeClass-Load
funcA
funcB
2021-05-09 19:59:09.303178+0800 DyleDemo[34074:1711187] Main函数

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;调整一下Framework的链接顺序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/86d42fc054037c4e.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;再看下下log&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 20:02:42.713300+0800 DyleDemo[34120:1713030] TestSecond-TestClassSecond-Load
2021-05-09 20:02:42.713925+0800 DyleDemo[34120:1713030] TestF-TestClass-Load
2021-05-09 20:02:42.713961+0800 DyleDemo[34120:1713030] ViewController-Load
2021-05-09 20:02:42.713983+0800 DyleDemo[34120:1713030] SomeClass-Load
funcA
funcB
2021-05-09 20:02:42.714103+0800 DyleDemo[34120:1713030] Main函数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;Frameworks(有多个的话按照链接顺序来) -&amp;gt; 主程序内的Load -&amp;gt; C++构造函数 -&amp;gt; Main&lt;/p&gt;
&lt;h2&gt;3.2 我们再加两个分类玩玩吧！&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Log如下&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 20:14:38.455892+0800 DyleDemo[34267:1718526] TestSecond-TestClassSecond-Load
2021-05-09 20:14:38.456556+0800 DyleDemo[34267:1718526] TestF-TestClass-Load
2021-05-09 20:14:38.456594+0800 DyleDemo[34267:1718526] ViewController-Load
2021-05-09 20:14:38.456615+0800 DyleDemo[34267:1718526] SomeClass-Load
2021-05-09 20:14:38.456633+0800 DyleDemo[34267:1718526] UIViewController+Test Load
2021-05-09 20:14:38.456651+0800 DyleDemo[34267:1718526] UIView+Test Load
funcA
funcB
2021-05-09 20:14:38.456782+0800 DyleDemo[34267:1718526] Main函数
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当前的顺序是这样的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2708c9abe2f3449e.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调整一下顺序能让分类提前加载Load么？我们试一下！&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e8cf363c5ff8150e.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 20:20:04.294895+0800 DyleDemo[34337:1721873] TestSecond-TestClassSecond-Load
2021-05-09 20:20:04.295441+0800 DyleDemo[34337:1721873] TestF-TestClass-Load
2021-05-09 20:20:04.295476+0800 DyleDemo[34337:1721873] ViewController-Load
2021-05-09 20:20:04.295495+0800 DyleDemo[34337:1721873] SomeClass-Load
2021-05-09 20:20:04.295511+0800 DyleDemo[34337:1721873] UIView+Test Load
2021-05-09 20:20:04.295526+0800 DyleDemo[34337:1721873] UIViewController+Test Load
funcA
funcB
2021-05-09 20:20:04.295640+0800 DyleDemo[34337:1721873] Main函数

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;项目内的分类在最后调用Load，在C++构造函数之前&lt;/p&gt;
&lt;h2&gt;3.3 Framework中的分类呢？&lt;/h2&gt;
&lt;p&gt;我们在两个Framework中分别添加一个分类&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/65c933a4eb32fc2a.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log如下&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 20:22:41.298458+0800 DyleDemo[34371:1723177] TestSecond-TestClassSecond-Load
2021-05-09 20:22:41.299001+0800 DyleDemo[34371:1723177] TestSecond-TestClassSecond+Cate-Load
2021-05-09 20:22:41.299032+0800 DyleDemo[34371:1723177] TestF-TestClass-Load
2021-05-09 20:22:41.299048+0800 DyleDemo[34371:1723177] TestF-TestClass+Cate-Load
2021-05-09 20:22:41.299076+0800 DyleDemo[34371:1723177] ViewController-Load
2021-05-09 20:22:41.299093+0800 DyleDemo[34371:1723177] SomeClass-Load
2021-05-09 20:22:41.299109+0800 DyleDemo[34371:1723177] UIView+Test Load
2021-05-09 20:22:41.299145+0800 DyleDemo[34371:1723177] UIViewController+Test Load
funcA
funcB
2021-05-09 20:22:41.299451+0800 DyleDemo[34371:1723177] Main函数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;和在主工程中类似，每个Framework内部的分类在Framework最后调用Load。&lt;/p&gt;
&lt;h2&gt;3.4 再来个子类看下&lt;/h2&gt;
&lt;p&gt;这次我们在Framework中建一个子类看看&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/2922f76f251c2769.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;2021-05-09 20:31:31.124655+0800 DyleDemo[34472:1726758] TestSecond-TestClassSecond-Load
2021-05-09 20:31:31.125259+0800 DyleDemo[34472:1726758] TestSecond-TestClassSecond+Cate-Load
2021-05-09 20:31:31.125288+0800 DyleDemo[34472:1726758] TestF-TestClass-Load
2021-05-09 20:31:31.125306+0800 DyleDemo[34472:1726758] TestF-TestSubClass-Load
2021-05-09 20:31:31.125323+0800 DyleDemo[34472:1726758] TestF-TestClass+Cate-Load
2021-05-09 20:31:31.125350+0800 DyleDemo[34472:1726758] ViewController-Load
2021-05-09 20:31:31.125367+0800 DyleDemo[34472:1726758] SomeClass-Load
2021-05-09 20:31:31.125427+0800 DyleDemo[34472:1726758] UIView+Test Load
2021-05-09 20:31:31.125575+0800 DyleDemo[34472:1726758] UIViewController+Test Load
funcA
funcB
2021-05-09 20:31:31.125796+0800 DyleDemo[34472:1726758] Main函数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;子类的Load先于分类&lt;/p&gt;
</content:encoded></item><item><title>Hook</title><link>https://blog.lab-lawliet.app/posts/article-08-hook-fab18ddbc5a66cd9/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-08-hook-fab18ddbc5a66cd9/</guid><description>fishhook-Github</description><pubDate>Wed, 05 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、常见的Hook技术&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Method Swizzle
&lt;ul&gt;
&lt;li&gt;利用OC的Runtime特性，动态改变SEL和IMP的对应关系，达到OC方法调用流程的改变。主要用于OC方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;fishhook
&lt;ul&gt;
&lt;li&gt;Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理，通过懒加载和非懒加载两个表的指针达到C函数Hook的目的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cydia Substrate
&lt;ul&gt;
&lt;li&gt;主要针对OC方法、C函数以及对函数地址进行Hook操作。不经针对iOS，安卓也可以用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、fishhook&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/facebook/fishhook&quot;&gt;fishhook-Github&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先回顾一下OC的动态特性
&lt;ul&gt;
&lt;li&gt;并不是直接调用方法的实现地址，而是通过一张selector和imp的映射表去找&lt;/li&gt;
&lt;li&gt;静态语言如C，是直接通过地址访问
&lt;ul&gt;
&lt;li&gt;但外部C函数调用是通过符号绑定来的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.1 rebind_symbols&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/*
 * For each rebinding in rebindings, rebinds references to external, indirect
 * symbols with the specified name to instead point at replacement for each
 * image in the calling process as well as for all future images that are loaded
 * by the process. If rebind_functions is called more than once, the symbols to
 * rebind are added to the existing list of rebindings, and if a given symbol
 * is rebound more than once, the later rebinding will take precedence.
 */
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数列表：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要Hook的rebinding数组&lt;/li&gt;
&lt;li&gt;长度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.2 rebinding&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/*
 * A structure representing a particular intended rebinding from a symbol
 * name to its replacement
 */
struct rebinding {
  const char *name;
  void *replacement;
  void **replaced;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;name&lt;/code&gt; 需要Hook的函数名称，C字符串
&lt;code&gt;replacement&lt;/code&gt; 新函数的地址
&lt;code&gt;replaced&lt;/code&gt; 原始函数的指针&lt;/p&gt;
&lt;h2&gt;2.3 使用 rebind_symbols hook NSLog&lt;/h2&gt;
&lt;p&gt;通过查看接口我们需要创建一些数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor blackColor];
    // 目标去Hook系统的NSLog函数
    
    struct rebinding nslog;
    nslog.name = &quot;NSLog&quot;;
    nslog.replacement = hook_NSLog;
    // 保存NSLog函数的指针
    nslog.replaced = (void *)&amp;amp;sys_NSLog;
    
    struct rebinding bds[] = {nslog};
    
    rebind_symbols(bds, 1);
}

/// 旧函数的指针
static void (*sys_NSLog)(NSString *format, ...);

// 新函数
void hook_NSLog(NSString *format, ...) {
    format = [format stringByAppendingString:@&quot;\n🌈&quot;];
    // 再调用系统的
    sys_NSLog(format);
}

- (void)touchesBegan:(NSSet&amp;lt;UITouch *&amp;gt; *)touches withEvent:(UIEvent *)event {
    NSLog(@&quot;RyukieLog&quot;);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2021-05-05 15:51:44.230666+0800 Hook[1650:239418] RyukieLog
🌈
2021-05-05 15:51:44.430579+0800 Hook[1650:239418] RyukieLog
🌈
2021-05-05 15:51:44.679392+0800 Hook[1650:239418] RyukieLog
🌈
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hook成功！&lt;/p&gt;
&lt;h2&gt;2.4 试一试hook C函数？&lt;/h2&gt;
&lt;p&gt;定义一个这样的函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void logFun(const char * str) {
    NSLog(@&quot;%s&quot;, str);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改rebinding信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct rebinding nslog;
nslog.name = &quot;logFun&quot;;
nslog.replacement = new_logfun;
nslog.replaced = (void *)&amp;amp;old_logfun;

static void(*old_logfun)(const char * str);

void new_logfun(const char * str) {
    str = &quot;🌈&quot;;
    // 再调用旧的
    old_logfun(str);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现并没有成功！应为这个是通过地址直接访问的。&lt;/p&gt;
&lt;h1&gt;三、重绑定符号&lt;/h1&gt;
&lt;p&gt;一个问题：编译的时候，NSLog的真实地址知道么？不知道。&lt;/p&gt;
&lt;p&gt;在MachO加载进内存时，dyld会先加载依赖的系统库进共享缓存。&lt;/p&gt;
&lt;p&gt;NSLog在Foundation框架中，加载后dyld会将NSLog的真实地址告诉MachO。&lt;/p&gt;
&lt;p&gt;验证一下fishhook的实现：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/42ee427411c44e9d.webp&quot; alt=&quot;hook&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在hook前后分别下断点，打开汇编断点。&lt;/p&gt;
&lt;p&gt;第一个断点：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-&amp;gt;  0x10233d234 &amp;lt;+188&amp;gt;: bl     0x10233e320               ; symbol stub for: NSLog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下一步：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;  0x104cae320 &amp;lt;+0&amp;gt;: nop    
    0x104cae324 &amp;lt;+4&amp;gt;: ldr    x16, #0x5cdc              ; (void *)0x0000000104cae464
    0x104cae328 &amp;lt;+8&amp;gt;: br     x16

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二个断点：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-&amp;gt;  0x10233d254 &amp;lt;+220&amp;gt;: bl     0x10233e320               ; symbol stub for: NSLog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;下一步：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-&amp;gt;  0x104cae320 &amp;lt;+0&amp;gt;: nop    
    0x104cae324 &amp;lt;+4&amp;gt;: ldr    x16, #0x5cdc              ; (void *)0x0000000104cad44c: hook_NSLog at /Users/RyukieW/RyukieSamaProject/Study/逆向/Code/Hook/Hook/Hook/ViewController.m:75
    0x104cae328 &amp;lt;+8&amp;gt;: br     x16
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bl     0x10233e320               ; symbol stub for: NSLog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一段是到&lt;code&gt;0x10233e320&lt;/code&gt;中去寻找符号绑定的地址&lt;/p&gt;
&lt;p&gt;第一次的第二步找到了系统原本函数的地址&lt;/p&gt;
&lt;p&gt;第二次走到第二步后发现已经是hookNSLog了&lt;/p&gt;
&lt;h1&gt;四、符号绑定的过程&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;间接符号表
&lt;ul&gt;
&lt;li&gt;MachO文件以外的，所有调用系统的都是外部的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;本地符号
&lt;ul&gt;
&lt;li&gt;上架去符号，去的是本地符号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;全局符号
&lt;ul&gt;
&lt;li&gt;可以暴露给外界&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4.1 桩&lt;/h2&gt;
&lt;p&gt;一块代码！&lt;/p&gt;
&lt;p&gt;上面一段中看到的一段汇编指令&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bl     0x10233e320               ; symbol stub for: NSLog&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x10233e320&lt;/code&gt;就是NSLog的桩&lt;/p&gt;
&lt;p&gt;bl -&amp;gt; 桩 -&amp;gt; 通过符号表去执行代码&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;懒加载符号表全部都是通过桩去进行绑定的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;五、总结&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;符号绑定过程
&lt;ul&gt;
&lt;li&gt;外部函数调用是执行桩里的代码
&lt;ul&gt;
&lt;li&gt;通过懒加载符号表里的地址去执行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;懒加载符号表里面默认保存的是binder的代码
&lt;ul&gt;
&lt;li&gt;bingder函数在非懒加载符号的表里面（程序运行就绑定好了）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MachO文件</title><link>https://blog.lab-lawliet.app/posts/article-06-macho-525bb6eaff0bc029/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-06-macho-525bb6eaff0bc029/</guid><description>Test.c</description><pubDate>Tue, 04 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、基础概念&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Mach-O文件其实是MachObject文件格式的缩写。是Mac以及iOS上客户性文件的格式。&lt;/li&gt;
&lt;li&gt;属于MachO格式的常见文件：
&lt;ul&gt;
&lt;li&gt;目标文件.o&lt;/li&gt;
&lt;li&gt;库文件
&lt;ul&gt;
&lt;li&gt;.a&lt;/li&gt;
&lt;li&gt;.dylib&lt;/li&gt;
&lt;li&gt;Framework&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;可执行文件&lt;/li&gt;
&lt;li&gt;dyld&lt;/li&gt;
&lt;li&gt;.dsym&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file&lt;/code&gt;指令可以产看文件类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二、我们通过一个简单的例子来看下MachO是怎么生成的&lt;/h1&gt;
&lt;p&gt;Test.c&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# include &amp;lt;stdio.h&amp;gt;

int main() {
    printf(&quot;Hello world&quot;);
    return 1;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test2.c&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# include &amp;lt;stdio.h&amp;gt;

int main2() {
    printf(&quot;Hello money&quot;);
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.1 源文件编译为.o文件（中间对象文件）&lt;/h2&gt;
&lt;p&gt;执行命令&lt;code&gt;clang -c Test.c Test2.c&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/7b3b55f9d55b63d1.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;看到编译出了两个对应的&lt;code&gt;.o&lt;/code&gt;文件&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以直接 -o 一步到位&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2.2 将.o文件生成可执行文件&lt;/h2&gt;
&lt;p&gt;执行命名&lt;code&gt;clang -o TestEx Test.o Test2.o&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;生成&lt;code&gt;TestEx&lt;/code&gt;的可执行文件，通过&lt;code&gt;file&lt;/code&gt;指令确认一下，这里可以发现生成的就是&lt;code&gt;MachO&lt;/code&gt;文件了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;file TestEx
TestEx: Mach-O 64-bit executable x86_64
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2.3 链接顺序对MachO文件的影响&lt;/h2&gt;
&lt;p&gt;我们看下当前生成TestEx文件的MD5&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;md5 TestEx
MD5 (TestEx) = 3db04e90a4e078438a03f7cb9d8bf8c8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再执行命名&lt;code&gt;clang -o TestEx2 Test2.o Test.o&lt;/code&gt;，调整.o文件顺序。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;➜  Create md5 TestEx2
MD5 (TestEx2) = b32bbfd76c8784f8a517249f5f7c4bec
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通过objdump查看MachO文件数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;➜  Create objdump --macho -d TestEx
TestEx:
(__TEXT,__text) section
_main:
100003f20:	55	pushq	%rbp
100003f21:	48 89 e5	movq	%rsp, %rbp
100003f24:	48 83 ec 10	subq	$16, %rsp
100003f28:	c7 45 fc 00 00 00 00	movl	$0, -4(%rbp)
100003f2f:	48 8d 3d 60 00 00 00	leaq	96(%rip), %rdi ## literal pool for: &quot;Hello world&quot;
100003f36:	b0 00	movb	$0, %al
100003f38:	e8 39 00 00 00	callq	0x100003f76 ## symbol stub for: _printf
100003f3d:	b9 01 00 00 00	movl	$1, %ecx
100003f42:	89 45 f8	movl	%eax, -8(%rbp)
100003f45:	89 c8	movl	%ecx, %eax
100003f47:	48 83 c4 10	addq	$16, %rsp
100003f4b:	5d	popq	%rbp
100003f4c:	c3	retq
100003f4d:	90	nop
100003f4e:	90	nop
100003f4f:	90	nop
_main2:
100003f50:	55	pushq	%rbp
100003f51:	48 89 e5	movq	%rsp, %rbp
100003f54:	48 83 ec 10	subq	$16, %rsp
100003f58:	48 8d 3d 43 00 00 00	leaq	67(%rip), %rdi ## literal pool for: &quot;Hello money&quot;
100003f5f:	b0 00	movb	$0, %al
100003f61:	e8 10 00 00 00	callq	0x100003f76 ## symbol stub for: _printf
100003f66:	b9 01 00 00 00	movl	$1, %ecx
100003f6b:	89 45 fc	movl	%eax, -4(%rbp)
100003f6e:	89 c8	movl	%ecx, %eax
100003f70:	48 83 c4 10	addq	$16, %rsp
100003f74:	5d	popq	%rbp
100003f75:	c3	retq
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;➜  Create objdump --macho -d TestEx2
TestEx2:
(__TEXT,__text) section
_main2:
100003f20:	55	pushq	%rbp
100003f21:	48 89 e5	movq	%rsp, %rbp
100003f24:	48 83 ec 10	subq	$16, %rsp
100003f28:	48 8d 3d 6f 00 00 00	leaq	111(%rip), %rdi ## literal pool for: &quot;Hello money&quot;
100003f2f:	b0 00	movb	$0, %al
100003f31:	e8 48 00 00 00	callq	0x100003f7e ## symbol stub for: _printf
100003f36:	b9 01 00 00 00	movl	$1, %ecx
100003f3b:	89 45 fc	movl	%eax, -4(%rbp)
100003f3e:	89 c8	movl	%ecx, %eax
100003f40:	48 83 c4 10	addq	$16, %rsp
100003f44:	5d	popq	%rbp
100003f45:	c3	retq
100003f46:	90	nop
100003f47:	90	nop
100003f48:	90	nop
100003f49:	90	nop
100003f4a:	90	nop
100003f4b:	90	nop
100003f4c:	90	nop
100003f4d:	90	nop
100003f4e:	90	nop
100003f4f:	90	nop
_main:
100003f50:	55	pushq	%rbp
100003f51:	48 89 e5	movq	%rsp, %rbp
100003f54:	48 83 ec 10	subq	$16, %rsp
100003f58:	c7 45 fc 00 00 00 00	movl	$0, -4(%rbp)
100003f5f:	48 8d 3d 44 00 00 00	leaq	68(%rip), %rdi ## literal pool for: &quot;Hello world&quot;
100003f66:	b0 00	movb	$0, %al
100003f68:	e8 11 00 00 00	callq	0x100003f7e ## symbol stub for: _printf
100003f6d:	b9 01 00 00 00	movl	$1, %ecx
100003f72:	89 45 f8	movl	%eax, -8(%rbp)
100003f75:	89 c8	movl	%ecx, %eax
100003f77:	48 83 c4 10	addq	$16, %rsp
100003f7b:	5d	popq	%rbp
100003f7c:	c3	retq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以确定生成的MachO文件不同了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MachO是一系列.o文件的集合&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;三：MachO文件结构&lt;/h1&gt;
&lt;h2&gt;3.1 Header&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;包含了二进制文件的一般信息
&lt;ul&gt;
&lt;li&gt;字节顺序、架构类型、加载指令的数量等&lt;/li&gt;
&lt;li&gt;使得一些信息可以快速确认
&lt;ul&gt;
&lt;li&gt;比如当前文件是用于32位还是64位，对应的处理器是什么、文件类型是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Header数据结构&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;可以再Xcode中快速打开文件 &lt;code&gt;Loader.h&lt;/code&gt;查看，这个文件时学习MachO非常好的一个入口！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
	uint32_t	magic;		/* mach magic number identifier */
	cpu_type_t	cputype;	/* cpu specifier */
	cpu_subtype_t	cpusubtype;	/* machine specifier */
	uint32_t	filetype;	/* type of file */
	uint32_t	ncmds;		/* number of load commands */
	uint32_t	sizeofcmds;	/* the size of all the load commands */
	uint32_t	flags;		/* flags */
	uint32_t	reserved;	/* reserved */
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;magic&lt;/code&gt; 快速定位是64位还是32位
&lt;code&gt;cputype&lt;/code&gt; CPU类型，比如ARM
&lt;code&gt;cpusubtype&lt;/code&gt; CPU具体类型，比如：arm64/armv7
&lt;code&gt;filetype&lt;/code&gt; 文件类型，比如：可执行文件
&lt;code&gt;ncmds&lt;/code&gt; LoadCommands条数
&lt;code&gt;sizeofcmds&lt;/code&gt; LoadCommands大小
&lt;code&gt;flags&lt;/code&gt; 标志位，标识二进制文件支持的功能。主要是和系统加载、链接有关。&lt;/p&gt;
&lt;h3&gt;使用&lt;code&gt;otool&lt;/code&gt;查看&lt;code&gt;Header&lt;/code&gt;内容&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;➜  Create otool -v -h TestEx2
TestEx2:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   X86_64        ALL  0x00     EXECUTE    16       1368   NOUNDEFS DYLDLINK TWOLEVEL PIE
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.2 Load commands&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内容包括区域的位置、符号表、动态符号表等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Rebase info offset&lt;/h3&gt;
&lt;p&gt;MachO文件在加载到内存中后（虚拟内存），会被分配一个随机的偏移值&lt;code&gt;ASLR&lt;/code&gt;。
我们使用静态分析工具看到的并不是真实的内存地址。&lt;/p&gt;
&lt;h3&gt;查看当前ASLR&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/948f8871c67e77b1.webp&quot; alt=&quot;Img&quot; /&gt;&lt;/p&gt;
&lt;p&gt;命令&lt;code&gt;image list -o -f | grep 当前MachO文件名&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(lldb) image list -o -f | grep WeChat
[  0] 0x0000000000118000 /Users/RyukieW/Library/Developer/Xcode/DerivedData/WeChat-ajurlvyhlazpsbhatmdivvridyee/Build/Products/Debug-iphoneos/WeChat.app/WeChat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ASLR &lt;code&gt;0x0000000000118000&lt;/code&gt; （随机的每次都不一样）
真实地址 &lt;code&gt;0x10011e640&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;用工具查看地址&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1b62d5e85343ab3c.webp&quot; alt=&quot;Img&quot; /&gt;
&lt;code&gt;0x0000000100006640&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;计算真实地址&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ASLR&lt;/code&gt; + &lt;code&gt;工具显示的地址&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用编程计算器验证成功！&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用&lt;code&gt;otool&lt;/code&gt;查看&lt;code&gt;Load commands&lt;/code&gt;内容&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;➜  Create otool -v -l TestEx2
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.3 Data&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;通常是文件中最大的部分&lt;/li&gt;
&lt;li&gt;包含Segement的具体数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://ming1016.github.io/2020/03/29/apple-system-executable-file-macho/&quot;&gt;戴铭-Apple 操作系统可执行文件 Mach-O&lt;/a&gt;
&lt;a href=&quot;https://www.jianshu.com/p/94f67bb84c93&quot;&gt;iOS逆向工程（九）：ASLR&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>利用Xcode进行重签名与调试</title><link>https://blog.lab-lawliet.app/posts/article-04-xcode-b0b94e7dcd79440f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-04-xcode-b0b94e7dcd79440f/</guid><description>Xcode - Build Phases - Run Script - 添加脚本即可</description><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、使用方式：&lt;/h2&gt;
&lt;p&gt;Xcode - Build Phases - Run Script - 添加脚本即可&lt;/p&gt;
&lt;h2&gt;二、脚本&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# ${SRCROOT} 它是工程文件所在的目录
TEMP_PATH=&quot;${SRCROOT}/Temp&quot;
#资源文件夹，我们提前在工程目录下新建一个APP文件夹，里面放ipa包
ASSETS_PATH=&quot;${SRCROOT}/APP&quot;
#目标ipa包路径
TARGET_IPA_PATH=&quot;${ASSETS_PATH}/*.ipa&quot;
#清空Temp文件夹
rm -rf &quot;${SRCROOT}/Temp&quot;
mkdir -p &quot;${SRCROOT}/Temp&quot;

#----------------------------------------
# 1. 解压IPA到Temp下
unzip -oqq &quot;$TARGET_IPA_PATH&quot; -d &quot;$TEMP_PATH&quot;
# 拿到解压的临时的APP的路径
TEMP_APP_PATH=$(set -- &quot;$TEMP_PATH/Payload/&quot;*.app;echo &quot;$1&quot;)
# echo &quot;路径是:$TEMP_APP_PATH&quot;


#----------------------------------------
# 2. 将解压出来的.app拷贝进入工程下
# BUILT_PRODUCTS_DIR 工程生成的APP包的路径
# TARGET_NAME target名称
TARGET_APP_PATH=&quot;$BUILT_PRODUCTS_DIR/$TARGET_NAME.app&quot;
echo &quot;app路径:$TARGET_APP_PATH&quot;

rm -rf &quot;$TARGET_APP_PATH&quot;
mkdir -p &quot;$TARGET_APP_PATH&quot;
cp -rf &quot;$TEMP_APP_PATH/&quot; &quot;$TARGET_APP_PATH&quot;



#----------------------------------------
# 3. 删除extension和WatchAPP.个人证书没法签名Extention
rm -rf &quot;$TARGET_APP_PATH/PlugIns&quot;
rm -rf &quot;$TARGET_APP_PATH/Watch&quot;



#----------------------------------------
# 4. 更新info.plist文件 CFBundleIdentifier
#  设置:&quot;Set : KEY Value&quot; &quot;目标文件路径&quot;
/usr/libexec/PlistBuddy -c &quot;Set :CFBundleIdentifier $PRODUCT_BUNDLE_IDENTIFIER&quot; &quot;$TARGET_APP_PATH/Info.plist&quot;


#----------------------------------------
# 5. 给MachO文件上执行权限
# 拿到MachO文件的路径
APP_BINARY=`plutil -convert xml1 -o - $TARGET_APP_PATH/Info.plist|grep -A1 Exec|tail -n1|cut -f2 -d\&amp;gt;|cut -f1 -d\&amp;lt;`
#上可执行权限
chmod +x &quot;$TARGET_APP_PATH/$APP_BINARY&quot;



#----------------------------------------
# 6. 重签名第三方 FrameWorks
TARGET_APP_FRAMEWORKS_PATH=&quot;$TARGET_APP_PATH/Frameworks&quot;
if [ -d &quot;$TARGET_APP_FRAMEWORKS_PATH&quot; ];
then
for FRAMEWORK in &quot;$TARGET_APP_FRAMEWORKS_PATH/&quot;*
do

#签名
/usr/bin/codesign --force --sign &quot;$EXPANDED_CODE_SIGN_IDENTITY&quot; &quot;$FRAMEWORK&quot;
done
fi

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、如何安装重签名的包进行调试&lt;/h2&gt;
&lt;h3&gt;3.1 安装&lt;/h3&gt;
&lt;p&gt;使用Xcode成功运行后会直接安装到手机上，也可以在&lt;code&gt;Products&lt;/code&gt;中选择&lt;code&gt;.app&lt;/code&gt;文件拷贝出来保存一下，通过下面的方式进行安装
&lt;img src=&quot;/content-assets/062c422e076b3312.webp&quot; alt=&quot;安装&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3.2 调试&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/048f4cb2839bb0bf.webp&quot; alt=&quot;调试1&quot; /&gt;
成功！愉快的学习吧~
&lt;img src=&quot;/content-assets/121449119f43fa8c.webp&quot; alt=&quot;调试2&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四：生成重签名后的IPA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;在项目的&lt;code&gt;Products&lt;/code&gt;下找到&lt;code&gt;.app&lt;/code&gt;文件，拷贝出来。&lt;/li&gt;
&lt;li&gt;创建&lt;code&gt;Payload&lt;/code&gt;文件夹，将上面获得的&lt;code&gt;.app&lt;/code&gt;文件放入其中。&lt;/li&gt;
&lt;li&gt;压缩&lt;code&gt;Payload&lt;/code&gt;文件夹，修改后缀为&lt;code&gt;.ipa&lt;/code&gt;即可。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>dylib注入</title><link>https://blog.lab-lawliet.app/posts/article-05-dylib-6c01a01c74a67e1a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-05-dylib-6c01a01c74a67e1a/</guid><description>可以发现系统动态库的路径是在系统下的</description><pubDate>Mon, 03 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一、分析MachO&lt;/h1&gt;
&lt;p&gt;可以发现系统动态库的路径是在系统下的&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d31d85252aed965.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;非系统库是在工程的相对路径中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/53c4773ec0b94bd9.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因为我们并没有源码，所以想要注入代码就要通过直接修改MachO文件来实现。&lt;/p&gt;
&lt;h1&gt;二、工具&lt;em&gt;&lt;strong&gt;yololib&lt;/strong&gt;&lt;/em&gt;&lt;/h1&gt;
&lt;p&gt;在&lt;a href=&quot;/posts/article-04-xcode-b0b94e7dcd79440f/&quot;&gt;利用Xcode进行重签名与调试&lt;/a&gt;中有提到如何使用脚本进行重签名，这里我们也可以通过修改脚本快速的使用工具进行MachO文件的修改。&lt;/p&gt;
&lt;p&gt;结尾加上这一句：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./yololib &quot;$TARGET_APP_PATH/$APP_BINARY&quot; &quot;Frameworks/你要注入的.dylib&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;可能会遇到 &lt;em&gt;&lt;strong&gt;./yololib: Permission denied&lt;/strong&gt;&lt;/em&gt; 错误，为文件添加权限即可 &lt;em&gt;&lt;strong&gt;chmod +x yololib&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;三、准备自己的dylib&lt;/h1&gt;
&lt;h2&gt;3.1 建一个&lt;code&gt;MacOS&lt;/code&gt;的&lt;code&gt;Library&lt;/code&gt;。&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/e8d112c2cb0fc9f4.webp&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@implementation RyukieHook
+ (void)load {
    NSLog(@&quot;\n\n\n\n\n\n\n\n\n\n\n RyukieHookSuccess \n\n\n\n\n\n\n\n\n\n\n&quot;);
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.2 修改&lt;code&gt;BaseSDK&lt;/code&gt;为&lt;code&gt;iOS&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/95ff19b159332fe8.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;3.3 设置编译时拷贝文件&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/f837ab9805a385d4.webp&quot; alt=&quot;拷贝1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e75d411a4f51de9.webp&quot; alt=&quot;拷贝2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/add3a601cab2e061.webp&quot; alt=&quot;拷贝3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;运行注入成功！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1a5632654ac26975.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>iOS应用签名原理</title><link>https://blog.lab-lawliet.app/posts/article-03-ios-407fb65b0d817456/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-03-ios-407fb65b0d817456/</guid><description>代码签名是对可执行文件或脚本进行数字签名，用来确认软件在签名后未被修改或者损坏的措施。和数字签名原理一样，只不过签名的数据是代码而已。</description><pubDate>Sat, 01 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一：代码签名&lt;/h2&gt;
&lt;p&gt;代码签名是对可执行文件或脚本进行数字签名，用来确认软件在签名后未被修改或者损坏的措施。和数字签名原理一样，只不过签名的数据是代码而已。&lt;/p&gt;
&lt;h3&gt;1.1 简单的代码签名&lt;/h3&gt;
&lt;p&gt;在iOS系统出来之前，以前主流的操作系统软件随便从哪里下载都能运行。存在安全隐患。苹果为了保证每一个安装到iOS设备上的App都是经过苹果允许的，设计了代码签名。&lt;/p&gt;
&lt;p&gt;最简单的方式就是通过：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;苹果官方生成非对称加密的一堆公私钥。&lt;/li&gt;
&lt;li&gt;在iOS系统中内置一个公钥，私钥由苹果后台保存。&lt;/li&gt;
&lt;li&gt;我们将App传到AppStore后&lt;/li&gt;
&lt;li&gt;苹果后台用私钥对App进行签名&lt;/li&gt;
&lt;li&gt;iOS系统下载这个App后，用公钥验证这个签名。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 问题&lt;/h3&gt;
&lt;p&gt;如果只能从AppStore安装App的话问题就简单了，但是实际上还是有很多渠道的。比如开发者调试安装，企业安装。这些就无法通过简单的代码签名来办到了。&lt;/p&gt;
&lt;h3&gt;1.3 苹果的需求&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;安装包不用上传AppStore，可以直接安装到手机上&lt;/li&gt;
&lt;li&gt;苹果为了保证系统的安全性，必须对安装的App拥有绝对的控制权
&lt;ul&gt;
&lt;li&gt;经过苹果允许才能安装&lt;/li&gt;
&lt;li&gt;不能被滥用导致开发者App也能被安装&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了达到上述目的苹果的方案是双层签名&lt;/p&gt;
&lt;h2&gt;二：双层代码签名&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/9a817a0d68af77c2.webp&quot; alt=&quot;双层签名&quot; /&gt;
上面的只是简单的一部分，结合描述文件达到更严格的限制&lt;/p&gt;
&lt;h2&gt;三：描述文件&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/64279df4bf5e72cb.webp&quot; alt=&quot;双层签名和描述文件&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四：流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mac电脑有一对密钥&lt;code&gt;公钥A&lt;/code&gt;+&lt;code&gt;私钥A&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;苹果服务器有一个&lt;code&gt;私钥B&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iPhone有一个&lt;code&gt;公钥B&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;电脑生成&lt;code&gt;CSR&lt;/code&gt;文件（&lt;code&gt;公钥A&lt;/code&gt;及&lt;code&gt;一些组织信息&lt;/code&gt;），向苹果请求&lt;code&gt;描述文件&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;描述文件
&lt;ul&gt;
&lt;li&gt;可安装的设备IDs&lt;/li&gt;
&lt;li&gt;AppID&lt;/li&gt;
&lt;li&gt;应用权限&lt;/li&gt;
&lt;li&gt;证书
&lt;ul&gt;
&lt;li&gt;被&lt;code&gt;私钥B&lt;/code&gt;加密&lt;/li&gt;
&lt;li&gt;&lt;code&gt;公钥A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;公钥A的&lt;code&gt;hash&lt;/code&gt;值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;电脑打包出App&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MachO文件&lt;/li&gt;
&lt;li&gt;App的签名（用&lt;code&gt;公钥A&lt;/code&gt;签名）&lt;/li&gt;
&lt;li&gt;证书&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安装到iPhone上&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;公钥B&lt;/code&gt;解出证书中的&lt;code&gt;公钥A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用解出的&lt;code&gt;公钥A&lt;/code&gt;验证App签名&lt;/li&gt;
&lt;li&gt;验证成功就可以安装了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Swift位移枚举</title><link>https://blog.lab-lawliet.app/posts/swift-swift-ef2bc376df8bcd91/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swift-ef2bc376df8bcd91/</guid><description>发现在Swift中它并不是枚举，而是装换为了结构体，使用的API出则是转换为了对应元素类型的数组。 我们需要自定义位移枚举的时候可以参考系统的这种实现方式。</description><pubDate>Mon, 29 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一：以系统中的一个位移枚举为例&lt;/h2&gt;
&lt;h3&gt;a. OC版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
    UIRectCornerTopLeft     = 1 &amp;lt;&amp;lt; 0,
    UIRectCornerTopRight    = 1 &amp;lt;&amp;lt; 1,
    UIRectCornerBottomLeft  = 1 &amp;lt;&amp;lt; 2,
    UIRectCornerBottomRight = 1 &amp;lt;&amp;lt; 3,
    UIRectCornerAllCorners  = ~0UL
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[view addRoundedCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) withRadii:CGSizeMake(12.f, 12.f)];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;b.Swift版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public struct UIRectCorner : OptionSet {
    public init(rawValue: UInt)
    
    public static var topLeft: UIRectCorner { get }

    public static var topRight: UIRectCorner { get }

    public static var bottomLeft: UIRectCorner { get }

    public static var bottomRight: UIRectCorner { get }

    public static var allCorners: UIRectCorner { get }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$0.addRoundedCorners([.topLeft, .topRight], withRadii: CGSize(width: 15, height: 15))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;c.分析&lt;/h3&gt;
&lt;p&gt;发现在Swift中它并不是枚举，而是装换为了&lt;code&gt;结构体&lt;/code&gt;，使用的API出则是转换为了对应元素类型的数组。
我们需要自定义位移枚举的时候可以参考系统的这种实现方式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Sports: OptionSet {
    let rawValue: Int

    static let running = Sports(rawValue: 1)
    static let cycling = Sports(rawValue: 2)
    static let swimming = Sports(rawValue: 4)
    static let fencing = Sports(rawValue: 8)
    static let shooting = Sports(rawValue: 32)
    static let horseJumping = Sports(rawValue: 512)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二：Swift:union&lt;/h2&gt;
&lt;p&gt;当然部分API并没有经过上面的这种转化，如：
下面就是我在异步处理图片的时候遇到的一个场景&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let bitmapInfo: CGBitmapInfo = CGBitmapInfo(rawValue: hasAlpha ? CGImageAlphaInfo.premultipliedFirst.rawValue : CGImageAlphaInfo.noneSkipFirst.rawValue).union(.byteOrder32Little)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正如注释中所说：&lt;code&gt;A new option set made up of the elements contained in this set, in other, or in both.&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>知识点梳理：算法数据结构</title><link>https://blog.lab-lawliet.app/posts/article-41cc168a57603d19/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-41cc168a57603d19/</guid><description>如果把一层循环的n改成m,时间复杂度就成了 O(m*n)</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;01 时间复杂度与空间复杂度&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;我们衡量算法的优劣主要通过两个维度&lt;code&gt;时间``空间&lt;/code&gt;,而这两个维度对应的就是&lt;code&gt;时间复杂度``空间复杂度&lt;/code&gt;.
但&lt;code&gt;鱼和熊掌不可兼得&lt;/code&gt;,我们只能在这两者间取得一个合适的平衡点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1.时间复杂度&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;算法所需要的时间工作量&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;标记公式
&lt;ul&gt;
&lt;li&gt;T(n) = O(n)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见的时间复杂度量级&lt;/h3&gt;
&lt;h4&gt;a.常数阶O(1)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b.线性阶O(n)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;for(i=1; i&amp;lt;=n; ++i)
{
   j = i;
   j++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;c.对数阶O(logN)&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;以2为底&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;int i = 1;
while(i&amp;lt;n)
{
    i = i * 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;d.线性对数阶(nlogN)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;for(m=1; m&amp;lt;n; m++)
{
    i = 1;
    while(i&amp;lt;n)
    {
        i = i * 2;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;e.平方阶(n²)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;for(x=1; i&amp;lt;=n; x++)
{
   for(i=1; i&amp;lt;=n; i++)
    {
       j = i;
       j++;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果把一层循环的&lt;code&gt;n&lt;/code&gt;改成&lt;code&gt;m&lt;/code&gt;,时间复杂度就成了 &lt;code&gt;O(m*n)&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for(x=1; i&amp;lt;=m; x++)
{
   for(i=1; i&amp;lt;=n; i++)
    {
       j = i;
       j++;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;f.立方阶O(n³)、K次方阶O(n^k)&lt;/h4&gt;
&lt;p&gt;参考的O(n²),O(n³)相当于三层n循环&lt;/p&gt;
&lt;h2&gt;2.空间复杂度&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;算法在运算中临时占用的控件的大小&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;标记公式
&lt;ul&gt;
&lt;li&gt;S(n) = O(n)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见的空间复杂度量级&lt;/h3&gt;
&lt;h4&gt;a. O(1)&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;如果算法执行所需要的临时空间不随着某个变量n的大小而变化，即此算法空间复杂度为一个常量，可表示为 O(1)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;int i = 1;
int j = 2;
++i;
j++;
int m = i + j;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;b. O(n)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;int[] m = new int[n]
for(i=1; i&amp;lt;=n; ++i)
{
   j = i;
   j++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这段代码中，第一行new了一个数组出来，这个数据占用的大小为n，这段代码的2-6行，虽然有循环，但没有再分配新的空间，因此，这段代码的空间复杂度主要看第一行即可，即 S(n) = O(n)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;02 排序题&lt;/h1&gt;
&lt;p&gt;这里简单的说下几种快速排序的不同之处，随机快排，是为了解决在近似有序的情况下，时间复杂度会退化为&lt;code&gt;O(n^2)&lt;/code&gt;,双路快排是为了解决快速排序在大量数据重复的情况下，时间复杂度会退化为&lt;code&gt;O(n^2)&lt;/code&gt;，三路快排是在大量数据重复的情况下，对双路快排的一种优化。&lt;/p&gt;
&lt;h2&gt;冒泡排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func bubbleSort() {
        let count = self.count
        for i in 0..&amp;lt;count {
            for j in 0..&amp;lt;(count - 1 - i) {
                if self[j] &amp;gt; self[j + 1] {
                    (self[j], self[j + 1]) = (self[j + 1], self[j])
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;选择排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func selectionSort() {
        let count = self.count
        for i in 0..&amp;lt;count {
            var minIndex = i
            for j in (i+1)..&amp;lt;count {
                if self[j] &amp;lt; self[minIndex] {
                    minIndex = j
                }
            }
            (self[i], self[minIndex]) = (self[minIndex], self[i])
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;插入排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func insertionSort() {
        let count = self.count
        guard count &amp;gt; 1 else { return }
        for i in 1..&amp;lt;count {
            var preIndex = i - 1
            let currentValue = self[i]
            while preIndex &amp;gt;= 0 &amp;amp;&amp;amp; currentValue &amp;lt; self[preIndex] {
                self[preIndex + 1] = self[preIndex]
                preIndex -= 1
            }
            self[preIndex + 1] = currentValue
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;快速排序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func quickSort() {
        func quickSort(left:Int, right:Int) {
            guard left &amp;lt; right else { return }
            var i = left + 1,j = left
            let key = self[left]
            while i &amp;lt;= right {
                if self[i] &amp;lt; key {
                    j += 1
                    (self[i], self[j]) = (self[j], self[i])
                }
                i += 1
            }
            (self[left], self[j]) = (self[j], self[left])
            quickSort(left: j + 1, right: right)
            quickSort(left: left, right: j - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;随机快排&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func quickSort1() {
        func quickSort(left:Int, right:Int) {
            guard left &amp;lt; right else { return }
            let randomIndex = Int.random(in: left...right)
            (self[left], self[randomIndex]) = (self[randomIndex], self[left])
            var i = left + 1,j = left
            let key = self[left]
            while i &amp;lt;= right {
                if self[i] &amp;lt; key {
                    j += 1
                    (self[i], self[j]) = (self[j], self[i])
                }
                i += 1
            }
            (self[left], self[j]) = (self[j], self[left])
            quickSort(left: j + 1, right: right)
            quickSort(left: left, right: j - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;双路快排&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;extension Array where Element : Comparable{
    public mutating func quickSort2() {
        func quickSort(left:Int, right:Int) {
            guard left &amp;lt; right else { return }
            let randomIndex = Int.random(in: left...right)
            (self[left], self[randomIndex]) = (self[randomIndex], self[left])
            var l = left + 1, r = right
            let key = self[left]
            while true {
                while l &amp;lt;= r &amp;amp;&amp;amp; self[l] &amp;lt; key {
                    l += 1
                }
                while l &amp;lt; r &amp;amp;&amp;amp; key &amp;lt; self[r]{
                    r -= 1
                }
                if l &amp;gt; r { break }
                (self[l], self[r]) = (self[r], self[l])
                l += 1
                r -= 1
            }
            (self[r], self[left]) = (self[left], self[r])
            quickSort(left: r + 1, right: right)
            quickSort(left: left, right: r - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三路快排&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 三路快排
extension Array where Element : Comparable{
    public mutating func quickSort3() {
        func quickSort(left:Int, right:Int) {
            guard left &amp;lt; right else { return }
            let randomIndex = Int.random(in: left...right)
            (self[left], self[randomIndex]) = (self[randomIndex], self[left])
            var lt = left, gt = right
            var i = left + 1
            let key = self[left]
            while i &amp;lt;= gt {
                if self[i] == key {
                    i += 1
                }else if self[i] &amp;lt; key{
                    (self[i], self[lt + 1]) = (self[lt + 1], self[i])
                    lt += 1
                    i += 1
                }else {
                    (self[i], self[gt]) = (self[gt], self[i])
                    gt -= 1
                }
                
            }
            (self[left], self[lt]) = (self[lt], self[left])
            quickSort(left: gt + 1, right: right)
            quickSort(left: left, right: lt - 1)
        }
        quickSort(left: 0, right: self.count - 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>知识点梳理：多线程</title><link>https://blog.lab-lawliet.app/posts/article-a9bd44995fddcb96/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-a9bd44995fddcb96/</guid><description>比如在内存中维护一份数据，有多处地方可能会同时操作这块数据，怎么能保证数据安全？ 这道题目总结得到要满足以下三点：</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. GCD实现多读单写&lt;/h1&gt;
&lt;p&gt;比如在内存中维护一份数据，有多处地方可能会同时操作这块数据，怎么能保证数据安全？
这道题目总结得到要满足以下三点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读写互斥&lt;/li&gt;
&lt;li&gt;写写互斥&lt;/li&gt;
&lt;li&gt;读读并发&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@implementation KCPerson
- (instancetype)init
{
    if (self = [super init]) {
       _concurrentQueue = dispatch_queue_create(&quot;com.kc_person.syncQueue&quot;, DISPATCH_QUEUE_CONCURRENT);
       _dic = [NSMutableDictionary dictionary];
    }
    return self;
}
- (void)kc_setSafeObject:(id)object forKey:(NSString *)key{
    key = [key copy];
    dispatch_barrier_async(_concurrentQueue, ^{
       [_dic setObject:object key:key];
    });
}

- (id)kc_safeObjectForKey：:(NSString *)key{
    __block NSString *temp;
    dispatch_sync(_concurrentQueue, ^{
        temp =[_dic objectForKey：key];
    });
    return temp;
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先我们要维系一个GCD 队列，最好不用全局队列，毕竟大家都知道全局队列遇到栅栏函数是有坑点的，这里就不分析了！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;因为考虑性能 死锁 堵塞的因素不考虑串行队列，用的是自定义的并发队列！&lt;code&gt;_concurrentQueue = dispatch_queue_create(&quot;com.kc_person.syncQueue&quot;, DISPATCH_QUEUE_CONCURRENT);&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;首先我们来看看读操作:&lt;code&gt;kc_safeObjectForKey&lt;/code&gt; 我们考虑到多线程影响是不能用异步函数的！说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程2 获取：&lt;code&gt;name&lt;/code&gt; 线程3 获取 &lt;code&gt;age&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果因为异步并发，导致混乱 本来读的是&lt;code&gt;name&lt;/code&gt; 结果读到了&lt;code&gt;age&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;我们允许多个任务同时进去! 但是读操作需要同步返回，所以我们选择:&lt;code&gt;同步函数&lt;/code&gt; &lt;strong&gt;（读读并发）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们再来看看写操作，在写操作的时候对key进行了copy, 关于此处的解释，插入一段来自参考文献的引用:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;函数调用者可以自由传递一个&lt;code&gt;NSMutableString&lt;/code&gt;的&lt;code&gt;key&lt;/code&gt;，并且能够在函数返回后修改它。因此我们必须对传入的字符串使用&lt;code&gt;copy&lt;/code&gt;操作以确保函数能够正确地工作。如果传入的字符串不是可变的（也就是正常的&lt;code&gt;NSString&lt;/code&gt;类型），调用&lt;code&gt;copy&lt;/code&gt;基本上是个空操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;这里我们选择 &lt;code&gt;dispatch_barrier_async&lt;/code&gt;, 为什么是栅栏函数而不是异步函数或者同步函数，下面分析：
&lt;ul&gt;
&lt;li&gt;栅栏函数任务：
&lt;ul&gt;
&lt;li&gt;之前所有的任务执行完毕，并且在它后面的任务开始之前，期间不会有其他的任务执行，这样比较好的促使 写操作一个接一个写&lt;code&gt;（写写互斥）&lt;/code&gt;，不会乱！&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么不是异步函数？
&lt;ul&gt;
&lt;li&gt;应该很容易分析，毕竟会产生混乱！&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么不用同步函数？
&lt;ul&gt;
&lt;li&gt;如果读写都操作了，那么用同步函数，就有可能存在：我写需要等待读操作回来才能执行，显然这里是不合理！&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2 多线程和锁&lt;/h1&gt;
&lt;p&gt;**进程：**是资源分配的基本单位，它是程序执行时的一个实例，在程序运行时创建。&lt;/p&gt;
&lt;p&gt;**线程：**是程序执行的最小单位，是进程的一个执行流，一个进程由多个线程组成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多线程：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个进程中并发执行多个线程，叫做&lt;strong&gt;多线程&lt;/strong&gt;。在一个时间片内，CPU只能&lt;strong&gt;处理一个线程中的一个任务&lt;/strong&gt;，对于一个单核CPU来说，在不同的时间片来执行不同线程中的任务，就形成了多个任务在同时执行的“假象”。&lt;/p&gt;
&lt;h2&gt;多线程的几种方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pthread&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;即&lt;code&gt;POSIX Thread&lt;/code&gt;，缩写称为&lt;code&gt;pthread&lt;/code&gt;，是线程的POSIX标准，&lt;strong&gt;是一套通用的多线程API&lt;/strong&gt;，&lt;strong&gt;可以&lt;/strong&gt;在&lt;code&gt;Unix/Linux/Windows&lt;/code&gt;等平台&lt;strong&gt;跨平台使用&lt;/strong&gt;。iOS中基本不使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSThread&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;苹果封装的面向对象的线程类，可以直接操作线程&lt;/strong&gt;，比起&lt;code&gt;GCD&lt;/code&gt;，&lt;code&gt;NSThread&lt;/code&gt;效率更高，&lt;strong&gt;由程序员自行创建&lt;/strong&gt;，当线程中的任务执行完毕后，线程会自动退出，&lt;strong&gt;程序员也可手动管理线程的生命周期&lt;/strong&gt;。使用频率较低。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GCD&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;全称&lt;code&gt;Grand Central Dispatch&lt;/code&gt;，&lt;strong&gt;由C语言实现，是苹果为多核并行运算提出的解决方案&lt;/strong&gt;，CGD会自动利用更多的CPU内核，&lt;strong&gt;自动管理线程的生命周期&lt;/strong&gt;，程序员只需要告诉GCD需要执行的任务，&lt;strong&gt;无需编写任何管理线程的代码&lt;/strong&gt;。GCD也是iOS使用频率最高的多线程技术。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSOperation&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;基于GCD封装的面向对象的多线程技术，常配合NSOperationQueue使用&lt;/strong&gt;，使用频率较高。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GCD 和 NSOperation 区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GCD&lt;/code&gt;仅仅支持&lt;code&gt;先进先出FIFO队列&lt;/code&gt;，不支持异步操作之间的依赖关系设置。而&lt;code&gt;NSOperation&lt;/code&gt;中的队列可以被重新设置优先级，从而实现不同操作的执行顺序调整。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSOperation&lt;/code&gt;支持&lt;code&gt;KVO&lt;/code&gt;，可以观察任务的执行状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GCD&lt;/code&gt;更接近底层，&lt;code&gt;GCD&lt;/code&gt;在追求性能的底层操作来说，是速度最快的。&lt;/li&gt;
&lt;li&gt;从异步操作之间的事务性，顺序行，依赖关系。&lt;code&gt;GCD&lt;/code&gt;需要自己写更多的代码来实现，而&lt;code&gt;NSOperation&lt;/code&gt;已经内建了这些支持。&lt;/li&gt;
&lt;li&gt;如果异步操作的过程需要更多的被交互和UI呈现出来，&lt;code&gt;NSOperation&lt;/code&gt;更好。底层代码中，任务之间不太互相依赖，而需要更高的并发能力，&lt;code&gt;GCD&lt;/code&gt;则更有优势。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;线程池原理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用线程执行任务的时候，需要到线程池中去取线程进行任务分配。&lt;/li&gt;
&lt;li&gt;首先判断线程池大小是否小于核心线程池大小，如果小于的话，创建新的线程执行任务；&lt;/li&gt;
&lt;li&gt;如果当前线程池大小&lt;strong&gt;大于&lt;/strong&gt;了核心线程池大小，然后开始判断工作队列是否已满，如果没满，将任务提交到工作队列。&lt;/li&gt;
&lt;li&gt;如果工作队列已满，判断线程池的线程是否都在工作，如果有空闲线程没有在工作，就交给它去执行任务。&lt;/li&gt;
&lt;li&gt;如果线程池中的线程都在工作，那么就交给饱和策略去执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;饱和策略分为下面四种：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AbortPolicy&lt;/code&gt; 直接抛出&lt;code&gt;RejectedExecutionExeception&lt;/code&gt; 异常来阻止系统正常运行；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CallerRunsPolicy&lt;/code&gt; 将任务回退到调用者；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DisOldestPolicy&lt;/code&gt; 丢掉等待最久的任务‘；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DisCardPolicy&lt;/code&gt; 直接丢弃任务。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;线程间通讯&lt;/h2&gt;
&lt;p&gt;直接同步或者异步向任务队列添加任务。&lt;/p&gt;
&lt;p&gt;通过NSPort端口的形式进行发送消息，实现不同的线程间的通信。使用的时候注意需要将NSPort加入的线程的RunLoop中去。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**直接消息传递：**通过&lt;code&gt;performSelector&lt;/code&gt;的一系列方法，可以实现由某一线程指定在另外的线程上执行任务。因为任务的执行上下文是目标线程，这种方式发送的消息将会自动的被序列化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**全局变量、共享内存块和对象：**在两个线程之间传递信息的另一种简单方法是使用全局变量，共享对象或共享内存块。尽管共享变量既快速又简单，但是它们比直接消息传递更脆弱。必须使用锁或其他同步机制仔细保护共享变量，以确保代码的正确性。 否则可能会导致竞争状况，数据损坏或崩溃。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**条件执行：**条件是一种同步工具，可用于控制线程何时执行代码的特定部分。您可以将条件视为关守，让线程仅在满足指定条件时运行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Runloop sources：&lt;strong&gt;一个自定义的&lt;code&gt;Runloop source配置&lt;/code&gt;可以让一个线程上收到特定的应用程序消息。由于&lt;code&gt;Runloop source&lt;/code&gt;是&lt;/strong&gt;事件驱动&lt;/strong&gt;的，因此在无事可做时，线程会自动进入睡眠状态，从而提高了线程的效率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**Ports and sockets：**基于端口的通信是在两个线程之间进行通信的一种更为复杂的方法，但它也是一种非常可靠的技术。更重要的是，端口和套接字可用于与外部实体（例如其他进程和服务）进行通信。为了提高效率，使用&lt;code&gt;Runloop source&lt;/code&gt;来实现端口，因此当端口上没有数据等待时，线程将进入睡眠状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**消息队列：**传统的多处理服务定义了&lt;code&gt;先进先出（FIFO）队列&lt;/code&gt;抽象，用于管理传入和传出数据。尽管消息队列既简单又方便，但是它们不如其他一些通信技术高效。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**Cocoa 分布式对象：**分布式对象是一种 Cocoa 技术，可提供基于端口的通信的高级实现。尽管可以将这种技术用于线程间通信，但是强烈建议不要这样做，因为它会产生大量开销。分布式对象更适合与其他进程进行通信，尽管在这些进程之间进行事务的开销也很高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;锁&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;线程安全&lt;/strong&gt;就是在同一时刻，对同一个数据操作的线程只有一个。这时就用到了锁。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**锁：**是保证线程安全的同步工具。每一个线程在访问数据资源之前，要先获取(acquire)锁，然后在访问结束之后释放(release)锁。如果锁已经被占用，其它要获取锁的线程会等待，直到锁重新可用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;iOS中的锁分为&lt;strong&gt;互斥锁&lt;/strong&gt;、&lt;strong&gt;自旋锁&lt;/strong&gt;、&lt;strong&gt;信号量&lt;/strong&gt;这三种。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;互斥锁&lt;/h3&gt;
&lt;p&gt;就是在多线程编程中，防止两条线程同时对同一公共资源(比如全局变量)进行读写。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@synchronized&lt;/code&gt;：是递归锁&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;synchronzied&lt;/code&gt;的每个对象，&lt;code&gt;runtime&lt;/code&gt;都会为其分配一个递归锁并存储在哈希表中。&lt;/li&gt;
&lt;li&gt;如果在&lt;code&gt;synchronzied&lt;/code&gt;内部对象被释放或为nil，会执行类似&lt;code&gt;objc_sync_nil&lt;/code&gt;的空方法。&lt;/li&gt;
&lt;li&gt;注意不要像&lt;code&gt;synchronzied&lt;/code&gt;传入&lt;code&gt;nil&lt;/code&gt; , 这将会从代码中移走线程安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;NSLock：&lt;/code&gt;遵循 NSLocking 协议，&lt;code&gt;lock&lt;/code&gt;方法是加锁，&lt;code&gt;unlock&lt;/code&gt;是解锁，&lt;code&gt;tryLock&lt;/code&gt;是尝试加锁，如果失败的话返回 NO，&lt;code&gt;lockBeforeDate&lt;/code&gt;: 是在指定Date之前尝试加锁，如果在指定时间之前都不能加锁，则返回NO。注意不能多次调用lock方法，会造成死锁。&lt;/p&gt;
&lt;h3&gt;自旋锁&lt;/h3&gt;
&lt;p&gt;线程会反复检查锁变量是否可用，线程在这一过程中保持执行，是一种忙等待，一旦获取了自旋锁，线程会一直保持该锁，直至显式释放自旋锁，自旋锁避免了进程上下文的调度开销，因此对于线程只会阻塞很短时间的场合是有效的。&lt;code&gt;atomic&lt;/code&gt;就是通过个set和get方法添加一个自旋锁。&lt;/p&gt;
&lt;h3&gt;信号量&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;dispatch_semaphore&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1). 通过&lt;code&gt;dispatch_semaphore_create(value)&lt;/code&gt;创建一个信号量，初始为1。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2). 等待信号量&lt;code&gt;dispatch_semaphore_wait&lt;/code&gt;，可以理解为lock加锁，会使信号量-1。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3). 发送信号量&lt;code&gt;dispatch_semaphore_signal&lt;/code&gt;，可以理解为 unlock解锁，会使 &lt;code&gt;signal&lt;/code&gt; 信号量+1。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;互斥锁是 &lt;code&gt;dispatch_semaphore&lt;/code&gt;在取值0/1时的特例。信号量可以有更多的取值空间，用来实现更加复杂的同步，而不仅仅是线程间互斥。&lt;/p&gt;
&lt;h1&gt;3 通知，能不能跨线程&lt;/h1&gt;
&lt;p&gt;不能跨线程，在哪个线程发送的通知，就会在哪个线程接收。所以需要手动切换到主线程更新UI。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;测试过程&lt;/code&gt;发出通知的线程决定接收通知处理代码线程
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;主线程&lt;/code&gt;发通知 — &lt;code&gt;子线程&lt;/code&gt;监听通知：接收通知代码在主线程处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;子线程&lt;/code&gt;发通知 — &lt;code&gt;主线程&lt;/code&gt;监听通知：接收通知代码在子线程处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4&lt;/h1&gt;
</content:encoded></item><item><title>面试题记录-C语言</title><link>https://blog.lab-lawliet.app/posts/article-c-ffaa0fc76c7390bf/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-c-ffaa0fc76c7390bf/</guid><description>下面的输出结果是什么?</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;001_int *ptr=(int *)(&amp;amp;a+1)&lt;/h2&gt;
&lt;p&gt;下面的输出结果是什么?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int main()
{
	int a[5]={1,2,3,4,5};
	int *ptr=(int *)(&amp;amp;a+1);
	printf(&quot;%d,%d\n&quot;,*(a+1),*(ptr-1));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;*(a+1)
&lt;ul&gt;
&lt;li&gt;这里吧&lt;code&gt;a&lt;/code&gt;当成一个指针&lt;/li&gt;
&lt;li&gt;a+1 = a + sizeof(int)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a+1&lt;/code&gt;指向&lt;code&gt;a&lt;/code&gt;的下一个整形地址即 &lt;code&gt;&amp;amp;a[1]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(&amp;amp;a+1)
&lt;ul&gt;
&lt;li&gt;先获取a的地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+1&lt;/code&gt;再获取下一个与&lt;code&gt;a&lt;/code&gt;同类型(&lt;code&gt;int[5]&lt;/code&gt;)的相邻地址&lt;/li&gt;
&lt;li&gt;&amp;amp;a+1 = &amp;amp;a + sizeof(a)&lt;/li&gt;
&lt;li&gt;即&amp;amp;a[5],a最后一个元素的下一个地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;*(ptr-1)
&lt;ul&gt;
&lt;li&gt;同*(a+1)原理&lt;/li&gt;
&lt;li&gt;指向了&amp;amp;a[5]前一个元素地址&lt;/li&gt;
&lt;li&gt;即&amp;amp;a[4]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;结果
&lt;ul&gt;
&lt;li&gt;2,5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>知识点梳理：网络</title><link>https://blog.lab-lawliet.app/posts/article-ccb779d078b4f4ac/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-ccb779d078b4f4ac/</guid><description>使用非对称加密是为了后面客户端生成的Pre-master secret密钥的安全，通过上面的步骤能得知，服务器向客户端发送公钥证书这一步是有可能被别人拦截的，如果使用对称加密的话，在客户端向服务端发送Pre-master secret密钥的时候，被黑客拦截的话，就能够使用公钥进行解码，就无法保证Pr</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一、 HTTPS 和 HTTP&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/45ee565b0eea981f.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.1 HTTPS协议 = HTTP协议 + SSL/TLS协议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SSL&lt;/code&gt;的全称是&lt;code&gt;Secure Sockets Layer&lt;/code&gt;，即安全套接层协议，是为网络通信提供安全及数据完整性的一种安全协议。&lt;/li&gt;
&lt;li&gt;TLS的全称是&lt;code&gt;Transport Layer Security&lt;/code&gt;，即安全传输层协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 即HTTPS是安全的HTTP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https&lt;/code&gt;, 全称&lt;code&gt;Hyper Text Transfer Protocol Secure&lt;/code&gt;，相比&lt;code&gt;http&lt;/code&gt;，多了一个&lt;code&gt;secure&lt;/code&gt;，这一个&lt;code&gt;secure&lt;/code&gt;是怎么来的呢？&lt;/li&gt;
&lt;li&gt;这是由&lt;code&gt;TLS（SSL）&lt;/code&gt;提供的！大概就是一个叫&lt;code&gt;openSSL&lt;/code&gt;的&lt;code&gt;library&lt;/code&gt;提供的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https&lt;/code&gt;和&lt;code&gt;http&lt;/code&gt;都属于&lt;code&gt;application layer&lt;/code&gt;，基于TCP（以及UDP）协议，但是又完全不一样。&lt;/li&gt;
&lt;li&gt;TCP用的port是80， https用的是443（值得一提的是，google发明了一个新的协议，叫QUIC，并不基于TCP，用的port也是443， 同样是用来给https的。谷歌好牛逼啊。）总体来说，https和http类似，但是比http安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.3 HTTPS的握手流程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1：客户端发起HTTPS请求，将SSL协议版本的信息发送给服务端。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2：服务器收到之后，会以Server Hello 报文作为应答。和客户端一样，报文中包含客户端支持的SSL的版本，加密组件列表。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3：服务器发送Certificate报文。报文中包含公开密钥证书。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4：然后服务器发送Server Hello Done报文通知客户端，最初阶段的SSL握手协商部分结束&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5：SSL第一次握手结束之后，客户端以Client Key Exchange报文作为会议。报文中包含通信加密中使用的一种被称为Pre-master secret的随机密码串&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6：接着客户端发送Change Cipher Space报文。该报文会提示服务器，在次报文之后的通信会采用Pre-master secret密钥加密&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7：客户端发送Finished 报文。该报文包含链接至今全部报文的整体校验值。这次握手协商是否能够成功，要以服务器是否能够正确揭秘该报文作为判定标准&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;8：服务器同样发送Change Cipher Space报文。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;9：服务器同样发送Finished报文。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10：服务器和客户端的Finished报文交换完毕之后，SSL连接建立完成，从此开始HTTP通信，通信的内容都使用Pre-master secret加密。然后开始发送HTTP请求&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;11：应用层收到HTTP请求之后，发送HTTP响应&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;12：最后有客户端断开连接&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.4 为什么密钥的传递需要使用非对称加密？&lt;/h3&gt;
&lt;p&gt;使用非对称加密是为了后面客户端生成的&lt;code&gt;Pre-master secret&lt;/code&gt;密钥的安全，通过上面的步骤能得知，服务器向客户端发送公钥证书这一步是有可能被别人拦截的，如果使用对称加密的话，在客户端向服务端发送&lt;code&gt;Pre-master secret&lt;/code&gt;密钥的时候，被黑客拦截的话，就能够使用公钥进行解码，就无法保证&lt;code&gt;Pre-master secret&lt;/code&gt;密钥的安全了&lt;/p&gt;
&lt;h3&gt;1.5 对称加密和非对称加密的区别？分别有哪些算法的实现？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;对称加密，加密的加密和解密使用同一密钥。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;非对称加密，使用一对密钥用于加密和解密，分别为公开密钥和私有密钥。公开密钥所有人都可以获得，通信发送方获得接收方的公开密钥之后，就可以使用公开密钥进行加密，接收方收到通信内容后使用私有密钥解密。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对称加密常用的算法实现有AES,ChaCha20,DES,不过DES被认为是不安全的;非对称加密用的算法实现有RSA，ECC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.6 双向认证了解么？&lt;/h3&gt;
&lt;p&gt;上面的HTTPS的通信流程只验证了服务端的身份，而服务端没有验证客户端的身份，双向认证是服务端也要确保客户端的身份，大概流程是客户端在校验完服务器的证书之后，会向服务器发送自己的公钥，然后服务端用公钥加密产生一个新的密钥，传给客户端，客户端再用私钥解密，以后就用此密钥进行对称加密的通信&lt;/p&gt;
&lt;h3&gt;1.7 HTTP2.0 的特性&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;内容安全
&lt;ul&gt;
&lt;li&gt;应为 http2.0 是基于 https 的，天然具有安全特性，通过 http2.0 的特性可 以避免单纯使用 https 的性能下降&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;二进制格式
&lt;ul&gt;
&lt;li&gt;http1.X 的解析是基于文本的，http2.0 将所有的传输信息分割为更小的 消息和帧，并对他们采用二进制格式编码，基于二进制可以让协议有更多的扩展性，比 如引入了帧来传输数据和指令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;多路复用
&lt;ul&gt;
&lt;li&gt;这个功能相当于是长连接的增强，每个 request 请求可以随机的混杂在一 起，接收方可以根据 request 的 id 将 request 再归属到各自不同的服务端请求里面&lt;/li&gt;
&lt;li&gt;另外多路复用中也支持了流的优先级，允许客户端告诉服务器那些内容是更优先级的资源，可以优先传输。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二、 TCP&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;2.1 三次握手&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/22a72fd3d7e3a263.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;TCP是面向连接的协议，建立连接需要经过&lt;code&gt;三次握手&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;客户端向服务端发起请求链接，首先发送&lt;code&gt;SYN&lt;/code&gt;报文，&lt;code&gt;SYN=1，seq=x&lt;/code&gt;,并且客户端进入&lt;code&gt;SYN_SENT&lt;/code&gt;状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务端收到请求链接，服务端向客户端进行回复，并发送响应报文，&lt;code&gt;SYN=1，seq=y,ACK=1,ack=x+1&lt;/code&gt;,并且服务端进入到&lt;code&gt;SYN_RCVD&lt;/code&gt;状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端收到确认报文后，向服务端发送确认报文，&lt;code&gt;ACK=1，ack=y+1&lt;/code&gt;，此时客户端进入到&lt;code&gt;ESTABLISHED&lt;/code&gt;，服务端收到用户端发送过来的确认报文后，也进入到&lt;code&gt;ESTABLISHED&lt;/code&gt;状态，此时链接创建成功&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 四次挥手&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/4a2b48522cb330d0.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;TCP断开连接需要经过 &lt;code&gt;四次挥手&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;客户端向服务端发起关闭链接，并停止发送数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务端收到关闭链接的请求时，向客户端发送回应，我知道了，然后停止接收数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当服务端发送数据结束之后，向客户端发起关闭链接，并停止发送数据&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端收到关闭链接的请求时，向服务端发送回应，我知道了，然后停止接收数据&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 为什么需要三次握手&lt;/h3&gt;
&lt;p&gt;为了防止已失效的连接请求报文段突然又传送到了服务端，因而产生错误，假设这是一个早已失效的报文段。但&lt;code&gt;server&lt;/code&gt;收到此失效的连接请求报文段后，就误认为是&lt;code&gt;client&lt;/code&gt;再次发出的一个新的连接请求。于是就向&lt;code&gt;client&lt;/code&gt;发出确认报文段，同意建立连接。假设不采用“三次握手”，那么只要server发出确认，新的连接就建立了。由于现在&lt;code&gt;client&lt;/code&gt;并没有发出建立连接的请求，因此不会理睬&lt;code&gt;server&lt;/code&gt;的确认，也不会向&lt;code&gt;server&lt;/code&gt;发送数据。但&lt;code&gt;server&lt;/code&gt;却以为新的运输连接已经建立，并一直等待&lt;code&gt;client&lt;/code&gt;发来数据。这样，&lt;code&gt;server&lt;/code&gt;的很多资源就白白浪费掉了。&lt;/p&gt;
&lt;h3&gt;2.4 为什么需要四次挥手&lt;/h3&gt;
&lt;p&gt;因为TCP是全双工通信的，在接收到客户端的关闭请求时，还可能在向客户端发送着数据，因此不能再回应关闭链接的请求时，同时发送关闭链接的请求&lt;/p&gt;
&lt;h3&gt;2.3 TCP/UDP&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TCP&lt;/code&gt;是传输控制协议，具有面向连接、可靠传输、点到点通信、全双工服务等特点，TCP侧重可靠传输。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UDP&lt;/code&gt;是用户数据报协议，具有非连接的、不可靠的、点到多点的通信等特点，UDP侧重快速传输。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.4 TCP/IP协议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TCP/IP&lt;/code&gt;不是一个协议，而是一个协议簇的统称。&lt;/li&gt;
&lt;li&gt;通常使用的网络是在TCP/IP协议簇的基础上运行的。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层&lt;/strong&gt;的&lt;code&gt;HTTP&lt;/code&gt;、&lt;code&gt;DNS&lt;/code&gt;、&lt;code&gt;FTP&lt;/code&gt;，&lt;strong&gt;传输层&lt;/strong&gt;的&lt;code&gt;TCP&lt;/code&gt;(传输控制协议)、&lt;code&gt;UDP&lt;/code&gt;(用户数据报协议)，&lt;strong&gt;网络层&lt;/strong&gt;的&lt;code&gt;IP&lt;/code&gt;等等都属于它内部的一个子集。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.5 TCP报文重点字段&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;序号&lt;/code&gt;：Seq序号，占32位，用来标识从TCP源端向目的端发送的字节流，发起方发送数据时对此进行标记。
&lt;code&gt;确认序号&lt;/code&gt;：Ack序号，占32位，只有ACK标志位为1时，确认序号字段才有效，Ack=Seq+1。
&lt;code&gt;标志位&lt;/code&gt;：共6个，即URG、ACK、PSH、RST、SYN、FIN等，具体含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;（A）&lt;code&gt;URG&lt;/code&gt;：紧急指针（urgent pointer）有效。&lt;/li&gt;
&lt;li&gt;（B）&lt;code&gt;ACK&lt;/code&gt;：确认序号有效。&lt;/li&gt;
&lt;li&gt;（C）&lt;code&gt;PSH&lt;/code&gt;：接收方应该尽快将这个报文交给应用层。&lt;/li&gt;
&lt;li&gt;（D）&lt;code&gt;RST&lt;/code&gt;：重置连接。&lt;/li&gt;
&lt;li&gt;（E）&lt;code&gt;SYN&lt;/code&gt;：发起一个新连接。&lt;/li&gt;
&lt;li&gt;（F）&lt;code&gt;FIN&lt;/code&gt;：释放一个连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.6 为什么不能用两次握手进行连接？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;因为三次握手是为了双方都知道彼此已做好准备，如果改成两次握手，会发生 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;假设客户端给服务端发送连接请求，服务端收到后发送确认连接给客户端，如果两次握手的话此时服务端会认为已经建立了连接，可以发送数据了&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但是如果确认连接数据丢失，客户端还不知道已经建立了连接&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会一直等待确认连接序号，导致数据传输超时&lt;/li&gt;
&lt;li&gt;服务端重复发送同样的数据，造成死锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.7 客户端进入&lt;code&gt;TIME_WAIT&lt;/code&gt;状态而不是直接进入&lt;code&gt;CLOSED&lt;/code&gt;状态是因为&lt;/h3&gt;
&lt;p&gt;我们必须要假想网络是不可靠的，你无法保证你最后发送的&lt;code&gt;ACK报文&lt;/code&gt;一定会被对方收到&lt;/p&gt;
&lt;p&gt;因此对方处于&lt;code&gt;LAST_ACK&lt;/code&gt;状态下的&lt;code&gt;SOCKET&lt;/code&gt;可能会因为超时未收到&lt;code&gt;ACK&lt;/code&gt;报文，而重发FIN报文&lt;/p&gt;
&lt;p&gt;所以这个&lt;code&gt;TIME_WAIT&lt;/code&gt;状态的作用就是用来重发可能丢失的&lt;code&gt;ACK&lt;/code&gt;报文。&lt;/p&gt;
&lt;h2&gt;五、 如何用Charles抓HTTPS的包？其中原理和流程是什么？&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;5.1 流程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先在手机上安装Charles证书&lt;/li&gt;
&lt;li&gt;在代理设置中开启Enable SSL Proxying&lt;/li&gt;
&lt;li&gt;之后添加需要抓取服务端的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5.2 原理&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Charles&lt;/code&gt;作为中间人，对客户端伪装成服务端，对服务端伪装成客户端。简单来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;截获客户端的HTTPS请求，伪装成中间人客户端去向服务端发送HTTPS请求&lt;/li&gt;
&lt;li&gt;接受服务端返回，用自己的证书伪装成中间人服务端向客户端发送数据内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体流程如下图:&lt;a href=&quot;https://juejin.im/post/5c9cbf1df265da60f6731f0a#heading-3&quot;&gt;扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-gold-cdn.xitu.io/2019/12/26/16f41a2f14a97620?imageView2/0/w/1280/h/960/ignore-error/1&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;六、 什么是中间人攻击？&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;中间人攻击就是截获到客户端的请求以及服务器的响应，比如&lt;code&gt;Charles&lt;/code&gt;抓取HTTPS的包就属于中间人攻击。&lt;/p&gt;
&lt;h3&gt;如何避免？&lt;/h3&gt;
&lt;p&gt;避免的方式：客户端可以预埋证书在本地，然后进行证书的比较是否是匹配的&lt;/p&gt;
&lt;h2&gt;八、 Socket&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;1. WebSocket 与 TCP Socket的区别&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Socket&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Socket&lt;/code&gt;是抽象层，并不是一个协议
&lt;ul&gt;
&lt;li&gt;是为了方便使用TCP或UDP而抽象出来的一层，是位于应用层和传输层(TCP/UDP)之间的一组接口。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WebSocket&lt;/code&gt;和HTTP一样，都是应用层协议&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是基于TCP 连接实现的双向通信协议，替代&lt;code&gt;HTTP轮询&lt;/code&gt;的一种技术方案。&lt;/li&gt;
&lt;li&gt;是全双工通信协议，&lt;code&gt;HTTP&lt;/code&gt;是单向的&lt;/li&gt;
&lt;li&gt;注意WebSocket和Socket是完全不同的两个概念。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WebSocket&lt;/code&gt;建立连接时通过HTTP传输，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;但是建立之后，在真正传输时候是不需要HTTP协议的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相对于传统 &lt;code&gt;HTTP&lt;/code&gt; 每次请求-应答都需要客户端与服务端建立连接的模式，&lt;code&gt;WebSocket&lt;/code&gt; 是类似 &lt;code&gt;Socket&lt;/code&gt; 的 &lt;code&gt;TCP 长连接&lt;/code&gt;的通讯模式，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一旦 &lt;code&gt;WebSocket&lt;/code&gt; 连接建立后，后续数据都以帧序列的形式传输。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在客户端断开 &lt;code&gt;WebSocket&lt;/code&gt; 连接或 &lt;code&gt;Server&lt;/code&gt; 端断掉连接前，不需要客户端和服务端重新发起连接请求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在海量并发及客户端与服务器交互负载流量大的情况下，极大的节省了网络带宽资源的消耗，有明显的性能优势，且客户端发送和接受消息是在同一个持久连接上发起，实时性优势明显。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. WebSocket 连接过程 —— 握手过程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.浏览器、服务器建立TCP连接，三次握手。这是通信的基础，传输控制层，若失败后续都不执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.TCP连接成功后，浏览器通过HTTP协议向服务器传送&lt;code&gt;WebSocket&lt;/code&gt;支持的版本号等信息。（开始前的HTTP握手）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.服务器收到客户端的握手请求后，同样采用&lt;code&gt;HTTP&lt;/code&gt;协议回馈数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.当收到了连接成功的消息后，通过&lt;code&gt;TCP&lt;/code&gt;通道进行传输通信。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Socket.IO&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Socket.IO&lt;/code&gt; 是一个为浏览器（客户端）和服务器之间提供实时，双向和基于事件的通信软件库。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Socket.IO&lt;/code&gt; 是把数据传输抽离成 Engine.IO，内部对轮询（Polling）和 WebSocket 等进行了封装，抹平一些细节和平台兼容的问题，提供统一的 API。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意 &lt;code&gt;Socket.IO&lt;/code&gt; 不是 &lt;code&gt;WebSocket&lt;/code&gt; 的实现，只是在必要时使用 &lt;code&gt;WebSocket&lt;/code&gt; 传输数据，并在此基础上会加一些 &lt;code&gt;MetaData&lt;/code&gt;。这就是为什么 &lt;code&gt;WebSocket&lt;/code&gt; 的客户端/服务器 无法和 &lt;code&gt;Socket.IO&lt;/code&gt; 的服务器/客户端进行通信。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;九、 POST和GET&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/POST-GET.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;9.1 POST安全?&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;其实也只是一点点罢了..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;get请求的URL会被缓存在浏览器及Web服务器日志里,可能泄露&lt;/li&gt;
&lt;li&gt;POST没有记录,只要数据服务器不被黑就安全&lt;/li&gt;
&lt;li&gt;抓包的话GG(非HTTPS)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9.2 GET相对POST最大的优势&lt;/h3&gt;
&lt;p&gt;可以手输URL&lt;/p&gt;
&lt;h3&gt;9.3 何时使用GET&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;请求中的URL可以被手动输入&lt;/li&gt;
&lt;li&gt;请求中的URL可以被存在书签里，或者历史里，或者快速拨号里面，或者分享给别人&lt;/li&gt;
&lt;li&gt;请求中的URL是可以被搜索引擎收录的&lt;/li&gt;
&lt;li&gt;带云压缩的浏览器，比如Opera mini/Turbo 2, 只有GET才能在服务器端被预取的&lt;/li&gt;
&lt;li&gt;请求中的URL可以被缓存&lt;/li&gt;
&lt;li&gt;可重复的交互例如: 取个数据,跳个页面用Get
&lt;ul&gt;
&lt;li&gt;不可重复的操作如创建,修改就用POST&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.zhihu.com/question/31640769?rf=37401322&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;十、 网络分层&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;1. 网络层级结构&lt;/h3&gt;
&lt;p&gt;TCP/IP四层结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用层&lt;/li&gt;
&lt;li&gt;传输层&lt;/li&gt;
&lt;li&gt;网络层&lt;/li&gt;
&lt;li&gt;数据链路层&lt;/li&gt;
&lt;li&gt;物理层&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OSI七层网络结构&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用层&lt;/li&gt;
&lt;li&gt;表示层&lt;/li&gt;
&lt;li&gt;会话层&lt;/li&gt;
&lt;li&gt;传输层&lt;/li&gt;
&lt;li&gt;网络层&lt;/li&gt;
&lt;li&gt;数据链路层&lt;/li&gt;
&lt;li&gt;物理层&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 优点&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;面试被问到了,记录下&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;各层之间是独立的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;某一层并不需要知道它的下一层是怎么实现的,而仅仅需要知道该层通过层间接口所提供的接口服务&lt;/li&gt;
&lt;li&gt;由于每一层间只实现相对独立的功能,因而可以将一个难以处理的复杂问题分解为若干个较容易处理的更小的问题,这样问题的复杂度就降低了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;灵活性好&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当某一层发生变化的时候只要层间关系保持不变,则这层的上下层均不受影响,此外,对某一层提供的服务还可以进行修改.当某一层提供的服务不再需要时,甚至可以将这层取消&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结构上可分割开&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;各层都可以采用最合适的技术来实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;易于实现和维护&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这种结构使得实现和调试一个庞大复杂的系统变得容易处理,因为整个系统已经被分解为若干个相对独立的子系统&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;能够促进标准化工作&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为每一层的功能机器提供的服务都已经有了明确的说明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分层是应该注意每一层的功能非常明确,若层数太少,会使得每一层协议太复杂&lt;/li&gt;
&lt;li&gt;层数太多又会在描述和综述各功能的系统工程任务时遇到较多困难&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>知识点梳理：计算机基础</title><link>https://blog.lab-lawliet.app/posts/article-d573197bde9ac595/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-d573197bde9ac595/</guid><description>1:预编译：主要处理以“#”开始的预编译指令。</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1 了解编译的过程么？分为哪几个步骤？&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;1:预编译：主要处理以“#”开始的预编译指令。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2:编译：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;词法分析：将字符序列分割成一系列的记号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语法分析：根据产生的记号进行语法分析生成语法树。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;语义分析：分析语法树的语义，进行类型的匹配、转换、标识等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;中间代码生成：源码级优化器将语法树转换成中间代码，然后进行源码级优化，比如把 1+2 优化为 3。中间代码使得编译器被分为前端和后端，不同的平台可以利用不同的编译器后端将中间代码转换为机器代码，实现跨平台。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目标代码生成：此后的过程属于编译器后端，代码生成器将中间代码转换成目标代码（汇编代码），其后目标代码优化器对目标代码进行优化，比如调整寻址方式、使用位移代替乘法、删除多余指令、调整指令顺序等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3:汇编：汇编器将汇编代码转变成机器指令。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;静态链接：链接器将各个已经编译成机器指令的目标文件链接起来，经过重定位过后输出一个可执行文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;装载：装载可执行文件、装载其依赖的共享对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态链接：动态链接器将可执行文件和共享对象中需要重定位的位置进行修正。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后，进程的控制权转交给程序入口，程序终于运行起来了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>知识点梳理：iOS底层</title><link>https://blog.lab-lawliet.app/posts/article-ios-14ac8616fc81eb2a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-ios-14ac8616fc81eb2a/</guid><description>KVC可以通过Key直接访问对象的属性，可以方便的在运行时对对象的属性进行访问和修改。</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1 KVC原理&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;KVC&lt;/code&gt;可以通过&lt;code&gt;Key&lt;/code&gt;直接访问对象的属性，可以方便的在运行时对对象的属性进行访问和修改。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;需要注意的是:&lt;/strong&gt;
有时我们通过&lt;code&gt;KVC&lt;/code&gt;去修改系统控件未暴露出的属性的时候，会出现部分内部API进行了调整导致崩溃。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;1.1 API&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;forKeyPath&lt;/code&gt; 用于访问对象内对象属性的属性，如：&lt;code&gt;user.name&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.2 写过程&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;查看&lt;code&gt;setKey:&lt;/code&gt;方法是否存在, 如果存在直接调用, 如果不存在进入下一步&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看&lt;code&gt;_setKey:&lt;/code&gt;方法是否存在, 如果存在直接调用, 如果不存在进入下一步&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看&lt;code&gt;+ (BOOL)accessInstanceVariablesDirectly&lt;/code&gt;方法的返回值, 默认返回&lt;code&gt;YES&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YES&lt;/code&gt;: 可以访问成员变量, 进入下一步&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NO&lt;/code&gt;: 不可以访问成员变量, 同时调用&lt;code&gt;- (void)setValue:(id)value forUndefinedKey:(NSString *)key&lt;/code&gt;方法, 如果方法不存在会抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用成员变量:&lt;code&gt;_key&lt;/code&gt;, &lt;code&gt;_isKey&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;isKey&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用顺序, 从左到右, 只有发现存在成员变量, 就不会在调用后续变量&lt;/li&gt;
&lt;li&gt;如果没有成员变量, 会调用&lt;code&gt;- (void)setValue:(id)value forUndefinedKey:(NSString *)key&lt;/code&gt;方法, 如果方法不存在会抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1.3 读过程&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;判断是否有这几个方法: &lt;code&gt;getKey&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;isKey&lt;/code&gt;, &lt;code&gt;_key&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从左到右, 如果有方法, 直接调用, 取值结束&lt;/li&gt;
&lt;li&gt;如果没有进入下一步&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;调用+ (BOOL)accessInstanceVariablesDirectly查看是否可以访问成员变量. 默认&lt;code&gt;YES&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YES&lt;/code&gt;: 可以访问成员变量, 进入下一步&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NO&lt;/code&gt;: 不可以访问成员变量, 判断是否实现&lt;code&gt;- (id)valueForUndefinedKey:(NSString *)key&lt;/code&gt;方法, 实现时调用, 未实现报错&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断是否有这几个成员变量: &lt;code&gt;_key&lt;/code&gt;, &lt;code&gt;_isKey&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;isKey&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从左到右, 如果有成员变量, 直接访问, 取值结束&lt;/li&gt;
&lt;li&gt;如果没有这几个成员变量, 直接进入下一步&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;判断是否实现&lt;code&gt;- (id)valueForUndefinedKey:(NSString *)key&lt;/code&gt;方法, 实现时调用, 未实现报错&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1.4 如何禁用KVC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果重写了类方法 &lt;code&gt;+ (BOOL)accessInstanceVariablesDirectly&lt;/code&gt; 返回 &lt;code&gt;NO&lt;/code&gt; 的话，那么会直接调用 &lt;code&gt;valueForUndefinedKey:&lt;/code&gt; 方法，默认是抛出异常&lt;/p&gt;
&lt;h1&gt;2 讲一下atomic的实现机制；为什么不能保证绝对的线程安全（最好可以结合场景来说）？&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;A: atomic的实现机制与保证范围&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;atomic&lt;/code&gt; 是 Objective-C 属性的修饰符，也是默认选项。对于编译器自动生成的 &lt;code&gt;getter/setter&lt;/code&gt;，它保证单次读取或单次赋值的原子性，读取或写入的属性值是完整的。不能理解成“写安全，读不安全”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;底层实现会根据属性类型、内存管理语义、编译器和 Runtime 版本选择相应的访问方式。以 &lt;code&gt;atomic strong/copy&lt;/code&gt; 对象属性为例，自动生成的访问器可以走 &lt;code&gt;objc_getProperty&lt;/code&gt;、&lt;code&gt;objc_setProperty&lt;/code&gt; 或 &lt;code&gt;objc_setProperty_atomic&lt;/code&gt; 等 Runtime 函数。下面的锁机制说明针对这些对象属性访问路径，不能直接套用到所有属性；例如 &lt;code&gt;int&lt;/code&gt;、&lt;code&gt;NSInteger&lt;/code&gt; 等简单类型可能通过原子读写实现，不一定调用这些函数或使用这些锁。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;内部使用什么锁？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在 Apple 当前公开的 &lt;code&gt;objc4&lt;/code&gt; 源码中，属性锁存放在 &lt;code&gt;PropertyLocks&lt;/code&gt; 中，类型为 &lt;code&gt;StripedMap&amp;lt;spinlock_t&amp;gt;&lt;/code&gt;。虽然名字叫 &lt;code&gt;spinlock_t&lt;/code&gt;，它实际上是 &lt;code&gt;objc_lock_t&lt;/code&gt; 的别名，在 Darwin 平台底层封装的是 &lt;code&gt;os_unfair_lock&lt;/code&gt;。因此不能只看名字就把它理解成传统的自旋锁，也不能把具体锁类型当作跨版本不变的 API 保证。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Runtime 使用“对象地址 + 成员变量偏移量”计算属性的存储地址 &lt;code&gt;slot&lt;/code&gt;，再根据该地址从 &lt;code&gt;PropertyLocks&lt;/code&gt; 中选择锁。同一个属性的 getter 和 setter 会选中同一把锁，但每次访问分别加锁、解锁，不能共同保护整个 &lt;code&gt;self.count++&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PropertyLocks&lt;/code&gt; 使用有限数量的锁，而不是给每个对象或每个属性单独创建一把锁。不同对象、不同属性的存储地址可能映射到同一把锁，因此可能产生额外的锁竞争。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关于原文的“iOS 8 个、Mac 64 个”：在本次核对的 &lt;code&gt;objc4/main&lt;/code&gt; 源码中，&lt;code&gt;StripedMap&lt;/code&gt; 的条件是 &lt;code&gt;TARGET_OS_IPHONE &amp;amp;&amp;amp; !TARGET_OS_SIMULATOR&lt;/code&gt; 时使用 8 个槽，其余情况使用 64 个槽。因此还需要区分真机与模拟器；这些数量属于该源码版本的实现细节。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;getter 与 setter 的加锁范围&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;对象属性的 atomic getter 在锁内读取并 &lt;code&gt;retain&lt;/code&gt; 对象，解锁后再处理返回值的自动释放，避免读取期间对象被并发替换并释放。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对象属性的 atomic setter 通常先在锁外对新值执行 &lt;code&gt;retain/copy&lt;/code&gt;，在锁内读取旧值并替换属性值，再解锁并释放旧值。锁保护的是属性值的读取或替换，不保护返回对象后对其内容的操作。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是 getter 路径的源码片段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 根据对象地址与成员变量偏移量，找到属性的存储位置。
id *slot = (id *)((char *)self + offset);
spinlock_t&amp;amp; slotlock = PropertyLocks.get()[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
return objc_autoreleaseReturnValue(value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;源码参考（本次核对日期：2026-10-11，&lt;code&gt;main&lt;/code&gt; 分支内容可能更新）：&lt;a href=&quot;https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc-accessors.mm&quot;&gt;objc-accessors.mm：属性访问与锁范围&lt;/a&gt;、&lt;a href=&quot;https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc-os.h&quot;&gt;objc-os.h：spinlock_t 别名&lt;/a&gt;、&lt;a href=&quot;https://github.com/apple-oss-distributions/objc4/blob/main/runtime/Threading/darwin.h&quot;&gt;Threading/darwin.h：底层锁实现&lt;/a&gt;、&lt;a href=&quot;https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc-private.h&quot;&gt;objc-private.h：StripedMap 与槽数量&lt;/a&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个保证针对自动生成的访问方法。直接访问成员变量会绕过这些方法；自行实现 &lt;code&gt;getter/setter&lt;/code&gt; 时，也需要自己实现相应的同步逻辑。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;B: 为什么 atomic 不能保证完整的线程安全？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景一：复合操作 &lt;code&gt;self.count++&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@property (atomic, assign) NSInteger count;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;self.count++&lt;/code&gt; 包含“读取 → 加一 → 写回”三个步骤。&lt;code&gt;atomic&lt;/code&gt; 保护单次读取和单次写入，但不会把整个自增过程作为一个原子操作。&lt;/p&gt;
&lt;p&gt;假设初始值为 &lt;code&gt;0&lt;/code&gt;，两个线程可能按下面的顺序执行：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;线程 A&lt;/th&gt;
&lt;th&gt;线程 B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;读取到 0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;读取到 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;加一并写入 1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;加一并写入 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最终结果为 &lt;code&gt;1&lt;/code&gt;，而预期为 &lt;code&gt;2&lt;/code&gt;，发生了更新丢失。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方式：保护整个“读—改—写”过程。&lt;/strong&gt; 可以使用同一把互斥锁，也可以把相关访问统一安排到同一个串行队列中。&lt;/p&gt;
&lt;p&gt;以下示例假设 &lt;code&gt;countLock&lt;/code&gt; 是提前初始化、供所有相关线程共用且不会被替换的 &lt;code&gt;NSLock&lt;/code&gt; 实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[self.countLock lock];

// 锁必须覆盖整个自增过程，不能只保护最后的写入。
self.count++;

[self.countLock unlock];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他修改 &lt;code&gt;count&lt;/code&gt; 的地方也必须遵守同一套同步规则。若其他读取需要与这段业务操作保持一致，也应使用同一把锁；采用串行队列时，则应统一通过该队列访问。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景二：可变容器的内容修改&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@property (atomic, strong) NSMutableArray *array;

[self.array addObject:obj];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码先通过 &lt;code&gt;getter&lt;/code&gt; 获取数组对象，再调用数组的 &lt;code&gt;addObject:&lt;/code&gt;。&lt;code&gt;atomic&lt;/code&gt; 保护的是属性所保存的对象引用的读取和赋值，不保护数组内部的修改。两个线程同时调用 &lt;code&gt;addObject:&lt;/code&gt; 仍不安全，并发读写数组内容也需要额外同步。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;面试回答示例：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;atomic&lt;/code&gt; 保证自动生成的 getter 和 setter 的单次访问具有原子性，但不保证复合操作或属性所指向对象的线程安全。例如 &lt;code&gt;self.count++&lt;/code&gt; 的读取和写回之间可能被其他线程插入，导致更新丢失；&lt;code&gt;atomic&lt;/code&gt; 的可变数组属性也不能保证并发添加元素安全。需要根据业务操作的范围，用同一把锁或同一个串行队列统一同步。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;参考：&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html&quot;&gt;Apple：Encapsulating Data&lt;/a&gt;、&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html&quot;&gt;Apple：Declared Properties&lt;/a&gt;。&lt;/p&gt;
&lt;h1&gt;3 Autoreleasepool所使用的数据结构是什么？AutoreleasePoolPage结构体了解么？&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Autoreleasepool&lt;/code&gt;是由多个&lt;code&gt;AutoreleasePoolPage&lt;/code&gt;以双向链表的形式连接起来的.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Autoreleasepool&lt;/code&gt;的基本原理：在每个自动释放池创建的时候，会在当前的&lt;code&gt;AutoreleasePoolPage&lt;/code&gt;中设置一个标记位，在此期间，当有对象调用&lt;code&gt;autorelsease&lt;/code&gt;时，会把对象添加 &lt;code&gt;AutoreleasePoolPage&lt;/code&gt;中&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若当前页添加满了，会初始化一个新页，然后用双向量表链接起来，并把新初始化的这一页设置为&lt;code&gt;hotPage&lt;/code&gt;,当自动释放池pop时，从最下面依次往上pop，调用每个对象的&lt;code&gt;release&lt;/code&gt;方法，直到遇到标志位。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;AutoreleasePoolPage&lt;/code&gt;结构如下&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AutoreleasePoolPage {
    magic_t const magic;
    id *next;//下一个存放autorelease对象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的线程
    AutoreleasePoolPage * const parent;//父节点
    AutoreleasePoolPage *child;//子节点
    uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
    uint32_t hiwat;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;4 iOS中内省的几个方法？class方法和objc_getClass方法有什么区别?&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;4.1 什么是内省？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在计算机科学中，内省是指计算机程序在运行时（Run time）检查对象（Object）类型的一种能力，通常也可以称作运行时类型检查。
不应该将内省和反射混淆。相对于内省，反射更进一步，是指计算机程序在运行时（Run time）可以访问、检测和修改它本身状态或行为的一种能力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;4.2 iOS中内省的几个方法？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isMemberOfClass&lt;/code&gt; //对象是否是某个类型的对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isKindOfClass&lt;/code&gt; //对象是否是某个类型或某个类型子类的对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isSubclassOfClass&lt;/code&gt; //某个类对象是否是另一个类型的子类&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isAncestorOfObject&lt;/code&gt; //某个类对象是否是另一个类型的父类&lt;/li&gt;
&lt;li&gt;&lt;code&gt;respondsToSelector&lt;/code&gt; //是否能响应某个方法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conformsToProtocol&lt;/code&gt; //是否遵循某个协议*&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4.3 &lt;code&gt;class&lt;/code&gt;方法和&lt;code&gt;object_getClass&lt;/code&gt;方法有什么区别?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;实例&lt;code&gt;class&lt;/code&gt;方法就直接返回&lt;code&gt;object_getClass(self)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;类&lt;code&gt;class&lt;/code&gt;方法直接返回&lt;code&gt;self&lt;/code&gt;，而&lt;code&gt;object_getClass&lt;/code&gt;(类对象)，则返回的是元类&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;5 分类和扩展有什么区别？可以分别用来做什么？分类有哪些局限性？分类的结构体里面有哪些成员？&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;分类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主要用来为某个类添加方法，属性，协议（我一般用来为系统的类扩展方法或者把某个复杂的类的按照功能拆到不同的文件里）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;扩展&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主要用来为某个类原来没有的成员变量、属性、方法。注：方法只是声明（我一般用扩展来声明私有属性，或者把.h的只读属性重写成可读写的）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;分类和扩展的区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;分类是在运行时把分类信息合并到类信息中，而扩展是在编译时，就把信息合并到类中的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类声明的属性，只会生成&lt;code&gt;getter/setter&lt;/code&gt;方法的声明，不会自动生成成员变量和&lt;code&gt;getter/setter&lt;/code&gt;方法的实现，而扩展会&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类不可用为类添加实例变量，而扩展可以&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类可以为类添加方法的实现，而扩展只能声明方法，而不能实现&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;分类的局限性：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;无法为类添加实例变量，但可通过关联对象进行实现，注：关联对象中内存管理没有weak，用时需要注意野指针的问题，可通过其他办法来实现，具体可参考&lt;a href=&quot;http://mrpeak.cn/blog/ios-weak/&quot;&gt;iOS weak 关键字漫谈&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类的方法若和类中原本的实现重名，会覆盖原本方法的实现，注：并不是真正的覆盖&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多个分类的方法重名，会调用最后编译的那个分类的实现&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;分类的结构体里有哪些成员&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct category_t {
    const char *name; //名字
    classref_t cls; //类的引用
    struct method_list_t *instanceMethods;//实例方法列表
    struct method_list_t *classMethods;//类方法列表
    struct protocol_list_t *protocols;//协议列表
    struct property_list_t *instanceProperties;//实例属性列表
    // 此属性不一定真正的存在
    struct property_list_t *_classProperties;//类属性列表
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;6 能不能简述一下 Dealloc 的实现机制&lt;/h1&gt;
&lt;p&gt;Dealloc 的实现机制是内容管理部分的重点，把这个知识点弄明白，对于全方位的理解内存管理的只是很有 必要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.Dealloc 调用流程&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.首先调用 &lt;code&gt;_objc_rootDealloc() &lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.接下来调用 &lt;code&gt;rootDealloc() &lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.这时候会判断是否可以被释放，判断的依据主要有 5 个，判断是否有以上五种情况&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NONPointer_ISA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;weakly_reference&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_assoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_cxx_dtor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_sidetable_rc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4-1.如果有以上五中任意一种，将会调用 &lt;code&gt;object_dispose()&lt;/code&gt;方法，做下一步的处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4-2.如果没有之前五种情况的任意一种，则可以执行释放操作，&lt;code&gt;C 函数的 free()&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.执行完毕。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2.object_dispose() 调用流程&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.直接调用 &lt;code&gt;objc_destructInstance()&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.之后调用 C 函数的 free()。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3.&lt;code&gt;objc_destructInstance()&lt;/code&gt; 调用流程&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.先判断 &lt;code&gt;hasCxxDtor&lt;/code&gt;，如果有 C++ 的相关内容，要调用 &lt;code&gt;object_cxxDestruct() &lt;/code&gt;，销毁 C++ 相关的内容。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.再判断 &lt;code&gt;hasAssocitatedObjects&lt;/code&gt;，如果有的话，要调用 &lt;code&gt;object_remove_associations()&lt;/code&gt;， 销毁关联对象的一系列操作。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.然后调用 &lt;code&gt;clearDeallocating()&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.执行完毕。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4.&lt;code&gt;clearDeallocating()&lt;/code&gt; 调用流程&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.先执行 &lt;code&gt;sideTable_clearDellocating()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;2.再执行 &lt;code&gt;weak_clear_no_lock&lt;/code&gt;，在这一步骤中，会将指向该对象的弱引用指针置为 &lt;code&gt;nil&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;3.接下来执行 &lt;code&gt;table.refcnts.eraser()&lt;/code&gt;，从引用计数表中擦除该对象的引用计数。&lt;/li&gt;
&lt;li&gt;4.至此为止，&lt;code&gt;Dealloc&lt;/code&gt; 的执行流程结束。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;7 静态链接了解么？静态库和动态库的区别？&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;静态链接是指将多个目标文件合并为一个可执行文件，直观感觉就是将所有目标文件的段合并。需要注意的是可执行文件与目标文件的结构基本一致，不同的是是否“可执行”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;静态库：链接时完整地拷贝至可执行文件中，被多次使用就有多份冗余拷贝。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态库：链接时不复制，程序运行时由系统动态加载到内存，供程序调用，系统只加载一次，多个程序共用，节省内存。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;8 App网络层有哪些优化策略？&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;优化DNS解析和缓存&lt;/li&gt;
&lt;li&gt;对传输的数据进行压缩，减少传输的数据&lt;/li&gt;
&lt;li&gt;使用缓存手段减少请求的发起次数&lt;/li&gt;
&lt;li&gt;使用策略来减少请求的发起次数，比如在上一个请求未着地之前，不进行新的请求&lt;/li&gt;
&lt;li&gt;避免网络抖动，提供重发机制&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;9 [self class] 与 [super class]&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@&quot;%@&quot;, NSStringFromClass([self class]));
        NSLog(@&quot;%@&quot;, NSStringFromClass([super class]));
    }
return self;
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;self和super的区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;self&lt;/code&gt; 是类的一个隐藏参数，每个方法的实现的第一个参数即为&lt;code&gt;self&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;super并不是隐藏参数，它实际上只是一个**”编译器标示符”**，它负责告诉编译器，当调用方法时，去调用父类的方法，而不是本类中的方法。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在调用&lt;code&gt;[super class]&lt;/code&gt;的时候，&lt;code&gt;runtime&lt;/code&gt;会去调用&lt;code&gt;objc_msgSendSuper&lt;/code&gt;方法，而不是&lt;code&gt;objc_msgSend&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
 
 
/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;
 
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &amp;amp;&amp;amp;  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;code&gt;objc_msgSendSuper&lt;/code&gt;方法中，第一个参数是一个&lt;code&gt;objc_super&lt;/code&gt;的结构体，这个结构体里面有两个变量，一个是接收消息的&lt;code&gt;receiver&lt;/code&gt;，一个是当前类的父类&lt;code&gt;super_class&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;入院考试第一题错误的原因就在这里，误认为&lt;code&gt;[super class]&lt;/code&gt;是调用的&lt;code&gt;[super_class class]&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;objc_msgSendSuper&lt;/code&gt;的工作原理应该是这样的:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;从&lt;code&gt;objc_super&lt;/code&gt;结构体指向的&lt;code&gt;superClass&lt;/code&gt;父类的方法列表开始查找selector，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到后以&lt;code&gt;objc-&amp;gt;receiver&lt;/code&gt;去调用父类的这个&lt;code&gt;selector&lt;/code&gt;。注意，最后的调用者是&lt;code&gt;objc-&amp;gt;receiver&lt;/code&gt;，而不是&lt;code&gt;super_class&lt;/code&gt;！&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;那么&lt;code&gt;objc_msgSendSuper&lt;/code&gt;最后就转变成&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;objc_msgSend(objc_super-&amp;gt;receiver, @selector(class))
 
+ (Class)class {
    return self;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;10 isKindOfClass 与 isMemberOfClass&lt;/h1&gt;
&lt;p&gt;下面代码输出什么？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface Sark : NSObject
 @end
 
 @implementation Sark
 @end
 
 int main(int argc, const char * argv[]) {
@autoreleasepool {
    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
    BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
 
   NSLog(@&quot;%d %d %d %d&quot;, res1, res2, res3, res4);
}
return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先来分析一下源码这两个函数的对象实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (Class)class {
    return self;
}
 
- (Class)class {
    return object_getClass(self);
}
 
Class object_getClass(id obj)
{
    if (obj) return obj-&amp;gt;getIsa();
    else return Nil;
}
 
inline Class 
objc_object::getIsa() 
{
    if (isTaggedPointer()) {
        uintptr_t slot = ((uintptr_t)this &amp;gt;&amp;gt; TAG_SLOT_SHIFT) &amp;amp; TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
    return ISA();
}
 
inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
    return (Class)(isa.bits &amp;amp; ISA_MASK);
}
 
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls-&amp;gt;superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
 
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls-&amp;gt;superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
 
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
 
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先题目中NSObject 和 Sark分别调用了class方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;+ (BOOL)isKindOfClass:(Class)cls&lt;/code&gt;方法内部，会先去获得&lt;code&gt;object_getClass&lt;/code&gt;的类，而&lt;code&gt;object_getClass&lt;/code&gt;的源码实现是去调用当前类的&lt;code&gt;obj-&amp;gt;getIsa()&lt;/code&gt;，最后在&lt;code&gt;ISA()&lt;/code&gt;方法中获得&lt;code&gt;meta class&lt;/code&gt;的指针。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着在&lt;code&gt;isKindOfClass&lt;/code&gt;中有一个循环，先判断&lt;code&gt;class&lt;/code&gt;是否等于&lt;code&gt;meta class&lt;/code&gt;，不等就继续循环判断是否等于&lt;code&gt;super class&lt;/code&gt;，不等再继续取&lt;code&gt;super class&lt;/code&gt;，如此循环下去。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/LGCooci/LGiOSQuestions/blob/master/%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6/isa%E6%B5%81%E7%A8%8B%E5%9B%BE.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;[NSObject class]&lt;/code&gt;执行完之后调用&lt;code&gt;isKindOfClass&lt;/code&gt;，第一次判断先判断&lt;code&gt;NSObject&lt;/code&gt; 和 &lt;code&gt;NSObject&lt;/code&gt;的&lt;code&gt;meta class&lt;/code&gt;是否相等，之前讲到&lt;code&gt;meta class&lt;/code&gt;的时候放了一张很详细的图，从图上我们也可以看出，&lt;code&gt;NSObject&lt;/code&gt;的&lt;code&gt;meta class&lt;/code&gt;与本身不等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着第二次循环判断&lt;code&gt;NSObject&lt;/code&gt;与&lt;code&gt;meta class&lt;/code&gt;的&lt;code&gt;superclass&lt;/code&gt;是否相等。还是从那张图上面我们可以看到：&lt;code&gt;Root class(meta)&lt;/code&gt; 的&lt;code&gt;superclass&lt;/code&gt; 就是 &lt;code&gt;Root class(class)&lt;/code&gt;，也就是&lt;code&gt;NSObject&lt;/code&gt;本身。所以第二次循环相等，于是第一行&lt;code&gt;res1&lt;/code&gt;输出应该为&lt;code&gt;YES&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同理，&lt;code&gt;[Sark class]&lt;/code&gt;执行完之后调用&lt;code&gt;isKindOfClass&lt;/code&gt;，第一次&lt;code&gt;for&lt;/code&gt;循环，&lt;code&gt;Sark&lt;/code&gt;的&lt;code&gt;Meta Class&lt;/code&gt;与&lt;code&gt;[Sark class]&lt;/code&gt;不等，第二次&lt;code&gt;for循环&lt;/code&gt;，&lt;code&gt;Sark&lt;/code&gt; &lt;code&gt;Meta Class的super class&lt;/code&gt; 指向的是 &lt;code&gt;NSObject Meta Class&lt;/code&gt;， 和 &lt;code&gt;Sark Class&lt;/code&gt;不相等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第三次for循环，&lt;code&gt;NSObject Meta Class&lt;/code&gt;的&lt;code&gt;super class&lt;/code&gt;指向的是&lt;code&gt;NSObject Class&lt;/code&gt;，和 &lt;code&gt;Sark Class&lt;/code&gt; 不相等。第四次循环，&lt;code&gt;NSObject Class&lt;/code&gt;的 &lt;code&gt;super class&lt;/code&gt; 指向 &lt;code&gt;nil&lt;/code&gt;， 和 &lt;code&gt;Sark Class&lt;/code&gt;不相等。第四次循环之后，退出循环，所以第三行的&lt;code&gt;res3输出为NO&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果把这里的Sark改成它的实例对象，&lt;code&gt;[sark isKindOfClass:[Sark class]&lt;/code&gt;，那么此时就应该输出&lt;code&gt;YES&lt;/code&gt;了。因为在&lt;code&gt;isKindOfClass&lt;/code&gt;函数中，判断sark的&lt;code&gt;meta class&lt;/code&gt;是自己的元类&lt;code&gt;Sark&lt;/code&gt;，第一次for循环就能输出&lt;code&gt;YES&lt;/code&gt;了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;isMemberOfClass&lt;/code&gt;的源码实现是拿到自己的&lt;code&gt;isa指针&lt;/code&gt;和自己比较，是否相等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二行&lt;code&gt;isa&lt;/code&gt; 指向 &lt;code&gt;NSObject&lt;/code&gt; 的 &lt;code&gt;Meta Class&lt;/code&gt;，所以和 &lt;code&gt;NSObject Class&lt;/code&gt;不相等。第四行，&lt;code&gt;isa&lt;/code&gt;指向&lt;code&gt;Sark&lt;/code&gt;的&lt;code&gt;Meta Class&lt;/code&gt;，和&lt;code&gt;Sark Class&lt;/code&gt;也不等，所以第二行&lt;code&gt;res2&lt;/code&gt;和第四行&lt;code&gt;res4&lt;/code&gt;都输出NO。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;11 Class与内存地址&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;下面的代码会？&lt;code&gt;Compile Error / Runtime Crash / NSLog…?&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
- (void)speak;
@end
@implementation Sark
- (void)speak {
    NSLog(@&quot;my name&apos;s %@&quot;, self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &amp;amp;cls;
    [(__bridge id)obj speak];
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这道题有两个难点。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难点一:&lt;code&gt;obj&lt;/code&gt;调用&lt;code&gt;speak&lt;/code&gt;方法，到底会不会崩溃。&lt;/li&gt;
&lt;li&gt;难点二:如果&lt;code&gt;speak&lt;/code&gt;方法不崩溃，应该输出什么？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;首先需要谈谈隐藏参数self和_cmd的问题。&lt;/strong&gt;
当&lt;code&gt;[receiver message]&lt;/code&gt;调用方法时，系统会在运行时偷偷地动态传入两个隐藏参数&lt;code&gt;self&lt;/code&gt;和&lt;code&gt;_cmd&lt;/code&gt;，之所以称它们为隐藏参数，是因为在源代码中没有声明和定义这两个参数。&lt;code&gt;self&lt;/code&gt;在已经明白了，接下来就来说说&lt;code&gt;_cmd&lt;/code&gt;。&lt;code&gt;_cmd&lt;/code&gt;表示当前调用方法，其实它就是一个方法选择器&lt;code&gt;SEL&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难点一，能不能调用&lt;code&gt;speak&lt;/code&gt;方法？&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;id cls = [Sark class]; 
void *obj = &amp;amp;cls;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案是可以的。&lt;code&gt;obj&lt;/code&gt;被转换成了一个指向&lt;code&gt;Sark Class&lt;/code&gt;的指针，然后使用&lt;code&gt;id&lt;/code&gt;转换成了&lt;code&gt;objc_object&lt;/code&gt;类型。&lt;code&gt;obj&lt;/code&gt;现在已经是一个&lt;code&gt;Sark&lt;/code&gt;类型的实例对象了。当然接下来可以调用speak的方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;难点二，如果能调用&lt;code&gt;speak&lt;/code&gt;，会输出什么呢？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;很多人可能会认为会输出sark相关的信息。这样答案就错误了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;正确的答案会输出&lt;/p&gt;
&lt;p&gt;&lt;code&gt;my name is &amp;lt;ViewController: 0x7ff6d9f31c50&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;内存地址每次运行都不同，但是前面一定是&lt;code&gt;ViewController。why？&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们把代码改变一下，打印更多的信息出来。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSLog(@&quot;ViewController = %@ , 地址 = %p&quot;, self, &amp;amp;self);
 
    id cls = [Sark class];
    NSLog(@&quot;Sark class = %@ 地址 = %p&quot;, cls, &amp;amp;cls);
 
    void *obj = &amp;amp;cls;
    NSLog(@&quot;Void *obj = %@ 地址 = %p&quot;, obj,&amp;amp;obj);
 
    [(__bridge id)obj speak];
 
    Sark *sark = [[Sark alloc]init];
    NSLog(@&quot;Sark instance = %@ 地址 = %p&quot;,sark,&amp;amp;sark);
 
    [sark speak];
 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们把对象的指针地址都打印出来。输出结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ViewController = &amp;lt;ViewController: 0x7fb570e2ad00&amp;gt; , 地址 = 0x7fff543f5aa8
Sark class = Sark 地址 = 0x7fff543f5a88
Void *obj = &amp;lt;Sark: 0x7fff543f5a88&amp;gt; 地址 = 0x7fff543f5a80
 
my name is &amp;lt;ViewController: 0x7fb570e2ad00&amp;gt;
 
Sark instance = &amp;lt;Sark: 0x7fb570d20b10&amp;gt; 地址 = 0x7fff543f5a78
my name is (null)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;objc_msgSendSuper2 解读&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;objc_msgSendSuper2方法入参是一个objc_super *super。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;
 
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &amp;amp;&amp;amp;  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以按viewDidLoad执行时各个变量入栈顺序从高到底为&lt;code&gt;self&lt;/code&gt;, &lt;code&gt;_cmd&lt;/code&gt;, &lt;code&gt;self.class&lt;/code&gt;, &lt;code&gt;self&lt;/code&gt;, &lt;code&gt;obj&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;第一个&lt;code&gt;self&lt;/code&gt;和第二个&lt;code&gt;_cmd&lt;/code&gt;是隐藏参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第三个&lt;code&gt;self.class&lt;/code&gt;和第四个&lt;code&gt;self&lt;/code&gt;是&lt;code&gt;[super viewDidLoad]&lt;/code&gt;方法执行时候的参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在调用&lt;code&gt;self.name&lt;/code&gt;的时候，本质上是&lt;code&gt;self&lt;/code&gt;指针在内存向高位地址偏移一个指针。在32位下面，一&lt;code&gt;个指针是4字节=4*8bit=32bit&lt;/code&gt;。&lt;strong&gt;（64位不一样但是思路是一样的）&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从打印结果我们可以看到，&lt;code&gt;obj&lt;/code&gt;就是&lt;code&gt;cls&lt;/code&gt;的地址。在&lt;code&gt;obj&lt;/code&gt;向上偏移&lt;code&gt;32bit&lt;/code&gt;就到了&lt;code&gt;0x7fff543f5aa8&lt;/code&gt;，这正好是&lt;code&gt;ViewController&lt;/code&gt;的地址。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;所以输出为my name is &lt;code&gt;&amp;lt;ViewController: 0x7fb570e2ad00&amp;gt;&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;至此，&lt;code&gt;Objc&lt;/code&gt;中的对象到底是什么呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实质：&lt;code&gt;Objc&lt;/code&gt;中的对象是一个指向&lt;code&gt;ClassObject&lt;/code&gt;地址的变量，即 &lt;code&gt;id obj = &amp;amp;ClassObject&lt;/code&gt; ， 而对象的实例变量 &lt;code&gt;void *ivar = &amp;amp;obj + offset(N)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;加深一下对上面这句话的理解，下面这段代码会输出什么？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSLog(@&quot;ViewController = %@ , 地址 = %p&quot;, self, &amp;amp;self);
 
    NSString *myName = @&quot;halfrost&quot;;
 
    id cls = [Sark class];
    NSLog(@&quot;Sark class = %@ 地址 = %p&quot;, cls, &amp;amp;cls);
 
    void *obj = &amp;amp;cls;
    NSLog(@&quot;Void *obj = %@ 地址 = %p&quot;, obj,&amp;amp;obj);
 
    [(__bridge id)obj speak];
 
    Sark *sark = [[Sark alloc]init];
    NSLog(@&quot;Sark instance = %@ 地址 = %p&quot;,sark,&amp;amp;sark);
 
    [sark speak];
 
}
ViewController = &amp;lt;ViewController: 0x7fff44404ab0&amp;gt; ,  地址  = 0x7fff56a48a78
Sark class = Sark  地址  = 0x7fff56a48a50
Void *obj = &amp;lt;Sark: 0x7fff56a48a50&amp;gt;  地址 = 0x7fff56a48a48
 
my name is halfrost
 
Sark instance = &amp;lt;Sark: 0x6080000233e0&amp;gt;  地址 = 0x7fff56a48a40
my name is (null)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于加了一个字符串，结果输出就完全变了，&lt;code&gt;[(__bridge id)obj speak]&lt;/code&gt;;这句话会输出&lt;code&gt;“my name is halfrost”&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原因还是和上面的类似。按&lt;code&gt;viewDidLoad&lt;/code&gt;执行时各个变量入栈顺序从高到底为&lt;code&gt;self&lt;/code&gt;，&lt;code&gt;_cmd&lt;/code&gt;，&lt;code&gt;self.class&lt;/code&gt;，&lt;code&gt;self&lt;/code&gt;，&lt;code&gt;myName&lt;/code&gt;，&lt;code&gt;obj&lt;/code&gt;。&lt;code&gt;obj&lt;/code&gt;往上偏移32位，就是&lt;code&gt;myName&lt;/code&gt;字符串，所以输出变成了输出&lt;code&gt;myName&lt;/code&gt;了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;12 iOS开发中的加密方式&lt;/h1&gt;
&lt;p&gt;iOS加密相关算法框架：&lt;code&gt;CommonCrypto&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1:对称加密： &lt;code&gt;DES、3DES、AES&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;加密和解密使用同一个密钥。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;加密解密过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;明文-&amp;gt;密钥加密-&amp;gt;密文&lt;/code&gt;，&lt;/li&gt;
&lt;li&gt;&lt;code&gt;密文-&amp;gt;密钥解密-&amp;gt;明文&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;优点：算法公开、计算量少、加密速度快、加密效率高、适合大批量数据加密；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：双方使用相同的密钥，密钥传输的过程不安全，易被破解，因此为了保密其密钥需要经常更换。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;AES：AES又称高级加密标准，是下一代的加密算法标准，支持128、192、256位密钥的加密，加密和解密的密钥都是同一个。iOS一般使用ECB模式，16字节128位密钥。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AES算法主要包括三个方面：&lt;strong&gt;轮变化&lt;/strong&gt;、&lt;strong&gt;圈数&lt;/strong&gt;和&lt;strong&gt;密钥扩展&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：高性能、高效率、灵活易用、安全级别高。&lt;/li&gt;
&lt;li&gt;缺点：加密与解密的密钥相同，所以前后端利用AES进行加密的话，如何安全保存密钥就成了一个问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;DES：数据加密标准，DES算法的入口参数有三个：&lt;code&gt;Key&lt;/code&gt;、&lt;code&gt;Data&lt;/code&gt;、&lt;code&gt;Mode&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;其中Key为7个字节共56位，是DES算法的工作密钥；Data为8个字节64位，是要被加密或被解密的数据；Mode为DES的工作方式，有两种：&lt;strong&gt;加密、解密&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;缺点：与AES相比，安全性较低。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;3DES：3DES是DES加密算法的一种模式，它使用3条64位的密钥对数据进行三次加密。是DES向AES过渡的加密算法，是DES的一个更安全的变形。它以DES为基本模块，通过组合分组方法设计出分组加密算法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;2.非对称加密:RSA加密&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非对称加密算法需要成对出现的两个密钥，公开密钥(&lt;code&gt;publickey&lt;/code&gt;) 和私有密钥(&lt;code&gt;privatekey&lt;/code&gt;) 。&lt;/li&gt;
&lt;li&gt;**加密解密过程：**对于一个私钥，有且只有一个与之对应的公钥。生成者负责生成私钥和公钥，并保存私钥，公开公钥。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;公钥加密，私钥解密；或者私钥数字签名，公钥验证。公钥和私钥是成对的，它们互相解密。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;1). **对信息保密，防止中间人攻击：**将明文通过接收人的公钥加密，传输给接收人，因为只有接收人拥有对应的私钥，别人不可能拥有或者不可能通过公钥推算出私钥，所以传输过程中无法被中间人截获。只有拥有私钥的接收人才能阅读。&lt;strong&gt;此方法通常用于交换对称密钥&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;2). **身份验证和防止篡改：**权限狗用自己的私钥加密一段授权明文，并将授权明文和加密后的密文，以及公钥一并发送出来，接收方只需要通过公钥将密文解密后与授权明文对比是否一致，就可以判断明文在中途是否被篡改过。&lt;strong&gt;此方法用于数字签名。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;**优点：**加密强度小，加密时间长，常用于数字签名和加密密钥、安全性非常高、解决了对称加密保存密钥的安全问题。&lt;/li&gt;
&lt;li&gt;**缺点：**加密解密速度远慢于对称加密，不适合大批量数据加密。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 哈希算法加密：&lt;code&gt;MD5加密&lt;/code&gt;、&lt;code&gt;.SHA加密&lt;/code&gt;、&lt;code&gt;HMAC加密&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哈希算法加密是通过哈希算法对数据加密，加密后的结果不可逆，即加密后不能再解密。&lt;/li&gt;
&lt;li&gt;**特点：**不可逆、算法公开、相同数据加密结果一致。&lt;/li&gt;
&lt;li&gt;**作用：**信息摘要，信息“指纹”，用来做数据识别的。如：用户密码加密、文件校验、数字签名、鉴权协议。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;MD5加密：**对不同的数据加密的结果都是定长的32位字符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;**.SHA加密：**安全哈希算法，主要适用于数字签名标准(&lt;code&gt;DSS&lt;/code&gt;)里面定义的数字签名算法(DSA)。对于长度小于&lt;code&gt;2^64&lt;/code&gt;位的消息，&lt;code&gt;SHA1&lt;/code&gt;会产生一个160位的消息摘要。当接收到消息的时候，这个消息摘要可以用来验证数据的完整性。在传输的过程中，数据很可能会发生变化，那么这时候就会产生不同的消息摘要。当然除了&lt;code&gt;SHA1&lt;/code&gt;还有&lt;code&gt;SHA256&lt;/code&gt;以及&lt;code&gt;SHA512&lt;/code&gt;等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;**HMAC加密：**给定一个密钥，对明文加密，做两次“散列”，得到的结果还是32位字符串。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;4. Base64加密&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一种编码方式，严格意义上来说不算加密算法。其作用就是将二进制数据编码成文本，方便网络传输。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;用 &lt;code&gt;base64&lt;/code&gt; 编码之后，数据长度会变大，增加了大约 1/3，但是好处是编码后的数据可以直接在邮件和网页中显示；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;虽然 &lt;code&gt;base64&lt;/code&gt; 可以作为加密，但是 &lt;code&gt;base64&lt;/code&gt; 能够逆运算，非常不安全！&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;base64 编码有个非常显著的特点，末尾有个 ‘=’ 号。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1). 将所有字符转化为ASCII码；&lt;/li&gt;
&lt;li&gt;2). 将ASCII码转化为8位二进制；&lt;/li&gt;
&lt;li&gt;3). 将二进制三位一组不足补0，共24位，再拆分成6位一组共四组；&lt;/li&gt;
&lt;li&gt;4). 统一在6位二进制前补两个0到八位；&lt;/li&gt;
&lt;li&gt;5). 将补0后的二进制转为十进制；&lt;/li&gt;
&lt;li&gt;6). 最后从Base64编码表获取十进制对应的Base64编码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;13 App安全，数字签名，App签名，重签名&lt;/h1&gt;
&lt;p&gt;因为应用实际上是一个加壳的ipa文件，但是有可能被砸壳甚至越狱手机下载的ipa包直接就是脱壳的，可以直接反编译，所以不要在&lt;code&gt;plist文件&lt;/code&gt;、项目中的&lt;code&gt;静态文件&lt;/code&gt;中存储关键的信息。所以敏感信息对称加密存储或者就存储到&lt;code&gt;keychain&lt;/code&gt;里。而且加密密钥也要定期更换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数字签名是通过&lt;code&gt;HASH算法&lt;/code&gt;和&lt;code&gt;RSA加密&lt;/code&gt;来实现的。&lt;/strong&gt;
我们将明文数据加上**通过&lt;code&gt;RSA加密&lt;/code&gt;的数据&lt;code&gt;HASH值&lt;/code&gt;**一起传输给对方，对方可以解密拿出&lt;code&gt;HASH值&lt;/code&gt;来进行验证。这个通过RSA加密&lt;code&gt;HASH值&lt;/code&gt;数据，我们称之为数字签名。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;App签名&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.在Mac开发机器上生成一对公钥和私钥，这里称为公钥L，私钥L(L：Local)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.苹果自己有固定的一对公钥和私钥，&lt;strong&gt;私钥&lt;/strong&gt;在苹果后台，&lt;strong&gt;公钥&lt;/strong&gt;在每个iOS设备上。这里称为公钥A，私钥A(A：Apple)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.把&lt;strong&gt;开发机器上的公钥L&lt;/strong&gt;传到苹果后台，用&lt;strong&gt;苹果后台的私钥A&lt;/strong&gt;去签名&lt;strong&gt;公钥L&lt;/strong&gt;。得到一个包含&lt;strong&gt;公钥L&lt;/strong&gt;以及其&lt;strong&gt;签名&lt;/strong&gt;数据证书。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.在苹果后台申请AppID，配置好&lt;strong&gt;设备ID列表&lt;/strong&gt;和&lt;strong&gt;APP可使用的权限&lt;/strong&gt;，再加上第③步的&lt;strong&gt;证书&lt;/strong&gt;，组成的数据用&lt;strong&gt;私钥A&lt;/strong&gt;签名，把数据和签名一起组成一个&lt;code&gt;Provisioning Profile&lt;/code&gt;描述文件，下载到&lt;strong&gt;本地Mac开发机器&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.在开发时，编译完一个APP后，用&lt;strong&gt;本地的私钥L&lt;/strong&gt;对这个APP进行签名，同时把第④步得到的&lt;code&gt;Provisioning Profile&lt;/code&gt;描述文件打包进APP里，文件名为&lt;code&gt;embedded.mobileprovision&lt;/code&gt;，把 APP安装到手机上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;6.在安装时，iOS系统取得证书，通过&lt;strong&gt;系统内置的公钥A&lt;/strong&gt;，去验证&lt;code&gt;embedded.mobileprovision&lt;/code&gt;的&lt;strong&gt;数字签名&lt;/strong&gt;是否正确，里面的证书签名也会再验一遍。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;7.确保了&lt;code&gt;embedded.mobileprovision&lt;/code&gt;里的数据都是苹果授权的以后，就可以取出里面的数据，做各种验证，包括用公钥 L 验证APP签名，验证设备 ID 是否在 ID 列表上，AppID 是否对应得上，权限开关是否跟 APP 里的 Entitlements 对应等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;14 OC数据类型&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/869753-5c06e93492e0f8bb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 基本数据类型&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;C语言基本数据类型（如&lt;code&gt;short、int、float&lt;/code&gt;等）在OC中都不是对象，只是一定字节的内存空间用于存储数值，他们都不具备对象的特性，没有属性方法可以被调用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OC中的基本数据类型：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NSInteger&lt;/code&gt;(相当于long型整数)、&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSUInteger&lt;/code&gt;(相当于unsigned long型整数)、&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CGFloat&lt;/code&gt;(在64位系统相当于double，32位系统相当于float)等。&lt;/li&gt;
&lt;li&gt;他们并不是类，只是用&lt;code&gt;typedef&lt;/code&gt;对基本数据类型进行了重定义，&lt;strong&gt;&lt;code&gt;他们依然只是基本数据类型&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;**枚举类型：**其本质是无符号整数。&lt;/li&gt;
&lt;li&gt;**BOOL类型：**是宏定义，OC底层是使用signed char来代表BOOL。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;② 指针数据类型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;指针数据类型包括：&lt;/strong&gt; &lt;code&gt;类class&lt;/code&gt;、&lt;code&gt;id&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;类class：&lt;/strong&gt;&lt;code&gt;NSString&lt;/code&gt;、&lt;code&gt;NSSet&lt;/code&gt;、&lt;code&gt;NSArray&lt;/code&gt;、&lt;code&gt;NSMutableArray&lt;/code&gt;、&lt;code&gt;NSDictionary&lt;/code&gt;、&lt;code&gt;NSMutableDictionary&lt;/code&gt;、&lt;code&gt;NSValue&lt;/code&gt;、&lt;code&gt;NSNumber(继承NSValue)&lt;/code&gt;等，都是class，创建后便是对象，继承&lt;strong&gt;NSObject&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OC中提供了&lt;code&gt;NSValue&lt;/code&gt;、&lt;code&gt;NSNumber&lt;/code&gt;来封装C语言的基本类型，这样我们就可以让他们具有面向对象的特征了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;id：&lt;/strong&gt;&lt;code&gt;id&lt;/code&gt;是指向Objective-C对象的指针，等价于C语言中的&lt;code&gt;void*&lt;/code&gt;，可以映射任何对象指针指向他，或者映射它指向其他的对象。常见的id类型就是类的delegate属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;集合NSSet和数组NSArray区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;都是存储不同的对象的地址；&lt;/li&gt;
&lt;li&gt;但是NSArray是有序的集合，NSSet是无序的集合，它们俩可以互相转换。&lt;/li&gt;
&lt;li&gt;NSSet会自动删除重复元素。&lt;/li&gt;
&lt;li&gt;集合是一种哈希表，运用散列算法，查找集合中的元素比数组速度更快，但是它没有顺序。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;③ 构造类型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**构造类型包括：**结构体、联合体&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;结构体：&lt;/strong&gt;&lt;code&gt;struct&lt;/code&gt;，将多个基本数据类型的变量组合成一个整体。结构体中访问内部成员用点运算符访问。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;联合体(共用体)：&lt;/strong&gt;&lt;code&gt;union&lt;/code&gt;，有些类似结构体&lt;code&gt;struct&lt;/code&gt;的一种数据结构，联合体(&lt;code&gt;union&lt;/code&gt;)和结构体(&lt;code&gt;struct&lt;/code&gt;)同样可以包含很多种数据类型和变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结构体和联合体的区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;结构体(&lt;code&gt;struct&lt;/code&gt;)中所有变量是“共存”的，同一时刻每个成员都有值，其&lt;code&gt;sizeof&lt;/code&gt;为所以成员的和。&lt;/p&gt;
&lt;p&gt;**优点：**是“有容乃大”，全面；&lt;/p&gt;
&lt;p&gt;**缺点：**是struct内存空间的分配是粗放的，不管用不用，&lt;strong&gt;全分配，会造成内存浪费。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;联合体(&lt;code&gt;union&lt;/code&gt;)中各变量是“互斥”的，同一时刻只有一个成员有值，其&lt;code&gt;sizeof&lt;/code&gt;为最长成员的&lt;code&gt;sizeof&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;**优点：**是内存使用更为精细灵活，也节省了内存空间。&lt;/p&gt;
&lt;p&gt;**缺点：**就是不够“包容”，修改其中一个成员时会覆盖原来的成员值；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;15 property和属性修饰符&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;@property的本质&lt;/strong&gt;是 &lt;code&gt;ivar(实例变量)&lt;/code&gt; + &lt;code&gt;setter&lt;/code&gt; + &lt;code&gt;getter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我们每次增加一个属性时内部都做了什么：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.系统都会在 &lt;code&gt;ivar_list&lt;/code&gt; 中添加一个成员变量的描述；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.在 &lt;code&gt;method_list&lt;/code&gt; 中增加 &lt;code&gt;setter&lt;/code&gt; 与 &lt;code&gt;getter&lt;/code&gt; 方法的描述；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.在属性列表中增加一个属性的描述；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.然后计算该属性在对象中的偏移量；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.给出 &lt;code&gt;setter&lt;/code&gt; 与 &lt;code&gt;getter&lt;/code&gt; 方法对应的实现，在 &lt;code&gt;setter&lt;/code&gt; 方法中从偏移量的位置开始赋值，在 &lt;code&gt;getter&lt;/code&gt; 方法中从偏移量开始取值，为了能够读取正确字节数，系统对象偏移量的指针类型进行了类型强转。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;修饰符：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MRC下：&lt;/strong&gt; &lt;code&gt;assign、retain、copy、readwrite、readonly、nonatomic、atomic&lt;/code&gt;等。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ARC下：&lt;/strong&gt;&lt;code&gt;assign、strong、weak、copy、readwrite、readonly、nonatomic、atomic、nonnull、nullable、null_resettable、_Null_unspecified&lt;/code&gt;等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面分别解释&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;assign&lt;/code&gt;：用于基本数据类型，不更改引用计数。如果修饰对象(对象在堆需手动释放内存，基本数据类型在栈系统自动释放内存)，会导致对象释放后指针不置为nil 出现野指针。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;retain&lt;/code&gt;：和strong一样，释放旧对象，传入的新对象引用计数+1；在MRC中和release成对出现。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;strong&lt;/code&gt;：在ARC中使用，告诉系统把这个对象保留在堆上，直到没有指针指向，并且ARC下不需要担心引用计数问题，系统会自动释放。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;weak&lt;/code&gt;：在被强引用之前，尽可能的保留，不改变引用计数；weak引用是弱引用，你并没有持有它；它本质上是分配一个不被持有的属性，当引用者被销毁(dealloc)时，weak引用的指针会自动被置为nil。&lt;strong&gt;可以避免循环引用。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;copy&lt;/code&gt;：一般用来修饰不可变类型属性字段，如：&lt;code&gt;NSString&lt;/code&gt;、&lt;code&gt;NSArray&lt;/code&gt;、&lt;code&gt;NSDictionary&lt;/code&gt;等。用copy修饰可以防止本对象属性受外界影响，在&lt;code&gt;NSMutableString&lt;/code&gt;赋值给&lt;code&gt;NSString&lt;/code&gt;时，修改前者 会导致 后者的值跟着变化。还有&lt;code&gt;block&lt;/code&gt;也经常使用 copy 修饰符，但是其实在ARC中编译器会自动对block进行copy操作，和strong的效果是一样的。但是在MRC中方法内部的block是在栈区，使用copy可以把它放到堆区。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;readwrite&lt;/code&gt;：可以读、写；编译器会自动生成setter/getter方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;readonly&lt;/code&gt;：只读；会告诉编译器不用自动生成setter方法。属性不能被赋值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nonatomic&lt;/code&gt;：非原子性访问。用nonatomic意味着可以多线程访问变量，会导致读写线程不安全。但是会提高执行性能。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;atomic&lt;/code&gt;：原子性访问。编译器会自动生成互斥锁，对 setter 和 getter 方法进行加锁来保证属性的 赋值和取值 原子性操作是线程安全的，但不包括可变属性的操作和访问。比如我们对数组进行操作，给数组添加对象或者移除对象，是不在atomic的负责范围之内的，所以给被atomic修饰的数组添加对象或者移除对象是没办法保证线程安全的。&lt;strong&gt;原子性访问的缺点是会消耗性能导致执行效率慢。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nonnull&lt;/code&gt;：设置属性或方法参数不能为空，专门用来修饰指针的，不能用于基本数据类型。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nullable&lt;/code&gt;：设置属性或方法参数可以为空。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;null_resettable&lt;/code&gt;：设置属性，get方法不能返回为空，set方法可以赋值为空。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;_Null_unspecified&lt;/code&gt;：设置属性或方法参数不确定是否为空。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;后四个属性应该主要就是为了提高开发规范，提示使用的人应该传什么样的值，如果违反了对规范值的要求，就会有警告。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;weak修饰的对象释放则自动被置为nil的实现原理：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Runtime&lt;/code&gt;维护了一个&lt;code&gt;weak表&lt;/code&gt;，存储指向某个对象的所有&lt;code&gt;weak指针&lt;/code&gt;。&lt;code&gt;weak表&lt;/code&gt;其实是一个&lt;code&gt;hash（哈希）表&lt;/code&gt;，&lt;code&gt;Key&lt;/code&gt;是所指对象的地址，&lt;code&gt;Value&lt;/code&gt;是&lt;code&gt;weak&lt;/code&gt;指针的地址数组（这个地址的值是所指对象的地址）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;weak 的实现原理可以概括一下三步：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1、初始化时：&lt;code&gt;runtime&lt;/code&gt;会调用&lt;code&gt;objc_initWeak&lt;/code&gt;函数，初始化一个新的weak指针指向对象的地址。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2、添加引用时：&lt;code&gt;objc_initWeak&lt;/code&gt;函数会调用 &lt;code&gt;objc_storeWeak()&lt;/code&gt;函数， &lt;code&gt;objc_storeWeak()&lt;/code&gt; 的作用是更新指针指向，创建对应的弱引用表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3、释放时，调用&lt;code&gt;clearDeallocating&lt;/code&gt;函数。&lt;code&gt;clearDeallocating&lt;/code&gt;函数首先根据对象地址获取所有&lt;code&gt;weak&lt;/code&gt;指针地址的数组，然后遍历这个数组把其中的数据设为&lt;code&gt;nil&lt;/code&gt;，最后把这个entry从weak表中删除，最后清理对象的记录。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;16 成员变量&lt;code&gt;ivar&lt;/code&gt;和属性&lt;code&gt;property&lt;/code&gt;的区别，以及不同关键字的作用&lt;/h1&gt;
&lt;p&gt;**成员变量：**成员变量的默认修饰符是&lt;code&gt;@protected&lt;/code&gt;、不会自动生成set和get方法，需要手动实现、不能使用点语法调用，因为没有set和get方法，只能使用&lt;code&gt;-&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;**属性：**属性会默认生成带下划线的成员变量和&lt;code&gt;setter/getter&lt;/code&gt;方法、可以用点语法调用，实际调用的是set和get方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：分类中添加的属性是不会自动生成  &lt;code&gt;setter/getter&lt;/code&gt;方法的，必须要手动添加。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**实例变量：**class类进行实例化出来的对象为实例对象&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键字作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;访问范围关键字&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@public&lt;/code&gt;：声明公共实例变量，在任何地方都能直接访问对象的成员变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@private&lt;/code&gt;：声明私有实例变量，只能在当前类的对象方法中直接访问，子类要访问需要调用父类的get/set方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@protected&lt;/code&gt;：可以在当前类及其子类对象方法中直接访问(系统默认)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@package&lt;/code&gt;：在同一个包下就可以直接访问，比如说在同一个框架。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键字&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@property&lt;/code&gt;：声明属性，自动生成一个以下划线开头的成员变量_propertyName(默认用@private修饰)、属性setter、getter方法的声明、属性setter、getter方法的实现。**注意：**在&lt;code&gt;协议@protocol&lt;/code&gt;中只会生成getter和setter方法的声明，所以不仅需要手动实现getter和setter方法还需要手动定义变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@sythesize&lt;/code&gt;：修改@property自动生成的_propertyName成员变量名，&lt;code&gt;@synthesize propertyName = newName；&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@dynamic&lt;/code&gt;：告诉编译器：属性的 setter 与 getter 方法由用户自己实现，不自动生成。**谨慎使用：**如果对属性赋值取值可以编译成功，但运行会造成程序崩溃，这就是常说的动态绑定。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@interface&lt;/code&gt;：声明类&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@implementation&lt;/code&gt;：类的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@selecter&lt;/code&gt;：创建一个SEL，类成员指针&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@protocol&lt;/code&gt;：声明协议&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@autoreleasepool&lt;/code&gt;：ARC中的自动释放池&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;@end&lt;/code&gt;：类结束&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;17 类簇&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;类簇&lt;/strong&gt;是Foundation框架中广泛使用的设计模式。&lt;strong&gt;类簇在公共抽象超类下对多个私有的具体子类进行分组&lt;/strong&gt;。以这种方式对类进行分组简化了面向对象框架的公共可见体系结构，而不会降低其功能丰富度。&lt;strong&gt;类簇是基于抽象工厂设计模式的&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;常见的类簇有 &lt;code&gt;NSString&lt;/code&gt;、&lt;code&gt;NSArray&lt;/code&gt;、&lt;code&gt;NSDictionary&lt;/code&gt;等。**以数组为例：**不管创建的是可变还是不可变的数组，在&lt;code&gt;alloc&lt;/code&gt;之后得到的类都是 &lt;code&gt;__NSPlaceholderArray&lt;/code&gt;。而当我们 &lt;code&gt;init&lt;/code&gt; 一个不可变的空数组之后，得到的是&lt;code&gt; __NSArray0&lt;/code&gt;；如果有且只有一个元素，那就是 &lt;code&gt;__NSSingleObjectArrayI&lt;/code&gt;；有多个元素的，叫做 &lt;code&gt;__NSArrayI&lt;/code&gt;；&lt;code&gt;init&lt;/code&gt; 出来一个可变数组的话，都是 &lt;code&gt;__NSArrayM&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;可以将抽象基类背后的复杂细节隐藏起来。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;程序员不会需要记住各种创建对象的具体类实现，简化了开发成本，提高了开发效率。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;便于进行封装和组件化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;减少了 if-else 这样缺乏扩展性的代码。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增加新功能支持不影响其他代码。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已有的类簇非常不好扩展。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;我们运用类簇的场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;出现 bug 时，可以通过崩溃报告中的类簇关键字，快速定位 bug 位置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在实现一些固定且并不需要经常修改的事物时，可以高效的选择类簇去实现。例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;针对不同版本，不同机型往往需要不同的设置，这时可以选择使用类簇。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;app 的设置页面这种并不需要经常修改的页面，可以使用类簇去创建大量重复的布局代码。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;18 设计模式&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;创建型模式：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单例模式：
&lt;ul&gt;
&lt;li&gt;在整个应用程序中，共享一份资源。保证在程序运行过程中，一个类只有一个实例，而且该实例只提供一个全局访问点供外界访问，从而方便控制实例个数，节约系统资源。
&lt;ul&gt;
&lt;li&gt;优点是：提供了对唯一实例的受控访问、可扩展、避免频繁创建销毁对象影响性能。&lt;/li&gt;
&lt;li&gt;缺点是：延长了声明周期，一直存在占用内存。如果两个单例循环依赖会造成死锁，所以尽量不去产生单例间的依赖关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;工厂方法模式：
&lt;ul&gt;
&lt;li&gt;通过类继承创建抽象产品，创建一种产品，子类化创建者并重载工厂方法以创建新产品。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;抽象工厂模式：
&lt;ul&gt;
&lt;li&gt;通过对象组合创建抽象产品，可以创建多系列产品，必须修改父类的接口才能支持新的产品。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结构型模式：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代理模式
&lt;ul&gt;
&lt;li&gt;代理用来处理事件的监听和参数传递。&lt;code&gt;@required&lt;/code&gt;修饰必须实现这个协议方法方法，&lt;code&gt;@optional&lt;/code&gt;修饰是可选实现。使用方法时最好先判断方法是否实现&lt;code&gt;respondsToSelector: &lt;/code&gt;，避免找不到方法而崩溃。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;delegate和block、Notification对比优缺点：delegate和block是一对一通信、block比delegate更加简洁清晰，但是如果通信事件较多时delegate运行成本较低且不易造成循环引用；通知适合一对多通信，代码清晰简单，但问题查找溯源会比较困难，并且注册通知要注意在合适的时间移除，避免对野指针发送消息引起崩溃（**注意：**iOS9之后已经做了弱引用处理不需要移除了，之前版本使用不安全引用__unsafe_unretained是为了兼容旧版本）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;类簇：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;见上边 5. 类簇&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;装饰模式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在不必改变原类文件和使用继承的情况下，动态地扩展一个对象的功能。如：&lt;strong&gt;分类&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;享元模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用共享物件，减少同一类对象的大量创建。如：&lt;code&gt;UITableviewCell&lt;/code&gt;复用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;行为型模式：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;观察者模式：
&lt;ul&gt;
&lt;li&gt;其本质上是一种&lt;code&gt;发布-订阅&lt;/code&gt;模型，用来消除具有不同行为的对象之间的耦合，通过这一模式，不同对象可以协同工作。如：&lt;strong&gt;KVO&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;命令模式：
&lt;ul&gt;
&lt;li&gt;是一种将方法调用封装为对象的设计模式，在iOS中具体实现为&lt;strong&gt;NSInvocation&lt;/strong&gt;。下边为&lt;code&gt;NSInvocation&lt;/code&gt;的实现代码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {
    NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(sendMessageWithPhone:WithName:)]; // 方法签名：用来获得方法的返回类型和参数类型
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self; // 目标：接收消息的对象
    invocation.selector = @selector(sendMessageWithPhone:WithName:); // 选择器：被发送的消息, 方法必须和签名中的方法一致。
    
    NSString *phone = @&quot;13512345678&quot;;
    // 注意：设置参数的索引时不能从0开始，因为0已经被self占用，1已经被_cmd占用
    [invocation setArgument:&amp;amp;phone atIndex:2]; // 参数：可以添加任意数量的参数。
    NSString *name = @&quot;Dezi&quot;;
    [invocation setArgument:&amp;amp;name atIndex:3];
    /*
     注：调用invocation的invoke方法，就代表需要执行NSInvocation对象中指定对象的指定方法，并且传递指定的参数
     */
    [invocation invoke];
}
- (void)sendMessageWithPhone:(NSString*)phone WithName:(NSString*)name {
    NSLog(@&quot;电话号=%@, 姓名=%@&quot;,phone, name);
}
// 电话号=13512345678, 姓名Dezi
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;MVC和MVVM算是架构。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;19 架构设计&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;MVC：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;M&lt;/strong&gt; 是数据模型&lt;code&gt;Model&lt;/code&gt;，负责处理数据，以及数据改变时发出通知(&lt;code&gt;Notification、KVO&lt;/code&gt;)，&lt;code&gt;Model&lt;/code&gt;和&lt;code&gt;View&lt;/code&gt;不能直接进行通信，这样会违背MVC设计模式；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;V&lt;/strong&gt; 是视图&lt;code&gt;View&lt;/code&gt;，用来展示界面，和用户进行交互，为了解耦合一般不会直接持有 或者 操作数据层中的数据模型(可以通过&lt;code&gt;action-target&lt;/code&gt;、&lt;code&gt;delegate&lt;/code&gt;、&lt;code&gt;block&lt;/code&gt;等方式解耦)；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; 是控制器&lt;code&gt;Controller&lt;/code&gt;用来调节&lt;code&gt;Model&lt;/code&gt;和&lt;code&gt;View&lt;/code&gt;之间的交互，可以直接与Model还有View进行通信，操作Model进行数据更新，刷新View。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;code&gt;View&lt;/code&gt;、&lt;code&gt;Model&lt;/code&gt;低耦合、高复用、容易维护。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;code&gt;Controller&lt;/code&gt;的代码过于臃肿，如果&lt;code&gt;View&lt;/code&gt;与&lt;code&gt;Model&lt;/code&gt;直接交互会导致&lt;code&gt;View&lt;/code&gt;和&lt;code&gt;Model&lt;/code&gt;之间的耦合性比较大、网络逻辑会加重&lt;code&gt;Controller&lt;/code&gt;的臃肿。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MVVM：&lt;/strong&gt;&lt;code&gt;Model - View - ViewModel&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MVVM&lt;/strong&gt;衍生于&lt;strong&gt;MVC&lt;/strong&gt;，是MVC的一种演进，促进了UI代码和业务逻辑的分离，抽取&lt;code&gt;Controller&lt;/code&gt;中的展示逻辑放到&lt;code&gt;ViewModel&lt;/code&gt;里边。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;M：&lt;/strong&gt; 数据模型&lt;code&gt;Model&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;V：&lt;/strong&gt; 就是&lt;code&gt;View&lt;/code&gt;和&lt;code&gt;Controller&lt;/code&gt;联系到一起，视为是一个组件&lt;code&gt;View&lt;/code&gt;。View和Controller都不能直接引用模型Model，可以引用视图模型ViewModel。ViewController 尽量不涉及业务逻辑，让 ViewModel 去做这些事情。ViewController 只是一个中间人，负责接收 View 的事件、调用 ViewModel 的方法、响应 ViewModel 的变化。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;VM：&lt;/strong&gt;&lt;code&gt;ViewModel&lt;/code&gt;负责封装业务逻辑、网络处理和数据缓存。使用ViewModel会轻微的增加代码量，但是总体上减少了代码的复杂性。&lt;code&gt;ViewModel&lt;/code&gt;之间可以有依赖。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;View&lt;/code&gt;引用&lt;code&gt;ViewModel&lt;/code&gt;，但反过来不行，因为如果VM跟V产生了耦合，不方便复用。即不要在&lt;code&gt;viewModel&lt;/code&gt;中引入&lt;code&gt;#import UIKit.h&lt;/code&gt;，任何视图本身的引用都不应该放在&lt;code&gt;viewModel&lt;/code&gt;中 (&lt;strong&gt;注意：基本要求，必须满足&lt;/strong&gt;)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ViewModel&lt;/code&gt;可以引用&lt;code&gt;Model&lt;/code&gt;，但反过来不行。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;低耦合、可复用、数据流向清晰、而且兼容MVC，便于代码的移植、并且ViewModel可以拆出来独立开发、方便测试。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;
类会增多、&lt;code&gt;ViewModel&lt;/code&gt;会越来越庞大、调用复杂度增加、双向绑定数据会导致问题调试变得困难。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MVVM&lt;/strong&gt;其实是MVC的变种。&lt;code&gt;MVVM&lt;/code&gt;只是帮&lt;code&gt;MVC&lt;/code&gt;中的&lt;code&gt;Controller&lt;/code&gt;瘦身，把一些逻辑代码和网络请求分离出去。不让Controller处理更多的东西，不会变得臃肿，&lt;code&gt;MVVM&lt;/code&gt;和&lt;code&gt;MVC&lt;/code&gt;可以根据实际需求进行灵活选择。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MVVM&lt;/strong&gt; 在使用当中，通常还会利用双向绑定技术，使得&lt;code&gt;Model&lt;/code&gt; 变化时，&lt;code&gt;ViewModel&lt;/code&gt;会自动更新，而&lt;code&gt;ViewModel&lt;/code&gt;变化时，&lt;code&gt;View&lt;/code&gt; 也会自动变化。OC中可以用**RAC(ReactiveCocoa)**函数响应式框架来实现响应式编程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;20 ReactiveCocoa的使用及优缺点&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ReactiveCocoa&lt;/strong&gt;简称RAC，是函数响应式编程框架，因为它具有函数式编程和响应式编程的特性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;由于该框架的编程思想，使得它具有相当魅惑人心的功能，它能实现传统设计模式和事件监听所能实现的功能，比如&lt;strong&gt;KVO、通知、block回调、action、协议&lt;/strong&gt;等等，它的全面性并不是它最为优越的特色，RAC最值得炫耀的是它提供了统一的消息传递机制，这种机制使得它的代码更加的简洁，同一功能代码块更少，这正是符合了我们编程的思想：高聚合、低耦合，它非常适合MVVM设计模式的开发。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;不过它也并不能完全取代传统的编码方式，在多人开发和代码维护方面，RAC还是有着一些让人头痛的问题。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;优点：
&lt;ul&gt;
&lt;li&gt;使用灵活方便、代码简洁、逻辑清晰&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点：
&lt;ul&gt;
&lt;li&gt;维护成本较高、问题溯源困难&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RAC的统一消息传递机制，其所以动作都离不开&lt;code&gt;信号(sigal)&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1). &lt;strong&gt;信号的创建、发送、接收&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 创建  此时为冷信号，并不会被触发
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id&amp;lt;RACSubscribersubscriber) {  
    // 发送信号  
    [subscriber sendNext:@&quot;oh my god&quot;];  
    // 回收资源。注意：手动创建一个signal一定要记得回收资源，不然程序会崩溃
    return [RACDisposable disposableWithBlock:^{  
        NSLog(@&quot;信号发送完成&quot;);  
    }];  
}];  
// 订阅信号后才会变为热信号，可以被触发
[signal subscribeNext:^(id x) {  
    NSLog(@&quot;singalContent:%@&quot;, x);  
}];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2). &lt;strong&gt;RAC的ControlEvents&lt;/strong&gt;
这个方法可以简单的实现监听操作，并且逻辑在其后的&lt;code&gt;block&lt;/code&gt;中处理，而且也能添加手势并进行监听。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[[self.textField rac_signalForControlEvents:UIControlEventEditingDidBegin] subscribeNext:^(id x) {  
    NSLog(@&quot;%@&quot;, x);  
}];  

UITapGestureRecognizer *tap = [UITapGestureRecognizer new];  
[[tap rac_gestureSignal] subscribeNext:^(id x) {  
    NSLog(@&quot;three:%@&quot;, x);  
}];  
[self.view addGestureRecognizer:tap];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3). &lt;strong&gt;RAC的KVO&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[[self.textField rac_valuesAndChangesForKeyPath:@&quot;text&quot; options:NSKeyValueObservingOptionNew observer:self] subscribeNext:^(id x) {  
    NSLog(@&quot;%@&quot;, x);  
}];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;4). &lt;strong&gt;RAC的通知&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil] subscribeNext:^(id x) {  
    NSLog(@&quot;键盘弹起&quot;);  
}];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;5). &lt;strong&gt;RAC的协议&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)viewDidLoad {  
    [super viewDidLoad];  
    // 代理  
    self.textField.delegate = self;  
    [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(id x) {  
        NSLog(@&quot;打印点击信息:%@&quot;, x);  
    }];  
}  
- (void)textFieldDidBeginEditing:(UITextField *)textField {  
    NSLog(@&quot;开始编辑了&quot;);  
}  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;6). &lt;strong&gt;RAC遍历数组和字典&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相当于枚举遍历，但是效率相比更高&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSArray *arr = @[@&quot;1&quot;, @&quot;2&quot;, @&quot;3&quot;, @&quot;4&quot;, @&quot;5&quot;];  
[arr.rac_sequence.signal subscribeNext:^(id x) {  
    NSLog(@&quot;arr : %@&quot;, x);  
}];  
NSDictionary *dic = @{@&quot;name&quot;:@&quot;yangBo&quot;, @&quot;age&quot;:@&quot;19&quot;};  
[dic.rac_sequence.signal subscribeNext:^(id x) {  
    NSLog(@&quot;dic : %@&quot;, x);  
}];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;7). &lt;strong&gt;RAC信号处理（map、filter、combine）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;① 对信号不做处理&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[self.textField rac_textSignal] subscribeNext:^(id x) {  
    NSLog(@&quot;doNothing:%@&quot;, x);  
}];  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;② 对信号进行过滤（filter）
可以对信号进行条件判断是否处理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[[self.textField rac_textSignal] filter:^BOOL(NSString* value) {
    if (value.length 3) {
        return YES;
    }
    return NO;
}] subscribeNext:^(id x) {
    NSLog(@&quot;filter:%@&quot;, x);
}];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;③ 对信号进行映射（map）
映射也可以理解为&lt;strong&gt;转换&lt;/strong&gt;，第一个block返回的是id类型，如果返回&lt;code&gt;&quot;map now&quot;&lt;/code&gt;，就相当于信号转换，第二个block打印的值就是你return的值&lt;code&gt;&quot;map now&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[[self.textField rac_textSignal] map:^id(NSString* value) {
    if (value.length 3) {
        return @&quot;map now&quot;;
    }
    return value;
}] subscribeNext:^(id x) {
    NSLog(@&quot;map:%@&quot;, x);
}];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;④ &lt;strong&gt;信号的联合（combine）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 创建需要联合的信号
RACSignal *firstCombineSignal = [self.textField rac_textSignal];
RACSignal *secondeCombineSignal = [tap rac_gestureSignal];
// 信号联合处理返回self.label的背景色
RAC(self.label, backgroundColor) = [RACSignal combineLatest:@[firstCombineSignal, secondeCombineSignal] reduce:^id(NSString *text, UITapGestureRecognizer * tap){
    // 这里进行信号逻辑判断和处理
    if (text.length == 3 &amp;amp;&amp;amp; tap.state == UIGestureRecognizerStateEnded) {
        return [UIColor redColor];
    }
    return [UIColor cyanColor];
}];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⑤ &lt;strong&gt;信号关联&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RAC(self.label, text) = [self.textField rac_textSignal];
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;21 类的继承，类能否多继承，协议能不能做继承&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OC的类不支持多继承只支持单继承。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;协议可以实现多继承，遵循多个协议即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息的转发也可以实现多继承，但并不建议，维护成本高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;继承和类别在实现中有何区别？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;category&lt;/code&gt; 可以在不获悉、不改变原来代码的情况下往里面添加并且只能添加方法不能删除修改。 如果类别和原来类中的方法产生名称冲突，则类别将覆盖原来的方法，因为类别具有更高的优先级。类别不会影响到其他类与原有类的关系。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;类别主要有3个作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;(1)将类的实现分散到多个不同文件或多个不同框架中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(2)创建对私有方法的前向引用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(3)向对象添加非正式协议。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;继承可以增加，修改或者删除方法，并且可以增加属性。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;22 分类(category)和类扩展(extension)的区别&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;1). 分类实现原理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;Category&lt;/code&gt;编译之后的底层结构是&lt;code&gt;struct category_t&lt;/code&gt;，里面存储着分类的对象方法、类方法、属性、协议信息。在程序运行的时候，&lt;code&gt;runtime&lt;/code&gt;会将&lt;code&gt;Category&lt;/code&gt;的数据，合并到类信息中（类对象、元类对象中）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2). Category和Extension的区别是什么？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;类扩展可以为类添加私有变量和私有方法，在类的源文件中书写，不能被子类继承，类扩展在编译的时候，它的数据就已经包含在类信息中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类可以为类添加方法并且可以被子类继承，因为分类是运行时才会将数据合并到类信息中。但是分类不能直接添加属性，需要借助运行时关联对象。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3). 分类为啥不能添加成员变量?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;从结构体可以知道，有&lt;code&gt;属性列表&lt;/code&gt;，所以分类可以声明属性，但是分类只会生成该属性对应的&lt;code&gt;get和set的声明&lt;/code&gt;，没有去实现该方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结构体没有成员变量列表，所以不能声明成员变量。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;23 如何实现week&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;weak&lt;/code&gt; 该属性定义了一种非拥有关系。为属性设置新值时，设置方法既不持有新值，也不释放旧值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;weak实现原理：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当一个对象被weak指针指向时，这个weak指针会以对象为key，存储到&lt;code&gt;sideTable类&lt;/code&gt;的&lt;code&gt;weak_table&lt;/code&gt;散列表上对应的一个weak指针数组里面。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当一个对象的&lt;code&gt;dealloc&lt;/code&gt;方法被调用时，&lt;code&gt;Runtime&lt;/code&gt;会以&lt;code&gt;obj&lt;/code&gt;为&lt;code&gt;key&lt;/code&gt;，从&lt;code&gt;sideTable&lt;/code&gt;的&lt;code&gt;weak_table&lt;/code&gt;散列表中，找出对应的weak指针列表，然后将里面的weak指针逐个置为&lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;key&lt;/code&gt;是&lt;strong&gt;weak指向的对象内存地址&lt;/strong&gt;，&lt;code&gt;value&lt;/code&gt;是&lt;strong&gt;所有指向该对象的weak指针表&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;24 字典注意事项：setvalue和setobject的区别&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setObject:ForKey&lt;/code&gt;：是&lt;code&gt;NSMutableDictionary&lt;/code&gt;特有的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setValue:ForKey&lt;/code&gt;：是&lt;code&gt;KVC&lt;/code&gt;的主要方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setobject&lt;/code&gt;中的&lt;code&gt;key&lt;/code&gt;和&lt;code&gt;value&lt;/code&gt;可以为nil以外的任何对象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;setValue&lt;/code&gt;中的key只能为字符串，value可以为&lt;code&gt;nil&lt;/code&gt;也可以为空对象&lt;code&gt;[NSNull null]&lt;/code&gt;以及全部对象&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;25 事件传递和响应机制&lt;/h1&gt;
&lt;p&gt;在iOS中只有继承了&lt;code&gt;UIResponder&lt;/code&gt;的对象才能接受并处理事件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事件传递&lt;/strong&gt;：事件的传递是从上到下（父控件到子控件）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;产生触摸事件A后，触摸事件会被添加到由&lt;code&gt;UIApplication&lt;/code&gt;管理的事件队列中(首先接收到事件的是UIApplication)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;UIApplication&lt;/code&gt;会从事件队列中取出最前面的事件(此处假设为触摸事件A)，将&lt;strong&gt;事件对象由上往下传递&lt;/strong&gt;(&lt;code&gt;UIApplication&lt;/code&gt;-&lt;code&gt;keyWindow&lt;/code&gt;-父控件-子控件)，查找最合适的控件处理事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只要事件传递给控件，就会调用自身的&lt;code&gt;hitTest:withEvent:&lt;/code&gt;方法，寻找能够响应事件最合适的view(其内部会调用&lt;code&gt;pointInside:withEvent:&lt;/code&gt;判断触摸点是否在自己身上)。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;响应机制&lt;/strong&gt;：从下到上（顺着响应者链条向上传递：子控件到父控件)
当事件传递到某个控件，但是最终&lt;code&gt;hitTest:withEvent:&lt;/code&gt;没有找到第一响应者，那么该事件会沿着响应者链向上回溯，如果&lt;code&gt;UIWindow实例&lt;/code&gt;和&lt;code&gt;UIApplication实例&lt;/code&gt;都不能处理该事件，则该事件会被丢弃。&lt;/p&gt;
&lt;p&gt;**响应者链条：**在iOS程序中视图的摆放是有前后关系的，一个控件可以放到另一个控件上面或下面，那么用户点击某个控件时是触发上面的控件还是下面的控件呢，这种先后关系构成一个链条就叫响应者链。也可以说，&lt;strong&gt;响应者链是由多个响应者对象连接起来的链条&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果父控件不能接受触摸事件，那么子控件就不可能接收到触摸事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UIView有三种情况不能接收事件：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;不接收用户交互：&lt;code&gt;userInteractionEnabled = NO&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;隐藏：&lt;code&gt;hidden = YES&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;透明：&lt;code&gt;alpha&amp;lt;0.01&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;子视图在父视图之外区域点击是否有效？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;无效**(父视图的clipsToBounds=NO，这样超过父视图bound区域的子视图内容也会显示)，&lt;strong&gt;因为父视图的&lt;code&gt;pointInside:withEvent:&lt;/code&gt;方法会返回NO，就不会向下遍历子视图了。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但是可以通过重写&lt;code&gt;pointInside:withEvent:&lt;/code&gt;来处理。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;26 runloop&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;runloop&lt;/code&gt;：通过系统内部维护的循环进行事件/消息管理的一个对象。runloop实际上就是一个&lt;code&gt;do...while&lt;/code&gt;循环，有任务时开始，无任务时休眠。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;其本质是通过&lt;code&gt;mach_msg()&lt;/code&gt;函数接收、发送消息。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RunLoop 与线程的关系：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RunLoop&lt;/code&gt;的作用就是来管理线程的，当线程的&lt;code&gt;RunLoop&lt;/code&gt;开启后，线程就会在执行完任务后，处于休眠状态，随时等待接受新的任务，不会退出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有主线程的&lt;code&gt;RunLoop&lt;/code&gt;是默认开启的，其他线程的&lt;code&gt;RunLoop&lt;/code&gt;需要手动开启。所以当程序开启后，主线程会一直运行，不会退出。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;runloop 事件循环机制内部流程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img-blog.csdnimg.cn/20191011113020740.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTM0ODAwNzA=,size_16,color_FFFFFF,t_70&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RunLoop主要涉及五个类：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CFRunLoop&lt;/code&gt;：RunLoop对象、&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopMode&lt;/code&gt;：五种RunLoop运行模式、&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopSource&lt;/code&gt;：输入源/事件源，包括&lt;code&gt;Source0&lt;/code&gt; 和 &lt;code&gt;Source1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopTimer&lt;/code&gt;：定时源，就是NSTimer、&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopObserver&lt;/code&gt;：观察者，用来监听RunLoop。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CFRunLoop&lt;/code&gt;：&lt;strong&gt;RunLoop对象&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CFRunLoopMode&lt;/code&gt;：&lt;strong&gt;RunLoop运行模式，有五种：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kCFRunLoopDefaultMode&lt;/code&gt;：默认的运行模式，通常主线程是在这个 Mode 下运行的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UITrackingRunLoopMode&lt;/code&gt;：界面跟踪 Mode，用于 ScrollView 追踪触摸滑动，保证界面滑动时不受其他 Mode 影响。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIInitializationRunLoopMode&lt;/code&gt;：在刚启动 App 时第进入的第一个 Mode，启动完成后就不再使用。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GSEventReceiveRunLoopMode&lt;/code&gt;：接受系统事件的内部 Mode，通常用不到。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kCFRunLoopCommonModes&lt;/code&gt;：是一个伪模式，可以在标记为CommonModes的模式下运行，RunLoop会自动将&lt;code&gt;_commonModeItems&lt;/code&gt;里的 &lt;code&gt;Source&lt;/code&gt;、&lt;code&gt;Observer&lt;/code&gt;、&lt;code&gt;Timer&lt;/code&gt; 同步到具有此标记的Mode里。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopSource&lt;/code&gt;：&lt;strong&gt;输入源/事件源，包括Source0 和 Source1两种：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Source1&lt;/code&gt;：基于mach_Port，处理来自系统内核或其它进程的事件，比如点击手机屏幕。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Source0 &lt;/code&gt;：非基于Port的处理事件，也就是应用层事件，需要手动标记为待处理和手动唤醒RunLoop。&lt;/li&gt;
&lt;li&gt;简单举例：一个APP在前台静止，用户点击APP界面，屏幕表面的事件会先包装成&lt;code&gt;Event&lt;/code&gt;告诉&lt;code&gt;source1(mach_port)&lt;/code&gt;，&lt;code&gt;source1&lt;/code&gt;唤醒&lt;code&gt;RunLoop&lt;/code&gt;将事件&lt;code&gt;Event&lt;/code&gt;分发给&lt;code&gt;source0&lt;/code&gt;，由&lt;code&gt;source0&lt;/code&gt;来处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopTimer&lt;/code&gt;：&lt;strong&gt;定时源&lt;/strong&gt;，就是NSTimer。在预设的时间点唤醒RunLoop执行回调。因为它是基于RunLoop的，因此它不是实时的（就是NSTimer 是不准确的。 因为RunLoop只负责分发源的消息。如果线程当前正在处理繁重的任务，就有可能导致Timer本次延时，或者少执行一次）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;CFRunLoopObserver&lt;/code&gt;：&lt;strong&gt;观察者，用来监听以下时间点：CFRunLoopActivity&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopEntry&lt;/code&gt;：RunLoop准备启动&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopBeforeTimers&lt;/code&gt;：RunLoop将要处理一些Timer相关事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopBeforeSources&lt;/code&gt;：RunLoop将要处理一些Source事件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopBeforeWaiting&lt;/code&gt;：RunLoop将要进行休眠状态,即将由用户态切换到内核态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopAfterWaiting&lt;/code&gt;：RunLoop被唤醒，即从内核态切换到用户态后&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopExit&lt;/code&gt;：RunLoop退出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;kCFRunLoopAllActivities&lt;/code&gt;：监听所有状态&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;各数据结构之间的联系：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1：&lt;code&gt;Runloop&lt;/code&gt;和&lt;code&gt;线程&lt;/code&gt;是&lt;strong&gt;一对一&lt;/strong&gt;的关系&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2：&lt;code&gt;Runloop&lt;/code&gt;和&lt;code&gt;RunloopMode&lt;/code&gt;是&lt;strong&gt;一对多&lt;/strong&gt;的关系&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3：&lt;code&gt;RunloopMode&lt;/code&gt;和&lt;code&gt;RunloopSource&lt;/code&gt;是&lt;strong&gt;一对多&lt;/strong&gt;的关系&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4：&lt;code&gt;RunloopMode&lt;/code&gt;和&lt;code&gt;RunloopTimer&lt;/code&gt;是&lt;strong&gt;一对多&lt;/strong&gt;的关系&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5：&lt;code&gt;RunloopMode&lt;/code&gt;和&lt;code&gt;RunloopObserver&lt;/code&gt;是&lt;strong&gt;一对多&lt;/strong&gt;的关系&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么 main 函数能够保持一直存在且不退出？&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在 main 函数内部会调用 &lt;code&gt;UIApplicationMain&lt;/code&gt; 这样一个函数，而在&lt;code&gt;UIApplicationMain&lt;/code&gt;内部会启动主线程的 &lt;code&gt;runloop&lt;/code&gt;，可以做到有消息处理时，能够迅速从&lt;strong&gt;内核态到用户态&lt;/strong&gt;的切换，立刻唤醒处理，而没有消息处理时通过&lt;strong&gt;用户态到内核态&lt;/strong&gt;的切换进入等待状态，&lt;strong&gt;避免资源占用&lt;/strong&gt;。&lt;strong&gt;因此 main 函数能够一直存在且不退出&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;27 runtime&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;什么是runtime？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;runtime 一套c、c++、汇编编写的API，为OC提供运行时功能。能够将数据类型的确定由编译期推迟到运行时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问1：方法的本质，问2：runtime的消息机制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法的本质其实就是发送消息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;发送消息主要流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;快速查找：&lt;code&gt;objc_msgSend&lt;/code&gt;查找&lt;code&gt;cache_t&lt;/code&gt;缓存消息&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;慢速查找：递归自己和父类查找方法&lt;code&gt;lookUpImpOrForward&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查找不到消息，进行动态方法解析：&lt;code&gt;resolveInstanceMethod&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息快速转发：&lt;code&gt;forwardingTargetForSelector&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;消息慢速转发：消息签名&lt;code&gt;methodSignatureForSelector&lt;/code&gt;和分发&lt;code&gt;forwardInvocation&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最终仍未找到消息：程序crash，报经典错误信息&lt;code&gt;unrecognized selector sent to instance xxx&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SEL是什么？IMP是什么？两者有什么联系？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SEL&lt;/code&gt;是方法编号，即方法名称，在dyld加载镜像时，通过read_image方法加载到内存的表中了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;IMP&lt;/code&gt;是函数实现指针，找IMP就是找函数的过程&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;两者的关系：&lt;/strong&gt;&lt;code&gt;sel&lt;/code&gt;相当于书本的目录标题，&lt;code&gt;imp&lt;/code&gt;就是书本的页码。查找具体的函数就是想看这本书里面具体篇章的内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1). 我们首先知道想看什么，也就是title -sel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2). 然后根据目录对应的页码 -imp&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3). 打开具体的内容 -方法的具体实现&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;runtime应用：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.方法的交换：具体应用拦截系统自带的方法调用（Method Swizzling黑魔法）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.实现给分类增加属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.实现字典的模型和自动转换&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;4.JSPatch替换已有的OC方法实行等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5.aspect 切面编程&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;能否向编译后得到的类中增加实例变量？能否向运行时创建的类中添加实例变量？为什么？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1.不能向编译后得到的类中增加实例变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2.可以向运行时创建的类中添加实例变量。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3.因为编译后的类已经注册在 runtime 中，类结构体中的 &lt;code&gt;objc_ivar_list&lt;/code&gt;&lt;strong&gt;实例变量的链表&lt;/strong&gt;和 &lt;code&gt;instance_size&lt;/code&gt; &lt;strong&gt;实例变量的内存大小&lt;/strong&gt;已经确定，同时&lt;code&gt;runtime&lt;/code&gt;会调用 &lt;code&gt;class_setIvarLayout&lt;/code&gt;或&lt;code&gt;class_setWeakIvarLayout&lt;/code&gt;来处理 &lt;code&gt;strong&lt;/code&gt;、&lt;code&gt;weak&lt;/code&gt;引用，所以不能向存在的类中添加实例变量。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;运行时创建的类是&lt;strong&gt;可以添加实例变量&lt;/strong&gt;，调用&lt;code&gt;class_addIvar&lt;/code&gt;函数。但是得在调用&lt;code&gt;objc_allocateClassPair&lt;/code&gt;之后，&lt;code&gt;objc_registerClassPair&lt;/code&gt;之前，原因同上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Category中添加属性和成员变量的区别&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Category&lt;/code&gt;它的主要作用是在不改变原有类的前提下，动态地给这个类添加一些方法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类的结构体指针中，没有属性列表，只有方法列表。原则上它只能添加方法，不能添加属性(成员变量)，但是可以借助运行时关联对象&lt;code&gt;objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);&lt;/code&gt;、&lt;code&gt;objc_getAssociatedObject(self,@selector(name));&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类中的可以写&lt;code&gt;@property&lt;/code&gt;，但不会生成setter/getter方法声明和实现，也不会生成私有的成员变量，会编译通过，但是引用变量会报错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果分类中有和原有类同名的方法，会优先调用分类中的方法，就是说会忽略原有类的方法，同名方法调用的优先级为 分类 本类 父类，因为方法是放在方法栈中，遵循先进后出原则；&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;28 isa指针&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;isa&lt;/code&gt;是一个&lt;code&gt;Class类型&lt;/code&gt;的指针，其源码结构为&lt;code&gt;isa_t&lt;/code&gt;联合体，在类中以Class对象存在，指向类的地址，大小为8字节(64位)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个实例对象都有&lt;code&gt;isa&lt;/code&gt;的指针指向对象的类。&lt;code&gt;Class&lt;/code&gt;里也有个&lt;code&gt;isa&lt;/code&gt;的指针指向&lt;code&gt;meteClass&lt;/code&gt;(元类)。元类保存了类方法的列表。当类方法被调用时，先会从本身查找类方法的实现，如果没有，元类会向他父类查找该方法。元类（meteClass）也是类，它也是对象，也有isa指针。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;isa的指向&lt;/strong&gt;：对象的&lt;code&gt;isa&lt;/code&gt;指向类，类的&lt;code&gt;isa&lt;/code&gt;指向元类(meta class)，元类&lt;code&gt;isa&lt;/code&gt;指向根元类，根元类的&lt;code&gt;isa&lt;/code&gt;指向本身，形成了一个封闭的内循环。isa可以帮助一个对象找到它的方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;isa指向图中类的继承关系&lt;/strong&gt;：&lt;code&gt;Teacher -&amp;gt; Person -&amp;gt; NSObject -&amp;gt; nil&lt;/code&gt;。这里需要注意的是根元类的父类是 &lt;code&gt;NSObject&lt;/code&gt;，&lt;code&gt;NSObject&lt;/code&gt;的父类是&lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
&lt;h1&gt;29 block&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;什么是Block&lt;/strong&gt;
Block是将函数及其执行上下文封装起来的对象。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么是Block调用&lt;/strong&gt;
Block调用即是函数的调用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Block的几种形式(类型)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Block有三种形式，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;全局Block&lt;/strong&gt;(&lt;code&gt;_NSConcreteGlobalBlock&lt;/code&gt;)：当我们声明一个block时，如果这个block没有捕获外部的变量，那么这个block就位于全局区(已初始化数据(.data)区)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;栈Block&lt;/strong&gt;(&lt;code&gt;_NSConcreteStackBlock&lt;/code&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1). ARC环境下，当我们声明并且定义了一个block，系统默认使用&lt;code&gt;__strong修饰符&lt;/code&gt;，如果该&lt;code&gt;Block&lt;/code&gt;捕获了外部变量，实质上是从&lt;code&gt;__NSStackBlock__&lt;/code&gt;转变到&lt;code&gt;__NSMallocBlock__&lt;/code&gt;的过程，只不过是系统帮我们完成了copy操作，将栈区的block迁移到堆区，延长了Block的生命周期。对于&lt;code&gt;栈block&lt;/code&gt;而言，变量作用域结束，空间被回收。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2). ARC的环境下，如果我们在声明一个block的时候，使用了&lt;code&gt;__weak&lt;/code&gt;或者&lt;code&gt;__unsafe__unretained&lt;/code&gt;的修饰符，那么系统就&lt;strong&gt;不会做copy的操作&lt;/strong&gt;，也就&lt;strong&gt;不会将其迁移到堆区&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;堆Block&lt;/strong&gt;(&lt;code&gt;_NSConcreteMallocBlock&lt;/code&gt;)：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1). 在MRC环境下，我们需要手动调用copy方法才可以将block迁移到堆区&lt;/li&gt;
&lt;li&gt;2). 而在ARC环境下，&lt;code&gt;__strong&lt;/code&gt;修饰的（默认）block捕获了外部变量就会位于堆区，&lt;code&gt;NSMallocBlock&lt;/code&gt;支持&lt;code&gt;retain&lt;/code&gt;、&lt;code&gt;release&lt;/code&gt;，会对其引用计数＋1或 -1。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只有局部变量 - 和定义的属性 才会拷贝到堆区&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1). 存储在程序的数据区域，在 block 内部没有引用任何外部变量。&lt;/p&gt;
&lt;p&gt;2). 使用外部变量并且未进行copy操作的block是栈block。&lt;/p&gt;
&lt;p&gt;3). 对&lt;code&gt;栈block&lt;/code&gt;进行copy操作，就是&lt;code&gt;堆block&lt;/code&gt;。对&lt;code&gt;堆Block&lt;/code&gt;进行copy，将会&lt;code&gt;增加引用计数&lt;/code&gt;。对&lt;code&gt;全局block&lt;/code&gt;进行copy，仍是&lt;code&gt;全局block&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在什么场景下使用__block修饰符呢？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;1). 对截获变量进行赋值操作需要添加&lt;code&gt;__block修饰符&lt;/code&gt;（赋值 != 使用）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;2). 对局部变量（基本数据类型和对象类型）进行赋值需要&lt;code&gt;__block修饰符&lt;/code&gt;。
其内部其实是对该&lt;code&gt;__block对象&lt;/code&gt;进行拷贝，所以通过&lt;code&gt;__block&lt;/code&gt;可以修改被截获变量的值且不会和外部变量互相影响。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;3). 对静态局部变量、全局变量、静态全局变量不需要&lt;code&gt;__block修饰符&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;block 的截获变量特性？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;基本数据类型的局部变量 Block 可以截获其值。&lt;/p&gt;
&lt;p&gt;对于对象类型的局部变量连同所有权修饰符一起截获。&lt;/p&gt;
&lt;p&gt;局部静态变量以指针的形式进行截获。&lt;/p&gt;
&lt;p&gt;全局变量和静态全局变量，block 是不截获的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;weak打破Block循环引用原理&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;block&lt;/code&gt;内部操作的是&lt;code&gt;weakSelf&lt;/code&gt;的指针地址，它和&lt;code&gt;self&lt;/code&gt;是两个不同的指针地址，即 没有直接持有&lt;code&gt;self&lt;/code&gt;，所以可以&lt;code&gt;weakSelf&lt;/code&gt;可以打破&lt;code&gt;self&lt;/code&gt;的循环引用关系 &lt;code&gt;self -block - weakSelf&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Protocol与静态派发、动态派发</title><link>https://blog.lab-lawliet.app/posts/swift-protocol-de1b8f25d81a68b2/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-protocol-de1b8f25d81a68b2/</guid><description>我们定义这样一个Protocol</description><pubDate>Thu, 25 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;一、 场景一&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;我们定义这样一个&lt;code&gt;Protocol&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol DescriptionProtocol {
    var name: String { get }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;添加拓展方法 &lt;code&gt;printDescription&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extension DescriptionProtocol {
    func printDescription() {
        print(&quot;我是：\(name)&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;新建类 &lt;code&gt;Apple&lt;/code&gt; 遵循协议 &lt;code&gt;DescriptionProtocol&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Apple: DescriptionProtocol {
    var name: String {
        return &quot;苹果&quot;
    }
    
    func printDescription() {
        print(&quot;\(name)好好吃啊！&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;新建类 &lt;code&gt;People&lt;/code&gt; 遵循协议 &lt;code&gt;DescriptionProtocol&lt;/code&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class People: DescriptionProtocol {
    var name: String {
        return &quot;人类&quot;
    }
    
    func printDescription() {
        print(&quot;\(name)真有意思！&quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;分别创建实例对象&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let aApple = Apple()
let aPeople = People()

aApple.printDescription()
aPeople.printDescription()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.1 思考&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;🤔思考一下会输出什么呢？














// 这里是调用了Class内实现的方法，而没有调用protocol的拓展实现
aApple.printDescription() // 苹果好好吃啊！
aPeople.printDescription() // 人类真有意思！
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 再通过这样的方法来调用：&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;func showDescription(of: DescriptionProtocol) {
    of.printDescription()
}

showDescription(of: aApple)
showDescription(of: aPeople)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;🤔思考一下会输出什么呢？
















showDescription(of: aApple) // 我是：苹果
showDescription(of: aPeople) // 我是：人类
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;是不是感觉很奇怪？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aApple.printDescription()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;这种方式直接调用了类定义的实例方法，而不是协议中实现的方法，是动态派发的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;showDescription&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;中调用的是Protocol中实现的函数，是静态派发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;printDescription&lt;/code&gt;在协议中和类中虽然同名，但却是毫不相干的函数，存储的区域也各不相同&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二、 场景二&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;我们将函数在协议中声明一下下，又会有什么结果呢？&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol DescriptionProtocol {
    var name: String { get }
    func printDescription()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;苹果好好吃啊！
人类真有意思！
苹果好好吃啊！
人类真有意思！
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aApple.printDescription()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;和场景一是一样的，是动态派发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;showDescription&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;调用了协议中声明的函数，是动态派发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三、 总结&lt;/h2&gt;
&lt;p&gt;由继承而得到的多态表现，本质上是把所有的方法的实现都放在一张&lt;code&gt;表&lt;/code&gt;里，每次调用都必须先在表中查询，可以称之为&lt;code&gt;动态调用&lt;/code&gt;。
因此才有了&lt;code&gt;覆写&lt;/code&gt;的概念：方法被覆写，即当子类调用该方法时，会使用子类的实现而非父类的。&lt;/p&gt;
&lt;p&gt;而协议与类有所不同。协议所定义的方法，也和类一样，是&lt;code&gt;动态调用&lt;/code&gt;的；但那些未出现在定义中，而仅出现于 &lt;code&gt;Extension&lt;/code&gt; 中的方法是&lt;code&gt;静态调用&lt;/code&gt;的，即这个方法属于协议，不属于协议的遵从者。&lt;/p&gt;
&lt;p&gt;性能上也是&lt;code&gt;静态&lt;/code&gt;优于&lt;code&gt;动态&lt;/code&gt;。像是一些高频调用的函数我们可以通过静态的方式来做提高效率。&lt;code&gt;动态调用&lt;/code&gt;虽然步骤会多一些，比如OC的消息转发机制，但是充满灵活性，在业务开发中会经常用到。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/flawless-app-stories/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d&quot;&gt;static-vs-dynamic-dispatch-in-swift&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>寄存器</title><link>https://blog.lab-lawliet.app/posts/article-01-0a8f9f909a6676b5/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/article-01-0a8f9f909a6676b5/</guid><description>高速缓存</description><pubDate>Tue, 23 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;寄存器是什么&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;CPU除了有控制器、运算器还有寄存器。其中寄存器的作用是进行数据的临时存储&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;CPU的运算速度非常快，为了性能CPU在内部开辟一小块零食存储区域，并在进行运算时先将数据从内存复制到这一小块临时区域中，运算时就在这一小块临时存储区域内进行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;高速缓存&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CPU每执行一条指令前都要从内存中将指令读取到CPU内执行。而寄存器的运行速度相比内存读写要快很多，为了性能，CPU还集成了一个&lt;strong&gt;高速缓存存储区域&lt;/strong&gt;当程序在运行时，先将需要执行的指令代码以及数据复制到高速缓存中去（由操作系统完成），CPU直接从高速缓存中依次读取指令来执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ARM64一个寄存器64位，8个字节&lt;/p&gt;
&lt;h1&gt;一：几种寄存器&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/1c1ec4e4de27baca.webp&quot; alt=&quot;各种寄存器&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;浮点寄存器
&lt;ul&gt;
&lt;li&gt;因为浮点数的存储以及运算的特殊性，CPU中专门提供浮点寄存器来处理浮点数
&lt;ul&gt;
&lt;li&gt;浮点寄存器64位：D0-D31 32位：S0-S31&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;向量寄存器
&lt;ul&gt;
&lt;li&gt;先在很多CPU支持向量运算（图形处理用的很多），为了支持向量计算而提供
&lt;ul&gt;
&lt;li&gt;向量寄存器：128位 V0-V31&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;异常状态寄存器&lt;/li&gt;
&lt;li&gt;通用寄存器(数据地址寄存器)
&lt;ul&gt;
&lt;li&gt;ARM64拥有32个64位的通用寄存器X0-X30以及XZR（零寄存器）&lt;/li&gt;
&lt;li&gt;为了兼容32位，所以ARM64拥有W0-W30和WZR30个32位寄存器&lt;/li&gt;
&lt;li&gt;32位寄存器并不是独立存在的。如：W0是X0的低32位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PC寄存器：指令指针寄存器
&lt;ul&gt;
&lt;li&gt;PC寄存器里面的值就是保存的CPU接下来需要执行的指令地址&lt;/li&gt;
&lt;li&gt;改变PC寄存器的值可以改变程序执行的流程&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1.1 通用寄存器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;x0-x7: 用于子程序调用时的参数传递，X0还用于返回值传递&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x0 - x30&lt;/code&gt;是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。 当使用 r0 - r30访问时，它就是一个64位的数。当使用 w0 - w30访问时，访问的是这些寄存器的低32位，如图：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/0d3a4bcf72109f47.webp&quot; alt=&quot;通用寄存器&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.2 CPSR 状态寄存器&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Current Program State Register&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;状态寄存器是32位的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3e05f25ca1001229.webp&quot; alt=&quot;CPSR&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPSR的低8位（包括I、F、T和M[4：0]）称为控制位，程序无法修改，除非CPU运行于&lt;code&gt;特权模式&lt;/code&gt;下，程序才能修改控制位&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N、Z、C、V&lt;/code&gt;均为&lt;code&gt;条件码标志位&lt;/code&gt;。它们的内容可被&lt;code&gt;算术或逻辑运算&lt;/code&gt;的结果所改变，并且可以决定某条指令是否被执行，意义重大&lt;/li&gt;
&lt;li&gt;NZCV是状态寄存器的条件标志位，分别代表运算过程中产生的状态，其中：
&lt;ul&gt;
&lt;li&gt;N, &lt;code&gt;negative condition flag&lt;/code&gt;，一般代表运算结果是负数
&lt;ul&gt;
&lt;li&gt;1 代表结果为负数&lt;/li&gt;
&lt;li&gt;0 代表结果为非负数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Z, &lt;code&gt;zero condition flag&lt;/code&gt;, 指令结果为0时Z=1，否则Z=0&lt;/li&gt;
&lt;li&gt;C, &lt;code&gt;carry condition flag&lt;/code&gt;, 一般情况下进行无符号数的运算
&lt;ul&gt;
&lt;li&gt;加法运算
&lt;ul&gt;
&lt;li&gt;当运算结果产生了进位（无符号溢出）C=1否则C=0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;减法运算（包括CMP）
&lt;ul&gt;
&lt;li&gt;当运算是产生了借位时（无符号溢出）则C=0，否则C=1&lt;/li&gt;
&lt;li&gt;对于位数为N的无符号数来说其对应的二进制的最高位，即第N-1位就是他的最高有效位，而假想存在的第N位就是相对于最高有效位的更高位。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;进位
&lt;ul&gt;
&lt;li&gt;我们知道，当两个数相加的时候，有可能从最高有效位向更高位进位。比如两个32位数据：&lt;code&gt;0xaaaaaaaa&lt;/code&gt; + &lt;code&gt;0xaaaaaaaa&lt;/code&gt;，将会产生进位。但由于进位值在32位中无法保存，我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候并不会丢弃这个进位值，而是记录在一个特殊的寄存器的某一位上。ARM上就用C为来记录这个进位值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;借位
&lt;ul&gt;
&lt;li&gt;当两个数据做减法的时候，有可能向更高位。比如：&lt;code&gt;0x00000000&lt;/code&gt; - &lt;code&gt;0x000000ff&lt;/code&gt;，将产生借位，借位后相当于计算&lt;code&gt;0x100000000&lt;/code&gt; - &lt;code&gt;0x000000ff&lt;/code&gt; = &lt;code&gt;0xffffff01&lt;/code&gt;，由于借了一位，所以C位用来标记借位。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/8a620ae36e2137ae.webp&quot; alt=&quot;Carry&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;V, &lt;code&gt;oVerflow condition flag&lt;/code&gt; 有符号运算有溢出时，V=1
&lt;ul&gt;
&lt;li&gt;正数 + 正数 = 负数 则 溢出&lt;/li&gt;
&lt;li&gt;负数 + 负数 = 正数 则 溢出&lt;/li&gt;
&lt;li&gt;正数 + 负数 不可能溢出&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;二：栈&lt;/h1&gt;
&lt;p&gt;后进先出
ARM64栈开口向底地址，即开辟的地址是朝底地址方向的&lt;/p&gt;
&lt;h2&gt;2.01 SP 和 FP寄存器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SP寄存器在任意时刻会保存栈顶的地址&lt;/li&gt;
&lt;li&gt;FP寄存器也称为X29寄存器属于通用寄存器，但是在某些时候我们利用它保存栈底的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：ARM64开始，取消了32位的LDM、STM、PUSH、POP指令，取而代之的是：ldr、ldp、str、stp。在ARM64中对栈的操作是16字节对齐的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2.02 编译器会根据编译情况开辟固定的栈空间&lt;/h2&gt;
&lt;h2&gt;2.03 函数调用栈&lt;/h2&gt;
&lt;p&gt;常见函数调用开辟和恢复栈空间&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sub sp, sp, #0x40&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;拉伸0x40（64字节）空间&lt;/li&gt;
&lt;li&gt;即sp寄存器地址减0x40&lt;/li&gt;
&lt;li&gt;开辟了64字节空间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stp x29, x30, [sp, #0x30]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;x29、x30寄存器入栈保护&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add x29, sp, #0x30&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;x29指向栈帧的底部&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ldp x29, x30, [sp, #0x30]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;恢复x29、x30寄存器的值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add sp, sp, #0x40&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;栈平衡，用完了要归位&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ret&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.03 内存读写指令&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;读写：数据都是往高地址读写的
即：一段数据一个字节无法完整保存的话，连续的多个地址进行保存，地址递增&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;str 指令 store register
&lt;ul&gt;
&lt;li&gt;将数据从寄存器中读出来，存到内存中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ldr 指令 load register
&lt;ul&gt;
&lt;li&gt;将数据冲内存中读出来，存到寄存器中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ldp stp 可以一次性操作两个寄存器&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.04 bl 和 ret 指令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bl&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;将下一条指令的地址放入&lt;code&gt;lr(x30)&lt;/code&gt;寄存器&lt;/li&gt;
&lt;li&gt;转到标号处执行指令&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ret&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;默认使用&lt;code&gt;lr(x30)&lt;/code&gt;寄存器的值，通过底层指令提示CPU此处作为下条指令地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lr(x30)&lt;/code&gt;寄存器
&lt;ul&gt;
&lt;li&gt;存放的是函数的返回地址，当&lt;code&gt;ret&lt;/code&gt;指令执行时，回去寻找&lt;code&gt;lr(x30)&lt;/code&gt;寄存器保存的地址&lt;/li&gt;
&lt;li&gt;&lt;code&gt;在嵌套调用函数的时候需要将 x30 入栈&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.05 函数的参数和返回值&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ARM64&lt;/code&gt;下，函数的参数是放在&lt;code&gt;x0 ~ x7&lt;/code&gt;（&lt;code&gt;w0 ~ w7&lt;/code&gt;）这8个寄存器中。如果超过八个参数就会入栈。&lt;/li&gt;
&lt;li&gt;函数的返回值会放在&lt;code&gt;X0&lt;/code&gt;寄存器中的。&lt;/li&gt;
&lt;li&gt;函数的局部变量是放在栈里面的&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>顶点着色器与片元着色器</title><link>https://blog.lab-lawliet.app/posts/opengl-01-0217a0e7e9718a0b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/opengl-01-0217a0e7e9718a0b/</guid><description>绘制对应的图片(点、线、三角形)，将图元转化成一组二维片段的过程，就是将顶点数据计算成的图片转化成片元，然后这些片元由片元着色器处理。</description><pubDate>Mon, 15 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;管道渲染流程
&lt;img src=&quot;/content-assets/1c6d6695846d2957.webp&quot; alt=&quot;渲染流程&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一：顶点着色器&lt;/h2&gt;
&lt;h3&gt;1.1 输入&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;着色器程序
&lt;ul&gt;
&lt;li&gt;描述顶点上执行操作的顶点着色器程序源代码/可执行文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;顶点着⾊器输入(属性)
&lt;ul&gt;
&lt;li&gt;用顶点数组提供每个顶点的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;统一变量(uniform)
&lt;ul&gt;
&lt;li&gt;顶点/片元着⾊器使用的不变数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;采样器
&lt;ul&gt;
&lt;li&gt;代表顶点着色器使用纹理的特殊统一变量类型，一般是ID类型，GLSL会根据ID标签查找到纹理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 输出&lt;/h3&gt;
&lt;h4&gt;1.2.1 图元装配&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;图元(Primitive)：
&lt;ul&gt;
&lt;li&gt;点、线、三角形等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;图元装配
&lt;ul&gt;
&lt;li&gt;将顶点数据计算成一个个图元，在这个阶段会执行裁剪、透视分割和Viewport变换操作。即确定顶点形成的图形，点或线或三角形等。&lt;/li&gt;
&lt;li&gt;对于每个图元，必须确定图元是否位于视锥体(屏幕上可见的3D空间区域)内：
&lt;ul&gt;
&lt;li&gt;图元没有完全在视锥体内，可能需要进行裁剪。&lt;/li&gt;
&lt;li&gt;图元完全处于该区域之外，它就会被抛弃&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1.2.2 光栅化&lt;/h4&gt;
&lt;p&gt;绘制对应的图片(点、线、三角形)，将图元转化成一组二维片段的过程，就是将顶点数据计算成的图片转化成片元，然后这些片元由片元着色器处理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/654ad096f5d6405c.webp&quot; alt=&quot;顶点&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1.3 顶点着色器业务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;矩阵变换位置&lt;/li&gt;
&lt;li&gt;计算光照公式生成出顶点颜色&lt;/li&gt;
&lt;li&gt;生成/变换纹理坐标&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二：片元着色器&lt;/h2&gt;
&lt;h3&gt;2.1 输入&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;着色器程序
&lt;ul&gt;
&lt;li&gt;描述片段上执行操作的片元着色器程序源代码/可执行文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;输入变量
&lt;ul&gt;
&lt;li&gt;光栅化单元用插值为每个片段生成的顶点着色器输出&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;统一变量(uniform)
&lt;ul&gt;
&lt;li&gt;顶点或片元着色器使用的不变数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;采样器
&lt;ul&gt;
&lt;li&gt;代表片元着色器使用纹理的特殊统一变量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 插值(Interpolation)：&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;光栅器&lt;/code&gt;在三角形的三个顶点之间进行&lt;code&gt;插值&lt;/code&gt;（或者通过另外一种技术一行一行的插值）并执行片元着色器遍历三角形的每一个像素。
片元着色器会返回光栅器存在&lt;code&gt;颜色缓存&lt;/code&gt;中用于显示的&lt;code&gt;像素颜色值&lt;/code&gt;（在其他一些额外的检测之后，比如：&lt;code&gt;深度测试depth test&lt;/code&gt;等）&lt;/p&gt;
&lt;h3&gt;2.3 片元着色器业务&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;计算颜色&lt;/li&gt;
&lt;li&gt;获取纹理值&lt;/li&gt;
&lt;li&gt;往像素点中填充颜色值、纹理值&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它可以⽤于&lt;code&gt;图⽚&lt;/code&gt;/&lt;code&gt;视频&lt;/code&gt;/&lt;code&gt;图形&lt;/code&gt;中每个像素的颜色填充(比如给视频添加滤镜,实际上就是将视频中每个图片的像素点颜色填充进行修改.)&lt;/p&gt;
&lt;h3&gt;2.4 逐片段操作&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;像素归属测试:
&lt;ul&gt;
&lt;li&gt;确定帧缓存区中位置(Xw,Yw)的像素目前是不是归属于OpenGL ES所有. 例如，如果⼀个显示OpenGL ES帧缓存区View被另外⼀个View所遮蔽.则窗口系统可以确定被遮蔽的像素不属于OpenGL ES上下⽂。从而不全显示这些像素。而像素归属测试是OpenGL ES的一部分，它不由开发者开人为控制，而是由OpenGL ES内部进行.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;裁剪测试:
&lt;ul&gt;
&lt;li&gt;裁剪测试确定(Xw,Yw)是否位于作为OpenGL ES状态的一部分裁剪矩形范围 内。如果该⽚段位于裁剪区域之外，则被抛弃。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;深度测试:
&lt;ul&gt;
&lt;li&gt;输⼊片段的深度值进步比较，确定片段是否拒绝测试&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;混合:
&lt;ul&gt;
&lt;li&gt;混合将新生成的⽚段颜色与保存在帧缓存的位置的颜色值组合起来&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;抖动:
&lt;ul&gt;
&lt;li&gt;抖动可用于最小化因为使用有限精度在帧缓存区中保存颜色值⽽产生的伪像.
&lt;img src=&quot;/content-assets/d7a0576485767f04.webp&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三：EGL (Embedded Graphics Library )&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OpenGL ES&lt;/code&gt;命令需要渲染上下文和绘制表面才能完成图形图像的绘制.
&lt;ul&gt;
&lt;li&gt;渲染上下文:
&lt;ul&gt;
&lt;li&gt;存储相关OpenGL ES状态.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;绘制表面:
&lt;ul&gt;
&lt;li&gt;是用于绘制图元的表面，它指定渲染所需要的缓存区类型，例如颜色缓冲区，深度缓冲区和模板缓冲区.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OpenGL ES API&lt;/code&gt;并没有提供如何创建渲染上下⽂或者上下⽂如何连接到原生窗口系统。 &lt;code&gt;EGL&lt;/code&gt;是&lt;code&gt;Khronos&lt;/code&gt;渲染&lt;code&gt;API&lt;/code&gt;(如&lt;code&gt;OpenGL ES&lt;/code&gt;)和原⽣窗口系统之间的接口。唯一⽀持&lt;code&gt;OpenGL ES&lt;/code&gt;却不⽀持&lt;code&gt;EGL&lt;/code&gt;的平台是&lt;code&gt;iOS&lt;/code&gt;。&lt;code&gt;Apple&lt;/code&gt;提供⾃己的&lt;code&gt;EGL API&lt;/code&gt;的&lt;code&gt;iOS&lt;/code&gt;实现,称为&lt;code&gt;EAGL&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;因为每个窗口系统都有不同的定义，所以&lt;code&gt;EGL&lt;/code&gt;提供基本的不透明类型&lt;code&gt;EGLDisplay&lt;/code&gt;，这个类型封装了所有系统相关性，用于和原生窗口系统接口.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于&lt;code&gt;OpenGL ES&lt;/code&gt;是基于&lt;code&gt;C&lt;/code&gt;的&lt;code&gt;API&lt;/code&gt;，因此它⾮常便携且受到广泛支持。作为&lt;code&gt;C&lt;/code&gt;的&lt;code&gt;API&lt;/code&gt;，它与&lt;code&gt;Objective-C&lt;/code&gt; &lt;code&gt;Cocoa Touch&lt;/code&gt;应⽤程序⽆缝集成。&lt;code&gt;OpenGL ES&lt;/code&gt;规范没有定义窗口层; 相
反，托管操作系统必须提供函数来创建一个接受命令的&lt;code&gt;OpenGL ES``渲染上下文&lt;/code&gt;和一个&lt;code&gt;帧缓冲区&lt;/code&gt;，其中写入任何绘图命令的结果。在&lt;code&gt;iOS&lt;/code&gt;上使用&lt;code&gt;OpenGL ES&lt;/code&gt;需要使⽤&lt;code&gt;iOS类&lt;/code&gt;来设置和呈现绘图表面，并使用&lt;code&gt;平台中立的API&lt;/code&gt;来呈现其内容。&lt;/p&gt;
</content:encoded></item><item><title>Anaconda</title><link>https://blog.lab-lawliet.app/posts/deeplearning-01-anaconda-8dc41347f5ab8ec8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/deeplearning-01-anaconda-8dc41347f5ab8ec8/</guid><description>同时可以对常用的工具进行管理。 01</description><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;可视化界面&lt;/h2&gt;
&lt;p&gt;同时可以对常用的工具进行管理。
&lt;img src=&quot;/content-assets/2ee1fde26bb59493.webp&quot; alt=&quot;01&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;环境管理&lt;/h2&gt;
&lt;p&gt;可以进行不同Python环境的配置，以适应不同项目不同开发环境。
&lt;img src=&quot;/content-assets/dd72f284dc54d772.webp&quot; alt=&quot;02&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>JupyterNotebook</title><link>https://blog.lab-lawliet.app/posts/deeplearning-02-jupyternotebook-5bfc8786ef11e2ee/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/deeplearning-02-jupyternotebook-5bfc8786ef11e2ee/</guid><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;JupyterNootbook&lt;/h1&gt;
&lt;h2&gt;设置默认目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Anaconda -&amp;gt; Enviroments -&amp;gt; Terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;初次使用会创建一个配置文件&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(base) gebiwangshushudeMacBook:~ RyukieW$ jupyter notebook --generate-config
Writing default config to: /Users/RyukieW/.jupyter/jupyter_notebook_config.py
(base) gebiwangshushudeMacBook:~ RyukieW$ 
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;打开配置脚本修改路径&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;c.NotebookApp.notebook_dir = &apos;指定路径&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>安装TensorFlow</title><link>https://blog.lab-lawliet.app/posts/deeplearning-03-tensorflow-f4e6c7c2506d9a26/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/deeplearning-03-tensorflow-f4e6c7c2506d9a26/</guid><description>通过Anaconda进入环境</description><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一：进入项目环境进行安装&lt;/h2&gt;
&lt;p&gt;通过&lt;code&gt;Anaconda&lt;/code&gt;进入环境&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Open Terminal&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/ac1e87a543592ef9.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tensorflow.org/install/&quot;&gt;官方指引&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二：验证安装成功&lt;/h2&gt;
&lt;p&gt;进入&lt;code&gt;Python&lt;/code&gt;交互模式，引入&lt;code&gt;TensorFlow&lt;/code&gt;头文件，没有报错就成功了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(TensorFlow2) gebiwangshushudeMacBook:~ RyukieW$ python
Python 3.8.8 (default, Feb 24 2021, 13:46:16) 
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&amp;gt;&amp;gt;&amp;gt; import tensorflow
&amp;gt;&amp;gt;&amp;gt; 
&amp;gt;&amp;gt;&amp;gt; exit()
(TensorFlow2) gebiwangshushudeMacBook:~ RyukieW$ 
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>线性可分</title><link>https://blog.lab-lawliet.app/posts/deeplearning-656a67ae43fbd26d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/deeplearning-656a67ae43fbd26d/</guid><description>样本可以用一条直线划分或者平面来划分</description><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;样本可以用一条直线划分或者平面来划分&lt;/p&gt;
</content:encoded></item><item><title>感知机与神经网络</title><link>https://blog.lab-lawliet.app/posts/deeplearning-936b7ee4051f7754/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/deeplearning-936b7ee4051f7754/</guid><description>单层感知机模型 用于解决线性可分二分的问题，非线性问题无能为力</description><pubDate>Thu, 04 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;深度学习发展史&lt;/h1&gt;
&lt;h2&gt;感知机&lt;/h2&gt;
&lt;p&gt;单层感知机模型
用于解决线性可分二分的问题，非线性问题无能为力&lt;/p&gt;
&lt;h2&gt;多层感知机模型（多层神经网络）&lt;/h2&gt;
&lt;p&gt;解决线性不可分问题&lt;/p&gt;
&lt;h2&gt;深度学习&lt;/h2&gt;
&lt;p&gt;非常多的多层神经网络&lt;/p&gt;
</content:encoded></item><item><title>循环的标签控制</title><link>https://blog.lab-lawliet.app/posts/swift-9166e36a0d528f21/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-9166e36a0d528f21/</guid><description>Game</description><pubDate>Wed, 14 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;滑梯游戏&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/907a019430001e49.webp&quot; alt=&quot;Game&quot; /&gt;&lt;/p&gt;
&lt;p&gt;游戏的规则如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;游戏盘面包括 25 个方格，游戏目标是达到或者超过第 25 个方格；&lt;/li&gt;
&lt;li&gt;每一轮，你通过掷一个六面体骰子来确定你移动方块的步数，移动的路线由上图中横向的虚线所示；&lt;/li&gt;
&lt;li&gt;如果在某轮结束，你移动到了梯子的底部，可以顺着梯子爬上去；&lt;/li&gt;
&lt;li&gt;如果在某轮结束，你移动到了蛇的头部，你会顺着蛇的身体滑下去。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)

// 一些转折点
board[03] = +08; 
board[06] = +11; 
board[09] = +09; 
board[10] = +02
board[14] = -10; 
board[19] = -11; 
board[22] = -02; 
board[24] = -08


var square = 0
var diceRoll = 0
while square &amp;lt; finalSquare {
    // 掷骰子
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // 根据点数移动
    square += diceRoll
    if square &amp;lt; board.count {
        // 如果玩家还在棋盘上，顺着梯子爬上去或者顺着蛇滑下去
        square += board[square]
    }
}
print(&quot;Game over!&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;滑梯变种-循环标签&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;增加一条规则：为了获胜，你必须刚好落在第 25 个方块中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // 骰子数刚好使玩家移动到最终的方格里，游戏结束。
        break gameLoop
    case let newSquare where newSquare &amp;gt; finalSquare:
        // 骰子数将会使玩家的移动超出最后的方格，那么这种移动是不合法的，玩家需要重新掷骰子
        continue gameLoop
    default:
        // 合法移动，做正常的处理
        square += diceRoll
        square += board[square]
    }
}
print(&quot;Game over!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种循环条件嵌套的场景下通过&lt;code&gt;标签&lt;/code&gt;可以方便精确的进行控制&lt;/p&gt;
&lt;p&gt;如果不使用标签的话，上面的&lt;code&gt;break&lt;/code&gt; &lt;code&gt;continue&lt;/code&gt; 只能控制&lt;code&gt;switch&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://swiftgg.gitbook.io/swift/swift-jiao-cheng/05_control_flow&quot;&gt;SwiftGG&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Array,Set,Dictionary</title><link>https://blog.lab-lawliet.app/posts/swift-array-set-dictionary-d4e3bb317a6e098f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-array-set-dictionary-d4e3bb317a6e098f/</guid><description>Img</description><pubDate>Wed, 14 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;三种集合类型：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Array
&lt;ul&gt;
&lt;li&gt;有序数据集合&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Set
&lt;ul&gt;
&lt;li&gt;无序无重复数据集合&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dictionary
&lt;ul&gt;
&lt;li&gt;无序键值对集合&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/65afbf5485bbf3a9.webp&quot; alt=&quot;Img&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Swift 中的数组、集合和字典必须明确其中保存的键和值类型，这样就可以避免插入一个错误数据类型的值。同理，对于获取到的值你也可以放心，其数据类型是确定的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Set&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;注意 &lt;code&gt;Swift&lt;/code&gt; 的 &lt;code&gt;Set&lt;/code&gt; 类型被桥接到 &lt;code&gt;Foundation&lt;/code&gt; 中的 &lt;code&gt;NSSet&lt;/code&gt; 类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;集合类型的Hash&lt;/h3&gt;
&lt;p&gt;一个类型想要存储在&lt;code&gt;Set&lt;/code&gt;中必须是可&lt;code&gt;Hash&lt;/code&gt;的。该类型必须提供一个方法来计算它的&lt;code&gt;Hash&lt;/code&gt;值。(&lt;code&gt;Hashable&lt;/code&gt;协议)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Swift&lt;/code&gt; 的所有基本类型（比如 &lt;code&gt;String&lt;/code&gt;、&lt;code&gt;Int&lt;/code&gt;、&lt;code&gt;Double&lt;/code&gt; 和 &lt;code&gt;Bool&lt;/code&gt;）默认都是可哈希化的，可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值默认也是可哈希化的。&lt;/p&gt;
&lt;h4&gt;语法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 声明
var favoriteGenres: Set&amp;lt;String&amp;gt; = [&quot;Rock&quot;, &quot;Classical&quot;, &quot;Hip hop&quot;]

// 推断
var favoriteGenres: Set = [&quot;Rock&quot;, &quot;Classical&quot;, &quot;Hip hop&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;集合操作&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/be55eae727d16c88.webp&quot; alt=&quot;Img&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/3c34f68e19d12d3b.webp&quot; alt=&quot;Img&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let houseAnimals: Set = [&quot;🐶&quot;, &quot;🐱&quot;]
let farmAnimals: Set = [&quot;🐮&quot;, &quot;🐔&quot;, &quot;🐑&quot;, &quot;🐶&quot;, &quot;🐱&quot;]
let cityAnimals: Set = [&quot;🐦&quot;, &quot;🐭&quot;]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>For-in跳跃遍历</title><link>https://blog.lab-lawliet.app/posts/swift-for-in-ebb2280aa6dec9b2/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-for-in-ebb2280aa6dec9b2/</guid><pubDate>Wed, 14 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // 每3小时渲染一个刻度线（3, 6, 9, 12）
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>String与Substring</title><link>https://blog.lab-lawliet.app/posts/swift-string-substring-9fd7d11b44eb43be/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-string-substring-9fd7d11b44eb43be/</guid><description>当你从字符串中获取一个子字符串 —— 例如，使用下标或者 prefix(_:) 之类的方法 —— 就可以得到一个 Substring 的实例，而非另外一个 String。Swift 里的 Substring 绝大部分函数都跟 String 一样，意味着你可以使用同样的方式去操作 Substring </description><pubDate>Wed, 14 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;当你从字符串中获取一个子字符串 —— 例如，使用下标或者 &lt;code&gt;prefix(_:)&lt;/code&gt; 之类的方法 —— 就可以得到一个 &lt;code&gt;Substring&lt;/code&gt; 的实例，而非另外一个 &lt;code&gt;String&lt;/code&gt;。&lt;code&gt;Swift&lt;/code&gt; 里的 &lt;code&gt;Substring&lt;/code&gt; 绝大部分函数都跟 &lt;code&gt;String&lt;/code&gt; 一样，意味着你可以使用同样的方式去操作 &lt;code&gt;Substring&lt;/code&gt; 和 &lt;code&gt;String&lt;/code&gt;。然而，跟 &lt;code&gt;String&lt;/code&gt; 不同的是，你只有在短时间内需要操作字符串时，才会使用 &lt;code&gt;Substring&lt;/code&gt;。当你需要长时间保存结果时，就把 &lt;code&gt;Substring&lt;/code&gt; 转化为 &lt;code&gt;String&lt;/code&gt; 的实例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let greeting = &quot;Hello, world!&quot;
let index = greeting.firstIndex(of: &quot;,&quot;) ?? greeting.endIndex
let beginning = greeting[..&amp;lt;index]
// beginning 的值为 &quot;Hello&quot;

// 把结果转化为 String 以便长期存储。
let newString = String(beginning)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就像 &lt;code&gt;String&lt;/code&gt;，每一个 &lt;code&gt;Substring&lt;/code&gt; 都会在内存里保存字符集。而 &lt;code&gt;String&lt;/code&gt; 和 &lt;code&gt;Substring&lt;/code&gt; 的区别在于性能优化上，&lt;code&gt;Substring&lt;/code&gt; 可以重用原 &lt;code&gt;String&lt;/code&gt; 的内存空间，或者另一个 &lt;code&gt;Substring&lt;/code&gt; 的内存空间（&lt;code&gt;String&lt;/code&gt; 也有同样的优化，但如果两个 &lt;code&gt;String&lt;/code&gt; 共享内存的话，它们就会相等）。这一优化意味着你在修改 &lt;code&gt;String&lt;/code&gt; 和 &lt;code&gt;Substring&lt;/code&gt; 之前都不需要消耗性能去复制内存。就像前面说的那样，&lt;code&gt;Substring&lt;/code&gt; 不适合长期存储 —— 因为它重用了原 &lt;code&gt;String&lt;/code&gt; 的内存空间，原 &lt;code&gt;String&lt;/code&gt; 的内存空间必须保留直到它的 &lt;code&gt;Substring&lt;/code&gt; 不再被使用为止。&lt;/p&gt;
&lt;p&gt;上面的例子，&lt;code&gt;greeting&lt;/code&gt; 是一个 &lt;code&gt;String&lt;/code&gt;，意味着它在内存里有一片空间保存字符集。而由于 &lt;code&gt;beginning&lt;/code&gt; 是 &lt;code&gt;greeting&lt;/code&gt; 的 &lt;code&gt;Substring&lt;/code&gt;，它重用了 &lt;code&gt;greeting&lt;/code&gt; 的内存空间。相反，&lt;code&gt;newString&lt;/code&gt; 是一个 &lt;code&gt;String&lt;/code&gt; —— 它是使用 &lt;code&gt;Substring&lt;/code&gt; 创建的，拥有一片自己的内存空间。下面的图展示了他们之间的关系：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/content-assets/060bf9945aba3ec5.webp&quot; alt=&quot;StringSubstring&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;参考：
&lt;a href=&quot;https://swiftgg.gitbook.io/swift/swift-jiao-cheng/03_strings_and_characters&quot;&gt;SwiftGG&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Switch元祖</title><link>https://blog.lab-lawliet.app/posts/swift-switch-56a1ad095ad5be24/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-switch-56a1ad095ad5be24/</guid><description>Img</description><pubDate>Wed, 14 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/content-assets/8096ff0d0407c4da.webp&quot; alt=&quot;Img&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print(&quot;\(somePoint) is at the origin&quot;)
case (_, 0):
    print(&quot;\(somePoint) is on the x-axis&quot;)
case (0, _):
    print(&quot;\(somePoint) is on the y-axis&quot;)
case (-2...2, -2...2):
    print(&quot;\(somePoint) is inside the box&quot;)
default:
    print(&quot;\(somePoint) is outside of the box&quot;)
}
// 输出“(1, 1) is inside the box”
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CodeReading-01-YYModel</title><link>https://blog.lab-lawliet.app/posts/framework-codereading-01-yymodel-95a22149d2a74a35/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/framework-codereading-01-yymodel-95a22149d2a74a35/</guid><description>加上.h只有5个文件相当轻量级的一个转模型框架,十分适合作为源码阅读计划的第一弹</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;感觉值对于优秀源码的学习阅读不是很够,开个坑,希望自己坚持下去
YY作者写的相关文章必看: https://blog.ibireme.com/2015/10/23/ios_model_framework_benchmark/&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;结构&lt;/h2&gt;
&lt;p&gt;加上&lt;code&gt;.h&lt;/code&gt;只有5个文件相当轻量级的一个转模型框架,十分适合作为源码阅读计划的第一弹&lt;/p&gt;
&lt;p&gt;&lt;code&gt;YYModel.h&lt;/code&gt;
&lt;code&gt;NSObject+YYModel.h&lt;/code&gt;
&lt;code&gt;NSObject+YYModel.m&lt;/code&gt;
&lt;code&gt;YYClassInfo.h&lt;/code&gt;
&lt;code&gt;YYClassInfo.m&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;YYModel.h&lt;/h2&gt;
&lt;p&gt;头文件&lt;/p&gt;
&lt;h2&gt;NSObject+YYModel&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;负责转模型的操作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;类型&lt;/h3&gt;
&lt;h4&gt;YYEncodingNSType&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;对应&lt;code&gt;Foundation&lt;/code&gt;框架中基本类型的枚举&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内联函数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;YYClassGetNSType(Class cls)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;类型映射&lt;/li&gt;
&lt;li&gt;返回: &lt;code&gt;YYEncodingNSType&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;YYEncodingTypeIsCNumber(YYEncodingType type)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;判断是否是&lt;code&gt;C&lt;/code&gt;的&lt;code&gt;number&lt;/code&gt;类型&lt;/li&gt;
&lt;li&gt;返回: &lt;code&gt;BOOL&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;从&lt;code&gt;id&lt;/code&gt;类型返回&lt;code&gt;NSNumber&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;其中对空的判断还有小数整数的处理值得一看&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NSDate *YYNSDateFromString(__unsafe_unretained NSString *string)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;从字符串获得&lt;code&gt;NSDate&lt;/code&gt;对象&lt;/li&gt;
&lt;li&gt;作用域的控制值得学习 &lt;code&gt;{}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Class YYNSBlockClass()&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;获取&lt;code&gt;NSBlock&lt;/code&gt;类&lt;/li&gt;
&lt;li&gt;在判断&lt;code&gt;value&lt;/code&gt;类型的时候有用到&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;通过一个 &lt;code&gt;keyPaths&lt;/code&gt; 来从字典中获取对应 &lt;code&gt;value&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;例如 &lt;code&gt;[@&quot;id&quot;,@&quot;ID&quot;,@&quot;myId&quot;]&lt;/code&gt; 来从&lt;code&gt;dic&lt;/code&gt;中获取一个ID&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Get the value with multi key (or key path) from dictionary&lt;/li&gt;
&lt;li&gt;The dic should be NSDictionary&lt;/li&gt;
&lt;li&gt;和上一个方法类似但这里的遍历时如果&lt;code&gt;key&lt;/code&gt;不是&lt;code&gt;NSString&lt;/code&gt;会调用上面的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,__unsafe_unretained _YYModelPropertyMeta *meta)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Get number from property.&lt;/li&gt;
&lt;li&gt;这里位移枚举的使用值得一看&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;void ModelSetNumberToProperty(__unsafe_unretained id model,__unsafe_unretained NSNumber *num,__unsafe_unretained _YYModelPropertyMeta *meta)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Set number to property.&lt;/li&gt;
&lt;li&gt;与上面的方法一对&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;void ModelSetValueForProperty(__unsafe_unretained id model,__unsafe_unretained id value,__unsafe_unretained _YYModelPropertyMeta *meta)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Set value to model with a property meta.&lt;/li&gt;
&lt;li&gt;核心函数之一&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Apply function for dictionary, to set the key-value pair to model.&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;void CFDictionaryApplyFunction(CFDictionaryRef theDict, CFDictionaryApplierFunction applier, void *context);&lt;/code&gt;方法中调用&lt;/li&gt;
&lt;li&gt;对字典中所有键值对执行&lt;code&gt;applier&lt;/code&gt;函数&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Apply function for model property meta, to set dictionary to model.&lt;/li&gt;
&lt;li&gt;和上面的类似,是数组的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Class&lt;/h3&gt;
&lt;h4&gt;结构体 ModelSetContext&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;typedef struct {
    void *modelMeta;  ///&amp;lt; _YYModelMeta
    void *model;      ///&amp;lt; id (self)
    void *dictionary; ///&amp;lt; NSDictionary (json)
} ModelSetContext;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;_YYModelPropertyMeta : NSObject&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;A property info in object model.&lt;/li&gt;
&lt;li&gt;表示模型对象中的属性信息&lt;/li&gt;
&lt;li&gt;对应&lt;code&gt;YYClassPropertyInfo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;可理解为 属性 中间件&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;_YYModelMeta : NSObject&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;A class info in object model.&lt;/li&gt;
&lt;li&gt;表示模型的类信息&lt;/li&gt;
&lt;li&gt;对应&lt;code&gt;YYClassInfo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;可理解为 类 中间件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef cache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&amp;amp;onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &amp;amp;kCFTypeDictionaryKeyCallBacks, &amp;amp;kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    if (!meta || meta-&amp;gt;_classInfo.needUpdate) {
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    return meta;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;其中使用了 &lt;code&gt;dispatch_semaphore&lt;/code&gt;信号量保证线程安全&lt;/li&gt;
&lt;li&gt;建立了一个静态缓存,缓存了每个&lt;code&gt;class&lt;/code&gt;对应的&lt;code&gt;_YYModelMeta&lt;/code&gt;对象,提高了性能&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;YYClassInfo&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;将&lt;code&gt;Runtime&lt;/code&gt;中&lt;code&gt;C&lt;/code&gt;的一些东西,通过面向对象的方式进行了封装,便于理解使用
转模型的基础&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;类型&lt;/h3&gt;
&lt;h4&gt;YYEncodingType&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Type encoding&apos;s type.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;列举了各类编码信息，包括值类型、方法限定类型、属性修饰类型。YYEncodingType使用掩码的方式对三类不同的枚举信息进行分类，各占据1个字节&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;///值类型
YYEncodingTypeMask                = 0xFF       
///方法限定类型
YYEncodingTypeQualifierMask      = 0xFF00       
///属性修饰类型
YYEncodingTypePropertyMask      = 0xFF0000      
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Class&lt;/h3&gt;
&lt;h4&gt;YYClassIvarInfo&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;成员变量
objc_ivar&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;YYClassMethodInfo&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;方法
objc_method&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;YYClassPropertyInfo&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;成员属性
property_t&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;YYClassInfo&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;类
objc_class&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;/.../

/// 成员变量列表
@property (nullable, nonatomic, strong, readonly) NSDictionary&amp;lt;NSString *, YYClassIvarInfo *&amp;gt; *ivarInfos; ///&amp;lt; ivars
/// 方法列表
@property (nullable, nonatomic, strong, readonly) NSDictionary&amp;lt;NSString *, YYClassMethodInfo *&amp;gt; *methodInfos; ///&amp;lt; methods
/// 成员属性
@property (nullable, nonatomic, strong, readonly) NSDictionary&amp;lt;NSString *, YYClassPropertyInfo *&amp;gt; *propertyInfos; ///&amp;lt;

/.../
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Tips:&lt;/h2&gt;
&lt;h3&gt;kCFNull&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;点进去&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/* Null representant */

typedef const struct CF_BRIDGED_TYPE(NSNull) __CFNull * CFNullRef;

CF_EXPORT
CFTypeID CFNullGetTypeID(void);

CF_EXPORT
const CFNullRef kCFNull;	// the singleton null instance
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;打印&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;id null1 = (id)kCFNull;
id null2 = [NSNull null];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;打印地址&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;null1=(NSNull *)0x10426eaf0
null2=(NSNull *)0x10426eaf0
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;拓展&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标志&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;(void *)0&lt;/td&gt;
&lt;td&gt;C指针的字面零值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nil&lt;/td&gt;
&lt;td&gt;(id)0&lt;/td&gt;
&lt;td&gt;Objective-C对象的字面零值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nil&lt;/td&gt;
&lt;td&gt;(Class)0&lt;/td&gt;
&lt;td&gt;Objective-C类的字面零值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NSNull&lt;/td&gt;
&lt;td&gt;[NSNull null]&lt;/td&gt;
&lt;td&gt;用来表示零值的单独的对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;static inline 内联函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;看到这样一个宏
&lt;code&gt;#define force_inline __inline__ __attribute__((always_inline))&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;挺好的一篇参考文章: http://www.cnblogs.com/wyk19910103/p/5660627.html&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;__unsafe_unretained&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;与&lt;code&gt;weak&lt;/code&gt;类似但有本质不同&lt;/li&gt;
&lt;li&gt;https://www.zhihu.com/question/55831650&lt;/li&gt;
&lt;li&gt;https://blog.csdn.net/bsplover/article/details/7707964&lt;/li&gt;
&lt;li&gt;https://blog.csdn.net/yueyansheng2/article/details/49813987&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;__bridge&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;OC&lt;/code&gt;与&lt;code&gt;C&lt;/code&gt;指针的转换&lt;/p&gt;
&lt;h3&gt;命名对内对外&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;对内的加&lt;code&gt;_&lt;/code&gt;前缀&lt;/li&gt;
&lt;li&gt;对外的不加&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CoreFoundation&lt;/code&gt;中也挺常见&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;CFDictionaryApplyFunction &amp;amp; CFDictionaryApplyFunction 函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;相对于 &lt;code&gt;Foundation&lt;/code&gt; 的方法来说，&lt;code&gt;CoreFoundation&lt;/code&gt; 的方法有更高的性能，用 &lt;code&gt;CFArrayApplyFunction()&lt;/code&gt; 和 &lt;code&gt;CFDictionaryApplyFunction()&lt;/code&gt; 方法来遍历容器类能带来不少性能提升，但代码写起来会非常麻烦。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能优化Tips:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;来自文章开头YY作者的文章,感觉相当重要这里&lt;code&gt;Copy&lt;/code&gt;来方便看&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;1. 缓存
Model JSON 转换过程中需要很多类的元数据，如果数据足够小，则全部缓存到内存中。

2. 查表
当遇到多项选择的条件时，要尽量使用查表法实现，比如 switch/case，C Array，如果查表条件是对象，则可以用 NSDictionary 来实现。

3. 避免 KVC
Key-Value Coding 使用起来非常方便，但性能上要差于直接调用 Getter/Setter，所以如果能避免 KVC 而用 Getter/Setter 代替，性能会有较大提升。

4. 避免 Getter/Setter 调用
如果能直接访问 ivar，则尽量使用 ivar 而不要使用 Getter/Setter 这样也能节省一部分开销。

5. 避免多余的内存管理方法
在 ARC 条件下，默认声明的对象是 __strong 类型的，赋值时有可能会产生 retain/release 调用，如果一个变量在其生命周期内不会被释放，则使用 __unsafe_unretained 会节省很大的开销。

访问具有 __weak 属性的变量时，实际上会调用 objc_loadWeak() 和 objc_storeWeak() 来完成，这也会带来很大的开销，所以要避免使用 __weak 属性。
创建和使用对象时，要尽量避免对象进入 autoreleasepool，以避免额外的资源开销。

6. 遍历容器类时，选择更高效的方法
相对于 Foundation 的方法来说，CoreFoundation 的方法有更高的性能，用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升，但代码写起来会非常麻烦。

7. 尽量用纯 C 函数、内联函数
使用纯 C 函数可以避免 ObjC 的消息发送带来的开销。如果 C 函数比较小，使用 inline 可以避免一部分压栈弹栈等函数调用的开销。

8. 减少遍历的循环次数
在 JSON 和 Model 转换前，Model 的属性个数和 JSON 的属性个数都是已知的，这时选择数量较少的那一方进行遍历，会节省很多时间。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;http://triplecc.github.io/blog/2016-06-05-yymodelyue-du-xiao-ji/&lt;/li&gt;
&lt;li&gt;https://lision.me/yymodel_x01/&lt;/li&gt;
&lt;li&gt;https://lision.me/yymodel_x02/&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>RYImagePicker-iOS图片视频选择器</title><link>https://blog.lab-lawliet.app/posts/framework-ryimagepicker-ios-b7699a32bccca136/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/framework-ryimagepicker-ios-b7699a32bccca136/</guid><description>pod &apos;RYImagePicker&apos;</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;导入:&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pod &apos;RYImagePicker&apos;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;照片选择器&lt;/h2&gt;
&lt;p&gt;支持&lt;code&gt;图片&lt;/code&gt; &lt;code&gt;视频&lt;/code&gt;选择
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14966318512725.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14963080079150.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14963079957246.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;配置:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/**
 最大可选图片数量  默认9张   设置为0 的话也是9张
 */
@property (nonatomic, assign) NSInteger maxSelectedNumber;
/**
 超过最大限制后的提示文案   默认文案 : &quot;选择图片不能超过XXOO张&quot;
 */
@property (nonatomic, copy) NSString *overMaxInfo;
/**
 是否显示iCloud相册   默认NO 不显示
 */
@property (nonatomic, assign) BOOL showiCloud;
/**
 仅显示视频资源   默认 NO 只显示图片
 */
@property (nonatomic, assign) BOOL videoOnly;
/**
 是否显示  &quot;原图&quot; 按钮    默认NO   不显示原图按钮
 */
@property (nonatomic, assign) BOOL isShowOriginalImageButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;方法:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;+ (instancetype)sharedInstance;
/**
 *  使用Manager 在当前控制器下 Present 出图片选择控制器
 *
 *  @param finished 点击完成的回调 传回选取的UIImage原图数组
 *  @param canceled 点击取消的回调
 */
- (void)presentImagePickerControllerWithFinishedHandler:(finishedPickImageRY)finished cancelHandler:(canceledPickImageRY)canceled;

/**
 选择图片完成的回调

 @param selectedImages 已经选择的图片_用来展示的 100x100 的缩略图
 @param selectedAssets 已经选择的图片_PHAsset对象
 @param info           一些配置项
 */
typedef void(^finishedPickImageRY)(NSArray *selectedImages,NSArray *selectedAssets,NSDictionary *info);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;回调说明:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/**
 完成的回调中的 info 字段_用户是否指定需要原图 1: 用户选择需要原图
 */
#define RY_ORIGINAL_IMAGE_NEEDED @&quot;RY_ORIGINAL_IMAGE_NEEDED&quot;

/**
 选择图片完成的回调
 
 @param selectedImages 已经选择的图片_用来展示的 100x100 的缩略图
 @param selectedAssets 已经选择的图片_PHAsset对象
 @param info           一些配置项
 */
typedef void(^finishedPickImageRY)(NSArray *selectedImages,NSArray *selectedAssets,NSDictionary *info);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用:&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;RYImagePickerManager *imanager = [RYImagePickerManager sharedInstance];
imanager.maxSelectedNumber = self.maxImagesCount;
[imanager presentImagePickerControllerWithFinishedHandler:^(NSArray *selectedImages,NSArray *selectedAssets,NSDictionary *info) {
                //TODO: 点击完成的回调
            } cancelHandler:^{
                //TODO: 点击取消的回调
            }];
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>RYImagesScroller-iOS高度自定义的图片轮播器</title><link>https://blog.lab-lawliet.app/posts/framework-ryimagesscroller-ios-28478330a3aa8280/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/framework-ryimagesscroller-ios-28478330a3aa8280/</guid><description>项目地址: https://github.com/RyukieSama/RYImagesScroller</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;之前在项目中遇到了需要图片和视频混合轮播的需求
但是没找到合适的轮播器于是就自己撸了一个&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;项目地址: https://github.com/RyukieSama/RYImagesScroller&lt;/p&gt;
&lt;h2&gt;图片轮播&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;支持自定义分页图片
支持自定义蒙版视图&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;混合轮播实现思路&lt;/h2&gt;
&lt;p&gt;外部传入自定义View数组 和 各个自定义View对应的index数组即可
传入的View会被覆盖在对应的Cell上面&lt;/p&gt;
&lt;h2&gt;效果展示&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/IMG_1675.PNG&quot; alt=&quot;IMG_1675&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/IMG_1676.PNG&quot; alt=&quot;IMG_1676&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图效果示例代码:
上图在第一个和第三个添加了一个开关和滑动条&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
_svImages = [[RYImagesScrollView alloc] initWithFrame:CGRectMake(0, 20, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width*3/7) pageStyle:RYImageScrollerPageStyleCustom];
        _svImages.autoScrollTimeInterval = 4.0f;
        _svImages.contentMode = UIViewContentModeScaleAspectFill;
        _svImages.normalPageImage = [UIImage imageNamed:@&quot;outdoor_icon_carousel&quot;];
        _svImages.currentPageImage = [UIImage imageNamed:@&quot;outdoor_icon_carousel_selected&quot;];

self.svImages.imageURLs = @[
                            @&quot;http://cc.cocimg.com/api/uploads/20170407/1491531818350790.png&quot;,
                            @&quot;http://cc.cocimg.com/api/uploads/20170407/1491531897388821.png&quot;,
                            @&quot;http://cc.cocimg.com/api/uploads/20170407/1491531754597727.png&quot;,
                            @&quot;http://cc.cocimg.com/api/uploads/20170407/1491531459990792.png&quot;,
                            @&quot;http://cc.cocimg.com/api/uploads/20170407/1491531504501824.jpg&quot;
                            ];
    
    //这里的意思是在第一页和第三页 添加自定义蒙版视图
    self.svImages.attachIndexArr = @[
                                     @0,
                                     @2
                                     ];
    //这里的意思是在第一页和第三页的 添加自定义蒙版视图数组   蒙版会自适应轮播控件的大小  如果需要自定义位置  最好用一个背景透明的容器装下
    self.svImages.attachViewArr = @[
                                    [[UISwitch alloc] init],
                                    [[UISlider alloc] init]
                                    ];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法：&lt;/h3&gt;
&lt;p&gt;初始化:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 初始化方法   并设置分页样式

 @param style 分页样式
 */
- (instancetype)initWithFrame:(CGRect)frame pageStyle:(RYImageScrollerPageStyle)style;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分页样式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef NS_ENUM(NSInteger, RYImageScrollerPageStyle) {
    /**
     系统分页样式
     */
    RYImageScrollerPageStyleNormal = 99,
    /**
     用户自定义分页样式
     */
    RYImageScrollerPageStyleCustom = 100
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其他参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 *  图片数组
 */
@property (nonatomic, strong) NSArray &amp;lt;NSString *&amp;gt; *imageURLs;
/**
 *  需要添加辅助view的序号数组 与 attachViewArr 一一对应
 */
@property (nonatomic, strong) NSArray &amp;lt;NSNumber *&amp;gt; *attachIndexArr;
/**
 *  辅助View数组
 */
@property (nonatomic, strong) NSArray &amp;lt;UIView *&amp;gt; *attachViewArr;
/**
 *  滚动时间间隔   默认2s
 */
@property (nonatomic, assign) CGFloat autoScrollTimeInterval;
/**
 *  默认 UIViewContentModeScaleAspectFill
 */
@property (nonatomic, assign) UIViewContentMode contentMode;
/**
 非当前展示页的分页图片  仅在 RYImageScrollerPageStyleCustom 下生效
 */
@property (nonatomic, strong) UIImage *normalPageImage;
/**
 当前页的分页图片   仅在 RYImageScrollerPageStyleCustom 下生效
 */
@property (nonatomic, strong) UIImage *currentPageImage;
/**
 *  点击图片的回调
 */
@property (nonatomic, copy) ImageScrollHandler handler_imageClick;
/**
 *  每次滚动完的回调
 */
@property (nonatomic, copy) ImageScrollHandler handler_scrollCallBack;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>RYPhotosBrowser</title><link>https://blog.lab-lawliet.app/posts/framework-ryphotosbrowser-0d0e25ac752a9e0e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/framework-ryphotosbrowser-0d0e25ac752a9e0e/</guid><description>iOS 9+ pod &apos;RYPhotosBrowser&apos;</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;亮点&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;可高度自定义的图片浏览控件&lt;/p&gt;
&lt;p&gt;支持多种图片传入类型:  &lt;code&gt;本地文件路径&lt;/code&gt; &lt;code&gt;网络图片NSString&lt;/code&gt;  &lt;code&gt;UIImage对象&lt;/code&gt;或&lt;code&gt;者三者混合&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;基于SDWebImage4.0&lt;/p&gt;
&lt;p&gt;可自定义加载阶段HUD&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;导入方式&lt;/h2&gt;
&lt;p&gt;iOS 9+
pod &apos;RYPhotosBrowser&apos;&lt;/p&gt;
&lt;h2&gt;两种弹出姿势&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;从图片容器放大恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14968906918877.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[RYImageBrowser showBrowserWithImageURLs:arr atIndex:[obj integerValue] withPageStyle:RYImageBrowserPageStyleAuto fromImageView:weakSelf.vCover withProgress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
                NSLog(@&quot;withProgress&quot;);
                [SVProgressHUD show];
            } changImage:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
                NSLog(@&quot;changImage&quot;);
                [SVProgressHUD dismiss];
            } loadedImage:^(NSInteger receivedSize, NSInteger expectedSize, NSURL *targetURL) {
                NSLog(@&quot;loadedImage&quot;);
                [SVProgressHUD dismiss];
            }];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;渐变放大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14968935108832.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[RYImageBrowser showBrowserWithImageURLs:arr atIndex:[obj integerValue] withPageStyle:RYImageBrowserPageStyleAuto];
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;设置分页样式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;typedef NS_ENUM(NSInteger, RYImageBrowserPageStyle) {
    /**
     9张以内显示点点点    大于9张显示文字序号
     */
    RYImageBrowserPageStyleAuto = 99,
    /**
     显示点点点
     */
    RYImageBrowserPageStylePoints = 100,
    /**
     显示文字
     */
    RYImageBrowserPageStyleText = 101,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调用方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/**
 show   自定义分页样式

 @param imageURLs 图片数组  UIImage 和 URLString(webURL/本地路径) 都行  混合也行   @[UIImage ,UIImage UIImage , ...]  或  @[NSString, NSString ,NSString , ...]  或 @[UIImage ,NSString ,UIImage , ...]
 @param index 当前第几张   从0 开始
 @param style 页码样式
 */
+ (void)showBrowserWithImageURLs:(NSArray *)imageURLs atIndex:(NSInteger)index withPageStyle:(RYImageBrowserPageStyle)style;

/**
 show   自定义分页样式
 
 @param imageURLs 图片数组  UIImage 和 URLString(webURL/本地路径) 都行  混合也行   @[UIImage ,UIImage UIImage , ...]  或  @[NSString, NSString ,NSString , ...]  或 @[UIImage ,NSString ,UIImage , ...]
 @param index 当前第几张   从0 开始
 @param style 页码样式
 @param imageView 点击的图片容器
 */
+ (void)showBrowserWithImageURLs:(NSArray *)imageURLs atIndex:(NSInteger)index withPageStyle:(RYImageBrowserPageStyle)style fromImageView:(UIView *)imageView;

/**
 show   自定义分页样式
 
 @param imageURLs 图片数组  UIImage 和 URLString(webURL/本地路径) 都行  混合也行   @[UIImage ,UIImage UIImage , ...]  或  @[NSString, NSString ,NSString , ...]  或 @[UIImage ,NSString ,UIImage , ...]
 @param index 当前第几张   从0 开始
 @param style 页码样式
 @param imageView 点击的图片容器
 @param progress 加载图片中的回调  在此可自定义添加HUD等操作
 @param changed 切换图片的回调
 @param loaded 图片完成的回调
 */
+ (void)showBrowserWithImageURLs:(NSArray *)imageURLs atIndex:(NSInteger)index withPageStyle:(RYImageBrowserPageStyle)style fromImageView:(UIView *)imageView withProgress:(RYWebImageDownloaderProgressBlock)progress changImage:(RYWebImageDownloaderProgressBlock)changed loadedImage:(RYWebImageDownloaderProgressBlock)loaded;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>数据库主键和外键</title><link>https://blog.lab-lawliet.app/posts/ios-0d1b096c8bba371a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-0d1b096c8bba371a/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: 什么是 主键 外键&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;学生表(学号,姓名,性别)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;学号是主键
课程表(课程号,课程名,学分)
课程号是主键
成绩表(学号,课程号,成绩)
学号/课程号 是外键&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如果公共关键字在一个关系中是主关键字,呢么这个公共关键字被称为另一关系的外键&lt;/li&gt;
&lt;li&gt;如果一个关系的外键作为主关键字的表被称为主表, 具有次此关键字的表被称为主表的从表&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: 外键的作用&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;外键用于保持数据一致性, 完整性&lt;/li&gt;
&lt;li&gt;主要目的是控制存储在外键表中的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: 主键的设计原则&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;主键应当是对用户无意义的&lt;/li&gt;
&lt;li&gt;主键应该是单列的,以提高链接和筛选操作的效率
&lt;ul&gt;
&lt;li&gt;复合键的使用通常出于两点考虑
&lt;ul&gt;
&lt;li&gt;主键应当具有意义
&lt;ul&gt;
&lt;li&gt;这被认为破坏了数据库提供了方便&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在描述多对多关系的链接表中可与使用两个外部键作为主键
&lt;ul&gt;
&lt;li&gt;该表可能成为其他从表的主表,并成为从表主键的一部分,使得之后的从表包含更多的列&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;永远不要更新主键&lt;/li&gt;
&lt;li&gt;主键不应该包含动态变化的数据(时间戳等)&lt;/li&gt;
&lt;li&gt;主键应当由计算机自动生成&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: 数据库主键选取策略&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;自动增长型&lt;/li&gt;
&lt;li&gt;手动增长型&lt;/li&gt;
&lt;li&gt;使用UniqueIdentifier SQL Server提供一个UniqueIdentifier数据类型（16字节），并提供一个生成函数NEWID()，生成一个唯一的UniqueIdentifier&lt;/li&gt;
&lt;li&gt;使用COMB类型
&lt;ul&gt;
&lt;li&gt;保留UniqueIdentifier的前10字节，后6字节表示生成时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五: 外键&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;外键的主要作用是用来控制数据库中数据的完整性,党对一个数据进行操作的时候,和他有关联的一个表或者多个表的数据能够同时发生改变&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.cnblogs.com/buptlyn/p/4555206.html&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>死锁</title><link>https://blog.lab-lawliet.app/posts/ios-1b8bac6f65ac9a80/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-1b8bac6f65ac9a80/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;  NSLog(@&quot;之前 - %@&quot;, [NSThread currentThread]);

    dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@&quot;sync - %@&quot;, [NSThread currentThread]);
    });

    NSLog(@&quot;之后 - %@&quot;, [NSThread currentThread]);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;打印完第一句后，dispatch_sync 立即阻塞当前的主线程，然后把 Block 中的任务放到 main_queue 中， main_queue 中的任务会被取出来放到主线程中执行，但主线程这个时候已经被阻塞了，所以 Block 中的任务就不能完成，它不完成，dispatch_sync 就会一直阻塞主线程，这就是死锁现象。导致主线程一直卡死。
当然这里只是一个例子,死锁并不只会发生在主线程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一: 概述&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;所谓&lt;code&gt;死锁&lt;/code&gt;,指的是两个或者两个以上的进程在执行的过程中,由于竞争资源或彼此通信造成的一种阻塞现象.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果一个线程需要并行处理多个任务，那么就可以创建多个线程，但是线程多了，往往会产生冲突，当一个线程锁定了一个资源A，而又想去锁定资源B，而在另一个线程中，锁定了资源B，而又想去锁定资源A以完成自身的操作，两个线程都想得到对方的资源，而不愿意释放自己的资源，造成两个线程都在等待，而无法执行，此时就是死锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;死锁的规范定义
&lt;ul&gt;
&lt;li&gt;集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件，那么该组进程是死锁的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意 死锁与正常阻塞不一样
正常阻塞：事务请求被其他事务锁定的资源的锁时，发出请求的事务一直等到该锁被释放。默认情况下，除非设置了LOCK_TIMEOUT，否则SQL Server事务不会超时。因为发出请求的事务未执行任何操作来阻塞拥有锁的事务，所以该事务是被阻塞，而不是陷入了死锁。最后，拥有锁的事务将完成并释放锁，然后发出请求地事务将获取锁并继续执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二: 四个必要条件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;互斥(资源独占)
&lt;ul&gt;
&lt;li&gt;一个资源一次只能被一个进程使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;请求与保持(部分分配,占有申请)
&lt;ul&gt;
&lt;li&gt;一个进程在申请新的资源的同时保持对原有资源的占有（只有这样才是动态申请，动态分配）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不可剥夺(不可强占)
&lt;ul&gt;
&lt;li&gt;资源申请者不能强行地从资源占有者手中夺取资源，资源只能由占有者自愿释放&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;循环等待
&lt;ul&gt;
&lt;li&gt;若干进程之间形成一种头尾相连的循环等待资源关&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: 产生原因&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;系统资源不足,竞争不可剥夺资源引起的进程死锁&lt;/li&gt;
&lt;li&gt;竞争临时资源引起的死锁&lt;/li&gt;
&lt;li&gt;进程推进顺序不当引起死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;四: 破坏死锁&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;需要破坏四个必要条件中的一个或几个&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;互斥(不能改变)
&lt;ul&gt;
&lt;li&gt;必然存在的,非共享设备所必须的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;破坏请求与保持
&lt;ul&gt;
&lt;li&gt;对一个进程在请求资源时，它不能持有不可剥夺资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;破坏不可剥夺
&lt;ul&gt;
&lt;li&gt;对一个已经保持了某些不可剥夺资源的进程，提出新的资源请求而不能得到满足时，它必须释放已经保持的所有资源，待以后需要时再重新申请&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;破坏循环
&lt;ul&gt;
&lt;li&gt;对系统所有资源类型进行线性排列，并赋予不同序号。规定每个进程必须按序号递增的顺序请求资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;五: 预防死锁&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;每个进程（事务）将所有要使用的数据全部加锁，否则，就不能继续执行&lt;/li&gt;
&lt;li&gt;预先对数据对象规定一个封锁顺序，所有进程（事务）都按这个顺序加锁&lt;/li&gt;
&lt;li&gt;保证进程处于安全进程序列&lt;/li&gt;
&lt;li&gt;按同一顺序访问对象&lt;/li&gt;
&lt;li&gt;避免事务中的用户交互&lt;/li&gt;
&lt;li&gt;保持事务简短并在一个批处理中&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://blog.csdn.net/weixin_39651041/article/details/79993975&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>堆栈的深度问题</title><link>https://blog.lab-lawliet.app/posts/ios-2992528ce90384f3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-2992528ce90384f3/</guid><description>关于栈深度问题通常会出现在递归中。因为程序在递归时，每一层递归的临时变量和参数，都是被保存在栈中的，所以递归调用的深度过多，就会造成栈空间存储不足。一般来说栈是向下生长的，堆是向上生长的。把内存地址像门牌号编号成 1 ~ 10000，栈的使用就是先用第 10000 号内存块，再用第 9999 号内存</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;关于栈深度问题通常会出现在递归中。因为程序在递归时，每一层递归的临时变量和参数，都是被保存在栈中的，所以递归调用的深度过多，就会造成栈空间存储不足。一般来说栈是向下生长的，堆是向上生长的。把内存地址像门牌号编号成 1 ~ 10000，栈的使用就是先用第 10000 号内存块，再用第 9999 号内存块，依次减小编号。而堆的话，是先用第 1 号内存块，再用第 2 号内存块，依次增加编号。
堆内存可以认为是没有上限的（除非你的硬盘空间不足），如果消耗光了计算机的内存，操作系统还会用硬盘的虚拟内存为你提供更多的内存，虚拟内存和内存的读写速度几倍一致。但是如果大量程序占用了虚拟内存，很可能会出现内存泄露问题。这种情况，虚拟内存很快就会被消耗完毕。
栈内存不同于堆内存，通常编译器都会指定程序的栈内存空间使用的大小，如果栈内存使用超出了限制，就会触发程序异常退出，即栈溢出错误（Stack Over flow）。但是iOS实际开发中很少出现栈溢出问题，这就从侧面反映出使用的递归比较少。苹果官方文档指明：对于主线程，栈内存为 1 MB；非主线程，栈内存为 512 KB。如果想测试这一点，在主线程创建一个大小为100万的数组，这是Xcode就会报错。&lt;/p&gt;
</content:encoded></item><item><title>项目路由方案</title><link>https://blog.lab-lawliet.app/posts/ios-65557cf2ba827928/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-65557cf2ba827928/</guid><description>这次组件化方案需要解决的主要问题有两点 解耦 和 取模型化</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;组件化之路:去模型化与路由&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;先吐槽下...
去年年底来到新公司, 公司项目代码有四五年了, 经手了十几号人.
规范什么的基本不存在, 耦合混乱, 垃圾奇葩代码遍地.
印象最深的是UI让改一个到处都有的一个样式的按钮的颜色.
MMP改的我要死不活的...连基本的复用都不存在
过年前准备对项目进行组件化重构&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;目标&lt;/h1&gt;
&lt;p&gt;这次组件化方案需要解决的主要问题有两点 &lt;code&gt;解耦&lt;/code&gt; 和 &lt;code&gt;取模型化&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;去模型化&lt;/h1&gt;
&lt;p&gt;这里我采用的是使用 &lt;code&gt;NSDictionary&lt;/code&gt; 来实现去模型化&lt;/p&gt;
&lt;h2&gt;使用模型有什么好处?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;使用数据较为便捷&lt;/li&gt;
&lt;li&gt;无须过多关心数据解析,交给第三方库即可&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;去模型话有什么好处?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;解耦&lt;/li&gt;
&lt;li&gt;极大减少项目文件数量&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;存在的问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hardcode&lt;/code&gt; 这是解耦去模型必然会出现的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;解耦_路由&lt;/h1&gt;
</content:encoded></item><item><title>隐藏导航栏下面的线</title><link>https://blog.lab-lawliet.app/posts/ios-7af2b2a460e0f90e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-7af2b2a460e0f90e/</guid><description>效果</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setShadowImage:[UIImage new]];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/03B6EE70E631F551954E55D027C8BE25.png&quot; alt=&quot;效果&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>autoreleasepool</title><link>https://blog.lab-lawliet.app/posts/ios-autoreleasepool-1ea167fa81f3705e/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-autoreleasepool-1ea167fa81f3705e/</guid><description>问：以上代码存在什么样的问题？如果循环的次数非常大时，应该如何修改？</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;自动释放池&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;系统
&lt;ul&gt;
&lt;li&gt;系统默认会在 main.m 文件中创建一个自动释放池。。。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;手动
&lt;ul&gt;
&lt;li&gt;例如在for循环中产生大量无法及时释放的临时变量&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;for (int i = 0; i &amp;lt; largeNumber; ++i) {
	NSString *str = @&quot;Hello World&quot;;
	str = [str stringByAppendingFormat:@&quot; - %d&quot;, i];
	str = [str uppercaseString];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;问：以上代码存在什么样的问题？如果循环的次数非常大时，应该如何修改？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    - 如果不在本次循环结束的时候消除的话节会一直存在直到程序运行过 } 才会被释放占用大量内存
    - 解决办法:
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;-(void)viewDidLoad {
	[super viewDidLoad];

    int largeNumber = 1000000;
	for (int i = 0; i &amp;lt; largeNumber; ++i) {
@autoreleasepool {
	NSString *str = @&quot;Hello World&quot;;
	str = [str stringByAppendingFormat:@&quot; - %d&quot;, i];
	str = [str uppercaseString];
		}// 循环执行到这里就会回头继续执行循环了
		//这样在循环内部就已经释放了自动释放池
	}
    // 不加自动释放池的话要执行到这里才会释放对象
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自动释放池的 创建 &amp;amp; 销毁&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每一次 主线程 的 消息循环 开始的时候会创建自动释放池&lt;/li&gt;
&lt;li&gt;消息循环结束前,会释放自动释放池&lt;/li&gt;
&lt;li&gt;自动释放池销毁的时候会向其内部所有的对象发送一个 release 消息 释放所有的对象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内存管理-OC&lt;/p&gt;
&lt;p&gt;消息循环&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保证应用程序不退出&lt;/li&gt;
&lt;li&gt;实现: 一个死循环不断地监视事件 (方法,点击事件等),并执行对应的方法&lt;/li&gt;
&lt;li&gt;[面试会问]app是如何运行的?
&lt;img src=&quot;https://github.com/RyukieSama/Ryukie.GitHub.io/blob/gh-pages/images/79FFBB31-E37D-440D-8870-E3092C958B2C.png?raw=true&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;执行流程详细描述:
&lt;ul&gt;
&lt;li&gt;应用程序开启 -&amp;gt; 创建一个消息循环 -&amp;gt; 监视事件 -&amp;gt; 接收到事件 -&amp;gt; 执行创建自动释放次并执行相应操作 -&amp;gt; 将执行中产生的对象丢入刚才创建的那个自动释放池 -&amp;gt; 消息执行结束 -&amp;gt; 自动释放池销毁(对其中所有对象发送一个 release消息,销毁对象) -&amp;gt; 一次消息循环结束 -&amp;gt; 开始下一次消息循环&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>为什么用dispatch-once实现单例</title><link>https://blog.lab-lawliet.app/posts/ios-dispatch-once-3e489e6b88ca9ed3/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-dispatch-once-3e489e6b88ca9ed3/</guid><description>dispatch_once是线程安全的,可以确保</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-为什么用dispatch_once实现单例&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;面试问到了
自己说到了这点(猜的是线程安全的查了一下确实是这样)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;+ (XXXX *)sharedInstance
{
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&amp;amp;onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dispatch_once&lt;/code&gt;是线程安全的,可以确保&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://www.cnblogs.com/hellocby/archive/2012/08/24/2654488.html
https://blog.csdn.net/qqMCY/article/details/88648000 单例alloc 、 copy的处理&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>合适的线程数量</title><link>https://blog.lab-lawliet.app/posts/ios-e6319d2f6af75291/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-e6319d2f6af75291/</guid><description>其实，如果全系统只运行你一个程序，只考虑测试代码，阻塞系数是0.5，当然是没错的，但是这个公式是针对全系统而言的，需要把全系统所有程序的线程占用的timeslice都计算在内考虑。理论计算是需要考虑所有细节的，这些细节占用的时间可一点不少哦。</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;多线程挺好
但是并不是越多越好
线程也是有消耗的
iOS主线程堆栈大小1m,子线程512kb,并且不能通过编译器开关或线程API函数来更改&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;最优线程数计算理论依据&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Venkat Subramaniam 博士在《Programming Concurrency on the JVM》中提到关于最优线程数的计算&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;
The minimum number of threads is equal to the number of available
cores. If all tasks are computation intensive, then this is all we need. Having
more threads will actually hurt in this case because cores would be context
switching between threads when there is still work to do. If tasks are IO
intensive, then we should have more threads.

We can compute the total number of threads we’d need as follows:
  Number of threads = Number of Available Cores / (1 - Blocking Coefficient)

 

 

 To determine the number of threads, we need to know two things:
 • The number of available cores
 • The blocking coefficient of tasks


 The first one is easy to determine; we can look up that information, even at
 runtime, as we saw earlier. It takes a bit of effort to determine the blocking
 coefficient. We can try to guess it, or we can use profiling tools or the java.
 lang.management API to determine the amount of time a thread spends on
 system/IO operations vs. on CPU-intensive tasks.

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;线程数 = 可用核数 / (1 - 阻塞系数 )&lt;/li&gt;
&lt;li&gt;阻塞系数 = 阻塞时间 / (阻塞时间 + CPU使用时间)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;网友的一点补充&lt;/h2&gt;
&lt;p&gt;其实，如果全系统只运行你一个程序，只考虑测试代码，阻塞系数是0.5，当然是没错的，但是这个公式是针对全系统而言的，需要把全系统所有程序的线程占用的timeslice都计算在内考虑。理论计算是需要考虑所有细节的，这些细节占用的时间可一点不少哦。&lt;/p&gt;
&lt;p&gt;当全系统线程数量远远大于执行核心数，则增加了线程的上下文环境交换时间等等，还有其他线程分走了许多CPU时间片，这部分时间可不少，可是却没有用在你的测试计算上。为简化理解，直接把这部分时间都划算到阻塞时间上，等于说阻塞系数变大，纯工作系数变小；分母就变小了，最佳线程数量就成倍增大了。&lt;/p&gt;
&lt;p&gt;如果还不理解，我举个例子，用实际场景来解释就明白了。&lt;/p&gt;
&lt;p&gt;一个加工厂有4条加工流水线，每一个人同时只能占用一条流水线加工自己的产品，假定一个人占据一条加工流水线完全饱和工作8小时能完成自己的加工，那么拆分到4条线上显然2小时可以加工完成，理论上。&lt;/p&gt;
&lt;p&gt;再假定人的饱和工作时间和空闲时间各占50%呢（构造阻塞系数0.5），在不考虑交班交接的时间，显然8个人同时排队加工才能这2小时完成，（加工厂只安排你的8个人）。&lt;/p&gt;
&lt;p&gt;但是事实工厂工况是怎么样的呢？这个加工厂有1000人在排队等着加工自己的产品(你安排了8个人过去加工你的产品 + 992个外人)，而且，一条流水线线上工作内容切换时，前一个人和后一个人交替交接需要占用不少时间，而且，992个外人也各自要加工自己的产品，分配给你那8个人的时间片机会就少了。为简化理解，那些时间都没为你的产品加工，都直接是划归到阻塞时间上去，阻塞系数变大，最佳线程数=CPU执行核心数/（1-阻塞系数）， 分母变小，结果自然变大。如果你要获得更多的时间片，你就要安排更多人过去，增大占时间片的机会。&lt;/p&gt;
&lt;p&gt;所以你安排8个人去加工，肯定2小时完成不了，可能实际用了3小时才加工完成。但是你若安排16个人过去排队加工，可能2.5小时就能加工完成了。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.cnblogs.com/bobsha/p/6178995.html&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>FMDB-死锁问题</title><link>https://blog.lab-lawliet.app/posts/ios-fmdb-4db54948f3316ce6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-fmdb-4db54948f3316ce6/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {    
            [db executeUpdate:@&quot;INSERT INTO myTable VALUES (?)&quot;, [NSNumber numberWithInt:1]];    
            [db executeUpdate:@&quot;INSERT INTO myTable VALUES (?)&quot;, [NSNumber numberWithInt:2]];    
            [db executeUpdate:@&quot;INSERT INTO myTable VALUES (?)&quot;, [NSNumber numberWithInt:3]];  
    [queue inDatabase:^(FMDatabase *db) {
        // do work B
    }];
            if (whoopsSomethingWrongHappened) {    
                    *rollback = YES; return;    
            }   
            // etc…    
            [db executeUpdate:@&quot;INSERT INTO myTable VALUES (?)&quot;, [NSNumber numberWithInt:4]];    
    }];  
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用fmdb进行数据库操作,出现inDatabase: was called reentrantly on the same queue, which would lead to a deadlock这样的崩溃错误.原因是在一个[queue inDataBase]的block中,又执行了一个inDataBase.这时第一个block需要执行结束才能出队列,第二个block才能执行.但是第一个block又包含了第二个block,也就是说第二个block不执行,第一个block就不能结束.于是就互相等待,也就是各种死锁&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>FMDB与WCDB</title><link>https://blog.lab-lawliet.app/posts/ios-fmdb-wcdb-7da30fe8d2b31991/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-fmdb-wcdb-7da30fe8d2b31991/</guid><description>有了ORM开发维护成本大大降低</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;参考:
https://cloud.tencent.com/developer/article/1005718 微信团队
http://blog.devtang.com/2012/04/22/use-fmdb/ 在iOS开发中使用FMDB&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;兼容性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;都是基于&lt;code&gt;SQLite&lt;/code&gt;的所有数据库是可以兼容的,迁移的话需要重新封装项目的数据库工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;性能&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;读写&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SQLite&lt;/code&gt;的 速度已经很快了,&lt;code&gt;FMDB&lt;/code&gt;之做了最简单的封装,&lt;code&gt;WCDB&lt;/code&gt;还做了ORM等封装,所以有了更多的步骤,性能稍劣于&lt;code&gt;FMDB&lt;/code&gt;5%(官方给出的数据)
&lt;ul&gt;
&lt;li&gt;优于多携程读的优势,可以和&lt;code&gt;FMDB&lt;/code&gt;持平了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;写操作通常是性能的瓶颈,做了针对性优化使&lt;code&gt;WCDB&lt;/code&gt;写操作优于&lt;code&gt;FMDB&lt;/code&gt;28%,批量写优于180%&lt;/li&gt;
&lt;li&gt;多线程读写优于&lt;code&gt;FMDB&lt;/code&gt;62%&lt;/li&gt;
&lt;li&gt;多线程写&lt;code&gt;FMDB&lt;/code&gt;直接返回错误&lt;code&gt;SQLITE_BUSY&lt;/code&gt;,无法完成&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;初始化&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SQLite&lt;/code&gt;连接的初始化速度会随着数据库内表的数量增加而逐渐上升,&lt;code&gt;WCDB&lt;/code&gt;也针对这个场景做了优化.相较于没有优化的&lt;code&gt;FMDB&lt;/code&gt;,&lt;code&gt;WCDB&lt;/code&gt;的初始化速度有107%的性能优势&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ORM&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Object Relational Mapping&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;有了ORM开发维护成本大大降低&lt;/p&gt;
&lt;h2&gt;数据库升级&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SQLite&lt;/code&gt; 的数据库升级一直是一个比较繁杂的问题&lt;/li&gt;
&lt;li&gt;通常的做法是，开发者自行定义一个版本号，并保存下来。数据库创建时每次检查版本号，若版本号较低，则对其字段进行升级，并更新版本号。但在多个版本的增增减减之后，版本的处理逻辑会越来越复杂，甚至可能弄错表内哪些字段是新增的，哪些是废弃的&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WCDB&lt;/code&gt;将数据库升级和&lt;code&gt;ORM&lt;/code&gt;结合起来，对于需要增删改的字段，只需直接在&lt;code&gt;ORM&lt;/code&gt;层面修改，并再次调用 &lt;code&gt;createTableAndIndexesOfName:withClass:&lt;/code&gt;接口即可自动升级&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;与 &lt;code&gt;FMDB&lt;/code&gt; 对比， &lt;code&gt;WCDB&lt;/code&gt; 使得开发者可以写更少的代码，但能获得更高的性能。
开发者不需要额外关注数据库升级和多线程操作的问题。同时， &lt;code&gt;WCDB&lt;/code&gt; 还提供了加密、统计、修复等功能。&lt;/p&gt;
</content:encoded></item><item><title>iOS重签名调研</title><link>https://blog.lab-lawliet.app/posts/ios-ios-2b3542f626c216a8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ios-2b3542f626c216a8/</guid><description>执行命令：</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;要想保留原APP的bundleId不变，则重签名的证书的bundleId规则如下：
原App的bundleId: com.abc.hell
用于重签名的bundleId:com.abc.hell.xyz
保证原APP的bundleId是重签名的bundleId的前缀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Step 1:  查看App证书情况&lt;/h2&gt;
&lt;p&gt;执行命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;codesign -d -vv /Users/RyukieW/Inke/技术相关/重签名调研/sui/Payload/Finance.app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Executable=/Users/RyukieW/Inke/技术相关/重签名调研/sui/Payload/Finance.app/Finance
Identifier=com.feidee.myfinance
Format=app bundle with Mach-O thin (arm64)
CodeDirectory v=20400 size=67736 flags=0x0(none) hashes=2108+5 location=embedded
Signature size=4820
Authority=iPhone Developer: xiangdong Ren (23Z6TN7Z56)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=Nov 14, 2018 at 10:58:22 AM
Info.plist entries=37
TeamIdentifier=3Q47P4VEX9
Sealed Resources version=2 rules=10 files=446
Internal requirements count=1 size=180

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: 查看是否被加密（是否破解）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gebiwangshushudeMacBook:Finance.app RyukieW$ otool -l Finance | grep cryp
     cryptoff 16384
    cryptsize 6897664
      cryptid 0
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;其中cryptid为0代表已经砸壳，即解密，为1或者2表示以第1类或者第2类加密方案加密。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Step 4： 查看本地证书并记录需要用到的证书&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;security find-identity -v -p codesigning
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;iPhone Developer: kai chen (8SRJU35B8T)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找一个开发环境配置文件生成entitlements.plist文件，后面签名要用到：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;security cms -D -i XX.mobileprovision &amp;gt; profile.plist
/usr/libexec/PlistBuddy -x -c &apos;Print :Entitlements&apos; profile.plist &amp;gt; entitlements.plist
cat entitlements.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;把准备好的开发环境配置文件复制到XX.app文件夹下：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;cp XX.mobileprovision Payload/XX.app/embedded.mobileprovision
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;修改包Info.plist中的Bundle Identifier与配置文件中的Bundle Identifier保持一致：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/usr/libexec/PlistBuddy -c &quot;Set :CFBundleIdentifier com.gdinke.meetstar.dev&quot; Info.plist
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;移除之前的签名文件夹：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;rm -rf _CodeSignature
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 5: 重签名&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;重签名framework&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;
codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Alamofire.framework

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; TZImagePickerController.framework

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; CryptoSwift.framework		
	
codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; WCDBSwift.framework

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceAlert.framework	
		
codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; XLActionController.framework


codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceImagePicker.framework	

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftAVFoundation.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceKeyboard.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftAccelerate.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceKit.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCore.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceLoadingView.framework	

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreAudio.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceNotice.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreData.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinancePoper.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreFoundation.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceRefresh.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreGraphics.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceRuler.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreImage.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; FinanceVendors.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreLocation.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; IQKeyboardManagerSwift.framework

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftCoreMedia.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Iverson.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftDarwin.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Kingfisher.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftDispatch.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Messi.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftFoundation.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Moya.framework				

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftMediaPlayer.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Pageboy.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftMetal.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; PopoverKit.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftObjectiveC.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Reachability.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftPhotos.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Result.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftQuartzCore.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; SnapKit.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftSwiftOnoneSupport.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; SwiftSVG.framework		

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftUIKit.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; Swifty.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftos.dylib

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; SwiftyJSON.framework			

codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; libswiftsimd.dylib





&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;.app文件重签名&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;codesign -f -s &quot;iPhone Developer: kai chen (8SRJU35B8T)&quot; --no-strict --entitlements=entitlements.plist Finance.app
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查看App签名信息&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 6: 打包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;zip -ry Finance.ipa Payload

// zip –ry 「输出文件名」 「输入文件名」

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;iOS逆向微信&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://cloud.tencent.com/developer/article/1445897
https://www.jianshu.com/p/31232eef35c5
https://juejin.im/post/5c7e796351882532cd57adc4#heading-19
iOS应用程序的重签名(打包)
https://blog.csdn.net/skylin19840101/article/details/60583893
iOS逆向必备绝技之ipa重签名
https://www.yangshebing.com/2018/01/06/iOS%E9%80%86%E5%90%91%E5%BF%85%E5%A4%87%E7%BB%9D%E6%8A%80%E4%B9%8Bipa%E9%87%8D%E7%AD%BE%E5%90%8D/?utm_source=tuicool&amp;amp;utm_medium=referral
超级签名-原理/机制/技术细节-完全解析
https://juejin.im/post/5cdeb72151882525cc707729&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>iOS多线程技术对比</title><link>https://blog.lab-lawliet.app/posts/ios-ios-4536bb5008ddbfd9/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ios-4536bb5008ddbfd9/</guid><description>Operation queue 提供了更多你在编写多线程程序时需要的功能，并隐藏了许多线程调度，线程取消与线程优先级的复杂代码，为我们提供简单的API入口。从编程原则来说，一般我们需要尽可能的使用高等级、封装完美的API，在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: NSThread&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;每个NSThread对象对应一个线程，真正最原始的线程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;优点
&lt;ul&gt;
&lt;li&gt;NSThread 轻量级最低，相对简单。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点
&lt;ul&gt;
&lt;li&gt;手动管理所有的线程活动，如生命周期、线程同步、睡眠等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: NSOperation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;自带线程管理的抽象类。
基于GCD&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;优点
&lt;ul&gt;
&lt;li&gt;自带线程周期管理，操作上可更注重自己逻辑。&lt;/li&gt;
&lt;li&gt;提供了一些在GCD中不容易实现的特性，如：限制最大并发数量、操作之间的依赖关系&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点
&lt;ul&gt;
&lt;li&gt;面向对象的抽象类，只能实现它或者使用它定义好的两个子类：NSInvocationOperation 和 NSBlockOperation。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: GCD&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;优点
&lt;ul&gt;
&lt;li&gt;最高效，避开并发陷阱。&lt;/li&gt;
&lt;li&gt;用Block定义任务，使用起来非常灵活便捷&lt;/li&gt;
&lt;li&gt;通过GCD，开发者不用再直接跟线程打交道，只需要向队列中添加代码块即可&lt;/li&gt;
&lt;li&gt;GCD在后端管理着一个线程池，GCD不仅决定着代码块将在哪个线程被执行，它还根据可用的系统资源对这些线程进行管理。从而让开发者从线程管理的工作中解放出来，通过集中的管理线程，缓解大量线程被创建的问题&lt;/li&gt;
&lt;li&gt;使用GCD，开发者可以将工作考虑为一个队列，而不是一堆线程，这种并行的抽象模型更容易掌握和使用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点
&lt;ul&gt;
&lt;li&gt;基于C实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: 场景&lt;/h2&gt;
&lt;p&gt;Operation queue 提供了更多你在编写多线程程序时需要的功能，并隐藏了许多线程调度，线程取消与线程优先级的复杂代码，为我们提供简单的API入口。从编程原则来说，一般我们需要尽可能的使用高等级、封装完美的API，在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候，简洁的GCD或许是个更好的选择，而Operation queue 为我们提供能更多的选择。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://yoferzhang.com/post/20160830ConcurrencyProgrammingGuide/
https://www.jianshu.com/p/d09e2638eb27&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>iOS多线程方案</title><link>https://blog.lab-lawliet.app/posts/ios-ios-9e00cc6de9f00879/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ios-9e00cc6de9f00879/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: 总览&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://api.cocoachina.com/uploads/20170707/1499394732413995.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pthread
&lt;ul&gt;
&lt;li&gt;几乎不用&lt;/li&gt;
&lt;li&gt;C语言的,全部由程序员自己管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NSThread
&lt;ul&gt;
&lt;li&gt;程序员自己管理线程,用得少&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GCD
&lt;ul&gt;
&lt;li&gt;自动管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NSOperation
&lt;ul&gt;
&lt;li&gt;基于GCD&lt;/li&gt;
&lt;li&gt;比GCD多了一些使用功能&lt;/li&gt;
&lt;li&gt;面向对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: GCD&lt;/h2&gt;
&lt;h3&gt;特点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GCD会自动是用更多的CPU内核&lt;/li&gt;
&lt;li&gt;自动管理线程生命周期(创建,调度,销毁等)&lt;/li&gt;
&lt;li&gt;程序员只用高数GCD想要如何执行什么任务,不用编写线程管理代码&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;group&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 enter leave 的时候一定要出入数量对等否则会崩溃的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;dispatch_barrier_async&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;多个任务分两组完成&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt; // 并发队列
    dispatch_queue_t queue = dispatch_queue_create(&quot;test&quot;, DISPATCH_QUEUE_CONCURRENT);

    // 异步执行
    dispatch_async(queue, ^{
        for (int i = 0; i &amp;lt; 3; i++) {
            NSLog(@&quot;栅栏：并发异步1   %@&quot;,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i &amp;lt; 3; i++) {
            NSLog(@&quot;栅栏：并发异步2   %@&quot;,[NSThread currentThread]);
        }
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@&quot;------------barrier------------%@&quot;, [NSThread currentThread]);
        NSLog(@&quot;******* 并发异步执行，但是34一定在12后面 *********&quot;);
    });

    dispatch_async(queue, ^{
        for (int i = 0; i &amp;lt; 3; i++) {
            NSLog(@&quot;栅栏：并发异步3   %@&quot;,[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i &amp;lt; 3; i++) {
            NSLog(@&quot;栅栏：并发异步4   %@&quot;,[NSThread currentThread]);
        }
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;dispatch_apply 并行遍历&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;和group关联的API,会在所有任务执行完成后执行后续操作
&quot;完成&quot;会在全部执行完成后打印出来&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;应用场景
&lt;ul&gt;
&lt;li&gt;可在某些场景代替for循环
&lt;ul&gt;
&lt;li&gt;全部遍历,无顺序要求&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;不同文件并行iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt; NSArray *array = [NSArray arrayWithObjects:@&quot;/Users/chentao/Desktop/copy_res/gelato.ds&quot;,
                      @&quot;/Users/chentao/Desktop/copy_res/jason.ds&quot;,
                      @&quot;/Users/chentao/Desktop/copy_res/jikejunyi.ds&quot;,
                      @&quot;/Users/chentao/Desktop/copy_res/molly.ds&quot;,
                      @&quot;/Users/chentao/Desktop/copy_res/zhangdachuan.ds&quot;,
                      nil];
    NSString *copyDes = @&quot;/Users/chentao/Desktop/copy_des&quot;;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    dispatch_async(dispatch_get_global_queue(0, 0), ^(){
        dispatch_apply([array count], dispatch_get_global_queue(0, 0), ^(size_t index){
            NSLog(@&quot;copy-%ld&quot;, index);
            NSString *sourcePath = [array objectAtIndex:index];
            NSString *desPath = [NSString stringWithFormat:@&quot;%@/%@&quot;, copyDes, [sourcePath lastPathComponent]];
            [fileManager copyItemAtPath:sourcePath toPath:desPath error:nil];
        });
        NSLog(@&quot;完成&quot;);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三: NSOperation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;基于GCD封装
配合NSOperationQueue实现多线程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;使用步骤
&lt;ul&gt;
&lt;li&gt;创建任务
&lt;ul&gt;
&lt;li&gt;先将需要执行的操作封装到NSOperation对象中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;创建队列
&lt;ul&gt;
&lt;li&gt;创建NSOperationQueue&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;将任务加入队列
&lt;ul&gt;
&lt;li&gt;将NSOperation加入到NSOperationQueue中&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用注意
&lt;ul&gt;
&lt;li&gt;由于NSOperation是抽象类需要使用其子类
&lt;ul&gt;
&lt;li&gt;NSInvocationOperation
&lt;ul&gt;
&lt;li&gt;指定Target和Selectore&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NSBlockOperation
&lt;ul&gt;
&lt;li&gt;基于Block&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;定义继承自NSOperation的子类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;NSOperationQueue&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;暂停和取消不是立刻取消当前操作，而是等当前的操作执行完之后不再进行新的操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://www.cocoachina.com/articles/19769&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>源代码到IPA</title><link>https://blog.lab-lawliet.app/posts/ios-ipa-afefc8e8dbaedf9b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ipa-afefc8e8dbaedf9b/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-源代码到IPA&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;进行编译&lt;/li&gt;
&lt;li&gt;生成DSYM文件
&lt;ul&gt;
&lt;li&gt;储存了16进制的函数地址映射&lt;/li&gt;
&lt;li&gt;崩溃日志中的地址通过此文件由地址映射到具体的函数位置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 编译&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Clang + LLVM&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;把源代码转换为更为地址的LLVM IR(Intermedia Represention)
LLVM根据上面转换的IR进行二进制文件的产出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;预处理
&lt;ul&gt;
&lt;li&gt;处理源文件中的宏定义,将代码中的宏用对应的定义的具体内容进行替换&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;便已生成IR(中间代码)&lt;/li&gt;
&lt;li&gt;汇编器生成汇编代码&lt;/li&gt;
&lt;li&gt;生成机器码&lt;/li&gt;
&lt;li&gt;链接&lt;/li&gt;
&lt;li&gt;生成Image,也就是最后的可执行文件&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 每次Command+B发生的事情(基于CocoaPod时)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;编译信息写入辅助文件,创建.app文件&lt;/li&gt;
&lt;li&gt;处理文件打包信息&lt;/li&gt;
&lt;li&gt;执行CocoaPod编译前脚本,checkPods Manifest.lock&lt;/li&gt;
&lt;li&gt;编译.m文件,使用CompileC和clang命令&lt;/li&gt;
&lt;li&gt;链接需要的Framework&lt;/li&gt;
&lt;li&gt;编译xib&lt;/li&gt;
&lt;li&gt;拷贝xib,资源文件&lt;/li&gt;
&lt;li&gt;编译ImageAssets&lt;/li&gt;
&lt;li&gt;处理Info.plist&lt;/li&gt;
&lt;li&gt;执行CocoaPod脚本
&lt;ul&gt;
&lt;li&gt;Run custom shell script &apos;Embed Pods Frameworks&apos;&lt;/li&gt;
&lt;li&gt;Run custom shell script &apos;Copy Pods Resources&apos;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;拷贝标准库&lt;/li&gt;
&lt;li&gt;创建.app文件和签名&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/0df698478b39&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>load与-initialize</title><link>https://blog.lab-lawliet.app/posts/ios-load-initialize-a79f432f25fa2e05/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-load-initialize-a79f432f25fa2e05/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;+ load&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;当类对象被引入时,runtime会向每一个类对象&lt;/li&gt;
&lt;li&gt;load方法会在每一个类甚至分类被引入时仅调用一次.&lt;/li&gt;
&lt;li&gt;调用的顺序是:
&lt;ul&gt;
&lt;li&gt;父类优先于子类,子类优先于分类&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;load方法不会被自动继承
&lt;ul&gt;
&lt;li&gt;子类中没有+load方法时系统不会多次调用父类+load&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Method Swizzle&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;+ initialize&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;第一次使用这个类的时候会调用这个方法-懒加载
&lt;ul&gt;
&lt;li&gt;会在第一次使用第一个类方法或实例方法之前被调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;初始化全局变量或静态变量&lt;/li&gt;
&lt;li&gt;load方法会被子类/分类继承
&lt;ul&gt;
&lt;li&gt;如果子类没有实现+initialize方法,会调用父类的+initialize方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>NSTimer与GCD</title><link>https://blog.lab-lawliet.app/posts/ios-nstimer-gcd-18262ebebb4a1525/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-nstimer-gcd-18262ebebb4a1525/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: 常用延时调用的方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NSObject的performSelector...
&lt;ul&gt;
&lt;li&gt;实际是在当前线程创建了一个Timer去执行任务&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NSTimer
&lt;ul&gt;
&lt;li&gt;+timerWithTimeInterval&lt;/li&gt;
&lt;li&gt;+scheduledTimerWithTimeInterval&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GCDTimer
&lt;ul&gt;
&lt;li&gt;dispatch_after&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: 非GCDTimer使用注意&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;必须有一个活跃的&lt;code&gt;RunLoop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;performSelector&lt;/code&gt; 和&lt;code&gt;scheduledTimerWithTimeInterval&lt;/code&gt; 都是基于RunLoop的.
主线程没关系,因为不会停止
如果在子线程的话需要手动开启RunLoop否则调用将会是无效的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;NSTimer的创建于撤销必须在同一个线程操作&lt;/li&gt;
&lt;li&gt;&lt;code&gt;performSelector&lt;/code&gt;创建取消也必须在同一个线程操作&lt;/li&gt;
&lt;li&gt;内存泄露问题
&lt;ul&gt;
&lt;li&gt;Timer的target会被Timer强引用&lt;/li&gt;
&lt;li&gt;Timer被RunLoop强引用&lt;/li&gt;
&lt;li&gt;所以想在&lt;code&gt;dealloc&lt;/code&gt;里去&lt;code&gt;invalidate&lt;/code&gt; Timer是不可能的&lt;/li&gt;
&lt;li&gt;计时完成后手动去&lt;code&gt;invalidate&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;RunLoop解除对Timer的引用&lt;/li&gt;
&lt;li&gt;Timer解除对Target的引用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: GCDTimer&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;系统会自动处理线程逻辑&lt;/li&gt;
&lt;li&gt;无须关心RunLoop问题
&lt;ul&gt;
&lt;li&gt;block的循环引用还是要注意下&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点
&lt;ul&gt;
&lt;li&gt;没有repeats选项&lt;/li&gt;
&lt;li&gt;直接使用代码比较多,最好封装一下&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/595974-438c0a9cb7a27160.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;四: 时间准确性&lt;/h2&gt;
&lt;h3&gt;NSTimer-较低&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;CFRunloopTimerRef 底层基于 &lt;code&gt;mk_timer&lt;/code&gt; 实现
基于RunLoop运行
使用之前必须注册到RunLoop,但RunLoop为节省资源并不会在非常准确的时机调用定时器,执行任务时,可能错过一个时间点只能等下个时间点再执行
NSTimer有个&lt;code&gt;tolerance&lt;/code&gt;属性可以用于设置宽容度,可提高精准度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;GCDTimer-准确&lt;/h3&gt;
&lt;h3&gt;CADisplayLink-较准确&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;CADisplayLink是一个执行频率（fps）和屏幕刷新相同（可以修改preferredFramesPerSecond改变刷新频率）的定时器，它也需要加入到RunLoop才能执行。与NSTimer类似，CADisplayLink同样是基于CFRunloopTimerRef实现，底层使用mk_timer（可以比较加入到RunLoop前后RunLoop中timer的变化）。和NSTimer相比它精度更高（尽管NSTimer也可以修改精度），不过和NStimer类似的是如果遇到大任务它仍然存在丢帧现象。通常情况下CADisaplayLink用于构建帧动画，看起来相对更加流畅，而NSTimer则有更广泛的用处。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/0c050af6c5ee
https://www.cnblogs.com/kenshincui/p/6823841.html&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>NSURLConnection-和-NSURLSession</title><link>https://blog.lab-lawliet.app/posts/ios-nsurlconnection-nsurlsession-9f93719e508701cf/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-nsurlconnection-nsurlsession-9f93719e508701cf/</guid><description>使用NSURLSession具有以下优点:</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;AFNetworking2.0-&amp;gt;3.0&lt;/code&gt; 用&lt;code&gt;NSURLSession&lt;/code&gt;替换了&lt;code&gt;NSURLConnection&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一: 背景&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;iOS早期版本提供了了&lt;code&gt;NSURLConnection&lt;/code&gt;来执行网络请求.
应用开发人员需要管理链接池,并处理应用后台/中断以及请求的恢复.
从&lt;code&gt;iOS7&lt;/code&gt;开始,苹果发布了&lt;code&gt;NSURLSession&lt;/code&gt;用来替代&lt;code&gt;NSURLConnection&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二: NSURLSession 优点&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;NSURLSession&lt;/code&gt;具有以下优点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NSURLSession&lt;/code&gt;对预防如其中的相关请求而言是一个可配置的容器.
&lt;ul&gt;
&lt;li&gt;例如:对服务器的所有请求都可以配置成始终包含访问令牌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;你可以得到后台联网的所有好处.这有助于提高电池寿命/支持UIKit的多任务/使用与过程传输相同的委托模型&lt;/li&gt;
&lt;li&gt;热和网络任务都可以暂停/停止/重启.此处与&lt;code&gt;NSURLConnection&lt;/code&gt;不同,此处不需要是&lt;code&gt;NSOperation&lt;/code&gt;的子类&lt;/li&gt;
&lt;li&gt;你可以使用&lt;code&gt;NSURLSession&lt;/code&gt;来配置会话以便在每个会话的基础上使用专用存储(缓存/cookie jar等)&lt;/li&gt;
&lt;li&gt;当使用&lt;code&gt;NSURLConnection&lt;/code&gt;时,如果遇到了身份验证问题,问题会在任何一个请求中返回,你无法明确知道那个请求遇到了这个问题.使用&lt;code&gt;NSURLSession&lt;/code&gt;,委托会处理身份验证&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSURLConnection&lt;/code&gt;有一些基于&lt;code&gt;Block&lt;/code&gt;的异步方法,但委托不能使用它们.当发起一个请求时,要么成功,要么失败,即使它需要身份验证.&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;NSURLSession&lt;/code&gt;你可以混合使用.也就是说,可以使用基于&lt;code&gt;Block&lt;/code&gt;的异步方法,还可以设置委托以处理身份验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: NSURLConnection vs NSURLSession&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NSURLConnection
&lt;ul&gt;
&lt;li&gt;一个&lt;code&gt;Connection&lt;/code&gt;对象代表一个任务,每个任务绑定一个&lt;code&gt;configuration&lt;/code&gt;,所有的&lt;code&gt;configuration&lt;/code&gt;共享一个全局储存器(global storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NSURLSession
&lt;ul&gt;
&lt;li&gt;一个&lt;code&gt;Session&lt;/code&gt;绑定一个&lt;code&gt;configuration&lt;/code&gt;,一个&lt;code&gt;Session&lt;/code&gt;可以创建多个&lt;code&gt;Task&lt;/code&gt;,每个&lt;code&gt;Session&lt;/code&gt;都有一个自己的储存器(private storage)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: NSURLSession API&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/1862021-6d34f00f640514f4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;NSURLSessionConfiguration&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/1862021-7b0c50fdae01a65c.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存,cookies,证书存储&lt;/li&gt;
&lt;li&gt;电池使用,网络服务类型&lt;/li&gt;
&lt;li&gt;请求的数量&lt;/li&gt;
&lt;li&gt;资源/请求超时时间&lt;/li&gt;
&lt;li&gt;TLS协议&lt;/li&gt;
&lt;li&gt;HTTP代理,cookies,头部&lt;/li&gt;
&lt;li&gt;子类话的存储器&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Configuration对象默认是可变类型,它们被使用的时候相当于被copy了一份不可变的,在session中不能对中绑定的Configuration进行修改&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;类型:
&lt;ul&gt;
&lt;li&gt;defaultSessionConfiguration : 系统默认&lt;/li&gt;
&lt;li&gt;ephemeralSessionConfiguration : 仅内存缓存, 不做磁盘缓存的配置&lt;/li&gt;
&lt;li&gt;backgroundSessionConfiguration : 这里需要指定一个identifier, identifier用来后台重连session对象. (做后台上传/下载就是这个config)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;NSURLSessionTask&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/1862021-651fb5b7d18581e4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;task被用来取代connection.一个task代表一个任务(相当于NSURLConnection对象)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供任务的状态和进度属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供任务的取消/挂起/恢复功能&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;区分data和upload任务&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提供断点续传的实现&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;分类:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NSURLSessionTask : Task的抽象基类&lt;/li&gt;
&lt;li&gt;NSURLSessionDataTask : 以NSData的形式接收一个URLRequest的内容&lt;/li&gt;
&lt;li&gt;NSURLSessionUploadTask : 上传NSData或者本地磁盘中的文件, 完成后以NSData的形式接收一个URLRequest的响应&lt;/li&gt;
&lt;li&gt;NSURLSessionDownloadTask : 下载完成后返回临时文件在本地磁盘的URL路径&lt;/li&gt;
&lt;li&gt;NSURLSessionStreamTask : 用于建立一个TCP/IP连接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;NSURLSessionDelegate&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/1862021-322f066750c730fb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个代理对象可以处理所有NSURLSession方法
&lt;ul&gt;
&lt;li&gt;Session/task/dataTask/DownloadTask&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;强引用代理对象直到session无效(与平时代理对象weak引用不同),session就会把全部task都取消/完成&lt;/li&gt;
&lt;li&gt;代理方法有可能会阻塞加载
&lt;ul&gt;
&lt;li&gt;记得调用completion handler这个block去让session继续工作, 否则会阻塞起来&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: Session分类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;sharedSession: 全局session有局限性&lt;/li&gt;
&lt;li&gt;自定义session: 自定义session,我们用的一般是这个&lt;/li&gt;
&lt;li&gt;后台session: 也是一种自定义session,专门用来做后台上传下载任务&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;类型由Configuration来决定&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;五: 后台传输&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;只支持上传下载任务,data任务不支持
只支持HTTPS
根据系统资源,很可能会取消掉后台任务,所以最好还是在前台完成
后台任务最好支持断点续传,分段传输&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;系统创建一个守护进程&lt;/li&gt;
&lt;li&gt;当你的app正在运行的时候, 正常调用代理方法传输数据给你&lt;/li&gt;
&lt;li&gt;当你的app退出了(回到桌面/crash), 守护进程会继续他的工作, 直到需要重新认证或者完成所有任务&lt;/li&gt;
&lt;li&gt;re-launch你的app, 然后re-create你的后台session, 然后后台session会re-connect守护进程.&lt;/li&gt;
&lt;li&gt;守护进程此时就会把数据给你.&lt;/li&gt;
&lt;li&gt;可以通过不同的identifier创建多个后台session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;线程安全&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;URLSession 的API全部都是线程安全的. 你可以在任何线程上创建session和tasks, task会自动调度到合适的代理队列中运行
后台传输的代理方法URLSession​Did​Finish​Events​For​Background​URLSession:​可能会在其他线程中被调用. 在该方法中你应该回到主线程然后调用completion handler去触发AppDelegate中的application:​handle​Events​For​Background​URLSession:​completion​Handler:​方法.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.cnblogs.com/kakaluote123/articles/5426923.html
https://www.jianshu.com/p/056b1817d25a
https://www.jianshu.com/p/94d214129d4d
&amp;lt;&amp;lt;高性能iOS应用开发&amp;gt;&amp;gt; GauravVaish&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>RunLoop</title><link>https://blog.lab-lawliet.app/posts/ios-runloop-fc4245ac67646833/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runloop-fc4245ac67646833/</guid><description>RunLoop 的数据源抽象类</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS App 运行原理&lt;/h1&gt;
&lt;h2&gt;App启动&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15347835107162.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;01_动态链接 &lt;code&gt;dyld&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;将程序依赖的动态链接库递归加载进内存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;02_ImageLoader&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;image&lt;/code&gt; 表示一个二进制文件（可执行文件或 so 文件），里面是被编译过的符号、代码等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageLoader&lt;/code&gt; 作用是将这些文件加载进内存，且每一个文件对应一个&lt;code&gt;ImageLoader&lt;/code&gt;实例来负责加载&lt;/li&gt;
&lt;li&gt;在程序运行时它先将动态链接的 &lt;code&gt;image&lt;/code&gt; 加载&lt;/li&gt;
&lt;li&gt;再从可执行文件 &lt;code&gt;image&lt;/code&gt; 递归加载所有符号&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;03_runtime&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15347839427918.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dyld&lt;/code&gt; 开始将程序二进制文件初始化&lt;/li&gt;
&lt;li&gt;交由 &lt;code&gt;ImageLoader&lt;/code&gt; 读取 &lt;code&gt;image&lt;/code&gt;，其中包含了我们的类、方法等各种符号&lt;/li&gt;
&lt;li&gt;由于 &lt;code&gt;runtime&lt;/code&gt; 向 &lt;code&gt;dyld&lt;/code&gt; 绑定了回调，当 &lt;code&gt;image&lt;/code&gt; 加载到内存后，&lt;code&gt;dyld&lt;/code&gt; 会通知 &lt;code&gt;runtime&lt;/code&gt; 进行处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runtime&lt;/code&gt; 接手后调用 &lt;code&gt;map_images&lt;/code&gt; 做解析和处理，接下来 &lt;code&gt;load_images&lt;/code&gt; 中调用 &lt;code&gt;call_load_methods&lt;/code&gt; 方法，遍历所有加载进来的 &lt;code&gt;Class&lt;/code&gt;，按继承层级依次调用 &lt;code&gt;Class&lt;/code&gt; 的 &lt;code&gt;+load&lt;/code&gt; 方法和其 &lt;code&gt;Category&lt;/code&gt; 的 &lt;code&gt;+load&lt;/code&gt; 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RunLoop&lt;/h2&gt;
&lt;h3&gt;01_什么是RunLoop&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使程序一直运行并接受用户输入&lt;/li&gt;
&lt;li&gt;决定程序在何时应该处理那些&lt;code&gt;事件&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;消息队列&lt;/li&gt;
&lt;li&gt;节省CPU时间&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;02_RunLoop 与 线程&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;线程&lt;/code&gt; 和 &lt;code&gt;RunLoop&lt;/code&gt; 之间是一一对应的(可嵌套)，其关系是保存在一个全局的 &lt;code&gt;Dictionary&lt;/code&gt; 里&lt;/li&gt;
&lt;li&gt;线程结束时销毁, 或手动退出&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;03_结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set&amp;lt;Source/Observer/Timer&amp;gt;
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15347785363414.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Source&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;RunLoop&lt;/code&gt; 的数据源抽象类&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RunLoop运行必须要在加入NSTimer或Source0、Sourc1、Observer输入后运行否则会直接退出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;Source0: 处理App内部事件, App自行管理&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;包含了一个&lt;code&gt;函数指针&lt;/code&gt;，它并不能主动触发事件&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;CFRunLoopSourceSignal(source)&lt;/code&gt;，将这个 Source 标记为待处理&lt;/li&gt;
&lt;li&gt;调用 &lt;code&gt;CFRunLoopWakeUp(runloop)&lt;/code&gt; 来唤醒 RunLoop，让其处理这个事件&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Source1: 由&lt;code&gt;RunLoop&lt;/code&gt;内核管理, &lt;code&gt;MachPort&lt;/code&gt;驱动(进程间通讯)&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;包含了一个 &lt;code&gt;mach_port&lt;/code&gt; 和一个 &lt;code&gt;函数指针&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;被用于通过内核和其他线程相互发送消息&lt;/li&gt;
&lt;li&gt;如解锁/摇晃等&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Timer&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;包含了时长 和 &lt;code&gt;函数指针&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Observer&lt;/h4&gt;
&lt;p&gt;向外部报告 &lt;code&gt;RunLoop&lt;/code&gt; 状态的变化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL &amp;lt;&amp;lt; 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL &amp;lt;&amp;lt; 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL &amp;lt;&amp;lt; 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL &amp;lt;&amp;lt; 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL &amp;lt;&amp;lt; 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL &amp;lt;&amp;lt; 7), // 即将退出Loop
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;Observer 与 AutoreleasePool(UIKit)&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;Observer&lt;/code&gt; 在&lt;code&gt;RunLoop&lt;/code&gt;两次&lt;code&gt;Sleep&lt;/code&gt;间 &lt;code&gt;Pop&lt;/code&gt;旧的并释放对象 &lt;code&gt;Push&lt;/code&gt;新的&lt;/p&gt;
&lt;h4&gt;Mode&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @&quot;kCFRunLoopDefaultMode&quot;
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在同一时间必须且只能在一个&lt;code&gt;Mode&lt;/code&gt;下运行&lt;/li&gt;
&lt;li&gt;切换&lt;code&gt;Mode&lt;/code&gt;必须退出&lt;code&gt;RunLoop&lt;/code&gt;,重启&lt;/li&gt;
&lt;li&gt;保证页面滑动流畅的关键&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;NSDefaultRunLoopMode // 默认状态 空闲状态
UITrackingRunLoopMode // 滑动
//私有Mode
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;当创建一个 &lt;code&gt;Timer&lt;/code&gt; 并加到 &lt;code&gt;DefaultMode&lt;/code&gt; 时，&lt;code&gt;Timer&lt;/code&gt; 会得到重复回调，但此时滑动一个&lt;code&gt;TableView&lt;/code&gt; 时，&lt;code&gt;RunLoop&lt;/code&gt; 会将 &lt;code&gt;mode&lt;/code&gt; 切换为 &lt;code&gt;TrackingRunLoopMode&lt;/code&gt;，这时 &lt;code&gt;Timer&lt;/code&gt; 就不会被回调，并且也不会影响到滑动操作。这两个 &lt;code&gt;Mode&lt;/code&gt; 都已经被标记为&lt;code&gt;Common&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;特殊&lt;code&gt;Mode&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NSRunLoopCommonModes
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;有时你需要一个 &lt;code&gt;Timer&lt;/code&gt;，在两个 &lt;code&gt;Mode&lt;/code&gt; 中都能得到回调，一种办法就是将这个 &lt;code&gt;Timer&lt;/code&gt; 分别加入这两个 &lt;code&gt;Mode&lt;/code&gt;。还有一种方式，就是将 &lt;code&gt;Timer&lt;/code&gt; 加入到顶层的 &lt;code&gt;RunLoop&lt;/code&gt; 的 &lt;code&gt;commonModeItems&lt;/code&gt; 中。&lt;code&gt;commonModeItems&lt;/code&gt; 被 &lt;code&gt;RunLoop&lt;/code&gt; 自动更新到所有具有 &lt;code&gt;Common&lt;/code&gt; 的 &lt;code&gt;Mode&lt;/code&gt; 里去。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>基本操作</title><link>https://blog.lab-lawliet.app/posts/ios-runtime-980cd273380b132f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runtime-980cd273380b132f/</guid><description>class_copyMethodList</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Runtime-基本操作&lt;/h1&gt;
&lt;h2&gt;1. 反射 &lt;code&gt;NSString&lt;/code&gt;,&lt;code&gt;Class&lt;/code&gt;,&lt;code&gt;SEL&lt;/code&gt;,&lt;code&gt;Protocol&lt;/code&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);

FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);

FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
FOUNDATION_EXPORT Protocol * _Nullable NSProtocolFromString(NSString *namestr) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 动态创建一个类&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;创建一个继承&lt;code&gt;NSObject&lt;/code&gt;的类,设定类名并初始化；&lt;/li&gt;
&lt;li&gt;为这个类增加一个实例变量,通过KVC给这个实例变量赋值。&lt;/li&gt;
&lt;li&gt;为这个类增加一个方法,在这个方法中打印一些值。&lt;/li&gt;
&lt;li&gt;通过这个类的实例调用新增的方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 获取一个类的所有方法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;class_copyMethodList&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;实例方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;objc/runtime.h&amp;gt;
@implementation NSObject (Runtime)

+ (NSArray *)fetchInstanceMethodList
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(self, &amp;amp;count);
    
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i &amp;lt; count; i++)
    {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;类方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (NSArray *)fetchClassMethodList
{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(object_getClass(self), &amp;amp;count);
    
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i &amp;lt; count; i++)
    {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 获取一个类的所有成员变量&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;class_copyIvarList&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void) getAllVariable {
    unsigned int count = 0;
    //获取类的一个包含所有变量的列表，IVar是runtime声明的一个宏，是实例变量的意思.
    Ivar *allVariables = class_copyIvarList([self class], &amp;amp;count);
    for(int i = 0;i&amp;lt;count;i++)
    {
        //遍历每一个变量，包括名称和类型（此处没有星号&quot;*&quot;）
        Ivar ivar = allVariables[i];
        const char *Variablename = ivar_getName(ivar); //获取成员变量名称
        const char *VariableType = ivar_getTypeEncoding(ivar); //获取成员变量类型
        NSLog(@&quot;(Name: %s) ----- (Type:%s)&quot;,Variablename,VariableType);
    }
    free(allVariables);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 获取一个类的所有属性变量&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;class_copyPropertyList&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void) getAllPropertyList {
    unsigned int count = 0;
    objc_property_t *allVariables = class_copyPropertyList([self class], &amp;amp;count);
    for(int i = 0;i&amp;lt;count;i++)
    {
        //遍历每一个变量，包括名称和类型（此处没有星号&quot;*&quot;）
        objc_property_t property = allVariables[i];
        const char *Variablename = property_getName(property); //获取属性变量名称
        const char *VariableType = property_getAttributes(property); //获取属性变量的属性
        NSLog(@&quot;(Name: %s) ----- (Type:%s)&quot;,Variablename,VariableType);
    }
    free(allVariables);
}
  //还有一种写法，注意 object_getClass 中的参数需要是一个对象实例，
  // 就是说 object_getClass([ChangePayPasswordViewController new]),
  //和 ChangePayPasswordViewController.Class 意义是一样的
  unsigned int a;
  objc_property_t * result = class_copyPropertyList(object_getClass([ChangePayPasswordViewController new]), &amp;amp;a);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 获取协议列表&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;+ (NSArray *)fetchProtocolList
{
   unsigned int count = 0;
   __unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &amp;amp;count);

   NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
   for (unsigned int i = 0; i &amp;lt; count; i++ )
   {
       Protocol *protocol = protocolList[i];
       const char *protocolName = protocol_getName(protocol);
       [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
   }
   return [NSArray arrayWithArray:mutableList];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. 动态给一个类新增一个方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/* 动态添加方法：
 第一个参数表示Class cls 类型；
 第二个参数表示待调用的方法名称；
 第三个参数(IMP)myAddingFunction，IMP一个函数指针，这里表示指定具体实现方法myAddingFunction；
 第四个参数表方法的参数，0代表没有参数；
 */
class_addMethod([person class], @selector(myAddingFunction), (IMP)myAddingFunction, 0);
//调用方法 【如果使用[per NewMethod]调用方法，在ARC下会报“no visible @interface&quot;错误】
[person performSelector:@selector(myAddingFunction)];

//具体的实现（方法的内部都默认包含两个参数Class类和SEL方法，被称为隐式参数。）
int myAddingFunction(id self, SEL _cmd){
    NSLog(@&quot;已新增方法:NewMethod&quot;);
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. 动态增加实例变量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;BOOL isSuccess = class_addIvar(MyClass, &quot;test&quot;, sizeof(NSString *), 0, &quot;@&quot;);
// 三目运算符
isSuccess?NSLog(@&quot;添加变量成功&quot;):NSLog(@&quot;添加变量失败&quot;);

参数一、类名
参数二、属性名称
参数三、开辟字节长度
参数四、对其方式
参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) （对象 静态类型或者id类型)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. 动态改变对象的某个变量值&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;可以是属性变量（注意加 _ ），也可以是私有的全局变量&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Person *person = [[Person alloc]init];
person.name = @&quot;asdas&quot;;
NSLog(@&quot;%@&quot;,person.name);
[self editObjectIvar:person :@&quot;_name&quot; :@&quot;新属性&quot;];
NSLog(@&quot;new :%@&quot;,person.name);

- (void)editObjectIvar :(id)object
                       :(NSString *)IvarStr
                       :(NSString *)IvarNewStr

{
    unsigned int count = 0;
    Ivar *allList = class_copyIvarList([object class], &amp;amp;count);
    for(int i = 0;i&amp;lt;count;i++)
    {
        //遍历每一个变量，包括名称和类型（此处没有星号&quot;*&quot;）
        Ivar ivar = allList[i];
        const char *Variablename = ivar_getName(ivar); //获取成员变量名称
        if ([[NSString stringWithUTF8String:Variablename] isEqualToString:IvarStr]) {
            object_setIvar(object, ivar, IvarNewStr); //name属性Tom被强制改为Mike。
        }
    }
    free(allList);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;10. 属性关联&lt;/h2&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h2&gt;11. 交换方法&lt;/h2&gt;
&lt;p&gt;略&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;https://www.jianshu.com/p/8225526096c7&lt;/p&gt;
</content:encoded></item><item><title>重写description打印对象信息</title><link>https://blog.lab-lawliet.app/posts/ios-runtime-description-7688b288eca3e9ef/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runtime-description-7688b288eca3e9ef/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Runtime-重写description打印对象信息&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;-(NSString *)description
{
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &amp;amp;count);
    
    for (int i = 0; i&amp;lt;count; i++) {
        objc_property_t property = properties[i];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@&quot;nil&quot;;      
       [dictionary setObject:value forKey:name];
    }
    
    free(properties);
    return [NSString stringWithFormat:@&quot;&amp;lt;%@: %p&amp;gt; -- %@&quot;,[self class],self,dictionary];
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>消息机制</title><link>https://blog.lab-lawliet.app/posts/ios-runtime-e0ea7b27670dd16b/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runtime-e0ea7b27670dd16b/</guid><description>将.m转换为.cpp的clang命令</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Runtime-消息机制&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;OC&lt;/code&gt;中调用方法称为&lt;code&gt;消息传递&lt;/code&gt;
&lt;code&gt;OC&lt;/code&gt;中消息传递采用动态绑定机制来决定具体调用哪个方法,但&lt;code&gt;OC&lt;/code&gt;不是在编译时决定调用什么方法而是在运行时决定&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;转换为CPP&lt;/h2&gt;
&lt;p&gt;将&lt;code&gt;.m&lt;/code&gt;转换为&lt;code&gt;.cpp&lt;/code&gt;的&lt;code&gt;clang&lt;/code&gt;命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;clang -rewrite-objc xxx.m
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;OC&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
@autoreleasepool {
     //为了方便查看转写后的C语言代码，将alloc和init分两步完成
    Person *p = [Person alloc];
    p = [p init];
    p.name = @&quot;Jiaming Chen&quot;;
    [p showMyself];
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;CPP&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass(&quot;Person&quot;), sel_registerName(&quot;alloc&quot;));
    p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName(&quot;init&quot;));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)p, sel_registerName(&quot;setName:&quot;), (NSString *)&amp;amp;__NSConstantStringImpl__var_folders_1f_dz4kq57d4b19s4tfmds1mysh0000gn_T_main_f5b408_mi_1);
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName(&quot;showMyself&quot;));

   }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;调用方法时具体发生了什么呢?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;现在本类的方法缓存列表中寻找有没有对应方法,如果有就执行&lt;/li&gt;
&lt;li&gt;如果没有缓存的,就在本类的犯法列表中查找,找到了就执行&lt;/li&gt;
&lt;li&gt;如果没找到,就到父类中查找,找到了就执行&lt;/li&gt;
&lt;li&gt;没找到,进入下面的流程&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;消息处理-ResolveMethod&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;如果沿继承树没有找到相关方法则会向接受者所属的类进行一次请求,看能否动态添加一个方法&lt;/li&gt;
&lt;li&gt;在相应的类及父类中找不到方法实现的话会执行&lt;code&gt;+ (BOOL)resolveInstanceMethod:(SEL)sel&lt;/code&gt;这个方法,如果没重写的话会返回&lt;code&gt;NO&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在该方法中我们可以为找不到实现的&lt;code&gt;SEL&lt;/code&gt;添加一个实现.这样就能防止调用一个不存在的方法时崩溃.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;消息单项转发&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;当&lt;code&gt;resolveInstanceMethod&lt;/code&gt;返回&lt;code&gt;NO&lt;/code&gt;,就会进入消息转发阶段&lt;code&gt;- (id)forwardingTargetForSelector: (SEL)aSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;该方法会返回一个累的对象,该对象包含&lt;code&gt;SEL&lt;/code&gt;对应的实现.如果返回&lt;code&gt;nil&lt;/code&gt;或&lt;code&gt;self&lt;/code&gt;的话说明找不到相应的转发进入下一步&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;消息多项转发&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果不将消息转发给其他对象就只能自己处理或者崩溃了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;获取方法的参数及返回数据类型,也就是说获取的是该方法的签名并返回,进入下一步&lt;/li&gt;
&lt;li&gt;返回&lt;code&gt;NSMethodSignature&lt;/code&gt;为&lt;code&gt;nil&lt;/code&gt;的话不会进入下一步,抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;具体的实现方式不再展开, 每种数据类型都有对应的符号代表,根据一定规则生成描述&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;- (void)forwardInvocation:(NSInvocation *)anInvocation&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runtime&lt;/code&gt;将要转发的消息封装为&lt;code&gt;NSInvocation&lt;/code&gt;调用&lt;code&gt;- (void)forwardInvocation: (NSInvocation*)invocation;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在这个函数里可以将NSInvocation多次转发到多个对象中&lt;/li&gt;
&lt;li&gt;调用这个方法如果不能处理就会调用父类的相关方法,一直到&lt;code&gt;NSObject&lt;/code&gt;的这个方法&lt;/li&gt;
&lt;li&gt;如果&lt;code&gt;NSObject&lt;/code&gt;都无法处理就会调用&lt;code&gt;doesNotRecognizeSelector:&lt;/code&gt;方法抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;NSInvocation&lt;/code&gt;有点类似于&lt;code&gt;java&lt;/code&gt;里的反射，它有一套完整的装备：&lt;code&gt;target&lt;/code&gt;，&lt;code&gt;selector&lt;/code&gt;，&lt;code&gt;returnValue&lt;/code&gt;，&lt;code&gt;ArgumentArray&lt;/code&gt;，有了它们，&lt;code&gt;NSInvocation&lt;/code&gt;就可以动态的&lt;code&gt;invoke&lt;/code&gt;任意对象的任意方法了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;https://www.jianshu.com/p/a4a7e91d38f3&lt;/p&gt;
</content:encoded></item><item><title>MethodSwizzling的问题</title><link>https://blog.lab-lawliet.app/posts/ios-runtime-methodswizzling-febbfdc7fc8e19e7/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runtime-methodswizzling-febbfdc7fc8e19e7/</guid><description>我所见过的使用method swizzling实现的方法在并发使用时基本都是安全的。95%的情况里这都不会是个问题。通常你替换一个方法的实现，是希望它在整个程序的生命周期里有效的。也就是说，你会把 method swizzling 修改方法实现的操作放在一个加号方法 +(void)load里，并在应</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Runtime-MethodSwizzling的问题&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;使用 Method Swizzling 编程就好比切菜时使用锋利的刀，一些人因为担心切到自己所以害怕锋利的刀具，可是事实上，使用钝刀往往更容易出事，而利刀更为安全。
Method swizzling 可以帮助我们写出更好的，更高效的，易维护的代码。但是如果滥用它，也将会导致难以排查的bug。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;相关方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;class_replaceMethod&lt;/code&gt; 替换类方法的定义
*&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method_exchangeImplementations&lt;/code&gt; 交换 2 个方法的实现
&lt;ul&gt;
&lt;li&gt;这里相当于调用了两次&lt;code&gt;method_setImplementation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;点进方法的注释里面可以看到相关注释&lt;/li&gt;
&lt;li&gt;即 A &amp;lt;-&amp;gt; B&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method_setImplementation&lt;/code&gt; 设置 1 个方法的实现
&lt;ul&gt;
&lt;li&gt;即 A -&amp;gt; B&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意点:
用&lt;code&gt;method_setImplementation&lt;/code&gt;的话单方面设置实现有的场景挺实用
&lt;code&gt;functionA&lt;/code&gt; 设置了 &lt;code&gt;functionB&lt;/code&gt;的实现
那么项目中使用 &lt;code&gt;functionA&lt;/code&gt; 和 &lt;code&gt;functionB&lt;/code&gt; 的地方就会有同样的实现
但是如果用 &lt;code&gt;method_exchangeImplementations&lt;/code&gt; 的话,就会有不同的实现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;看一下 RNSwizzle 的封装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;//
//  RNSwizzle.m
//  MethodSwizzle

#import &quot;RNSwizzle.h&quot;
#import &amp;lt;objc/runtime.h&amp;gt;

@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector 
               withIMP:(IMP)newIMP {
  Class class = [self class];
  Method origMethod = class_getInstanceMethod(class,
                                              origSelector);
  IMP origIMP = method_getImplementation(origMethod);
  
  if(!class_addMethod(self, origSelector, newIMP,
                      method_getTypeEncoding(origMethod)))
  {
    method_setImplementation(origMethod, newIMP);
  }
  
  return origIMP;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;方法交换不是自动的&lt;/h2&gt;
&lt;p&gt;我所见过的使用method swizzling实现的方法在并发使用时基本都是安全的。95%的情况里这都不会是个问题。通常你替换一个方法的实现，是希望它在整个程序的生命周期里有效的。也就是说，你会把 method swizzling 修改方法实现的操作放在一个加号方法 +(void)load里，并在应用程序的一开始就调用执行。你将不会碰到并发问题。假如你在 +(void)initialize初始化方法中进行swizzle，那么……rumtime可能死于一个诡异的状态。&lt;/p&gt;
&lt;h2&gt;改变不是自己的代码&lt;/h2&gt;
&lt;p&gt;这是swizzling的一个问题。我们的目标是改变某些代码。swizzling方法是一件灰常灰常重要的事，当你不只是对一个NSButton类的实例进行了修改，而是程序中所有的NSButton实例。因此在swizzling时应该多加小心，但也不用总是去刻意避免。&lt;/p&gt;
&lt;p&gt;想象一下，如果你重写了一个类的方法，而且没有调用父类的这个方法，这可能会引起问题。大多数情况下，父类方法期望会被调用（至少文档是这样说的）。如果你在swizzling实现中也这样做了，这会避免大部分问题。还是调用原始实现吧，如若不然，你会费很大力气去考虑代码的安全问题。&lt;/p&gt;
&lt;h2&gt;命名冲突&lt;/h2&gt;
&lt;p&gt;命名冲突贯穿整个Cocoa的问题. 我们常常在类名和类别方法名前加上前缀。不幸的是，命名冲突仍是个折磨。但是swizzling其实也不必过多考虑这个问题。我们只需要在原始方法命名前做小小的改动来命名就好，比如通常我们这样命名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码运行正确，但是如果my_setFrame: 在别处被定义了会发生什么呢？
这个问题不仅仅存在于swizzling，这里有一个替代的变通方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&amp;amp;SetFrameIMP];
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来不那么Objectice-C了（用了函数指针），这样避免了selector的命名冲突。
最后给出一个较完美的swizzle方法的定义：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp &amp;amp;&amp;amp; store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;交换方法影响了方法调用&lt;/h2&gt;
&lt;p&gt;项目中调用不同方法名的问题
上面讨论过了&lt;/p&gt;
&lt;h2&gt;交换的顺序&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;多个有继承关系的类的对象swizzle时，先从父对象开始&lt;/li&gt;
&lt;li&gt;这样才能保证子类方法拿到父类中的被swizzle的实现&lt;/li&gt;
&lt;li&gt;在+(void)load中swizzle不会出错，就是因为load类方法会默认从父类开始调用&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>实现NSCoding的自动归档和自动解档</title><link>https://blog.lab-lawliet.app/posts/ios-runtime-nscoding-3b8f2e32ec304789/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-runtime-nscoding-3b8f2e32ec304789/</guid><description>借助Runtime我们可以对此进行封装</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Runtime-实现NSCoding的自动归档和自动解档&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;需要自定义模型持久化的时候我们往往需要为各个模型实现对应的&lt;code&gt;encode``decode&lt;/code&gt;方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;借助&lt;code&gt;Runtime&lt;/code&gt;我们可以对此进行封装&lt;/p&gt;
&lt;h2&gt;解档&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    
    if (self = [super init]) {
        Class c = self.class;
        // 截取类和父类的成员变量
        while (c &amp;amp;&amp;amp; c != [NSObject class]) {
            unsigned int count = 0;
            Ivar *ivars = class_copyIvarList(c, &amp;amp;count);
            for (int i = 0; i &amp;lt; count; i++) {
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
                id value = [aDecoder decodeObjectForKey:key];
                if (value){// 容错
                     [self setValue:value forKey:key];
                }
            }
            // 获得c的父类
            c = [c superclass];
            free(ivars);
        }
    }
    return self;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;这里需要注意的是如果版本升级后&lt;code&gt;Model&lt;/code&gt;的属性发生了变化这里可能会发生崩溃
所以需要在&lt;code&gt;swtValueForKey&lt;/code&gt;的时候做判断&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;解档&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;- (void)encodeWithCoder:(NSCoder *)aCoder{
    
    Class c = self.class;
    // 截取类和父类的成员变量
    while (c &amp;amp;&amp;amp; c != [NSObject class]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(c, &amp;amp;count);
        for (int i = 0; i &amp;lt; count; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            id value = [self valueForKey:key];
            if (value){
                 [aCoder encodeObject:value forKey:key];
             }
        }
        c = [c superclass];
        // 释放内存
        free(ivars);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SQLite数据库修复</title><link>https://blog.lab-lawliet.app/posts/ios-sqlite-fff0f4dc5c7beadd/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-sqlite-fff0f4dc5c7beadd/</guid><description>TODO:</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TODO:&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://juejin.im/entry/58ff4c7544d9040069fb061e&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Storyboard中UnwindSegue的使用</title><link>https://blog.lab-lawliet.app/posts/ios-storyboard-unwindsegue-8530e9a410e3bba7/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-storyboard-unwindsegue-8530e9a410e3bba7/</guid><description>在一系列跳转关系的控制器中的根控制器中添加一个方法 如下图,我就该在红框控制器中添加 </description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;在最下层控制器中添加 &lt;code&gt;unwind segue&lt;/code&gt; 的 &lt;code&gt;IBAction&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;在一系列跳转关系的控制器中的根控制器中添加一个方法
如下图,我就该在红框控制器中添加
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15082331645192.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;之后在该控制器后续关联的控制器中点击 &lt;code&gt;exit&lt;/code&gt; 可以看到这个 &lt;code&gt;segue&lt;/code&gt; 了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15094637614639.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;想我这里联线的是按钮 &lt;code&gt;toMain&lt;/code&gt;  点击按钮之后就会回到写有 &lt;code&gt;unwindSwgue&lt;/code&gt; 的页面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15094639919780.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;当然传值操作和普通 &lt;code&gt;segue&lt;/code&gt; 一样可以再下面的方法中进行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15094641190591.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>使用TTF字体</title><link>https://blog.lab-lawliet.app/posts/ios-ttf-0b795191c562078f/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ttf-0b795191c562078f/</guid><description>在项目的 Info.plist 中添加 Fonts provided by application 在其中填入字体文件名</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS 使用TTF字体&lt;/h1&gt;
&lt;p&gt;在项目的 &lt;code&gt;Info.plist&lt;/code&gt; 中添加 &lt;code&gt;Fonts provided by application&lt;/code&gt; 在其中填入字体文件名&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15066076915843.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15066114496262.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以通过下面的方式通过 &lt;code&gt;fontName&lt;/code&gt; 设置 &lt;code&gt;font&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let familyNames = UIFont.familyNames
        for familyName in familyNames {
            print(&quot;++++++ &quot; + familyName + &quot; ++++++&quot;)
            let fontNames = UIFont.fontNames(forFamilyName: familyName)
            for fontName in fontNames {
                print(fontName)
            }
            print(&quot;=================================&quot;)
        }
        
        return UIFont(name: &quot;hahahahahaah&quot;, size: fontSize)!
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>UI调试-UIDebuggingInformationOverlay</title><link>https://blog.lab-lawliet.app/posts/ios-ui-uidebugginginformationoverlay-0217243e87dea097/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-ui-uidebugginginformationoverlay-0217243e87dea097/</guid><description>直接上代码吧 由于使用的是Apple的私有API所以只能通过下面的方式来调用 写在AppDelegate里即可</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;最近看到一篇关于使用Apple私有API进行UI调试的文章(http://ryanipete.com/blog/ios/swift/objective-c/uidebugginginformationoverlay/)
在这里简单说下怎么使用
话说这玩意儿本身不是很稳定,有时在调试窗口中就会崩溃...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;p&gt;直接上代码吧
由于使用的是Apple的私有API所以只能通过下面的方式来调用
写在AppDelegate里即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [NSClassFromString(@&quot;UIDebuggingInformationOverlay&quot;) performSelector:NSSelectorFromString(@&quot;prepareDebuggingOverlay&quot;)];
    return YES;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意:
只能在开发调试阶段使用上架之前一定要干掉不然会被拒掉的哦&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;调起调试窗口&lt;/h2&gt;
&lt;p&gt;在任意页面使用双指 单击状态栏即可调起调试窗口
通过按住调试窗口的顶部可以拖动调整调试窗口的位置
也可以通过左上角按钮进行全屏和窗口模式的切换
如图效果:
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962186443463.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;下面来看看都有哪些功能吧&lt;/h2&gt;
&lt;h3&gt;View Hierarchy 视图集合&lt;/h3&gt;
&lt;p&gt;这里会显示当前&lt;code&gt;Window&lt;/code&gt;下的视图集合,层级关系页十分清晰,点击后面的&lt;code&gt;i&lt;/code&gt;图标还可查看对应View的详细信息
你还可以在不同&lt;code&gt;Window&lt;/code&gt;间切换查看
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962198828930.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962200654350.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;VC Hierarchy 控制器集合&lt;/h3&gt;
&lt;p&gt;这里的控制器集合和上面的视图集合差不多
提供的信息也挺详细的
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962202375596.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962202461191.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962202549313.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Ivar Explorer 变量&lt;/h3&gt;
&lt;p&gt;这里我们可以看到&lt;code&gt;UIApplication&lt;/code&gt;的所有变量
感觉实际用处不是很大,变量过多也没有筛选功能,而且...有时滑滑还会Crash掉😂
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962206787017.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Measure 测量&lt;/h3&gt;
&lt;p&gt;挺实用的功能
有几种模式可以选择
使用方法:
手指长按屏幕移动,调试窗口中有个红色小方块对应的就是当前你按压的点&lt;/p&gt;
&lt;h4&gt;水平测量&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;不开启&lt;code&gt;ViewMode&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以你按压的点的位置水平方向,最近的两个可见的轮廓边缘最近距离,无视控件
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962215907592.png&quot; alt=&quot;&quot; /&gt;
通过下面这张图可以明显的感觉到这个测量是根据轮廓来的...居然测了我老婆头发边缘到左边的距离
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962222065291.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开启&lt;code&gt;ViewMode&lt;/code&gt;
开启&lt;code&gt;ViewMode&lt;/code&gt;后测量的就是按压所在控件的尺寸了
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962228241136.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;垂直测量&lt;/h4&gt;
&lt;p&gt;具体和水平测量差不多我就不多赘述了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不开启&lt;code&gt;ViewMode&lt;/code&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962216076414.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;开启&lt;code&gt;ViewMode&lt;/code&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962227921357.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Spec Compare 效果对比&lt;/h3&gt;
&lt;p&gt;这个功能在对比UI效果还原度上真乃神器也~
首先把UI妹纸给你的UI图发到手机上
在需要对比的页面上双击状态栏唤起调试工具
点击 &lt;code&gt;Spec Compare&lt;/code&gt; 进入下面的页面
点击右上 &lt;code&gt;Add&lt;/code&gt; 从手机相册导入UI图
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962193092278.png&quot; alt=&quot;&quot; /&gt;
点击导入的UI图即可进入对比模式
调试工具会将导入的UI图覆盖在当前页面上
通过单指上下滑动即可调整透明度,从而进行UI还原度的对比,效果如下
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/14962193475733.png&quot; alt=&quot;&quot; /&gt;
双击屏幕即可退出对比模式&lt;/p&gt;
&lt;h3&gt;System Color Audit&lt;/h3&gt;
&lt;p&gt;还没发现有啥用...点进去啥都木有来着
待发掘&lt;/p&gt;
</content:encoded></item><item><title>UIWebView自适应高度</title><link>https://blog.lab-lawliet.app/posts/ios-uiwebview-aa70656768702f00/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-uiwebview-aa70656768702f00/</guid><description>初遇这个问题时我首先想到的是在webViewDidFinishLoad 这个代理方法中通过获取webView的ScrollView的大小去改变内嵌部分的高度</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;场景&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;最近项目中遇到Native页面内需要内嵌H5页面的需求
内嵌的H5页面用来暂时运营后台动态配置的一些运营内容
需要自适应H5内容的高度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;思路&lt;/h2&gt;
&lt;h3&gt;思路一: webViewDidFinishLoad&lt;/h3&gt;
&lt;p&gt;初遇这个问题时我首先想到的是在&lt;code&gt;webViewDidFinishLoad&lt;/code&gt; 这个代理方法中通过获取&lt;code&gt;webView&lt;/code&gt;的&lt;code&gt;ScrollView&lt;/code&gt;的大小去改变内嵌部分的高度&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- (void)webViewDidFinishLoad:(UIWebView *)webView {
    //TODO: 获取高度并改变容器大小
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是
这个方法执行的时候页面可能还没有完全展示出来,web页面渲染是需要时间的
所以
在&lt;code&gt;webViewDidFinishLoad&lt;/code&gt;中去获取到的高度并不准确&lt;/p&gt;
&lt;h3&gt;思路二: KVO&lt;/h3&gt;
&lt;p&gt;对!
监听&lt;code&gt;webView&lt;/code&gt;的&lt;code&gt;scrollView&lt;/code&gt;的contentSize的变化&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;添加监听&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;[self.hybridWebView.scrollView addObserver:self forKeyPath:@&quot;contentSize&quot; options:NSKeyValueObservingOptionNew context:nil];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;触发&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary&amp;lt;NSKeyValueChangeKey,id&amp;gt; *)change context:(void *)context {
    if ([keyPath isEqualToString:@&quot;contentSize&quot;]) {
       //TODO: 获取高度并改变容器大小
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>UIWebView和WKWebView</title><link>https://blog.lab-lawliet.app/posts/ios-uiwebview-wkwebview-dc694b5a7c9dd2c7/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-uiwebview-wkwebview-dc694b5a7c9dd2c7/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: UIWebView的优缺点&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;支持缓存和NSURLProtocol拦截&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;内存泄露&lt;/li&gt;
&lt;li&gt;加载大数据资源时内存占用大,耗电量高&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: WKWebView的优缺点&lt;/h2&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;加载网页速度提升,内存占用低(UIWebView的1/3 ~ 1/4)&lt;/li&gt;
&lt;li&gt;提供加载网页进度的属性&lt;/li&gt;
&lt;li&gt;拥有高达60FPS滚动刷新率及内置手势&lt;/li&gt;
&lt;li&gt;高效的App和web信息交换通道&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;没有缓存(WKWebsiteDataStore - iOS9之后)&lt;/li&gt;
&lt;li&gt;不支持NSURLProtocol拦截(可通过私有方法实现桥接)
&lt;ul&gt;
&lt;li&gt;拦截的POST数据不完整基本等于没用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;资源代理&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;iOS11开始 &lt;code&gt;WKURLSchemeHandler&lt;/code&gt; 可用来加载自定义资源&lt;/li&gt;
&lt;li&gt;注意：
&lt;ul&gt;
&lt;li&gt;setURLSchemeHandler注册时机只能在WKWebView创建WKWebViewConfiguration时注册。&lt;/li&gt;
&lt;li&gt;WKWebView 只允许开发者拦截自定义 Scheme 的请求，不允许拦截 “http”、“https”、“ftp”、“file” 等的请求，否则会crash。&lt;/li&gt;
&lt;li&gt;WKWebView加载网页前，要在user-agent添加个标志，H5遇到这个标识就使用customScheme,否则就是用原来的http或https。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: Cookie&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;WKWebView 上请求不会自动携带 Cookie ，解决方案是 loadRequest 时，从 NSHTTPCookieStorage 中获取对应的 cookie，通过 WKUserScript 设置到 WKWebView 界面中的 document.cookies 中，&lt;/li&gt;
&lt;li&gt;注意的是，注入 cookie 的脚本执行时机需要设置为 WKUserScriptInjectionTimeAtDocumentStart 。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四: 跨域&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;WebKit框架对跨域进行了安全性检查限制，不允许跨域，比如从一个 &lt;code&gt;HTTP&lt;/code&gt; 页对 &lt;code&gt;HTTPS&lt;/code&gt; 发起请求是无效的(Safari支持)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解决办法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在该方法中特殊处理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://upload-images.jianshu.io/upload_images/1200910-1039aa39914f2e79.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/873&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/90a90bd13aac&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;iOS App秒开H5优化总结&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://mp.weixin.qq.com/s?__biz=MzA5NzMwODI0MA==&amp;amp;mid=2647762931&amp;amp;idx=1&amp;amp;sn=ce6e47875d8d278110fca1f18fec6f28&amp;amp;chksm=8887dcccbff055da059008e463bb26829dbb94ad7b94d1bf7f70231060498388832fc7f3d75c&amp;amp;scene=21#wechat_redirect&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;iOS App秒开H5实战总结&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://mp.weixin.qq.com/s/1Rfizwcn9EV2rby0btSMaw&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;WKWebVIew那些坑&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&amp;amp;mid=2653578513&amp;amp;idx=1&amp;amp;sn=961bf5394eecde40a43060550b81b0bb&amp;amp;scene=21#wechat_redirect&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>为什么选VIPER</title><link>https://blog.lab-lawliet.app/posts/ios-viper-85a0d5cc09eaf2e8/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-viper-85a0d5cc09eaf2e8/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-为什么选VIPER&lt;/h1&gt;
&lt;h2&gt;一: 优点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;拆分的职责更细,便于编写测试用例&lt;/li&gt;
&lt;li&gt;VIPER内部通过Protocol解耦,能灵活应对变化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: 和MV(X)系列的对比&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;这里的Presenter和MVP中的不同
&lt;ul&gt;
&lt;li&gt;这里的P并没有主要负责业务逻辑&lt;/li&gt;
&lt;li&gt;主要是用来V/I间传递事件,以及管理一些View交互逻辑&lt;/li&gt;
&lt;li&gt;I承载业务及数据逻辑&lt;/li&gt;
&lt;li&gt;I的可测试性会更强&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;VIPER和MVX相比多了几个要维护的东西
&lt;ul&gt;
&lt;li&gt;View事件管理&lt;/li&gt;
&lt;li&gt;数据事件管理&lt;/li&gt;
&lt;li&gt;事件和业务的转化&lt;/li&gt;
&lt;li&gt;总结每个业务用例&lt;/li&gt;
&lt;li&gt;模块内分层隔离&lt;/li&gt;
&lt;li&gt;模块间通信&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三: 好架构应该有的特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;能把代码按职责划分解耦&lt;/li&gt;
&lt;li&gt;可测试性&lt;/li&gt;
&lt;li&gt;易用,维护成本低,改动成本小&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://juejin.im/post/5ad1b93d518825557b4d2d44&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>weak实现原理</title><link>https://blog.lab-lawliet.app/posts/ios-weak-85ea6ac8b6c2829a/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/ios-weak-85ea6ac8b6c2829a/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-weak实现原理&lt;/h1&gt;
&lt;h2&gt;描述&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;系统维护了一个&lt;code&gt;hash&lt;/code&gt;表,
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Key&lt;/code&gt;是所指对象的地址,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Value&lt;/code&gt;是&lt;code&gt;weak&lt;/code&gt;指针的地址数组&lt;/li&gt;
&lt;li&gt;弱引用,应用对象的引用计数不会+1&lt;/li&gt;
&lt;li&gt;并在对象被释放的时候自动设为&lt;code&gt;nil&lt;/code&gt;,这是和&lt;code&gt;assign&lt;/code&gt;本质的区别,用&lt;code&gt;assign&lt;/code&gt;修饰对象的话会发生野指针的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实现过程&lt;/h2&gt;
&lt;h3&gt;初始化时&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runtime&lt;/code&gt;调用&lt;code&gt;objc_initWeak&lt;/code&gt;函数创建一个新的&lt;code&gt;weak&lt;/code&gt;指针指向修饰的对象的地址&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;//初始化weak表
/** 
 * Initialize a fresh weak pointer to some object location. 
 * It would be used for code like: 
 *
 * (The nil case) 
 * __weak id weakPtr;
 * (The non-nil case) 
 * NSObject *o = ...;
 * __weak id weakPtr = o;
 * 
 * @param addr Address of __weak ptr. 
 * @param val Object ptr. 
 */
id objc_initWeak(id *addr, id val)
{
    *addr = 0;
    if (!val) return nil;
    return objc_storeWeak(addr, val); // 存储weak对象
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;添加引用时&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;objc_initWeak&lt;/code&gt;会调用&lt;code&gt;objc_storeWeak&lt;/code&gt;函数,更新指针指向,创建对应弱引用表&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/** 
 * This function stores a new value into a __weak variable. It would
 * be used anywhere a __weak variable is the target of an assignment.
 * 
 * @param location The address of the weak pointer itself
 * @param newObj The new object this weak ptr should now point to
 * 
 * @return \e newObj
 */
id
objc_storeWeak(id *location, id newObj)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    spinlock_t *lock1;
#if SIDE_TABLE_STRIPE &amp;gt; 1
    spinlock_t *lock2;
#endif

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    oldObj = *location;
    
    oldTable = SideTable::tableForPointer(oldObj);
    newTable = SideTable::tableForPointer(newObj);
    
    lock1 = &amp;amp;newTable-&amp;gt;slock;
#if SIDE_TABLE_STRIPE &amp;gt; 1
    lock2 = &amp;amp;oldTable-&amp;gt;slock;
    if (lock1 &amp;gt; lock2) {
        spinlock_t *temp = lock1;
        lock1 = lock2;
        lock2 = temp;
    }
    if (lock1 != lock2) spinlock_lock(lock2);
#endif
    spinlock_lock(lock1);

    if (*location != oldObj) {
        spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE &amp;gt; 1
        if (lock1 != lock2) spinlock_unlock(lock2);
#endif
        goto retry;
    }

    weak_unregister_no_lock(&amp;amp;oldTable-&amp;gt;weak_table, oldObj, location);
    newObj = weak_register_no_lock(&amp;amp;newTable-&amp;gt;weak_table, newObj, location);
    // weak_register_no_lock returns nil if weak store should be rejected

    // Set is-weakly-referenced bit in refcount table.
    if (newObj  &amp;amp;&amp;amp;  !newObj-&amp;gt;isTaggedPointer()) {
        newObj-&amp;gt;setWeaklyReferenced_nolock();
    }

    // Do not set *location anywhere else. That would introduce a race.
    *location = newObj;
    
    spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE &amp;gt; 1
    if (lock1 != lock2) spinlock_unlock(lock2);
#endif

    return newObj;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;weak_unregister_no_lock&lt;/code&gt;旧对象解除注册
*&lt;/li&gt;
&lt;li&gt;&lt;code&gt;weak_register_no_lock&lt;/code&gt;新对象添加注册
*&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;释放时&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;调用&lt;code&gt;objc_release&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;引用计数为0调用&lt;code&gt;dealloc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dealloc&lt;/code&gt;中调用了&lt;code&gt;_objc_rootDealloc&lt;/code&gt;函数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_objc_rootDealloc&lt;/code&gt;调用了&lt;code&gt;object_dispose&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;调用&lt;code&gt;objc_destructInstance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;objc_destructInstance&lt;/code&gt;首先根据对象地址获取所有&lt;code&gt;weak&lt;/code&gt;指针地址数组,遍历置为&lt;code&gt;nil&lt;/code&gt;,然后把这&lt;code&gt;weak&lt;/code&gt;表中所有该地址的记录删除&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>iOS-常见崩溃分析</title><link>https://blog.lab-lawliet.app/posts/issue-ios-7f8152a3ef134816/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/issue-ios-7f8152a3ef134816/</guid><description>在pch中:</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1: 字典插入nil&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;通过Runtime替换,为系统方法添加容错
&lt;ul&gt;
&lt;li&gt;同时添加日志,上报记录错误&lt;/li&gt;
&lt;li&gt;保证程序运行稳定的情况下,暴露错误&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2: 越界&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;处理方式同上&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. unrecognized selector&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;可以再消息传递流程内进行防护&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. TableView &amp;amp; CollectionView cell代理方法存在返回 nil 的情况&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;别偷懒&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. EXC_BAD_ACCESS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;开启僵尸对象调试选项进行调试&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. SIGPIPE 异常&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;经实验在多个ios版本下都不再触发SIGPIPE，完美解决问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;对一个端已经关闭的socket调用两次write，第二次write将会产生SIGPIPE信号，该信号默认结束进程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在pch中:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 仅在 IOS 系统上支持 SO_NOSIGPIPE
#if defined(SO_NOSIGPIPE) &amp;amp;&amp;amp; !defined(MSG_NOSIGNAL)
    // We do not want SIGPIPE if writing to socket.
    const int value = 1;
    setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &amp;amp;value, sizeof(int));
#endif
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. 子线程UI操作崩溃&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;放主线程&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. SIGABRT 异常&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;这是一个让程序终止的标识，会在断言、app内部、操作系统用终止方法抛出。通常发生在异步执行系统方法的时候。如CoreData、NSUserDefaults等，还有一些其他的系统多线程操作。
这并不一定意味着是系统代码存在bug，代码仅仅是成了无效状态，或者异常状态。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;9. dispatch_group崩溃&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;enter 数量和 leave 数量要匹配&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;10. 对象释放的线程要和它处理事情的线程一致&lt;/h2&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/c7efbc283480
https://www.jianshu.com/p/c4bd2960b3fa&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>使用KVC设置UIAlertAction按钮颜色的Crash</title><link>https://blog.lab-lawliet.app/posts/issue-kvc-uialertaction-crash-8a9f8f1aa490e507/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/issue-kvc-uialertaction-crash-8a9f8f1aa490e507/</guid><description>最近线上发现了如上面所示的bug</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;=&amp;gt; Start [&amp;lt;UIAlertAction 0x1702b63e0&amp;gt; setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key _titleTextColor.
	-&amp;gt; translating『 0x1005eb9a0 』=&amp;gt; -[UsedHouseDetail firstCollectTip] QFangWang/HouseDetail/Controller/UsedHouseDetail.m: line 509
	-&amp;gt; translating『 0x1005eb668 』=&amp;gt; __39-[UsedHouseDetail RequestColletionData]_block_invoke.481 QFangWang/HouseDetail/Controller/UsedHouseDetail.m: line 472
	-&amp;gt; translating『 0x1007b0cfc 』=&amp;gt; -[YTKNetworkAgent handleRequestResult:] /Users/Fish/iOS-Client/QFangWang/Pods/YTKNetwork/YTKNetwork/YTKNetworkAgent.m: line 262
	-&amp;gt; translating『 0x1007b020c 』=&amp;gt; __33-[YTKNetworkAgent ytk_addRequst:]_block_invoke.145 /Users/Fish/iOS-Client/QFangWang/Pods/YTKNetwork/YTKNetwork/YTKNetworkAgent.m: line 156
	-&amp;gt; translating『 0x100227524 』=&amp;gt; main QFangWang/main.mm: line 13
=&amp;gt; End [&amp;lt;UIAlertAction 0x1702b63e0&amp;gt; setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key _titleTextColor.
&amp;gt;------------------------------------------------------&amp;lt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最近线上发现了如上面所示的bug&lt;/p&gt;
&lt;p&gt;是为了实现改变 &lt;code&gt;UIAlertAction&lt;/code&gt; 默认按钮颜色的地方使用了下面方式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[action setValue:[UIColor redColor] forKey:@&quot;_titleTextColor&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在某些系统版本中 &lt;code&gt;UIAlertAction&lt;/code&gt; 中并没有 &lt;code&gt;_titleTextColor&lt;/code&gt; 这个成员变量而导致了崩溃
具体可以通过 &lt;code&gt;runtime&lt;/code&gt; 获取成员列表来看看&lt;/p&gt;
&lt;h1&gt;fix&lt;/h1&gt;
&lt;p&gt;判断是否有 &lt;code&gt;_titleTextColor&lt;/code&gt; 来设置
或者
通过设置 &lt;code&gt;alertController.view.tintColor&lt;/code&gt; 来实现
需要注意的是: 在8.4上由于层级结构的问题  需要在 &lt;code&gt;presentAlertVC&lt;/code&gt; 后设置 &lt;code&gt;tintColor&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;参考&lt;/h1&gt;
&lt;p&gt;http://www.jianshu.com/p/8a2b5a3a9167&lt;/p&gt;
</content:encoded></item><item><title>UICollectionView的一些问题</title><link>https://blog.lab-lawliet.app/posts/issue-uicollectionview-f7043258777d84fb/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/issue-uicollectionview-f7043258777d84fb/</guid><description>在部分系统上数据源有变更(例如清空了reloadData)的情况下会发生如上crash</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;崩溃 [UICollectionViewData validateLayoutInRect:]&lt;/h2&gt;
&lt;p&gt;在部分系统上数据源有变更(例如清空了reloadData)的情况下会发生如上&lt;code&gt;crash&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;参考链接:
https://stackoverflow.com/questions/18339030/uicollectionview-assertion-error-on-stale-data&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;解决办法:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    [self.cvCollection reloadData];
    [self.cvCollection.collectionViewLayout invalidateLayout];
    //部分系统上 reload   [UICollectionViewData validateLayoutInRect:]  crash

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;iOS 8 上设置 sectionInset left 无效&lt;/h2&gt;
&lt;p&gt;只有在使用了适应尺寸时&lt;code&gt;left&lt;/code&gt;才会失效&lt;/p&gt;
&lt;p&gt;自适应尺寸&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;self.flLayout.estimatedItemSize = CGSizeMake(50, 50);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是系统的bug,后面系统版本没发现这种问题&lt;/p&gt;
&lt;p&gt;解决方法:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (isIOS8) {
        self.cvCollection.contentInset = UIEdgeInsetsMake(0, 15, 0, 15);
    } else {
        self.flLayout.sectionInset = UIEdgeInsetsMake(0, 15, 0, 15);
    }

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>直播技术相关</title><link>https://blog.lab-lawliet.app/posts/livetec-c02fcb7e32eefc0d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/livetec-c02fcb7e32eefc0d/</guid><description>将主播录制的音视频, 推送到服务器, 再由服务器分发给观众</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一.原理&lt;/h2&gt;
&lt;h3&gt;直播原理&lt;/h3&gt;
&lt;p&gt;将主播录制的音视频, 推送到服务器, 再由服务器分发给观众&lt;/p&gt;
&lt;h3&gt;直播环节&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;推流端: 采集 处理 编码 推流&lt;/li&gt;
&lt;li&gt;服务端处理: 转码 录制 截图 审核&lt;/li&gt;
&lt;li&gt;播放器: 拉流 解码 渲染&lt;/li&gt;
&lt;li&gt;互动: 聊天室 礼物 赞&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二.相关框架&lt;/h2&gt;
&lt;h3&gt;采集端&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据采集
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AVFoundation&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;美颜
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GPUImage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;音频压缩
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FFmpeg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;视频压缩
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X264&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;推流
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;libremp&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;播放端&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;播放
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ijkplayer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;视频解码
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FFmepeg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;视频硬解码
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;VideoToolBox&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;音频硬解码
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AudioToolBox&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三.流媒体&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;流媒体开发
&lt;ul&gt;
&lt;li&gt;网络层&lt;code&gt;Socket&lt;/code&gt;负责传输&lt;/li&gt;
&lt;li&gt;协议层&lt;code&gt;RTMP&lt;/code&gt;或&lt;code&gt;HLS&lt;/code&gt;负责网络打包&lt;/li&gt;
&lt;li&gt;封装层&lt;code&gt;FLV&lt;/code&gt;或&lt;code&gt;TS&lt;/code&gt;负责编解码数据的封装&lt;/li&gt;
&lt;li&gt;编码层&lt;code&gt;h.264&lt;/code&gt;和&lt;code&gt;acc&lt;/code&gt;负责图像视频的压缩&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GOP
&lt;ul&gt;
&lt;li&gt;画面组,一个GOP就是一组连续的画面,每个画面都是一帧,一个GOP就是很多帧的集合&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;码率
&lt;ul&gt;
&lt;li&gt;图片压缩后每秒显示的数据量&lt;/li&gt;
&lt;li&gt;常常看见视频播放软件中的1024，720、高清、标清和流畅等，指的就是各种码率&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;帧率
&lt;ul&gt;
&lt;li&gt;每秒帧数&lt;/li&gt;
&lt;li&gt;人眼16帧的话看起来就是连贯的了&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;压缩比
&lt;ul&gt;
&lt;li&gt;压缩前的每秒数据量/码率&lt;/li&gt;
&lt;li&gt;同一视频源,压缩比越高画质越差&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;视频封装格式和视频压缩编码标准
&lt;ul&gt;
&lt;li&gt;就好像项目工程和编程语言,&lt;code&gt;封装格式&lt;/code&gt;就是一个项目的工程,&lt;code&gt;视频编码&lt;/code&gt;方式就是编程语言,一个项目工程可以用不同语言开发&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四.推流&lt;/h2&gt;
&lt;h3&gt;数据传输框架&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;librtmp&lt;/code&gt;是用来传输&lt;code&gt;RTMP&lt;/code&gt;协议格式的数据&lt;/p&gt;
&lt;h3&gt;数据传输协议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RTMP&lt;/code&gt;是实时消息传输协议，&lt;code&gt;Adobe Systems&lt;/code&gt;公司为&lt;code&gt;Flash播放器&lt;/code&gt;和服务器之间音频、视频和数据传输开发的开放协议，因为是开放协议所以都可以使用了&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RTMP&lt;/code&gt;协议用于对象、视频、音频的传输。这个协议建立在&lt;code&gt;TCP协议&lt;/code&gt;或者&lt;code&gt;轮询HTTP协议&lt;/code&gt;之上。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RTMP&lt;/code&gt;协议就像一个用来装数据包的容器，这些数据可以是&lt;code&gt;FLV&lt;/code&gt;中的视音频数据。一个单一的连接可以通过不同的通道传输多路网络流，这些通道中的包都是按照固定大小的包传输的。&lt;code&gt;chunk&lt;/code&gt;是消息包。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;五.拉流&lt;/h2&gt;
&lt;h3&gt;直播协议选择&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;即时性要求高或有互动需求可以采用&lt;code&gt;RTMP&lt;/code&gt;/&lt;code&gt;RTSP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;有回放或跨平台需求的&lt;code&gt;HLS&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;直播协议对比&lt;/h3&gt;
&lt;h4&gt;HLS&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HLS是由Apple公司定义的用于实时流传输的协议，HLS基于HTTP协议实现，传输内容包括两部分，一是M3U8描述文件，二是TS媒体文件。可实现流媒体的直播和点播，主要应用在iOS系统。 HLS是以点播的技术方式来实现直播。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HLS是自适应码率流播，客户端会根据网络状况自动选择不同码率的视频流，条件允许的情况下使用高码率，网络繁忙的时候使用低码率，并且自动在二者间随意切换。这对移动设备网络状况不稳定的情况下保障流畅播放非常有帮助。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实现方法是服务器端提供多码率视频流，并且在列表文件中注明，播放器根据播放进度和下载速度自动调整。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HLS与RTMP对比：HLS主要是延时比较大，RTMP主要优势在于延时低。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HLS协议的小切片方式会生成大量的文件，存储或处理这些文件会造成大量资源浪费。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;相比使用RTSP协议的好处在于，一旦切分完成，之后的分发过程完全不需要额外使用任何专门软件，普通的网络服务器即可，大大降低了CDN边缘服务器的配置要求，可以使用任何现成的CDN，而一般服务器很少支持RTSP。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;HTTP-FLV&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;HTTP-FLV是基于HTTP协议流式的传输媒体内容。相对于RTMP，HTTP更简单和广为人知，内容延迟同样可以做到1~3秒，打开速度更快，因为HTTP本身没有复杂的状态交互。所以从延迟角度来看，HTTP-FLV要优于RTMP。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;RTSP&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;RTSP：实时流传输协议，定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;RTP&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RTP：实时传输协议，RTP是建立在UDP协议上的，常与RTCP一起使用，其本身并没有提供按时发送机制或其它服务质量（QoS）保证，它依赖于低层服务去实现这一过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RTCP：RTP的配套协议,主要功能是为RTP所提供的服务质量（QoS）提供反馈，收集相关媒体连接的统计信息，例如传输字节数，传输分组数，丢失分组数，单向和双向网络延迟等等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;六.解码&lt;/h2&gt;
&lt;h3&gt;解封装&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;demuxing（分离）&lt;/code&gt;：从视频流、音频流，字幕流合成的文件(容器格式（FLV，TS）)中， 分解出视频、音频或字幕，各自进行解码。&lt;/p&gt;
&lt;h3&gt;音频编码框架&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;fdk_aac&lt;/code&gt;：音频编码解码框架，PCM音频数据和AAC音频数据互转&lt;/p&gt;
&lt;h3&gt;编码介绍&lt;/h3&gt;
&lt;h4&gt;硬解码&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用&lt;code&gt;GPU&lt;/code&gt;解码,减少&lt;code&gt;CPU&lt;/code&gt;运算
&lt;ul&gt;
&lt;li&gt;优点:播放流畅,低功耗,解码速度快,&lt;/li&gt;
&lt;li&gt;缺点:兼容不好&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;软解码&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;用&lt;code&gt;CPU&lt;/code&gt;解码
&lt;ul&gt;
&lt;li&gt;优点:兼容好&lt;/li&gt;
&lt;li&gt;缺点:加大&lt;code&gt;CPU&lt;/code&gt;负担,解码速度慢,耗电增加,没有硬解码流畅&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;七.播放&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;ijkplayer&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;B站开源的一个基于&lt;code&gt;FFmpeg&lt;/code&gt;的开源&lt;code&gt;Android/iOS&lt;/code&gt;视频播放器
&lt;ul&gt;
&lt;li&gt;API易于集成&lt;/li&gt;
&lt;li&gt;编译配置可裁剪，方便控制安装包大小&lt;/li&gt;
&lt;li&gt;支持硬件加速解码，更加省电&lt;/li&gt;
&lt;li&gt;简单易用，指定拉流URL，自动解码播放&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://www.woshipm.com/pd/408632.html/comment-page-1&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Socket-Little-Endian-Big-Endian</title><link>https://blog.lab-lawliet.app/posts/livetec-socket-little-endian-big-endian-adcc587d13c9d404/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/livetec-socket-little-endian-big-endian-adcc587d13c9d404/</guid><description>socket编程为什么需要htons(), ntohl(), ntohs()，htons() 函数 https://blog.csdn.net/myyllove/article/details/83380209 关于Big Endian 和 Little Endian https://blog.cs</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;socket编程为什么需要htons(), ntohl(), ntohs()，htons() 函数
https://blog.csdn.net/myyllove/article/details/83380209
关于Big Endian 和 Little Endian
https://blog.csdn.net/sunshine1314/article/details/2309655
浅谈C语言中的联合体
https://www.cnblogs.com/dolphin0520/archive/2011/10/03/2198493.html
判断处理器是Big_endian的还是Little_endian的
https://www.cnblogs.com/codingmengmeng/p/7845958.html&lt;/p&gt;
</content:encoded></item><item><title>网络理解</title><link>https://blog.lab-lawliet.app/posts/network-a87c3447f80a77af/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/network-a87c3447f80a77af/</guid><description>IP地址+端口=Socket</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;iOS-网络理解&lt;/h1&gt;
&lt;h2&gt;一:网络层级结构&lt;/h2&gt;
&lt;h3&gt;TCP/IP四层结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;应用层&lt;/li&gt;
&lt;li&gt;传输层&lt;/li&gt;
&lt;li&gt;网络层&lt;/li&gt;
&lt;li&gt;网络接口层&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OSI七层网络结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;应用层&lt;/li&gt;
&lt;li&gt;表示层&lt;/li&gt;
&lt;li&gt;会话层&lt;/li&gt;
&lt;li&gt;传输层&lt;/li&gt;
&lt;li&gt;网络层&lt;/li&gt;
&lt;li&gt;数据链路层&lt;/li&gt;
&lt;li&gt;物理层&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二:HTTP协议-超文本传输协议&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;应用层&lt;/code&gt;面向对象的协议
常基于&lt;code&gt;TCP&lt;/code&gt;链接方式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;特点:
&lt;ul&gt;
&lt;li&gt;支持客户端/服务端模式&lt;/li&gt;
&lt;li&gt;简单快捷灵活&lt;/li&gt;
&lt;li&gt;客户端每次发送的请求,都需要服务器回送响应,请求结束后主动释放链接.俗称&lt;code&gt;短连接&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三:TCP协议&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;传输层&lt;/code&gt;
提供面向链接的可靠的字节流服务,提供&lt;code&gt;超时重发&lt;/code&gt;,&lt;code&gt;丢弃重复数据&lt;/code&gt;,&lt;code&gt;校验数据&lt;/code&gt;,&lt;code&gt;流量控制&lt;/code&gt;等功能
在正式收发数据前,需要建立三次握手建立可靠的链接&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;三次握手
&lt;ul&gt;
&lt;li&gt;客户端向服务器发送一个SYN报文(syn=j),计入&lt;code&gt;SYN_SEND&lt;/code&gt;状态,等待服务器确认&lt;/li&gt;
&lt;li&gt;服务器接收到客户端的SYN报文,确认客户端的SYN(ack=j+1),同时自己也发送一个SYN报文(syn=k),即&lt;code&gt;SYN+ACK&lt;/code&gt;报文,服务器进入&lt;code&gt;SYN_RECV&lt;/code&gt;状态&lt;/li&gt;
&lt;li&gt;客户端收到服务器的&lt;code&gt;SYN+ACK&lt;/code&gt;报文,向服务器发送确认报文ACK(ack=k+1),此包发送完毕,客户端服务端进入&lt;code&gt;ESTABLISHED&lt;/code&gt;状态,完成三次握手&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;四:UDP协议&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;传输层&lt;/code&gt;
面向非链接,不保证可靠性的数据传输协议,没有超时重发等机制,故传输速度很快
不与对方建立连接,直接把数据包丢过去,所以适合少量数据且可靠性要求不高的场景&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;五:Socket&lt;/h2&gt;
&lt;p&gt;IP地址+端口=Socket&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;应用程序通过&lt;code&gt;Socket&lt;/code&gt;向网络发送请求或响应
它是一个针对TCP/UDP编程的接口,基于它建立TCP/UDP连接.
&lt;code&gt;Socket&lt;/code&gt;连接就是所谓的长连接,一旦建立起连接,不会主动断掉&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;六:HTTP-Socket-TCP 的关系&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;HTTP协议提供了封装或者显示数据的具体形式&lt;/li&gt;
&lt;li&gt;Socket连接提供了网络通信的能力&lt;/li&gt;
&lt;li&gt;TCP连接提供如何在网络中传输&lt;/li&gt;
&lt;li&gt;socket是纯C语言的,跨平台&lt;/li&gt;
&lt;li&gt;HTTP协议是基于socket的,底层使用的就是socket&lt;/li&gt;
&lt;li&gt;创建Socket连接时，可以指定使用的传输层协议(TCP或UDP),当使用TCP协议进行连接时，该Socket连接就是一个TCP连接&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;七:TCP/UDP的区别&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;基于连接和无连接&lt;/li&gt;
&lt;li&gt;TCP对系统资源占用较多,UDP较少&lt;/li&gt;
&lt;li&gt;UDP程序结构简单&lt;/li&gt;
&lt;li&gt;TCP是字节流数据类型,UDP是用户数报文类型&lt;/li&gt;
&lt;li&gt;TCP保证数据可靠性,UDP不保证,可能丢包&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;八.Socket工作流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;服务端创建一个socket&lt;/li&gt;
&lt;li&gt;绑定服务器主机地址,监听一个端口&lt;/li&gt;
&lt;li&gt;调用accept函数，一致初一阻塞状态&lt;/li&gt;
&lt;li&gt;当客户端有客户端请求，并自己进入阻塞，一直到服务返回数据&lt;/li&gt;
&lt;li&gt;服务端保存客户端的socket，然后向客户单的socket 写入数据&lt;/li&gt;
&lt;li&gt;客户端接收后可以发送close消息断开链接，那么就完成一次完成的socket交流&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;九:HTTP和Socket的区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HTTP连接&lt;/code&gt;是短连接,&lt;code&gt;Socket连接&lt;/code&gt;是长连接
&lt;ul&gt;
&lt;li&gt;尽管&lt;code&gt;HTTP1.1&lt;/code&gt;开始支持持久链接但仍无法保证始终链接&lt;/li&gt;
&lt;li&gt;Socket建立TCP三次握手后,除非一方自动断开链接否则链接将一直保持&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HTTP&lt;/code&gt;服务端无法主动发送消息
&lt;ul&gt;
&lt;li&gt;采用&lt;code&gt;请求-响应&lt;/code&gt;机制&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用场景
&lt;ul&gt;
&lt;li&gt;HTTP
&lt;ul&gt;
&lt;li&gt;双方不需要同时保持在线,如客户端资源获取,上传等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Socket
&lt;ul&gt;
&lt;li&gt;IM APNs等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>iOS-网络安全之HTTPS</title><link>https://blog.lab-lawliet.app/posts/network-ios-https-edadf999b1f81a6d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/network-ios-https-edadf999b1f81a6d/</guid><description>文档说明</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: 中间人攻击&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;经常会提到的就是中间人攻击&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Man-in-the-middle attack(MITM)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;攻击者插入到原本直接通信的双方之间,双方已为还在直接跟对方通信,但实际是通过了攻击者这个中间人,信息会被它获取或篡改&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;时机&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;HTTPS在建立了TCP链接后,会进行SSL握手来校验证书,写上加密密钥.&lt;/li&gt;
&lt;li&gt;所以一般中间人攻击分为
&lt;ul&gt;
&lt;li&gt;SSL链接建立前的攻击&lt;/li&gt;
&lt;li&gt;HTTPS传输中的攻击&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常见攻击&lt;/h3&gt;
&lt;h4&gt;a. SSL证书欺骗攻击&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;较为常见的攻击
通过ARP欺骗,DNS劫持甚至网管劫持,将客户端访问重定向到攻击者的服务器.让客户端与攻击者的机器建立HTTPS(伪造的证书)链接.
浏览器会提示证书不可信,用户不点击继续浏览就能避免被劫持.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;防范措施
&lt;ul&gt;
&lt;li&gt;iOS原生
&lt;ul&gt;
&lt;li&gt;使用系统API建立HTTPS链接就没问题的,系统默认是没有信任相关伪造证书的不会SSL握手成功&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WebView
&lt;ul&gt;
&lt;li&gt;如果忽略证书校验的话可能会被劫持&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;b. SSL剥离攻击(SSLSTrip)&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;将HTTPS链接降级为HTTP链接
客户端直接访问HTTPS链接,攻击者是无法进行降级的
攻击的主要方式是利用用户不会每次都会输入&lt;code&gt;HTTPS://xxxx.xxx.com&lt;/code&gt;这样的链接来访问网站&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;防范措施
&lt;ul&gt;
&lt;li&gt;原生
&lt;ul&gt;
&lt;li&gt;无法劫持App内部HTTPS会话&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WebView
&lt;ul&gt;
&lt;li&gt;如果在全网HTTPS的情况下,可以对打开的链接做检查&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;c. 针对SSL算法进行攻击&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;上述两种攻击一般只能影响到WebApp,很难攻击到NativeApp.
牛逼的Hacker会对SSL算法相关的漏洞进行攻击,密码学相关了...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;防范措施
&lt;ul&gt;
&lt;li&gt;最好对服务端的SSL/TLS配置进行升级
&lt;ul&gt;
&lt;li&gt;只支持尽量高版本的TLS(最低TLSv1)&lt;/li&gt;
&lt;li&gt;禁用一些已经爆出安全隐患的加密方法&lt;/li&gt;
&lt;li&gt;使用2048位的数字证书&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;二: 打保证书校验&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本地打保证书进App,使用&lt;code&gt;SecTrustSetAnchorCertificates(SecTrustRef trust, CFArrayRef anchorCertificates)&lt;/code&gt;来设置&lt;code&gt;Anchor Certificate&lt;/code&gt;进行校验&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;文档说明&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Calling this function without also calling SecTrustSetAnchorCertificatesOnly disables the trusting of any anchors other than the ones specified by this function call.

https://developer.apple.com/library/mac/documentation/Security/Reference/certifkeytrustservices/#//apple_ref/c/func/SecTrustCopyAnchorCertificates
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，单纯调用SecTrustSetAnchorCertificates方法后不调用SecTrustSetAnchorCertificatesOnly来验证证书，则只会相信SecTrustSetAnchorCertificates传入的证书，而不会信任其他锚点证书。关于这一点，SecTrustSetAnchorCertificatesOnly方法参数讲解中也有说明：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anchorCertificatesOnly:

If true, disables trusting any anchors other than the ones passed in with the SecTrustSetAnchorCertificates function. If false, the built-in anchor certificates are also trusted. If SecTrustSetAnchorCertificates is called and SecTrustSetAnchorCertificatesOnly is not called, only the anchors explicitly passed in are trusted.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只相信传入的锚点证书，也就只会验证通过由这些锚点证书签发的证书。这样就算被验证的证书是由系统其他信任的锚点证书签发的，也无法验证通过。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;选择哪一级的CA证书就需要自行判断了
如果选择末端的叶子证书,可能导致域名变化后无法访问,甚至证书过期的情况&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://oncenote.com/2015/09/16/Security-2-HTTPS2/ Jaminzzhang博客
https://www.jianshu.com/p/86dcf94b43e2 WebView证书验证&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>SSL-TLS四次握手</title><link>https://blog.lab-lawliet.app/posts/network-ssl-tls-d516bceb1ba6ec80/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/network-ssl-tls-d516bceb1ba6ec80/</guid><description>数字证书的签发机构CA，在接收到申请者的资料后进行核对并确定信息的真实有效，然后就会制作一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的，而数字签名则是CA机构对证书内容进行hash加密后得到的，而这个数字签名就是我们验证证书是否是有可信CA签发的数据。</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一: 图解四次握手&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/TLS%E5%9B%9B%E6%AC%A1%E6%8F%A1%E6%89%8B.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;二: 数字证书&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;数字证书是一个电子文档, 其中包含了持有者的信息/公钥/证明证书有效的数字签名.
数字证书室友证书认证机构(CA)来负责签发管理
数字证书的类型有多种,HTTPS使用的是SSL证书&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;CA组织的结构&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CA组织顶层是根CA&lt;/li&gt;
&lt;li&gt;根CA下可以授权给多个二级CA&lt;/li&gt;
&lt;li&gt;二级CA又可以授权给多个三级CA
&lt;ul&gt;
&lt;li&gt;那么是不是可以无限信任下去呢?
&lt;ul&gt;
&lt;li&gt;显然不行(&lt;code&gt;Basic Constraint校验漏洞&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;X.509证书中的&lt;code&gt;Basic Constraint&lt;/code&gt;包含了这是不是一个CA机构，以及有效证书路径的&lt;code&gt;最大深度&lt;/code&gt;（即，这个CA还能否继续签发CA机构证书及其签发子CA证书的路径深度）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;SSL证书市场,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;签发流程&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/CA%E7%AD%BE%E5%8F%91%E6%95%B0%E5%AD%97%E8%AF%81%E4%B9%A6%E6%B5%81%E7%A8%8B.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;数字证书的签发机构CA，在接收到申请者的资料后进行核对并确定信息的真实有效，然后就会制作一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的，而数字签名则是CA机构对证书内容进行hash加密后得到的，而这个数字签名就是我们验证证书是否是有可信CA签发的数据。&lt;/p&gt;
&lt;h3&gt;验证流程&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/RyukieSama/RyukieImage/master/Blog/CerValiate%E8%AF%81%E4%B9%A6%E9%AA%8C%E8%AF%81.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接收到一份数字证书Cer1后,对证书内容做Hash得到数字摘要H1&lt;/li&gt;
&lt;li&gt;从签发该证书的机构CA1的证书中找到公钥,对证书上的签名进行解密,得到证书Cer1签名的Hash摘要H2&lt;/li&gt;
&lt;li&gt;对比H1 H2 如果相等表示证书没有被篡改(还未知有效性)&lt;/li&gt;
&lt;li&gt;CA机构的数字证书是公开的,所有人都可以拿到.这个证书中的数字签名是上一级生成的,所以可以一直验证下去,直到根证书.根CA证书是自验证的(即数字签名是用自己的私钥生成了).合法的根CA证书会被浏览器和操作系统加入到信任CA列表中,这样完成了最终验证&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;注意:
一定要保护好自己的环境(浏览器,操作系统)中的根CA证书信任列表,信任了根证书就代表任何根证书下的字CA签发的证书,所以不要随便添加根CA证书信任&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;iOS中的验证流程
&lt;ul&gt;
&lt;li&gt;先获取到需要验证的信任对象&lt;/li&gt;
&lt;li&gt;使用系统默认的验证方式验证(自建证书不用,不在信任列表中)&lt;/li&gt;
&lt;li&gt;第二步验证通过后就可以直接通过了,建立连接&lt;/li&gt;
&lt;li&gt;验证不通过,取消验证流程,拒绝连接请求&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;http://oncenote.com/2014/10/21/Security-1-HTTPS/ Jaminzzhang博客
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html 阮一峰
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html 阮一峰&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>逃逸闭包和非逃逸闭包</title><link>https://blog.lab-lawliet.app/posts/swift-9824e571ebcd04f6/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-9824e571ebcd04f6/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Swift-逃逸闭包和非逃逸闭包&lt;/h1&gt;
&lt;h2&gt;逃逸闭包_@escaping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个接受一个闭包作为参数的函数,如果参数需要在函数结束之后才被调用&lt;/li&gt;
&lt;li&gt;也就是闭包脱离了函数的作用域&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;非逃逸闭包_@noescape&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一个接受一个闭包作为参数的函数,闭包会在函数结束前被调用&lt;/li&gt;
&lt;li&gt;非逃逸闭包&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;内存管理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;非逃逸闭包不会存在循环引用,因为在函数作用域内就释放了&lt;/li&gt;
&lt;li&gt;开发中使用非逃逸闭包有利于内存优化&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自动逃逸&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;注意
&lt;ul&gt;
&lt;li&gt;当闭包被当做参数,包裹在其他类型中(元祖/枚举/case/Optional可选型)中会自动逃逸&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>向下向上取整</title><link>https://blog.lab-lawliet.app/posts/swift-b02a335879a59f91/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-b02a335879a59f91/</guid><description>ceil(x)返回不小于x的最小整数值（然后转换为double型）。</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Swift 向下向上取整&lt;/h1&gt;
&lt;p&gt;ceil(x)返回不小于x的最小整数值（然后转换为double型）。&lt;/p&gt;
&lt;p&gt;floor(x)返回不大于x的最大整数值。&lt;/p&gt;
&lt;p&gt;round(x)返回x的四舍五入整数值。&lt;/p&gt;
</content:encoded></item><item><title>使用Carthage替代CocoaPods</title><link>https://blog.lab-lawliet.app/posts/swift-carthage-cocoapods-1b0999408a84d184/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-carthage-cocoapods-1b0999408a84d184/</guid><description>和使用CocoaPods一样, 这里也需要一个无后缀的配置文件 Carthfile</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Swift使用Carthage替代CocoaPods&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;官方地址: https://github.com/Carthage/Carthage
参考文章: https://www.jianshu.com/p/76b9ff09f99c&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;集成&lt;/h2&gt;
&lt;h3&gt;通过 &lt;code&gt;Homebrew&lt;/code&gt; 安装&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;brew install carthage
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;项目配置文件&lt;/h3&gt;
&lt;p&gt;和使用&lt;code&gt;CocoaPods&lt;/code&gt;一样, 这里也需要一个无后缀的配置文件 &lt;code&gt;Carthfile&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;配置依赖库&lt;/h4&gt;
&lt;p&gt;熟悉&lt;code&gt;Podfile&lt;/code&gt;的话这里就不会陌生了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;github &quot;Alamofire/Alamofire&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;对于&lt;code&gt;github&lt;/code&gt;指名后会去&lt;code&gt;github&lt;/code&gt;上找对对应的库
&lt;ul&gt;
&lt;li&gt;格式: &lt;code&gt;github Username/ProjectName&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt; 支持&lt;code&gt;http://&lt;/code&gt; &lt;code&gt;git://&lt;/code&gt; &lt;code&gt;ssh://&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;==&lt;/code&gt; 毫无疑问是说必须为指定版本，如果没有，就不下载编译；
&lt;code&gt;&amp;gt;=&lt;/code&gt; 使用大于或等于指定版本的库，如果有最新，则使用最新的；
&lt;code&gt;~&amp;gt;&lt;/code&gt; 则是一个开区间，如“~&amp;gt;1.1.0”则会返回“1.1.1~1.9.9”之间的版本，不包括2.0.0；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;编译第三方库&lt;/h2&gt;
&lt;p&gt;类似&lt;code&gt;pod install&lt;/code&gt;操作, &lt;code&gt;cd&lt;/code&gt; 到工程目录执行下面命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;carthage update --platform iOS
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一般这里指定一下平台好些,否则会编译出mac,tv,iOS个平台的,完全没必要&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行完成后会看到有个&lt;code&gt;Carthage&lt;/code&gt;目录
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179696493656.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;build&lt;/code&gt;中就是编译好的第三方库&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Checkouts&lt;/code&gt;中是第三方库的缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;导入第三方库&lt;/h2&gt;
&lt;p&gt;手动将&lt;code&gt;Carthage/Build&lt;/code&gt;中的&lt;code&gt;framework&lt;/code&gt;加到项目中
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179703205983.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179703379835.jpg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179703670579.jpg&quot; alt=&quot;&quot; /&gt;
先在就阔以在项目中使用&lt;code&gt;Alamofire&lt;/code&gt;咯&lt;/p&gt;
&lt;h2&gt;一些坑&lt;/h2&gt;
&lt;h3&gt;01: has no shared framework schemes&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;*** Skipped building R.swift due to the error:
Dependency &quot;R.swift&quot; has no shared framework schemes for any of the platforms: iOS
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;因为&lt;code&gt;Carthage&lt;/code&gt;需要库工程的&lt;code&gt;Scheme&lt;/code&gt;需要为&lt;code&gt;Shared&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;没有的话可能是因为&lt;code&gt;git&lt;/code&gt;忽略了相应文件 &lt;code&gt;xcuserdata&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://stackoverflow.com/questions/35054788/carthage-no-shared-framework-schemes-for-ios-platform-for-my-own-framework
https://www.jianshu.com/p/a7defcf2e327&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Copy-on-write</title><link>https://blog.lab-lawliet.app/posts/swift-cow-09051595b914ae91/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-cow-09051595b914ae91/</guid><description>Copy on write</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Swift COW&lt;/h1&gt;
&lt;h2&gt;什么是 COW&lt;/h2&gt;
&lt;p&gt;Copy on write&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;COW是一种常见的计算机技术，有助于在复制结构时提高效率
&lt;ul&gt;
&lt;li&gt;例如一个数组有100个元素，赋值到另一个数组，赋值所有元素，即最终两个数组内容相同&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当使用COW的话
&lt;ul&gt;
&lt;li&gt;当将两个变量指向同一数组时，他们指向相同的底层数据。两个变量指向相同的数据可能看起来矛盾，当修改第二个变量的时候，Swift才会去复制一个副本，第一个不会改变。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通过延迟复制操作，直到实际使用到的时候 才去复制，以此确保没有浪费的工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;COW是特别添加到Swift数组和字典的功能，自定义的数据类型不会自动实现。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>OC项目中加入Swift混编</title><link>https://blog.lab-lawliet.app/posts/swift-oc-swift-2fe30a5d474f8e38/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-oc-swift-2fe30a5d474f8e38/</guid><description>这里的 Product Module Name 需要和 工程名称 一致</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;年后项目准备引入 &lt;code&gt;Swift&lt;/code&gt;
新的模块需求使用 &lt;code&gt;Swift&lt;/code&gt; 编写
需要支持与 &lt;code&gt;OC&lt;/code&gt; 与 &lt;code&gt;Swift&lt;/code&gt; 的混编
这里描述一下前期准备的工作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Project_defines module 配置&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15167571775287.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Project_product module name 配置&lt;/h2&gt;
&lt;p&gt;这里的 &lt;code&gt;Product Module Name&lt;/code&gt; 需要和 &lt;code&gt;工程名称&lt;/code&gt; 一致&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15167572476625.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;代码修改&lt;/h2&gt;
&lt;h3&gt;在需要调用到 &lt;code&gt;Swift&lt;/code&gt; 的地方 &lt;code&gt;import&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;工程名称-Swift.h&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;注意点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;这个文件 &lt;code&gt;import&lt;/code&gt; 不会自动提示&lt;/li&gt;
&lt;li&gt;而且如果项目汇总没有 &lt;code&gt;swift&lt;/code&gt; 文件的话编译也会报错&lt;/li&gt;
&lt;li&gt;不要自己去创建这个名字的文件&lt;/li&gt;
&lt;li&gt;只需要在项目中创建一个 &lt;code&gt;Swift&lt;/code&gt; 文件这个文件就会自动生成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;需要暴露给 &lt;code&gt;Swift&lt;/code&gt; 调用的&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;OC&lt;/code&gt; 项目中创建 &lt;code&gt;swift&lt;/code&gt; 文件后会创建一个桥接文件 &lt;code&gt;工程名-Bridging-Header.h&lt;/code&gt;
所有需要暴露给 &lt;code&gt;swift&lt;/code&gt; 调用的文件都在这里 &lt;code&gt;import&lt;/code&gt; 即可&lt;/p&gt;
&lt;h4&gt;注意点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;需要注意的是: 如果 &lt;code&gt;import&lt;/code&gt; 的 &lt;code&gt;.h&lt;/code&gt; 中有用到 &lt;code&gt;全局导入的引用&lt;/code&gt; 时,编译会报错,这时需要在该被 &lt;code&gt;import&lt;/code&gt; 的 &lt;code&gt;.h&lt;/code&gt; 文件中添加相关引用或者使用 &lt;code&gt;@class&lt;/code&gt; 即可&lt;/li&gt;
&lt;li&gt;项目中的 &lt;code&gt;Swift&lt;/code&gt; 文件中使用上面 &lt;code&gt;import&lt;/code&gt; 过的 &lt;code&gt;OC类&lt;/code&gt; 无须再单独 &lt;code&gt;import&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果在添加到桥接文件中后某些类型依旧报错 可以尝试将其在桥接文件中的位置提前&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Apple官方混编指南&lt;/h2&gt;
&lt;p&gt;https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-ID122&lt;/p&gt;
&lt;h2&gt;国内汉化&lt;/h2&gt;
&lt;p&gt;https://github.com/CocoaChina-editors/Welcome-to-Swift/blob/master/UsingSwiftwithCocoaandObjective-C中文手册.md&lt;/p&gt;
</content:encoded></item><item><title>OC老项目Swift混编的一些坑</title><link>https://blog.lab-lawliet.app/posts/swift-oc-swift-85733290eee00c78/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-oc-swift-85733290eee00c78/</guid><description>在桥接文件中添加CoreLocation库的引用依旧出现上述错误</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;01: 桥接后 &lt;code&gt;Unknown type name&lt;/code&gt; 问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;场景:&lt;/strong&gt; 在桥接文件中添加了某&lt;code&gt;OC&lt;/code&gt;类的引用 编译时报错&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;XXX.h&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15171929167561.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在桥接文件中添加&lt;code&gt;CoreLocation&lt;/code&gt;库的引用依旧出现上述错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#import &quot;XXX.h&quot;
#import &amp;lt;CoreLocation/CoreLocation.h&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;解决方法:&lt;/strong&gt;
将 &lt;code&gt;CoreLocation&lt;/code&gt; 的引用置顶&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#import &amp;lt;CoreLocation/CoreLocation.h&amp;gt;
#import &quot;XXX.h&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Optional实质</title><link>https://blog.lab-lawliet.app/posts/swift-optional-7d3f991001290603/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-optional-7d3f991001290603/</guid><description>我们先看看Swift中Optional是怎么实现的把</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Swift-Optional实质&lt;/h1&gt;
&lt;h2&gt;底层实现&lt;/h2&gt;
&lt;p&gt;我们先看看&lt;code&gt;Swift&lt;/code&gt;中&lt;code&gt;Optional&lt;/code&gt;是怎么实现的把&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public enum Optional&amp;lt;Wrapped&amp;gt; : _Reflectable, NilLiteralConvertible {
    case None
    case Some(Wrapped)
    /// Construct a `nil` instance.
    public init()
    /// Construct a non-`nil` instance that stores `some`.
    public init(_ some: Wrapped)
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map&amp;lt;U&amp;gt;(@noescape f: (Wrapped) throws -&amp;gt; U) rethrows -&amp;gt; U?
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap&amp;lt;U&amp;gt;(@noescape f: (Wrapped) throws -&amp;gt; U?) rethrows -&amp;gt; U?
    /// Create an instance initialized with `nil`.
    public init(nilLiteral: ())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很明显可以看出，它实际是一个枚举。
&lt;code&gt;.Some&lt;/code&gt;的关联值就是我们解包出来的&lt;/p&gt;
&lt;h2&gt;? &amp;amp; ！&lt;/h2&gt;
&lt;p&gt;在我们对一个&lt;code&gt;Optional&lt;/code&gt;的变量进行赋值的时候，我们实际上是将初始值放到&lt;code&gt;.Some&lt;/code&gt;中，取值是则是从&lt;code&gt;.Some&lt;/code&gt;中取出。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;强制解包，告诉系统这个变量不为&lt;code&gt;nil&lt;/code&gt; 且 &lt;code&gt;.Some&lt;/code&gt;中一定有值&lt;/li&gt;
&lt;li&gt;如果为空则会崩溃&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;解包时会先判断是否为&lt;code&gt;nil&lt;/code&gt;如果不为空则正常解包，否则会返回&lt;code&gt;nil&lt;/code&gt;即默认初始值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>R-Swift-安全的资源组织方案forSwift</title><link>https://blog.lab-lawliet.app/posts/swift-r-swift-forswift-be434ea89cb826ec/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-r-swift-forswift-be434ea89cb826ec/</guid><description>在项目中经常会遇到通过字符串进行的一些资源操作比如图片什么的,维护修改成本较高</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;官方链接: https://github.com/mac-cain13/R.swift
参考文章:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;http://swifter.tips/safe-resource/
https://www.jianshu.com/p/6234e6e79b15&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h2&gt;场景&lt;/h2&gt;
&lt;p&gt;在项目中经常会遇到通过字符串进行的一些资源操作比如图片什么的,维护修改成本较高&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一般操作&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let icon = UIImage(named: &quot;settings-icon&quot;)
let font = UIFont(name: &quot;San Francisco&quot;, size: 42)
let color = UIColor(named: &quot;indictator highlight&quot;)
let viewController = CustomViewController(nibName: &quot;CustomView&quot;, bundle: nil)
let string = String(format: NSLocalizedString(&quot;welcome.withName&quot;, comment: &quot;&quot;), locale: NSLocale.current, &quot;Arthur Dent&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;code&gt;R.Swift&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let color = R.color.indicatorHighlight()
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName(&quot;Arthur Dent&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;是不是显得十分清爽&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;导入&lt;code&gt;R.Swift&lt;/code&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pod &apos;R.swift&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;install&lt;/code&gt;完成后添加 &lt;code&gt;New Run Script Phase&lt;/code&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179849878526.jpg&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;添加脚本 &lt;code&gt;&quot;$PODS_ROOT/R.swift/rswift&quot; generate &quot;$SRCROOT&quot;&lt;/code&gt;
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179848803178.jpg&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Command+B&lt;/code&gt;编译一次后会在目录下生成一个&lt;code&gt;R.generated.swift&lt;/code&gt;文件,将其拖入工程
&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/15179854353794.jpg&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一定不要选中&lt;code&gt;Copy items if needed&lt;/code&gt;
因为每次编译都会重新生成该文件所以最好在&lt;code&gt;.gitignore&lt;/code&gt;中添加忽略&lt;code&gt;*.generated.swift &lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Struct与Class</title><link>https://blog.lab-lawliet.app/posts/swift-struct-class-5883f92b3ab31384/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-struct-class-5883f92b3ab31384/</guid><description>根据对象生命周期形态，把使用的类型分成两大类</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Swift 中的类型&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Types
&lt;ul&gt;
&lt;li&gt;命名类型
&lt;ul&gt;
&lt;li&gt;protocol&lt;/li&gt;
&lt;li&gt;enum&lt;/li&gt;
&lt;li&gt;struct&lt;/li&gt;
&lt;li&gt;class&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;复合类型
&lt;ul&gt;
&lt;li&gt;tuple&lt;/li&gt;
&lt;li&gt;function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;实际上其他类型都是通过命名类型创建的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Struct Class Enum&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Swift 提供了多种结构化存储数据的方式，他们是：Struct、Class、Enum
Swift 表中库中绝大多数类型都是Struct，Class、Enum只有很小一部分&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;共同点：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;都可以用 protocol&lt;/li&gt;
&lt;li&gt;都可以使用 extension&lt;/li&gt;
&lt;li&gt;都可以使用泛型&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;如何选择&lt;/h3&gt;
&lt;p&gt;根据对象生命周期形态，把使用的类型分成两大类&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须具有明确生命周期，被明确的初始化，使用，释放
&lt;ul&gt;
&lt;li&gt;用Class&lt;/li&gt;
&lt;li&gt;例如：
&lt;ul&gt;
&lt;li&gt;数据库连接，管理对象等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;没有明确的生命周期
&lt;ul&gt;
&lt;li&gt;用Struct或Enum&lt;/li&gt;
&lt;li&gt;例如：
&lt;ul&gt;
&lt;li&gt;不用修改只是用来使用其值的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Struct值类型本质&lt;/h3&gt;
&lt;p&gt;某个属性诗歌结构体，改变其某个属性，也会重新赋值一个新对象&lt;/p&gt;
&lt;h2&gt;值类型和引用类型&lt;/h2&gt;
&lt;h3&gt;差异对比&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;引用类型必须指明&lt;code&gt;init&lt;/code&gt;方法&lt;/li&gt;
&lt;li&gt;引用类型关注的是对象本身
&lt;ul&gt;
&lt;li&gt;使用值类型创建新对象时将会复制&lt;/li&gt;
&lt;li&gt;使用引用类型时，新变量将引用同一个对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;引用类型的默认值是可以修改的
&lt;ul&gt;
&lt;li&gt;给Struct的方法默认是只读的。如果要修改要用&lt;code&gt;mutating&lt;/code&gt;修饰&lt;/li&gt;
&lt;li&gt;&lt;code&gt;class&lt;/code&gt;中不用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/a10f554ac63b
https://juejin.im/post/5c653aa6e51d457fbf5dc298&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>纯Swift路由方案探究</title><link>https://blog.lab-lawliet.app/posts/swift-swift-e2708f70903e622d/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swift-e2708f70903e622d/</guid><description>前言</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前言&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;OC&lt;/code&gt; 写了几年了到了新公司开始写&lt;code&gt;Swift&lt;/code&gt;
项目正在成长期, 开始人也不多, 模块化程度不高
于是就整了这套路由方案
发现现在很多Swift路由方案很大程度上还是使用了&lt;code&gt;OC&lt;/code&gt;的特性比如需要继承&lt;code&gt;NSObject&lt;/code&gt;
作为一名&lt;code&gt;Swift&lt;/code&gt;萌新, 这次路由方案的设计是很好的一次对&lt;code&gt;Swift&lt;/code&gt;的探索&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;回顾历史 Objective-c&lt;/h2&gt;
&lt;p&gt;以往路由方案的核心主要是 &lt;code&gt;+Load注册&lt;/code&gt; &lt;code&gt;反射&lt;/code&gt; 等&lt;/p&gt;
&lt;h3&gt;+ Load&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;+ (void)Load {
    // 注册唯一标示 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;反射&lt;/h3&gt;
&lt;p&gt;生成类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Class myClass = NSClassFromString(@&quot;DetailViewController&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方法调用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    SEL action = NSSelectorFromString(selectorName);
    
    if (![self respondsToSelector:action]) {
        return;
    }
    
    IMP imp = [self methodForSelector:action];
    void (*func)(id, SEL, id) = (void *)imp;
    func(self, action, data);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;调度形式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
 // Target-Action
 [self performTarget:@&quot;ModuleA&quot; action:@&quot;Func&quot; params:@{ @&quot;name&quot;:@&quot;OldWang&quot; }]
 
 // URL
 [self open:@&quot;demo://ModuleA/showDetail?id=100&quot;]
 
 // ... 
 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Swift&lt;/code&gt;中没有 &lt;code&gt;+Load&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Swift&lt;/code&gt;包含命名空间, 反射较为麻烦, 且必须继承&lt;code&gt;NSObject&lt;/code&gt;才能反射&lt;/li&gt;
&lt;li&gt;硬编码多维护成本较高, 无法编译检查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RYRouter&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;http://ohfpqyfi7.bkt.clouddn.com/RouterUML02-2.jpg&quot; alt=&quot;RouterUML02-2&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Swift性能分析</title><link>https://blog.lab-lawliet.app/posts/swift-swift-e3967175c7620347/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swift-e3967175c7620347/</guid><description>一般程序的内存区域，除了代码段和数据段之外，剩下的主要是堆内存和栈内存。</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;编译器优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一般的编译过程中会把源文件先生成目标文件（.o文件）然后连接器将不同的目标文件组合起来，生成可执行文件。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Swift&lt;/code&gt;编译器引入了&lt;code&gt;Whole Module Optimizations&lt;/code&gt;优化机制
&lt;ul&gt;
&lt;li&gt;如果有一个传入泛型的函数&lt;code&gt;funcA&lt;/code&gt;，在整个项目中只传入了&lt;code&gt;Int&lt;/code&gt;型，那么编译器就不会在编译成支持其他类型的方法了。&lt;/li&gt;
&lt;li&gt;类似的优化还有很多，为编译器提供了更多信息，使编译器可以从全局角度出发做更多的优化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;内存分配和引用计数优化&lt;/h2&gt;
&lt;p&gt;一般程序的内存区域，除了代码段和数据段之外，剩下的主要是堆内存和栈内存。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;堆(heap)
&lt;ul&gt;
&lt;li&gt;堆内存一般由程序员自己申请、指明大小、释放，是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或 缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张); 当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;栈 (stack heap)
&lt;ul&gt;
&lt;li&gt;又称堆栈, 由编译器自动创建/分配/释放，是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}” 中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外, 在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值 也会被存放回栈中。由于栈的后进先出特点,所以 栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基于堆栈的优化&lt;/h2&gt;
&lt;p&gt;Swift中值类型存放在栈中，应用类型存放在堆中。
苹果也明确指出建议使用值类型。苹果为何这么建议呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据结构
&lt;ul&gt;
&lt;li&gt;存放在栈里的数据结构较为简单，只有一些值相关的东西&lt;/li&gt;
&lt;li&gt;堆上的数据结构较为复杂，包含了type、retainCount等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据的分配与读取
&lt;ul&gt;
&lt;li&gt;存放在栈中的数据从栈区底部推入 (push)，从栈区顶部弹出 (pop)，类似一个数据结构中的栈。由于我们只能够修改栈的末端，因此我们可以通过维护一个指向栈末端的指针来实现这种数据结构，并且在其中进行内存的分配和释放只需要重新分配该整数即可。所以栈上分配和释放内存的代价很小。&lt;/li&gt;
&lt;li&gt;存放在堆中的数据并不是直接 push/pop，类似数据结构中的链表，需要通过一定的算法找出最优的未使用的内存块，再存放数据。同时销毁内存时也需要重新插值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;多线程处理
&lt;ul&gt;
&lt;li&gt;栈，是线程独有的不用考虑多线程问题&lt;/li&gt;
&lt;li&gt;堆，数据时多线程共享的，需要同步锁来解决这个问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;所以基于内存分配方面的考虑，应该更多的使用栈而不是堆，达到优化效果&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;值类型的一个优化&lt;/h2&gt;
&lt;p&gt;有这样一个结构体&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Struct User {
    var avatar = UIImage。。。
    var name = &quot;老王&quot;
}

var userA = User()
var userB = userA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样结构体中的引用类型的引用计数会增加
如果我们对其中的引用类型再进行一层封装的话就可以减少不必要的引用计数&lt;/p&gt;
&lt;h2&gt;方法调用的优化&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Objective-C 中方法的调用，从本质上来说都是向相应的对象发送消息。方法经编译器编译过后一般就变成了objc_msgSend函数，该函数的第一个参数是接受消息的对象，第二个参数是消息的名字，后面的都是消息携带的名字，参数从0到 n 个不等。&lt;/li&gt;
&lt;li&gt;正是基于这一点Objective-C 中，我们可以字符串去调用方法，就可以用变量来传递这个字符串，进而可以实现一些运行时动态调用，语言提供的 NSSelectorFromString 是一个很好的说明，runtime 也因此被开发者奉为神器，被广大开发这熟知的JSPatch 也是基于这点实现的。因为这种动态性的设计使得Objective-C 语言变得异常灵活。&lt;/li&gt;
&lt;li&gt;但是，Objective-C语言动态化这种灵活性是以查表的方式找出函数地址，既然查表操作，当然要付出时间代价。苹果官网文档中介绍了方法调用时，函数地址查询过程，苹果也发现了这种方式调用起来会很慢，所以一种这种的办法就是缓存方法调用的查询结果，但即便是这样，性能上同将函数地址硬编码到代码中这种方式相比还是有一些差距。&lt;/li&gt;
&lt;li&gt;相比于Objective-C，Swift语言直接放弃了Objective-C这个动态化机制。就这一方面而言，Swift如今算是和很多主流语言保持了一直。因为舍弃了动态特性，Swift语言势必比Objective-C快了一些，但在一定程度上丢失了灵活性。相信不久的将来，Swift势必会引入一些动态特性，不过目前而言这并不是它的首要目标。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;协议类型&lt;/h2&gt;
&lt;p&gt;如有下面两个协议&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol MyProtocol

struct Owner: MyProtocol {
    var name: String
}

struct User: MyProtocol {
    var name: String
    var url: String
}

let userA = User()
let owner = Owner()


var array: [MyProtocol] = [userA, owner]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的数组内的元素是不同类型的，占用空间不一致。无法高效的定位元素位置。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;苹果的解决方案
&lt;ul&gt;
&lt;li&gt;Existential Container
&lt;ul&gt;
&lt;li&gt;这是一个额外的容器，大小一定
&lt;ul&gt;
&lt;li&gt;分为5段（word）
&lt;ul&gt;
&lt;li&gt;前三段用来存储元数据的值，如果存储不下则会在堆内存上开辟空间存放，而在第一段中存入指针&lt;/li&gt;
&lt;li&gt;第四段：Value Witness Table(VWT)。每个类型都对应这样一个表，用来存储值的创建，释放，拷贝等操作函数。(管理 Existential Container 生命周期)&lt;/li&gt;
&lt;li&gt;第五段：Protocol Witness Table(PWT)，用于存放协议（Protocol）对应的函数的实现函数地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;为什么Swift编译很慢？
&lt;ul&gt;
&lt;li&gt;因为Swift在编译的时候做了很多事情，所以消耗时间比较多是正常的。如对类型的分析等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;为什么Swift相比较OC会更快？
&lt;ul&gt;
&lt;li&gt;编译器 Whole Module Optimizations 机制的全局优化、更多的栈内存分配、更少的引用计数、更多的静态、协议类型的使用等都是Swift比OC更快的原因。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://www.jianshu.com/p/0ca59322c08b&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Swift5新特性</title><link>https://blog.lab-lawliet.app/posts/swift-swift5-0c46a299daf7a539/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swift5-0c46a299daf7a539/</guid><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;https://juejin.im/post/5c4b3e91e51d452c8e6d6bcc#heading-1&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>SwiftPackage使用</title><link>https://blog.lab-lawliet.app/posts/swift-swiftpackage-1b4a3a0378a03dde/</link><guid isPermaLink="true">https://blog.lab-lawliet.app/posts/swift-swiftpackage-1b4a3a0378a03dde/</guid><description>先在目标文件夹打开终端</description><pubDate>Tue, 13 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;官方文档: https://github.com/apple/swift-package-manager
当前不支持 iOS, watchOS, or tvOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;查看当前版本&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;swift package --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;初始化项目&lt;/h2&gt;
&lt;p&gt;先在目标文件夹打开终端&lt;/p&gt;
&lt;h3&gt;01 初始化项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;swift package init --type executable
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;02 生成工程文件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;此处初始话的项目名称会和目录名称一致&lt;/li&gt;
&lt;li&gt;如: 目录名&lt;code&gt;Demo&lt;/code&gt; 文件即为&lt;code&gt;Demo.xcodeproj&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;swift package generate-xcodeproj
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TODO:&lt;/h2&gt;
&lt;p&gt;突然发现不能用在&lt;code&gt;iOS&lt;/code&gt;上就暂时搁置了...&lt;/p&gt;
</content:encoded></item></channel></rss>