好久没有《带你领略 iOS 知识体系的全貌》了,现在开始一个新的篇章 —— 应用开发篇。在这一篇,我们会讲到 iOS 开发中的GUI 框架、响应式框架、动画、A/B 方案以及消息总线。
前文推荐:
如果你错过了第一个篇章 —— 基础篇,可以从这里跳转:
21 | 除了 Cocoa,iOS 还可以用哪些 GUI 框架开发?#
优化 App 的启动速度,除了可以从主线程上入手外,还可以考虑 GUI(Graphical User Interface,图形用户界面)上的优化。
目前流行的 GUI 框架#
现在流行的 GUI 框架除了 Cocoa Touch 外,还有 Texture(原名 AsyncDisplayKit)、 WebKit、React Native(Facebook)以及 Flutter(Google),它们的对比如下:
GUI 框架 | Cocoa Touch | Texture | WebKit | React Native | Flutter |
---|---|---|---|---|---|
Platform support | iOS | iOS | iOS、Android | iOS、Android | iOS、Android |
Programming language | OC、Swift | OC、Swift | JavaScript | JavaScript | Dart |
Render engine | CoreAnimation | CoreAnimation | WebCore | Native | Skia |
Layout method | Frame、Auto Layout | FlexBox | Frame、FlexBox | Frame、FlexBox | Frame、FlexBox、LayoutWidgets |
-
FlexBox 布局可以让 iOS 开发者用到前端先进的 W3C 标准响应式布局,iOS 新推出的 UIStackView 布局方式,也是按照 FlexBox 布局思路来设计的。Cocoa Touch 框架本身不支持 FlexBox 布局,但是通过 Facebook 的 Yoga 库也能够使用 FlexBox 布局。
-
Texture 框架的基本单元是基于 UIView 抽象的节点 ASDisplayNode。相比 UIView,ASDisplayNode 是线程安全的,可以在后台线程上并行实例化和配置整个层级结构。正因为 Texture 使用了异步节点计算,所以它可以提高主线程的响应速度。
-
在 iOS 开发中,我们最常使用的 UIWebView 和 WKWebView 控件都是基于 WebKit 框架。关于 WebKit 框架,作者曾写过一篇博客 “深入剖析 WebKit”,详细分析了它的原理。
-
对 React Native 和 Flutter 框架渲染的相关介绍,后面的原生与前端共舞篇会详细介绍。
GUI 框架里都有什么?#
控件、渲染树、渲染层树。
-
控件主要负责界面元素数据的存储和更新;
-
渲染树这种抽象的树结构主要记录控件之间的关系;
-
渲染层树由渲染层对象组成,渲染层对象是根据 GUI 框架的优化条件来确定创建的,它记录包含了哪些控件,结合渲染树布局可以生成 Bitmap,最终供 GPU 来渲染。
它们之间的关系图如下:
其它:
-
使用 WebKit 的网页显示慢,不是因为它的渲染性能不如其他框架,而主要是由于加载 CSS 和 JavaScript 资源的方式导致的。另外,解析 HTML、CSS、JavaScript 时需要兼容老版本,JavaScript 类型推断失败会重来,列表缺少重用机制等原因,也导致 WebKit 框架的整体性能没有其他框架好。
-
Flutter 本来也是基于 Chrome 浏览器引擎的。后来,考虑到 Flutter 的性能,谷歌去掉了它对 HTML、CSS、JavaScript 的支持,改用自家的 Dart 语言以甩掉历史包袱,具体的细节可以查看采访 Flutter 创始人 Eric 的视频—— 知乎。
渲染流程#
GUI 框架中的渲染技术一直很稳定,一般都会经过布局、渲染、合成这三个阶段。
-
布局阶段:主要依据渲染树计算出控件的大小和位置。
-
渲染阶段:主要是利用图形函数计算出界面的内容。一般情况下,对于 2D 平面的渲染都是使用 CPU 计算,对 3D 空间的渲染会使用 GPU 计算。
-
合成阶段:主要是合并图层,节省显示内存。
Texture 里 Node 的异步绘制:
对于那些希望能够在用户交互体验上进行大幅提升的 iOS 开发者来说,Texture 具有很小的切换成本和大幅提升的性能收益。
其优势就是:
-
开发了线程安全的 ASDisplayNode;
-
能够很好的和 UIView 共生。
异步绘制原理:
ASDisplayLayer (CALayer 的封装)是整个绘制的起点,绘制事件先在 displayBlock 设置好,然后 ASDisplayNode (替代了 CALayer 里的 delegate)调用 displayBlock 来进行异步绘制。因为 displayBlock 用的是线程安全的 Core Graphics,所以可以把 displayBlock 放到后台线程去异步执行。
22 | 细说 iOS 响应式框架变迁#
响应式框架介绍#
定义:能够支持响应式编程范式的框架。
特点:使用响应式框架,在编程时就可以使用数据流传播数据的变化,响应这个数据流的计算模型会自动计算出新的值,然后将新的值通过数据流传给下一个响应的计算模型,如此反复下去,直到没有响应者为止。
现状:iOS 响应式框架有 ReactiveCocoa(简称 RAC)、RxSwift,但是都没有流行起来,直到前端推出 React.js 后,响应式思路才遍地开花。
那为什么 ReactiveCocoa 在 iOS 原生开发中就没流行起来呢?
带着问题往下看#
ReactiveCocoa 框架的思路,与 React.js 的思路基本是一致的。那我们就来看看 React.js 框架做了些什么?关键在于增加了虚拟文档对象模型(Virtual DOM)。
React.js 框架的底层有个 Virtual DOM,它与页面组件状态绑定,和 DOM(文档对象模型)之间存在映射与转换关系。其渲染原理如下图所示:
可以看出:
-
React.js 框架先操作 Virtual DOM,此时并不会直接进行 DOM 渲染,而是在完成了 Diff 计算得到所有实际变化的节点后,再进行一次 DOM 操作,然后整体渲染。Virtual DOM 相当于 JavaScript 和 DOM 之间的一个缓存。
-
而不像 JavaScript 每次操作 DOM,就会全部重新渲染,性能损耗很大。
回到问题:为什么 ReactiveCocoa 在 iOS 原生开发中就没流行起来呢?
对于前端来说,DOM 树的结构非常复杂,进行一次完整的 DOM 树变更,会带来严重的性能问题,Virtual DOM 可以很好解决这个问题。
而对于 iOS 原生的 Cocoa Touch 框架来说:
-
这种性能问题并不存在,其界面节点树结构比 DOM 树简单得多;
-
并且,其渲染机制与前端也不同,Cocoa Touch 每次更新视图时不会立刻进行整个视图节点树的重新渲染,而是会通过 setNeedsLayout 方法先标记该视图需要重新布局,直到绘图循环到这个视图节点时才开始调用 layoutSubviews 方法进行重新布局,最后再渲染。
所以,ReactiveCocoa 框架并没有为 iOS 的 App 带来更好的性能。当一个框架可有可无,而且没有明显收益时,一般团队是没有理由去使用的。
其它:ReactiveCocoa 里面也有很多值得我们学习的地方,比如:
-
上层接口设计思想:函数响应式编程方式,可以通过回调、KVO 来实现;
-
宏的运用:参考 Reactive Cocoa Tutorial: 神奇的 Macros——sunnyxx。
23 | 如何构造酷炫的动画效果?#
业界痛点#
-
手动编写动画的代码非常复杂,很多动画细节的调整需要和动画设计师不断沟通打磨;
-
iOS、Android、Web 各平台开发者需要各自维护动画代码。
发问:有什么办法能够将动画制作和编程开发隔离开,专人做专事,并且多平台动画效果保持一致呢?
Lottie#
有的,它就是 Lottie 框架,Airbnb 开源的一个动画框架。
使用步骤:
-
动画设计师使用After Effects 制作动画,然后通过 Bodymovin 插件将动画导成 JSON 文件;
-
开发者使用 Lottie 加载和渲染这个 JSON 文件,从而自动转换成对应的动画代码。
实现原理:
Lottie 在 iOS 内做的事情就是:
1)将 JSON 文件(After Effects 制作动画生成的中间媒介)的内容一一映射到 iOS 中 LayerModel、Keyframe、ShapeItem、DashElement、Marker、Mask、Transform 这些类的属性中,保存;
2)再通过 CoreAnimation 进行渲染。
所以,Lottie 实际上自动化了动画设计文件转化为开发代码的过程,把设置 LayerModel 等属性的任务交给了:JSON 文件和 Lottie 映射规则。
💡Tips:
-
Lottie 这样的工作流程或许就是未来的趋势,就像 iOS 现在的发展趋势一样,越来越多的业务逻辑不再需要全部使用 Objective-C 或 Swift 来实现了,而是使用 JavaScript 语言或者 DSL 甚至是工具来描述业务,然后将描述业务的代码转换成一种中间代码,比如 JSON,不同平台再对相同的中间代码进行解析处理,以执行中间代码描述的业务逻辑。
-
Lottie 详细的说明和使用示例代码,可以参考 Lottie 官方 iOS 教程。Lottie 不仅支持物理效果,还支持页面切换的过场动画。
-
没有动画设计师配合的开发者,可以去 LottieFiles 上看看,这是一个动画设计师分享作品的平台,每个动画效果的 JSON 文件都可下载使用。
24 | A/B 测试:验证决策效果的利器#
A/B 测试定义#
A/B 测试,也叫桶测试或分流测试,指的是针对一个变量的两个版本 A 和 B,测试用户的不同反应,从而判断出哪个版本更有效,类似统计学领域使用的双样本假设测试。
简单地说,A/B 测试就是检查不同的 App 用户在使用不同版本的功能时,哪个版本的用户反馈最好。
App 开发中的 A/B 测试:
在 App 版本迭代中,我们可以把旧版本理解为 A/B 测试里的 A 版本,把新版本理解为 B 版本。两个版本同时存在,B 版本一开始是将小部分用户放到 B 测试桶里,逐步扩大用户范围,通过分析 A 版本和 B 版本的数据,看哪个版本更接近期望的目标,最终确定用哪个版本。
总的来说,A/B 测试是以数据驱动的可回退的灰度方案,客观、安全、风险小,是一种成熟的试错机制。
A/B 测试全景设计#
一个 A/B 测试框架主要包括三部分,结构图如下:
-
策略服务,为策略制定者提供策略,包含决策流程和策略维度。
- 一般由服务端提供,方便服务端随时根据用户群的维度分布分配测试桶。
-
A/B 测试 SDK,集成在客户端内,用来控制上层业务走不同的策略。
-
推荐:SkyLab,作者 Mattt 也是我们熟悉的 AFNetworking 网络库和 Alamofire 网络库的作者,该库在接口设计上也是使用 block 来接收 A/B 版本的区别处理,易用性很高,值得学习。
-
生效机制:如果一个策略只在一个地方生效的话,可以使用热启动生效机制;而如果一个策略在多个地方生效的话,最好使用冷启动生效机制。
-
-
日志系统,负责反馈策略结果供分析人员分析不同策略执行的结果。
- 一般由服务端提供。
25 | 怎样构建底层的发布和订阅事件总线?#
事件总线定义#
事件总线是对发布和订阅设计模式的一种实现,通过发布、订阅可以将组件间一对一和一对多的耦合关系解开。
这种设计模式,特别适合数据层通过异步发布数据的方式告知 UI 层订阅者,使得 UI 层和数据层可以不用耦合在一起,在重构数据层或者 UI 层时不影响业务层。
iOS 里已有的相关技术#
-
Block 和 Delegate。只适合一对一的模式,如果需要不断异步发布给下一个数据订阅者的话(消息之间有因果关系),会出现回调嵌套其他回调的情况。
-
KVO 和 NSNotificationCenter。它们支持一对多的模式。但是使用 KVO 是强依赖属性的,只要更新了属性就会发布给所有的观察者,对应关系过于灵活,难以管控和维护;使用 NSNotificationCenter 也有类似的问题,通过字符串来维护发布者和订阅者之间的关系,不仅可读性差,而且和 KVO 一样面临着难以管控和维护的情况。
Q:那么有没有好的第三方库可以处理事件总线呢?
其实,前面提到的响应式第三方库 ReactiveCocoa 和 RxSwift 对事件总线的支持都是没有问题的,但这两个库更侧重的是响应式编程,事件总线只是其中很小的一部分。所以,使用它们就有点大材小用了。
而现在前端领域有一种模式叫作 Promise,这是一种专门针对异步数据操作编写的一套统一规则的模式。
Promise#
本质上,这种模式是通过 Promise 对象保存异步数据操作,同时 Promise 对象提供统一的异步数据操作事件处理的接口。
Promise 对象会有三种状态:
-
pending:异步事件正在等待处理;
-
fulfilled:异步事件成功完成;
-
rejected:异步事件没有成功完成。
还有两个重要的方法:then 和 catch。
Promise 对象每次执行完 then 或者 catch 方法后,都会返回先前的 Promise 对象,同时该 Promise 对象的状态会根据异步操作的结果而改变。
-
then:执行 then 方法对应订阅操作,Promise 对象触发 then 方法对应发布操作。then 方法执行完返回 Promise 对象,并能够继续同步执行多个 then 方法,由此,实现了一个发布操作对应多个订阅事件。
-
catch:如果执行 then 方法后返回的 Promise 对象是 rejected 状态的话,程序会直接执行 catch 方法。
Q:那么 iOS 中如何使用 Promise 模式呢?
引入 PromiseKit(Homebrew 的作者 Max Howell 开发)。
此外,PromiseKit 还为苹果的 API 提供了扩展,如 UIKit、Foundation、CoreLocation、QuartzCore、CloudKit 等等,甚至还支持了第三方的框架 Alamofire,具体可参考 PromiseKit Organization。
通过简单、清晰、规范的 Promise 接口将异步的数据获取、业务逻辑、界面串起来,对于日后的维护或重构都会容易很多,赶快去试试吧~
好了,今天的分享就到这里~下次我们会进入《带你领略 iOS 知识体系的全貌》应用开发篇(下),涉及 JSON 处理、布局框架、富文本、TDD/BDD 和编码规范等相关内容,别忘了关注,咱们下期见!