iOS Development Guidelines
  • Introduction
  • 规范
    • 0. 介绍
    • 1. 序言
    • 2. 代码命名规范
      • 2.1. 代码命名基础
      • 2.2. 方法(Method)命名
      • 2.3. 函数(Function)命名
      • 2.4. 属性(Property)与数据类型命名
      • 2.5. 其它命名规范
      • 2.6. 可接受缩略名
    • 3. 代码格式规范
      • 3.1. 代码注释格式
      • 3.2. 代码结构与排版
    • 4. 开发实践
      • 4.1. Objective-C保留字
    • 5. Xcode工程结构
    • 6. 版本控制
      • 6.1. Git基本配置
      • 6.2. Git分支模型
      • 6.3. SVN源代码管理规范
      • 6.4. SVN的标准目录结构
    • 7. 附录
      • 7.1. Xcode扩展插件
      • 7.2. 第三方开源库
    • 8. 参考
    • 9. iOS开发优化
  • Swift编码规范
  • Objective-C新特性
  • iOS生命周期
  • Apple 官方设计指南
    • iOS 人机交互指南
      • 概览 - 设计理念
      • 概览 - iOS 10 新功能
      • 概览 - 接口要素
      • 交互 - 3D Touch
      • 交互 - 辅助功能
      • 交互 - 音频
      • 交互 - 身份验证
      • 交互 - 数据输入
      • 交互 - 反馈
      • 交互 - 文件处理
      • 交互 - 初次启动体验
      • 交互 - 手势
      • 交互 - 加载
      • 交互 - 模态
      • 交互 - 导航
      • 交互 - 评分和评论
      • 交互 - 请求权限
      • 交互 - 设置
      • 交互 - 术语
      • 交互 - 撤销与重做
      • 系统功能 - 多任务
      • 系统功能 - 通知
      • 系统功能 - 打印
      • 系统功能 - 快速预览
      • 系统功能 - Siri
      • 系统功能 - TV 供应商
      • 可视化设计 - 动画
      • 可视化设计 - 品牌化
      • 可视化设计 - 颜色
      • 可视化设计 - 布局
      • 图像 - 应用图标
  • Apple 官方开发指南
    • App 发布指南
      • 待完善
    • Cocoa 代码指南
      • 代码命名基础
      • 方法命名
      • 函数命名
      • 属性和数据类型命名
      • 可接受的缩写词和首字母缩写词
      • 针对框架开发者的技术细节
    • 核心蓝牙编程指南
      • 待完善
  • iOS 杂谈
    • Auto Layout 是怎么进行自动布局的性能如何
    • App 启动速度的优化与监控
    • 多人的大项目,架构怎么设计更合理
    • 链接器:符号是怎么绑定到地址上的
    • App 如何通过注入动态库的方式实现极速编译调试
    • 静态分析工具的选择
    • Clang的App 提质
    • 无侵入的埋点方案如何实现
    • 包大小:如何从资源和代码层面实现全方位瘦身
    • iOS 崩溃千奇百怪如何全面监控
    • 如何利用 RunLoop 原理去监控卡顿
    • 临近 OOM,如何获取详细内存分配信息,分析内存问题
    • 日志监控:怎样获取 App 中的全量日志
    • 性能监控:衡量 App 质量的那把尺
    • 远超想象的多线程的那些坑
    • 怎么减少 App 电量消耗
    • 除了 Cocoa,iOS还可以用哪些 GUI 框架开发
    • 细说 iOS 响应式框架变迁,哪些思想可以为我所用
    • 如何构造酷炫的物理效果和过场动画效果
    • A/B 测试:验证决策效果的利器
    • 怎样构建底层的发布和订阅事件总线
    • 如何提高 JSON 解析的性能
    • 如何用 Flexbox 思路开发?跟自动布局比,Flexbox 好在哪
    • 怎么应对各种富文本表现需求
    • 如何在 iOS 中进行面向测试驱动开发和面向行为驱动开发
    • 如何制定一套适合自己团队的 iOS 编码规范
    • iOS 系统内核 XNU:App 如何加载
    • iOS 黑魔法 Runtime Method Swizzling 背后的原理
    • libffi:动态调用和定义 C 函数
    • iOS 是怎么管理内存的
    • 如何编写 Clang 插件
    • 打通前端与原生的桥梁:JavaScriptCore 能干哪些事情
    • React Native、Flutter 等,这些跨端方案怎么选
    • 原生布局转到前端布局,开发思路有哪些转变
    • iOS原生、大前端和Flutter分别是怎么渲染的
    • 剖析使 App 具有动态化和热更新能力的方案
  • Flutter
    • 0.Flutter学习笔记以及问题记录
    • 1.Dart基础快速入门
    • 2.什么是声明式UI
    • 3.Flutter入门基础知识
    • 4.项目结构、资源、依赖和本地化
    • 6.布局与列表
    • 7.状态管理
    • 8.路由与导航
    • 9.手势检测及触摸事件处理
    • 9.线程和异步UI
    • 10.主题和文字处理
    • 11.表单输入与富文本
    • 12.调用硬件、第三方服务以及平台交互、通知
    • 13.基于Http实现网络操作
    • 14.图片控件开发详解
    • 15.异步:Future与FutureBuilder实用技巧
    • 16.APP首页框架搭建-Scaffold与PageView
Powered by GitBook
On this page
  • 目前流行的GUI框架
  • GUI 框架里都有什么?
  • 渲染流程
  • Texture 里 Node 的异步绘制
  • 小结

Was this helpful?

  1. iOS 杂谈

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

Previous怎么减少 App 电量消耗Next细说 iOS 响应式框架变迁,哪些思想可以为我所用

Last updated 4 years ago

Was this helpful?

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

在“App 启动速度优化与监控”中,谈到如何实现方法级别的启动优化,从而实现整个 App 启动速度的优化。

通过这篇文章的内容你会发现,把可以优化方法的工作都处理完之后,比如主线程上的大量计算、IO 操作、懒加载(也叫作延时加载,即当对象需要用到的时候再去加载),就只剩下了GUI(Graphical User Interface 图形用户界面) 相关的方法。

在iOS开发时,默认使用的都是系统自带的 Cocoa Touch 框架,所以如果你还想进一步提高界面响应速度,赶超其他使用 Cocoa Touch框架的 App 用户体验时,就要考虑使用其他的 GUI 框架来优化 App 界面的响应速度了。

接下来,我们就一起聊聊除了 Cocoa Touch 框架外,还有哪些 GUI 框架可以用来做 iOS 开发。

目前流行的GUI框架

现在流行的 GUI 框架除了 Cocoa Touch 外,还有 WebKit、Flutter、Texture(原名 AsyncDisplayKit)、Blink、Android GUI 等。其中,WebKit、Flutter、Texture 可以用于 iOS 开发。接下来,我就和你说说这三款GUI框架。

WebKit 框架包含了 WebCore 和 JavaScriptCore,使用 HTML 和 CSS 进行布局,使用JavaScript 编写程序。WebKit 还提供了 Objective-C 应用程序编程接口,方便基于 Cocoa API 的应用使用。在iOS开发中,我们最常使用的UIWebView和WKWebView控件都是基于WebKit框架。

关于 WebKit框架,感兴趣的话,你可以去看一下。

Flutter 是 Google公司于2017年推出的一个移动应用开发的 GUI 框架,使用 Dart 语言编写程序,一套代码可以同时运行在iOS和Android平台。对Flutter 的相关介绍,我会在专栏后面的文章“React Native、Flutter 等跨端方案,应该怎么选?”和“iOS 原生、大前端和 Flutter 分别是怎么渲染的?”里,和你详细说明。

Texture框架的基本单元,是基于 UIView 抽象的节点 ASDisplayNode。和 UIView 不同的是 ,ASDisplayNode 是线程安全的,可以在后台线程上并行实例化和配置整个层级结构。Texture框架的开发语言,使用的是苹果公司自家的 Objective-C 和 Swift。

WebKit、Flutter、Texture这三个 GUI 框架,与Cocoa Touch的对比,如下图所示:

图1 WebKit、Flutter、Texture框架,与Cocoa Touch的对比

通过这个对比,我们可以发现,Texture框架和Cocoa Touch框架,在使用的编程语言、渲染这两个方面,是完全一样的。其实,Texture框架,正是建立在Cocoa Touch框架之上的。

我们再从这些框架使用的布局来看一下,Texture 和其他 GUI 框架一样都是使用的应用更加广泛的FlexBox布局。使用FlexBox 布局的好处是,可以让iOS开发者用到前端先进的W3C标准响应式布局。目前, FlexBox 已经是布局的趋势,连 iOS 新推出的 UIStackView 布局方式,也是按照 FlexBox 布局思路来设计的。

另外,Texture 是这些框架中唯一使用异步节点计算的框架。使用异步节点计算,可以提高主线程的响应速度。所以,Texture在节点计算上的效率要比其他框架高。

基于以上三个方面的原因,如果要从Cocoa Touch框架前移到其他的GUI框架,从学习成本、收益等角度考虑的话,转到Texture会是个不错的选择。

因此,我会和你重点分析一下Texture框架。因为现在的GUI技术已经非常成熟了,各种GUI框架的底层也大同小异,所以接下来我会先和你介绍GUI框架中的通用性内容,然后再与你讲述Texture的独特之处。

GUI 框架里都有什么?

GUI 框架的基本单元是控件,你熟悉的按钮、图片、文本框等等就是控件。

控件主要负责界面元素数据的存储和更新,这些原始数据都存储在控件的属性上,直接更新控件的属性就能够完成界面元素更新操作,控件的属性设置成不同的值会让界面元素呈现不同的外观。

控件之间的关系是由渲染树(Render Tree)这种抽象的树结构来记录的。渲染树关注的是界面的布局,控件在界面中的位置和大小都是由渲染树来确定。

基于渲染树,GUI 框架还会创建一个渲染层树(RenderLayer Tree),渲染层树由渲染层对象组成,根据 GUI 框架的优化条件来确定创建哪些渲染层对象,每次新创建一个渲染层对象就会去设置它的父对象和兄弟对象。渲染层对象创建完毕,接下来就需要将各渲染层对象里的控件按照渲染树布局生成 Bitmap,最后 GPU 就可以渲染 Bitmap 来让你看到界面了。

控件、渲染树、渲染层树之间的关系,如下图所示:

WebKit 和 Flutter 都是开源项目,我们可以通过它们的代码看到 GUI 框架具体是怎么实现控件、渲染树、渲染层树和生成 Bitmap 的。

WebKit 在 GUI 框架层面的效率并不低,单就渲染来说,它的性能一点也不弱于 Cocoa Touch 和 Flutter 框架。

  • 使用WebKit的网页显示慢,主要是由于 CSS(Cascading Style Sheet) 和 JavaScript 资源加载方式导致的。

  • 同时,解析时 HTML、CSS、JavaScript 需要兼容老版本,JavaScript 类型推断失败会重来,列表缺少重用机制等原因,导致WebKit框架的整体性能没有其他框架好。

这些年来,虽然 GUI 框架百家争鸣,但其渲染技术却一直很稳定。接下来,我就和你详细说说 GUI 框架中的渲染过程。

渲染流程

GUI 框架中的渲染,一般都会经过布局、渲染、合成这三个阶段。

由于 Frame 布局需要精确描述每个界面元素的具体位置和大小,无论从代码编写,还是从代码可读性上看,成本都会高很多。所以说,FlexBox 对于 iOS 开发来说是另外一种很好的选择。

渲染阶段的主要工作,是利用图形函数计算出界面的内容。一般情况下,对于 2D 平面的渲染都是使用CPU 计算,对3D空间的渲染会使用 GPU 计算。

Cocoa Touch 和 Texture 框架使用的是 Core Animation,3D 使用的是 Metal 引擎。Flutter 使用的是 Skia,3D 使用的是 OpenGL(ES)。

在渲染这方面,我觉得 WebKit 做得更出色,考虑到多平台支持,WebKit 将渲染接口抽象了出来,实现层根据平台进行区分,比如在 iOS 上就用 CoreGraphics 来渲染,在 Android 就用 Skia 渲染。

合成阶段的主要工作,是合并图层。这样做的目的,主要是节省显示内存,只显示一屏需要的像素,也只进行这些像素数据的计算。这个过程完成后,再将所得数据进行光栅化处理,最后交给 GPU 渲染成你可以看到的 Bitmap。

关于 WebKit、Cocoa Touch、Flutter框架渲染相关的内容,我会在后面“iOS 原生、大前端和 Flutter 分别是怎么渲染的?”的文章里和你详细说明。

通过上面的内容,我们可以看到,主流 GUI 框架的内容和渲染流程,区别并不是很大。

但 Texture 对于那些希望能够在用户交互体验上进行大幅提升的 iOS 开发者来说,很小的切换成本,同时性能能大幅提升的收益,其实是很有诱惑力的。

通过对GUI框架都包括什么和渲染流程的分析,再次印证了我们的观点:Texture是个值得推荐的框架,不仅在于它与Cocoa Touch框架的相似使得学习成本较低,还得益于它本身在性能上的先进性。

那么,接下来我就再跟你说说 Texture 最大的优势是什么?以及它是怎么做到的?

Texture 里 Node 的异步绘制

Texture 最大的优势就是开发了线程安全的 ASDisplayNode,而且还能够很好的和 UIView 共生。这样的话,我们就可以在原有使用 UIView 开发的程序基础之上使用 Texture,而不用完全重构所有界面。

随着 CPU 多核技术的发展,界面渲染计算都在主线程完成,对于多核 CPU 来说确实是有些浪费。ASDisplayNode 是 UIView 和 CALayer 的抽象,能实现不在主线程执行视图的布局绘制和层级计算,充分发挥多核 CPU 的优势。

首先,我们来看看 Texture 最核心的线程安全节点 ASDisplayNode 是做什么的?

在Cocoa Touch 框架里,当 CALayer 内容更新时会去找 CALayer 的 delegate,也就是 displayLayer: 方法。UIView 会实现 displayLayer: 这个 delegate 方法。UIView 里实现 drawRect: 这个 delegate 方法能够自定义 CALayer。

在 Texture 中,ASDisplayNode 替代了这个delegate,解耦了 UIView 和 CALayer,并将 UIView 包装成 ASDisplayView,将 CALayer 包装成 ASDisplayLayer 供外部使用。

然后,我们再来看看ASDisplayNode 是如何进行异步绘制的?

ASDisplayLayer 是整个绘制的起点,绘制事件先在 displayBlock 设置好,然后 ASDisplayNode 调用 displayBlock 来进行异步绘制。整个过程分为三步。

第一步,得到 displayBlock。这个 Block 里有需要绘制的内容,对应的代码如下:

asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];

其中,displayBlock 就是需要绘制的内容。

第二步,定义一个回调 completionBlock ,绘制完成后执行。代码如下:

asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){  ASDisplayNodeCAssertMainThread();  if (!canceled && !isCancelledBlock()) {    // displayBlock 执行的是绘图操作,返回的类型是 UIImage    UIImage *image = (UIImage *)value;    BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero));    if (stretchable) {      ASDisplayNodeSetResizableContents(layer, image);    } else {      layer.contentsScale = self.contentsScale;      // 设置为 CALayer 的寄宿图      layer.contents = (id)image.CGImage;    }    [self didDisplayAsyncLayer:self.asyncLayer];        if (rasterizesSubtree) {      ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {        [node didDisplayAsyncLayer:node.asyncLayer];      });    }  }};

上面代码中,completionBlock 里就是绘制完成后需要去执行的事情,内容是完成 CALayer 寄宿图的设置。 value 是 displayBlock 返回的值,类型是 UIImage。displayBlock 用的是线程安全的 Core Graphics,所以你可以安心地把 displayBlock 放到后台线程去异步执行。

第三步,如果设置为异步展示,就先向上找到属性 asyncdisplaykit_parentTransactionContainer 为 YES 的 CALayer,获取 containerLayer 的 ASAsyncTransaction,然后将 displayBlock 添加到 ASAsyncTransaction 的调度队列里,根据 drawingPriority 优先级执行displayBlock。具体代码如下:

if (asynchronously) {  // 向上找到属性 asyncdisplaykit_parentTransactionContainer 为 YES 的 CALayer  CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer;    // 获取 containerLayer 的 ASAsyncTransaction  _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;    // 将 displayBlock 添加到 ASAsyncTransaction 的调度队列里  [transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];} else {  // 设置为不是异步就直接调用 displayBlock 进行绘制  UIImage *contents = (UIImage *)displayBlock();  completionBlock(contents, NO);}

可以看到,displayBlock 被添加到 ASAsyncTransaction 里进行调度。这里,ASAsyncTransactionQueue 是 Texture 的调度队列。

通过上面这三步,Texture就完成了 ASDisplayNode 的异步绘制。

小结

通过今天的介绍,你会发现在选择GUI 框架时,渲染方面的区别并不大,而且渲染技术相对比较成熟。所以,我们需要在框架的易用性,以及与现有工程的兼容上做更多的考虑。

如果你想提高 App 的使用体验,让界面操作更流畅的话,我推荐你使用Texture。Texture 易用性和兼容性都很好,同时 Texture 的学习成本与收益比也是最高的。而且,Texture 代码本身的质量很高,有很多值得学习的地方。

图2 控件、渲染树、渲染层树之间的关系

开始的时候,Flutter 也是基于 Chrome 浏览器引擎的。后来,谷歌公司考虑到Flutter的性能,所以去掉了HTML、CSS、JavaScript 的支持,而改用自家的Dart 语言以甩掉历史包袱。关于这方面的细节,你可以查看来了解 。

布局阶段要完成的主要工作,是依据渲染树计算出控件的大小和位置。WebKit 用 CSS 来布局,CSS 会提供 Frame 布局和 FlexBox 布局;Flutter 也支持 Frame 布局和 FlexBox 布局;Cocoa Touch 框架本身不支持 FlexBox 布局,但是通过 Facebook 的 也能够使用 FlexBox 布局。

Flutter 创始人 Eric 的采访视频
Yoga 库