2 应用开发篇(上)

好久没有《带你领略iOS知识体系的全貌》了,现在开始一个新的篇章——应用开发篇。在这一篇,我们会讲到iOS开发中的GUI框架、响应式框架、动画、A/B方案以及消息总线

前文推荐

如果你错过了第一个篇章——基础篇,可以从这里跳转:

《带你领略iOS知识体系的全貌》基础篇(上)

《带你领略iOS知识体系的全貌》基础篇(中)

《带你领略iOS知识体系的全貌》基础篇(下)

《带你领略iOS知识体系的全貌》基础篇(最佳学习路径)

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 具有很小的切换成本和大幅提升的性能收益。

其优势就是:

  1. 开发了线程安全的 ASDisplayNode;

  2. 能够很好的和 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 框架渲染原理——《极客时间》

可以看出

  • 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 里面也有很多值得我们学习的地方,比如:

23 | 如何构造酷炫的动画效果?

业界痛点

  • 手动编写动画的代码非常复杂,很多动画细节的调整需要和动画设计师不断沟通打磨;

  • iOS、Android、Web 各平台开发者需要各自维护动画代码。

发问:有什么办法能够将动画制作和编程开发隔离开,专人做专事,并且多平台动画效果保持一致呢?

Lottie

有的,它就是 Lottie 框架,Airbnb 开源的一个动画框架。

使用步骤

  1. 动画设计师使用After Effects 制作动画,然后通过 Bodymovin 插件将动画导成 JSON 文件;

  2. 开发者使用 Lottie 加载和渲染这个 JSON 文件,从而自动转换成对应的动画代码。


实现原理

Lottie 在 iOS 内做的事情就是:

1)将 JSON 文件(After Effects 制作动画生成的中间媒介)的内容一一映射到 iOS 中 LayerModel、Keyframe、ShapeItem、DashElement、Marker、Mask、Transform 这些类的属性中,保存;

2)再通过 CoreAnimation 进行渲染。

所以,Lottie 实际上自动化了动画设计文件转化为开发代码的过程,把设置 LayerModel 等属性的任务交给了:JSON 文件和 Lottie 映射规则。


💡Tips

  1. Lottie 这样的工作流程或许就是未来的趋势,就像 iOS 现在的发展趋势一样,越来越多的业务逻辑不再需要全部使用 Objective-C 或 Swift 来实现了,而是使用 JavaScript 语言或者 DSL 甚至是工具来描述业务,然后将描述业务的代码转换成一种中间代码,比如 JSON,不同平台再对相同的中间代码进行解析处理,以执行中间代码描述的业务逻辑。

  2. Lottie 详细的说明和使用示例代码,可以参考 Lottie 官方 iOS 教程。Lottie 不仅支持物理效果,还支持页面切换的过场动画。

  3. 没有动画设计师配合的开发者,可以去 LottieFiles 上看看,这是一个动画设计师分享作品的平台,每个动画效果的 JSON 文件都可下载使用。

作者:Okiri George

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 测试方案的结构图——《极客时间》
  • 策略服务,为策略制定者提供策略,包含决策流程和策略维度。

    • 一般由服务端提供,方便服务端随时根据用户群的维度分布分配测试桶。
  • 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 对象会有三种状态

  1. pending:异步事件正在等待处理;

  2. fulfilled:异步事件成功完成;

  3. 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 和编码规范等相关内容,别忘了关注,咱们下期见!