What’s New In Cocoa Touch

性能优化

滚动

列表在滚动时当 Cell 第一次加载新视图的第一帧会比之后的复用消耗多点的 CPU。

为什么第一帧会比其他帧更消耗 CPU?

// UITableView Cell Load Cost

import UIKit

class MyTableViewDataSource {

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Dequeue or allocate cell
        // Populate cell with model data
    
        return myCell
    }

}

// call layoutSubviews on UIViews in the cell
// call draw() on UIViews in the cell

因为在首次加载 cell 的时候通常会做以下工作:

  • 出列或为新 cell 分配内存。
  • 为 cell 填充数据内容。(比如从数据库加载内容,编解码图片等)
  • 布局 cell 子视图。(比如调整 frame 等)。

什么指标才能保证界面的流畅滚动?

我们的设备通常是 60Hz 屏幕,这种情况下必须在 16ms 内处理完所有的事情,不然会丢帧。 在 120Hz 的 iPad Pro 上只有 8ms。

好消息是从 iOS 10 开始引入了新的预读取 API,它可以在后台线程来执行一些消耗性能的任务,比如模型数据绑定、数据库读取文件等。这些操作是提前执行的,所以当你即将显示新 cell 的时候,它已就绪。

// UITableView Pre-Fetching

protocol UITableViewDataSourcePrefetching {

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

}

在大多数情况下这个 API 很有帮助,但还是会有些问题。

第一种情况:

深浅颜色表示交替显示不同帧,同一种颜色表示显示同一帧,红色表示出现问题帧的消耗时长。

出现问题的帧加载时间超过了最大时间 16ms,导致需要两倍的时间,所以出现了丢帧。为什么会出现这个现象?因为在加载当前 cell 的时候同时在计算下一个需要显示的帧,导致过度消耗资源出现了冲突。

iOS 12 中优化了这个冲突,现在会正确的顺序执行而不会是之前的同步执行导致冲突。

第二种情况:

在某些情况下 CPU 没有后台任务时会比有少量后台任务时的滚动页面更容易出现丢帧。

因为在没有高消耗操作时的单纯滚动页面,Apple 会把 CPU 控制在低性能状态来保证不过度消耗电量来延长电池寿命。当下一帧需要进行复杂计算的时候再把 CPU 调度到高性能模式的话已经来不及了,所以出现了丢帧。


iOS 12 中会把 UIKit 中滚动发生临界区域的情况向下传递到低级别 CPU 性能控制器中来预测峰值性能计算何时出现。这样就保证了尽可能快速的切换到 CPU 高性能模式来加载 cell。

内存

App 使用的内存越多性能消耗就越大。

因为如果你的一个操作只会消耗小部分内存并且系统空余内存能够满足你的需求那不会有什么影响。但如果你的需求需要消耗的内存大于系统能提供的内存并且需要长时间占用这块内存时,系统就会从其他 App 里获取你所需的内存,这个步骤就会要求系统来执行一些调度操作,这个操作会消耗一些时间。

iOS 12 增加了 Automatic Backing Store 技术来优化上诉需求。

上面的两张图片在以前的 iOS 中 iPhone X 的肖像模式中都会占用 2.2M 的内存,右边的只是简单的黑白素描图片也会占用同等大小的内存,显然这是不合理的。

在 iOS 12 中 CoreGraphics 会根据内容来处理,通过 Backing Store 技术低深度图像会使用 8 bpp 而不是高保真图像的 64 bpp。这样上图的黑白图只需要 275 K 内存。

自动布局

iOS 12 中优化了子视图的布局约束相互依赖的性能,从指数级增加优化为线性增加。

相互嵌套的视图也优化成了线性。

Framework 更新

全局类型和常量变成了嵌套类型和常量

比如:

// Swift 4

let UIFloatRangeZero: UIFloatRange
let UIFloatRangeInfinite: UIFloatRange

// Swift 4.2

struct UIFloatRange {
	static let zero: UIFloatRange
	static let infinite: UIFloatRange
}

// Swift 4

class NSNotification : NSObject {

	struct Name {
		class let didChangeStatusBarOrientation: NSNotification.Name
	}

}

let UIApplicationStatusBarOrientationUserInfoKey: String

// Swift 4.2

class UIApplication : UIResponder {

    class let didChangeStatusBarOrientationNotification: NSNotification.Name
    class let statusBarOrientationUserInfoKey: String

}
  • Swift 4.2 中所有的类型都可以 JSON 编解码。

原深感摄像头表情包 API

// New Info.plist key

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

var presentationContext: MSMessagesAppPresentationContext

enum MSMessagesAppPresentationContext : UInt {
	case messages
	case media

}

新增了上面的 plist 和 API 来申明保存表情包时的当前语境,比如是在信息还是相机。

自动填充密码

iOS 12 新增了注册流程自动填充密码的支持。可以根据业务额外的控制密码不支持的字符、长度等。