01 | 建立你自己的iOS开发知识体系

基础模块

我把 iOS 开发者需要掌握的整个基础知识,按照 App 的开发流程(开发、调试测试、发布、上线)进行了划分,如下图所示。

  • 开发阶段:启动流程、界面布局、架构设计
  • 调试测试阶段:提速调试、静态分析
  • 发布阶段:自动埋点、体积优化
  • 上线阶段:崩溃监控、卡顿监控、日志收集、性能监控、多线程问题、电量问题

应用开发模块

  • GUI 框架:UIKit、Core Animation、Core Graphics、Core Image、OpenGL ES
  • 响应式框架:ReactCocoa、RXSwift、EasyReact
  • 动画:Pop、RXTransitions
  • A/B 方案
  • 消息总线:PromiseKit、SwiftTask
  • JSON 处理:JSONModel、Mantle、JSONDecoder
  • 布局框架:Masonry、SnapKit、Cartography、Yoga
  • 富文本:YYText、DTCoreText
  • TDD/BDD
  • 编码规范

原理模块

  • 系统内核 XNU
  • AOP :Runtime Method Swizzling、libffi
  • 内存管理
  • 编译

原生与前端

  • JavaScriptCore
  • 跨端方案:React Native、Weex、Flutter、H5
  • 布局区别:原生布局、前端布局
  • 渲染区别:原生渲染、React Native 渲染、Flutter 渲染
  • 动态化方案分析:WaxPatch、JSPatch、OCS、低风险方案

(值得一说的是,从 H5 到 Flutter,渲染底层图形库都使用的是 Skia。也就是说,这么多年来渲染底层技术就基本没有变过。而且,向 Flutter 的演进也只是去掉了 H5 对低版本标准的支持。但,仅仅是去掉这些兼容代码,就使性能提升了数倍。)

02 | App 启动速度怎么做优化与监控?

  • 冷启动是指, App 点击启动前,它的进程不在系统里,需要系统新创建一个进程分配给它启动的情况。这是一次完整的启动过程。
  • 热启动是指 ,App 在冷启动后用户将 App 退后台,在 App 的进程还在系统里的情况下,用户重新启动进入 App 的过程,这个过程做的事情非常少。

就像 +load() 方法,一个耗时 4 毫秒,100 个就是 400 毫秒,这种耗时用户也是能明显感知到的。

在 main() 函数执行前,系统主要会做下面几件事情:

  • 加载可执行文件(App 的.o 文件的集合);
  • 加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;
    O* bjc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;
  • 初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。

相应地,这个阶段对于启动速度优化来说,可以做的事情包括:

  • 减少动态库加载。每个库本身都有依赖关系,苹果公司建议使用更少的动态库,并且建议在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司建议最多使用 6 个非系统动态库。
  • 减少加载启动后不会去使用的类或者方法。
  • +load() 方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换掉。因为,在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。不要小看这 4 毫秒,积少成多,执行 +load() 方法对启动速度的影响会越来越大。
  • 控制 C++ 全局变量的数量。

main() 函数执行后

  • 首屏初始化所需配置文件的读写操作;
  • 首屏列表大数据的读取;
  • 首屏渲染的大量计算等。

更加优化的开发方式,应该是从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后,将这些初始化功能分别放到合适的阶段进行。

首屏渲染完成后

方法级别的启动优化

对 App 启动速度的监控,主要有两种手段。

  • 第一种方法是,定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode 工具套件里自带的 Time Profiler ,采用的就是这种方式。

定时间隔设置得长了,会漏掉一些方法,从而导致检查出来的耗时不精确;
而定时间隔设置得短了,抓取堆栈这个方法本身调用过多也会影响整体耗时,导致结果不准确。

  • 第二种方法是,对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。

03 | Auto Layout 是怎么进行自动布局的,性能如何?

  • Auto Layout 背后使用的 Cassowary 算法
  • 苹果公司经过一番努力,终于在 iOS 12 上用到了 Cassowary 算法的界面更新策略,使得 Auto Layout 的性能得到了大幅提升。iOS 12 使得 Auto Layout 具有了和手写布局几乎相同的高性

04 | 项目大了人员多了,架构怎么设计更合理?

对于 iOS 这种面向对象编程的开发模式来说,我们应该遵循以下五个原则,即 SOLID 原则。

  • 单一功能原则:对象功能要单一,不要在一个对象里添加很多功能。

  • 开闭原则:扩展是开放的,修改是封闭的。

  • 里氏替换原则:子类对象是可以替代基类对象的。

  • 接口隔离原则:接口的用途要单一,不要在一个接口上根据不同入参实现多个功能。

  • 依赖反转原则:方法应该依赖抽象,不要依赖实例。iOS 开发就是高层业务方法依赖于协议。

  • 底层可以是与业务无关的基础组件,比如网络和存储等;

  • 中间层一般是通用的业务组件,比如账号、埋点、支付、购物车等;

  • 最上层是迭代业务组件,更新频率最高。

  • 弹窗组件、按钮组件、日志组件、发布组件、支付组件、购物车组件

  • 协议式和中间者两种架构

05 | 链接器:符号是怎么绑定到地址上的?

使用编译器和解释器执行代码的特点,我们就可以概括如下:

  • 采用编译器生成机器码执行的好处是效率高,缺点是调试周期长。
  • 解释器执行的好处是编写调试方便,缺点是执行效率低。

06 | App 如何通过注入动态库的方式实现极速编译调试?

Injection 会监听源代码文件的变化,如果文件被改动了,Injection Server 就会执行 rebuildClass 重新进行编译、打包成动态库,也就是 .dylib 文件。编译、打包成动态库后使用 writeSting 方法通过 Socket 通知运行的 App。writeString 的代码如下:

1
2
3
4
5
6
7
8
- (BOOL)writeString:(NSString *)string {
const char *utf8 = string.UTF8String;
uint32_t length = (uint32_t)strlen(utf8);
if (write(clientSocket, &length, sizeof length) != sizeof length ||
write(clientSocket, utf8, length) != length)
return FALSE;
return TRUE;
}

07 | Clang、Infer 和 OCLint ,我们应该使用谁来做静态分析?

这里有三个常用的复杂度指标,可以帮助我们度量是否需要优化和重构代码。

  • 圈复杂度高。圈复杂度,指的是遍历一个模块时的复杂度,这个复杂度是由分支语句比如 if、case、while、for,还有运算符比如 &&、||,以及决策点,共同确定的。一般来说,圈复杂度在以 4 以内是低复杂度,5 到 7 是中复杂度,8 到 10 是高复杂度,11 以上时复杂度就非常高了,这时需要考虑重构,不然就会因为测试用例的数量过高而难以维护。而这个圈复杂度的值,是很难通过人工分析出来的。而静态分析器就可以根据圈复杂度规则,来监控圈复杂度,及时发现代码是否过于复杂,发现问题后及早解决,以免造成代码过于复杂难以维护。

  • NPath 复杂度高。NPath 度量是指一个方法所有可能执行的路径数量。一般高于 200 就需要考虑降低复杂度了。

  • NCSS 度量高。NCSS 度量是指不包含注释的源码行数,方法和类过大会导致代码维护时阅读困难,大的 NCSS 值表示方法或类做的事情太多,应该拆分或重构。一般方法行数不过百,类的行数不过千。

  • OCLint、Infer、Clang 静态分析器

08 | 如何利用 Clang 为 App 提质?

Token 类型,分为下面这 4 类。

  • 关键字:语法中的关键字,比如 if、else、while、for 等;
  • 标识符:变量名;
  • 字面量:值、数字、字符串;
  • 特殊符号:加减乘除等符号。

09 | 无侵入的埋点方案如何实现?

10 | 包大小:如何从资源和代码层面实现全方位瘦身?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ming$ du -h Reveal.framework/*
0B Reveal.framework/Headers
0B Reveal.framework/Reveal
16K Reveal.framework/Versions/A/Headers
21M Reveal.framework/Versions/A
21M Reveal.framework/Versions

ming$ file Reveal.framework/Versions/A/Reveal
Reveal.framework/Versions/A/Reveal: Mach-O universal binary with 5 architectures: * [i386:current ar archive] * [arm64]
Reveal.framework/Versions/A/Reveal (for architecture i386): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture armv7): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture armv7s): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture x86_64): current ar archive
Reveal.framework/Versions/A/Reveal (for architecture arm64): current ar archive

App Thinning 有三种方式,包括:App Slicing、Bitcode、On-Demand Resources。

  • App Slicing,会在你向 iTunes Connect 上传 App 后,对 App 做切割,创建不同的变体,这样就可以适用到不同的设备。
  • On-Demand Resources,主要是为游戏多关卡场景服务的。它会根据用户的关卡进度下载随后几个关卡的资源,并且已经过关的资源也会被删掉,这样就可以减少初装 App 的包大小。
  • Bitcode ,是针对特定设备进行包大小优化,优化不明显。

无用图片资源

删除无用图片的过程,可以概括为下面这 6 大步。

  • 通过 find 命令获取 App 安装包中的所有资源文件,比如 find /Users/daiming/Project/ -name。
  • 设置用到的资源的类型,比如 jpg、gif、png、webp。
  • 使用正则匹配在源码中找出使用到的资源名,比如 pattern = @”@”(.+?)””。
  • 使用 find 命令找到的所有资源文件,再去掉代码中使用到的资源文件,剩下的就是无用资源了。
  • 对于按照规则设置的资源名,我们需要在匹配使用资源的正则表达式里添加相应的规则,比如 @“image_%d”。
  • 确认无用资源后,就可以对这些无用资源执行删除操作了。这个删除操作,你可以使用 NSFileManger 系统类提供的功能来完成。

图片资源压缩

代码瘦身

LinkMap 结合 Mach-O 找无用代码

通过 ObjC 的 runtime 源码,我们可以找到怎么判断一个类是否初始化过的函数,如下:

1
2
3
4
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}

11 | 热点问题答疑(一):基础模块问题答疑

加载动态库的方式有两种:

  • 一种是,在程序开始运行时通过 dyld 动态加载。通过 dyld 加载的动态库需要在编译时进行链接,链接时会做标记,绑定的地址在加载后再决定。
  • 第二种是,显式运行时链接(Explicit Runtime Linking),即在运行时通过动态链接器提供的 API dlopen 和 dlsym 来加载。这种方式,在编译时是不需要参与链接的。

12 | iOS 崩溃千奇百怪,如何全面监控?

崩溃类型:

  • 数组越界:在取数据索引时越界,App 会发生崩溃。还有一种情况,就是给数组添加了 nil 会崩溃。
  • 多线程问题:在子线程中进行 UI 更新可能会发生崩溃。
    多个线程进行数据的读取操作,因为处理时机不一致,比如有一个线程在置空数据的同时另一个线程在读取这个数据,可能会出现崩溃情况。
  • 主线程无响应:如果主线程超过系统规定的时间无响应,就会被 Watchdog 杀掉。这时,崩溃问题对应的异常编码是 0x8badf00d。关于这个异常编码,我还会在后文和你说明。野指针:指针指向一个已删除的对象访问内存区域时,会出现野指针崩溃。
  • 野指针问题是需要我们重点关注的,因为它是导致 App 崩溃的最常见,也是最难定位的一种情况。关于野指针等内存相关问题,我会在第 14 篇文章“临近 OOM,如何获取详细内存分配信息,分析内存问题?”里和你详细说明

信号可捕获崩溃:

  • KVO 问题
  • NSNotification 线程问题
  • 数组越界
  • 野指针
    。。。

信号不可捕获崩溃:

  • 后台任务超时
  • 内存打爆
  • 主线程卡顿超阈值
    。。。

iOS 后台保活的 5 种方式:Background Mode、Background Fetch、Silent Push、PushKit、Background Task。

  • 使用 Background Mode 方式的话,App Store 在审核时会提高对 App 的要求。通常情况下,只有那些地图、音乐播放、VoIP 类的 App 才能通过审核。
  • Background Fetch 方式的唤醒时间不稳定,而且用户可以在系统里设置关闭这种方式,导致它的使用场景很少。
  • Silent Push 是推送的一种,会在后台唤起 App 30 秒。它的优先级很低,会调用 application:didReceiveRemoteNotifiacation:fetchCompletionHandler: 这个 delegate,和普通的 remote push notification 推送调用的 delegate 是一样的。
  • PushKit 后台唤醒 App 后能够保活 30 秒。它主要用于提升 VoIP 应用的体验。
  • Background Task 方式,是使用最多的。App 退后台后,默认都会使用这种方式。

我们采集到的崩溃日志,主要包含的信息为:进程信息、基本信息、异常信息、线程回溯。

  • 进程信息:崩溃进程的相关信息,比如崩溃报告唯一标识符、唯一键值、设备标识;
  • 基本信息:崩溃发生的日期、iOS 版本;
  • 异常信息:异常类型、异常编码、异常的线程;
  • 线程回溯:崩溃时的方法调用栈。

完整异常编码:https://en.wikipedia.org/wiki/Hexspeak

常见的异常编码就是如下三种:

  • 0x8badf00d,表示 App 在一定时间内无响应而被 watchdog 杀掉的情况。
  • 0xdeadfa11,表示 App 被用户强制退出。
  • 0xc00010ff,表示 App 因为运行造成设备温度太高而被杀掉。

13 | 如何利用 RunLoop 原理去监控卡顿?

导致卡顿问题的几种原因:

  • 复杂 UI 、图文混排的绘制量过大;
  • 在主线程上做网络同步请求;
  • 在主线程做大量的 IO 操作;
  • 运算量过大,CPU 持续高占用;
  • 死锁和主子线程抢锁。

RunLoop 只有在下面四个事件出现时才会被再次唤醒:

  • 基于 port 的 Source 事件;
  • Timer 时间到;
  • RunLoop 超时;
  • 被调用者唤醒。

监控卡顿的有效方法是利用 Runloop 状态改变监测

WatchDog 在不同状态下设置的不同时间,如下所示:

  • 启动(Launch):20s;
  • 恢复(Resume):10s;
  • 挂起(Suspend):10s;
  • 退出(Quit):6s;
  • 后台(Background):3min(在 iOS 7 之前,每次申请 10min; 之后改为每次申请 3min,可连续申请,最多申请到 10min)。

14 | 临近 OOM,如何获取详细内存分配信息,分析内存问题?

四种内容问题:

  • 内存过大被系统强杀
  • 访问未分配的内存: XNU 会报 EXC_BAD_ACCESS 错误,信号为 SIGSEGV Signal #11 ,可以通过崩溃信息获取到
  • 访问已分配但未提交的内存:XNU 会拦截分配物理内存,出现问题的线程分配内存页时会被冻结
  • 没有遵守权限访问内存:内存页面的权限标准类似 UNIX 文件权限。如果去写只读权限的内存页面就会出现错误,XNU 会发出 SIGBUS Signal #7 信号,可以通过崩溃信息获取到

15 | 日志监控:怎样获取 App 中的全量日志?

这些在 App 里记录的所有日志,比如用于记录用户行为和关键操作的日志,就是全量日志了。

  • 第一个是使用官方提供的接口 ASL 来获取;
  • 第二个是通过一招吃遍天下的 fishhook 来 hook 的方法;
  • 第三个方法,需要用到 dup2 函数和 STDERR 句柄。我们只有了解了这些知识点后,才会想到这个方

16 | 性能监控:衡量 App 质量的那把尺

线下使用 Instruments 工具,线上主要集中在 CPU 使用率、FPS 的帧率和内存这三个方面。

Analysis Core 收集和处理数据的过程,可以大致分为以下这三步:

  • 处理我们配置好的各种数据表,并申请存储空间 store;
  • store 去找数据提供者,如果不能直接找到,就会通过 Modeler 接收其他 store 的输入信号进行合成;
  • store 获得数据源后,会进行 Binding Solution 工作来优化数据处理过程。

os_signpost 的主要作用,是让你可以在程序中通过编写代码来获取数据。你可以在工程中的任何地方通过 os_signpost API ,将需要的数据提供给 Analysis Core。

17 | 远超你想象的多线程的那些坑

常驻线程
并发问题
内存问题

18 | 怎么减少 App 电量消耗?

开启定位,网络请求是不是频繁,亦或是定时任务时间是不是间隔过小

遍历所有线程,去查看是哪个线程的 CPU 使用百分比过高

苹果公司专门维护了一个电量优化指南“Energy Efficiency Guide for iOS Apps”,分别从 CPU、设备唤醒、网络、图形、动画、视频、定位、加速度计、陀螺仪、磁力计、蓝牙等多方面因素提出了电量优化方面的建议。所以,当使用了苹果公司的电量优化指南里提到的功能时,严格按照指南里的最佳实践去做就能够保证这些功能不会引起不合理的电量消耗。

同时,苹果公司在 2017 年 WWDC 的 Session 238 也分享了一个关于如何编写节能 App 的主题“Writing Energy Efficient Apps”。

19 | 热点问题答疑(二):基础模块问题答疑

减少卡顿四个因素:线程过多(超过64)、cpu 使用率阈值超过 90%

20 | iOS开发的最佳学习路径是什么?

21 | 除了 Cocoa,iOS还可以用哪些 GUI 框架开发?

现在流行的 GUI 框架除了 Cocoa Touch 外,还有 WebKit、Flutter、Texture(原名 AsyncDisplayKit)、Blink、Android GUI 等。其中,WebKit、Flutter、Texture 可以用于 iOS 开发。

22 | 细说 iOS 响应式框架变迁,哪些思想可以为我所用?

分享了 ReactiveCocoa 这种响应式编程框架难以在 iOS 原生开发中流行开的原因。从本质上看,响应式编程没能提高 App 的性能,是其没能流行起来的主要原因。在调试上,由于 ReactiveCocoa 框架采用了 Monad 模式,导致其底层实现过于复杂,从而在方法调用堆栈里很难去定位到问题。这,也是 ReactiveCocoa 没能流行起来的一个原因。但, ReactiveCocoa 的上层接口设计思想,可以用来提高代码维护的效率,还是可以引入到 iOS 开发中的。ReactiveCocoa 里面还有很多值得我们学习的地方,比如说宏的运用。

23 | 如何构造酷炫的物理效果和过场动画效果?

24 | A/B 测试:验证决策效果的利器

25 | 怎样构建底层的发布和订阅事件总线?

26 | 如何提高 JSON 解析的性能?

27 | 如何用 Flexbox 思路开发?跟自动布局比,Flexbox 好在哪?

28 | 怎么应对各种富文本表现需求?

苹果官方的TextKit和 YYText来展示富文本

29 | 如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发?

在开始阶段最好能让团队中编码习惯好、喜欢交流的人来做审核人,以起到良好的示范作用,并以此作为后续的执行标准。

30 | 如何制定一套适合自己团队的 iOS 编码规范?

常量、变量、属性、条件语句、循环语句、函数、类,以及分类这 8 个方面

31 | iOS 开发学习资料和书单推荐

Raywenderlich出版的图书质量都非常不错,可以一步一步教你掌握一些开发知识,内容非常实用,而且这些图书的涉及面广。比如,这些图书包括有 ARKit、Swift 服务端的 Vapor 和 Kitura、Metal、数据结构和算法的 Swift 版、设计模式、Core Data、iOS 动画、Apple 调试和逆向工程、RxSwift、Realm、2D 和 3D 游戏开发等各个方面。另外,objc.io家的图书会从原理和源代码实现的角度来讲解知识点,也非常不错,内容比 Raywenderlich 出版的图书更深入,适合有一定 iOS 开发经验的人阅读。Raywenderlich 和 objc.io 的书基本都是 Swift 来写的。如果你想更深入地理解 Objective-C 的话,我推荐《Objective-C 高级编程》这本书。这本书里的知识点并不多,主要讲的是内存管理、Block、GCD(Grand Central Dispatch)。

32 | 热点问题答疑(三)

很有可能由于历史原因或者 App 的特性决定了有些 App 的性能无法达到另一个 App 的标准。又或者说,有些 App 需要进行大量的重构,才能要达到另一个 App 的性能标准,而这些重构明显不是一朝一夕就能落地执行的。特别是业务还在快跑的情况下,你只能够有针对性地去做优化,而不是大量的重构。

WatchDog 是苹果公司设计的一种机制,主要是为了避免 App 界面无响应造成用户无法操作,而强杀掉 App 进程。WatchDog 在启动时设置的是 20 秒,前台时设置的是 10 秒,后台时设置的是 10 分钟。

对于第三方库的使用,我的建议是:如果和业务强相关,比如埋点或者 A/B 测试这样的库,最好是自建,你可以借鉴开源库的思路;一些基础的、通用性强的库,比如网络库和持续化存储的库,直接使用成熟的第三方库,既可以节省开发和维护时间,还能够提高产品质量;还有种情况就是,如果你所在团队较小,只有几个 iOS 开发人员,那么还是要尽可能地使用开源项目,你可以在 Awesome iOS上去找到适合团队的项目。

33 | iOS 系统内核 XNU:App 如何加载?

iOS 系统是基于 ARM 架构的,大致可以分为四层:

  • 最上层是用户体验层,主要是提供用户界面。这一层包含了 SpringBoard、Spotlight、Accessibility。
  • 第二层是应用框架层,是开发者会用到的。这一层包含了开发框架 Cocoa Touch。
  • 第三层是核心框架层,是系统核心功能的框架层。这一层包含了各种图形和媒体核心框架、Metal 等。
  • 第四层是 Darwin 层,是操作系统的核心,属于操作系统的内核态。这一层包含了系统内核 XNU、驱动等。

XNU 的架构

  • XNU 内部由 Mach、BSD、驱动 API IOKit 组成,这些都依赖于 libkern、libsa、Platform Expert。

XNU 加载就是为 Mach-O 创建一个新进程,建立虚拟内存空间,解析 Mach-O 文件,最后映射到内存空间。流程可以概括为:fork 新进程;为 Mach-O 分配内存;解析 Mach-O;读取 Mach-O 头信息;遍历 load command 信息,将 Mach-O 映射到内存;启动 dyld。

34 | iOS 黑魔法 Runtime Method Swizzling 背后的原理

  • 第一个风险是,需要在 +load 方法中进行方法交换。因为如果在其他时候进行方法交换,难以保证另外一个线程中不会同时调用被交换的方法,从而导致程序不能按预期执行。
  • 第二个风险是,被交换的方法必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须在调用的时候使用,而不是方法交换时使用。
  • 第三个风险是,交换的方法如果依赖了 cmd,那么交换后,如果 cmd 发生了变化,就会出现各种奇怪问题,而且这些问题还很难排查。特别是交换了系统方法,你无法保证系统方法内部是否依赖了 cmd。
  • 第四个风险是,方法交换命名冲突。如果出现冲突,可能会导致方法交换失败。

35 | libffi:动态调用和定义 C 函数

36 | iOS 是怎么管理内存的?

虚拟内存

37 | 如何编写 Clang 插件?

38 | 热点问题答疑(四)

卡顿时重点检查:首先收集卡顿信息,关注发生卡顿时的方法调用堆栈,确定发生卡顿时cpu占用率

39 | 打通前端与原生的桥梁:JavaScriptCore 能干哪些事情?

40 | React Native、Flutter 等,这些跨端方案怎么选?

41 | 原生布局转到前端布局,开发思路有哪些转变?

42 | iOS原生、大前端和Flutter分别是怎么渲染的?

  • 原生:更新视图树和图层树、CPU 计算显示内容、Render Server 转渲染树、Render Server 调用 GPU 执行六段处理并显示
  • 大前端:WebView(WebKit)、React Native 调原生(多出脚本代码解析工作、JavaScript 解释器执行比原生差、WebView 无法访问 GPU)
  • Flutter:Flutter 的 C++ 层,使用的是 Skia 库。

WebView 需要额外解析 HTML + CSS + JavaScript 代码,而类 React Native 方案则需要解析 JSON + JavaScript。HTML + CSS 的复杂度要高于 JSON,所以解析起来会比 JSON 慢。也就是说,首次内容加载时, WebView 会比类 React Native 慢。

43 | 剖析使 App 具有动态化和热更新能力的方案

热更新能力的初衷是,能够及时修复线上问题,减少 Bug 对用户的伤害。而动态化的目的,除了修复线上问题外,还要能够灵活更新 App 版本。

动态化的三种方案:

  • JavaScriptCore 解释器方案 : JSPatch、React Native、Weex
  • 代码转译方案 : DynamicCocoa
  • 自建解释器方案 :OCSVM

类 C 语言的转译,可以使用 LLVM 套件中 Clang 提供的 LibTooling,通过重载 HandleTranslationUnit() 函数,使用 RecursiveASTVistor 来遍历 AST,获取代码的完整信息,然后转换成新语言的代码。

工具

技术概念

Combine : 苹果官方的响应式编程框架