WWDC 2018:Cocoa Touch新特性与改进

5,227 阅读12分钟

今年的Session 202 - What's New in Cocoa Touch是这样的逻辑:

  • UIKit旧有功能的性能优化
    • 滚动新增数据预获取功能
    • 降低图片渲染不必要的内存开销
    • 优化Auto Layout计算的时间复杂度
  • UIKit旧有功能的API调整
    • 框架中旧的全局变量、常量以及函数被嵌套进对象,使得它们看上去更Swift
    • 淘汰旧版本的编解码API,新增更安全的编解码API
  • UIKit的新功能
    • Notification的交互、分组、设置
    • Messages的动态贴纸、iPhone X下Home Bar的交互响应
    • 密码的自动生成和填充
    • Siri Shortcut

UIKit的性能提升


  • UIKit通过“数据预加载”和“CPU算力调整”来做了滚动优化,这两项优化对于UITableView和UICollectionView甚至你自己定制的UIScrollView都有效。
  • UIKit针对图片渲染相关API的优化了内存开销
  • UIKit优化了Auto Layout运算的开销

“数据预加载”的背后是一个这样的故事:

我们举UITabelView为例子,一次完整的Cell加载过程是这样的:

  1. 在UITableView的DataSource的- tableView:cellForRowAt:方法中,工程师将Cell实例从缓存队列中取出,或直接初始化一个新cell
  2. 工程师将数据取出,然后赋值给Cell实例
  3. UIKit会调用layoutSubviews给cell的子view作布局
  4. UIKit将cell绘制出来并展示

只有一帧之内做完这4件事情,UITableView滚动的时候才不会卡。

在以上4步中,第2步开销大小是取决于不同场合不同业务逻辑的。因为有的时候你用于展示这个Cell的数据可能已经预先整理过,直接拿来就可用,不会有什么大的开销。但有的时候你用于展示这个Cell的数据是来自于数据库、或者本地文件、或者其它地方的,那么就会多一个从其他地方取数据整理数据的过程,那么就会增加一笔额外开销,这一笔开销就很有可能导致掉帧卡顿。

如上图所示,在之前UIKit的实现中,预先取数据这个行为本身也是有的,而且也是异步的。但由于取数据的时机是跟加载Cell的时机是同时开始的,所以CPU又要预先取数据,也要加载、渲染Cell。这就导致了CPU在一帧的时间内同时做两件事,就有可能导致掉帧。

而这次的优化,就是把预先取数据的行为放在了Cell渲染完成之后。这样就可以在一帧刚开始的时候,将CPU从数据预加载的事务中解放出来,全力去做Cell相关的事情,这样就能够尽早将Cell渲染完交差。然后CPU再进行数据异步预加载的事情。

在这一轮优化中,UIKit针对数据预加载提供了新的Protocol。拿UITableView来说,就提供了UITableViewDataSourcePrefetching,这个Protocol中包含两个方法:

func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
func tableView(_ tableView: UITableView,
cancelPrefetchingForRowsAt: indexPaths [IndexPath])
- (void)tableView:prefetchRowsAt:
- (void)tableview:cancelPrefetchingForRowsAt:

实现这个protocol时,只有prefetchRowsAt这个方法是必须的。cancelPrefetchingForRowsAt这个会由系统调用,比如当你cell的数据是需要下载才能得到的,那么结合断点续传功能和这个protocol,你就能很优雅地处理cell数据加载的事情。


“算力调整”的背后是这样的故事:

苹果工程师在不断地做性能检测的时候,又发现了一个这样的情况:如果用户之前在看一些加载开销比较小的Cell,然后用户突然滚动到加载开销比较大的Cell时,UIKit依旧会出现掉帧的卡顿现象。而且此时系统本身也没什么负载,也没有什么开销大的后台任务在运行,因此就排除了CPU负载过大导致卡顿这一可能。

那为什么CPU没什么负载,却依旧出现了卡顿呢?他们研究了一轮,发现原来这是因为CPU性能调度逻辑导致的,这段过程是这样:

  1. 当用户之前一直在看加载开销小的Cell的时候,其本身需要的CPU性能并不高,那么此时CPU性能调度管理器就更加倾向于保持低性能运行,这样能够省电。
  2. 当用户突然划到一个加载开销大的Cell的时候,CPU性能调度管理器也并不知道你这时候要加载一个开销那么大的Cell,于是它就还是在低性能的状态下去加载这个开销比较大的Cell。
  3. 当CPU性能调度器反应过来这个Cell原来需要那么大的开销的时候,它就会切到高性能去消化这个负载。不过往往这时候再提高CPU性能就已经晚了,就有很大几率会导致卡顿。

这个过程往往会发生在Feed流类似的应用中。比如你之前一直在看的Cell都是文字内容的,开销极小。突然你划到视频Cell了,这个cell开销很大,CPU性能调度管理器在Cell加载到一半了才去提高性能等级,那就很容易出现卡顿的情况了。

找到原因之后,优化的事情就好做了。现在苹果工程师打通了整个流程,使得UIKit的信息能够传递到CPU性能调度管理器。当UIKit在加载下一个Cell的时候,如果发现这是一个加载开销比较大的Cell,UIKit就会去通知CPU性能调度器,告诉调度器赶紧提高性能。这样一来,因为CPU性能已经提前提上来了,那么系统就能最大可能避免掉帧卡顿的情况发生。

也就是说,之前CPU的调度情况是这样:

优化之后,CPU的调度就变成了这样:

因为UIKit预测到了下一个Cell开销比较大,所以提前跟CPU性能调度器打了招呼,于是CPU的性能就立刻提上来了。相比之前CPU性能慢慢往上提的情况,就更不容易出现卡顿。


图片渲染相关API的内存优化

演讲者先说了一遍内存是如何影响UI性能的,这一段其实可以说得很简单:当系统剩余内存不多,而此时你的应用又需要很大的一片内存时,系统就要通过回收内存或强行关闭后台App的方式来为你的应用腾出内存来。系统为你腾内存这一操作的开销导致了你的App此时需要更长时间的等待,进而影响了你App的UI性能。

为了缓解这个问题,iOS 12引入了Automatic Backing Store这项技术。这项技术主要是针对画图类的App起作用,对于其他的场合无效。本质上的实现就是通过在保证色彩不失真的基础上,使用更少的数据量,去表达一个像素的颜色。

如下图所示,在64位色的情况下,左右两张图片的数据量是一样的,都是2.2M左右。虽然右边只有黑白两色,但是依旧使用了64位去存储每个像素的颜色色值。

所以呢,苹果就想了个办法,说既然右边这张图片只有黑白两色,那我干嘛还要用64位去表达一个颜色呢?8位就足够了啊。于是,右边这个图每像素的颜色用8位来表达之后,就变成只有275K了,于是内存就省了:

那为什么不直接用1位呢?反正也就黑白两色。对不起,因为色表标准最小就是8位的,如果你用了1位色,屏幕是不支持这套标准的。Automatic Backing Store对左边这幅图无效,因为它不是图片压缩算法,它只是在你用画笔画完图片并且渲染的时候,把原来需要64位去存储的单个像素的色值,改成用8位去存储而已,这个过程是有可能产生一定的色彩失真的。所以Automatic Backing Store只适合画图类应用,在以下3个场景中,它是默认开启的:

  • UIView.draw()
  • UIGraphicsImageRenderer
  • UIGraphicsImageRenderer.Range

有关Automatic Backing Store的内容,还可以看Session 219 - Image and Graphics Best Practices


Auto Layout计算的优化

iOS 12中,Auto Layout做了非常大的优化。

当一个View包含了N个布局上互相独立的子View时,Auto Layout的计算耗时是随着子view数量的增加而线性增加的。因为苹果工程师们的努力,iOS 12的计算耗时比iOS 11少了一些,如下图:

图A

当一个View包含了N个子View,且这N个子View的布局存在依赖的情况时。iOS 11下Auto Layout的计算耗时是随着子View的数量增加而指数性增长的。在iOS 12优化之后,计算耗时呈线性增长,这可以说是一个比较大的优化了。(但我一直是认为其实这本身就应该是一个线性时间内可以完成的任务,iOS 11的时候工程师怎么把它实现成指数性增长的了?这个工程师该杀),如下图:

当N个View相互嵌套时,iOS 11下Auto Layout的计算耗时是随着子View的数量增加而指数性增长的。在iOS 12优化之后,计算耗时呈线性增长。我还是觉得这本身也是一个线性时间内可以完成的任务,当初在iOS 11把它实现成指数性增长任务的工程师该杀。如下图:

更多的内容可以看Session 220 - High Performance Auto Layout


UIKit旧有API的修改


  • 更Swift的UIKit
  • 更安全的编解码

更Swift的UIKit

在Swift 4.2中,有些全局的Type被挪进了对象中,包括但不限于:

还有些全局常量也被挪进了有关对象中,包括但不限于:

还有些全局函数也被挪近了有关对象中,包括但不限于:

这么一来,UIKit看起来确实更Swift了,只是升级4.2的时候,又要面对一堆编译错误了。


更安全的编解码

演讲者只是在这里提了一句,iOS 12提供了更安全的编解码API,旧的API将会被淘汰。具体是什么情况要去看Session 222 - Data You Can Trust


UIKit 新功能


  • Notification
  • Messages
  • Automatic Passwords and Security Code AutoFill
  • Siri Shortcuts

Notification API修改

  • Interaction

    • iOS 12加强了针对Notification的交互,大部分界面开发者可以自由定制了。
  • Grouping

    • iOS 12会将众多Notification分组并展示,并且提供了API让开发者在一定程度上决定如何进行分组。
  • Settings

    • iOS 12向开发者提供了接口,使得开发者能够让用户直接开启或关闭通知。

以上三点演讲者只是一带而过,更多内容请看:

Session 710 - What's New in User Notifications

Session 711 - Using Grouped Notifications


Messages

iOS 12中,你可以将Message的人脸识别贴纸带入camera中。如果你需要针对这些做更多的定制的话,你需要在Info.Plist中添加以下内容,MSMessagesAppPresentationContextMedia其实就是指的camera:

<key>MSSupportedPresentationContexts</key>
<array>
    <string>MSMessagesAppPresentationContextMessages</string>
    <string>MSMessagesAppPresentationContextMedia</string>
</array>

而且你也可以根据以下API查看一条Message是不是动态的:


var presentationContext: MSMessagesAppPresentationContext

enum MSMessagesAppPresentationContext : UInt {
 case messages
 case media
}

iOS 12也给Message添加了新的交互操作:之前在底部bar处水平滑动,系统会自动切换App。现在你的Message App可以接收这个事件,可以选择覆盖系统切换App的行为,或者执行你自己的行为。


Automatic Passwords and Security Code AutoFill

之前的iOS版本中,已经实现了自动输入密码的功能:

在iOS 12中,工程师可以在修改密码的地方也弹出提示,告知用户是否需要将密码保存在iCloud Keychain:

iOS 12还能够帮你自动生成密码,在注册流程和修改密码流程都能用,只要开发者给Password的输入框打上tag告知这是一个密码输入框就好了:

在输入手机号,发送验证码,输入验证码流程中,iOS 12在键盘上提供了读取验证码的功能,这样能够使得用户不必切来切去:

以上这些内容演讲者也都只是稍微提了一下,具体内容可以看这里:Session 204 - Automatic Strong Passwords and Security Code AutoFill


Safe Area

演讲者把之前就已经有的SafeArea Insets讲了一遍,没有提到任何新内容。只是说希望大家能够尽早使用Safe Area Insets去做不同设备的UI适配。

最后给到了一个Session:Session 235 - UIKit: Apps for Every Size and Shape,根据演讲者的说法来看,这个Session里面可能有新内容,大家若是有兴趣的话可以看一看。


Siri Shortcuts

Siri Shortcurs能够使得你的App响应Siri的调度。具体的做法是响应来自各个App的Intents。当然,你也可以给自己的App设定Intents。每一个Intents都有不同的类型:

Siri会根据Intent找到对应的App,然后再解析用户给出的指令,来决定本次操作是哪种类型,进而开发者给到对应的响应操作。

具体的情况还是要看以下这些Session:

Session 211 - Introduction to Siri Shortcuts

Session 214 - Building for Voice with Siri Shortcuts

Session 217 - Siri Shortcuts on the Siri Watch Face


以上就是WWDC 2018 - Session 202 - What's New in Cocoa Touch的解说