高效能 iOS 应用理论 - 第一部分
高效能编码实践
引言
我们的应用程序应该节能。对于电池续航不佳的用户来说,这是一个不可避免的标准。如果一个应用程序消耗的能量更多,用户甚至可能准备删除该应用程序。我们知道设备中的几乎所有进程都会消耗能量。在本文中,我们将探讨一些节能理论。
能耗
了解耗电量主要集中在哪里很重要。
- CPU - 几乎所有进程都使用 CPU。因此,这里需要有效利用 CPU。这只能通过对批量处理、优先级排序和调度等任务进行更好的优化来实现。
- 设备处于唤醒状态 – 我们知道当设备处于睡眠模式时,它消耗的能量较少。一旦进入唤醒模式,它的绝大多数部件都会开始供电。请确保您的应用程序让设备进入睡眠状态。同样,除非万不得已,否则不要唤醒设备。
- 网络操作 – 当您应用程序中的网络层开始运行时,应用程序会消耗更多电量。在这里,我们需要做出重大贡献来减少功耗。
- 图形 – 每一个屏幕更新都需要额外的能量。标准的窗口和控件已经设计成可以高效地使用能量。但当涉及到自定义窗口和自定义控件时,请确保您的内容在有新数据可用之前不会被重新渲染。尽量避免用相同的内容刷新 UI 进程,以及在内容视图不可见时刷新内容。
- 位置 – 如果应用程序持续跟踪用户的物理位置,并尝试以高精度访问位置数据,则会导致电池电量使用量很高。因此,在使用位置 API 时,需要有智能的实施方法和更好的规划。
节能编码
在本文中,我们将重点关注我们可以应用优化以实现高效电池使用的一些主要领域。
进程
后台执行
让我们首先看看可能导致不必要电池消耗的活动
- 未能向系统通知后台活动已完成。这可能导致设备保持活动状态并消耗能量
- 播放静音音频
- 在应用程序位于后台时更新位置
- 可以推迟的后台下载或上传
当应用程序进入后台时,这里有一些节能编码实践
- 当应用程序调用
applicationWillResignActive
时,我们应该允许应用程序暂停或停止进程、保存数据或停止任何 UI 更新。在应用程序因来电、短信或用户切换到另一个应用程序而进入非活动状态之前,会调用此委托方法。func applicationWillResignActive(_ application: UIApplication) { // Do all the closure activities here }
- 如果应用程序在挂起之前需要更多时间来完成任务,可以使用一个名为
beginBackgroundTaskWithExpirationHandler
的 API 来提供额外的执行时间。任务完成后,我们必须调用endBackgroundTask
: 来通知系统任务已完成。func applicationDidEnterBackground(_ application: UIApplication) { var bgTask = UIApplication.shared.beginBackgroundTask { // This closure gets called before the background remaining time reaches to zero. // Do all the cleanup activities before being suspended. } DispatchQueue.global().async { // Do the task here // once the task is completed, call End Task. System will call the closure // of beginBackgroundTask before being suspended UIApplication.shared.endBackgroundTask(bgTask) bgTask=UIBackgroundTaskInvalid } }
- 后台应用程序刷新 API 比定时器更适合更新您的内容。
定时器
我们需要尽可能避免使用定时器。如果 UI 更新可以在用户交互时或新内容可用时进行,那将是一个好习惯。如果定时器执行对您的应用程序场景是强制性的,那么请增加间隔。
根据服务质量 (QoS) 优先处理工作
如果应用程序可以通过应用并行和并发来有效地利用多个核心、内存等。那么它不仅响应更灵敏,而且能耗效率也更高。
当应用程序执行操作和队列时,我们可以指定 QoS。这将允许系统调整工作的优先级,以便进行调度、CPU 和 I/O 分配以及计时器延迟。
以下是主要的 QoS
NSQualityOfServiceUserInteractive
NSQualityOfServiceUserInitiated
NSQualityOfServiceUtility
NSQualityOfServiceBackground
这里有一个如何创建具有 QoS 优先级的 workItem
的代码示例。
let userInitiatedWorkItem = DispatchWorkItem(qos: .userInitiated, flags: .assignCurrentContext) {
// execute appropriate task here
}
DispatchQueue.main.async(execute: userInitiatedWorkItem)
let bgWorkItem = DispatchWorkItem(qos: .background, flags: .assignCurrentContext) {
// execute appropriate task here
}
DispatchQueue.main.async(execute: bgWorkItem)
网络
- 利用缓存数据 – 应用程序不应一遍又一遍地获取相同的数据。这会导致额外的成本和额外的能量消耗。在内容更改或用户交互时下载数据。
- 定义可暂停和可恢复的网络加载 –
NSURLSession
允许开发人员在事务中断时暂停或恢复事务。这将帮助应用程序不重复上传相同的内容。 - 如果可能,传输压缩数据。
- 如果网络类在网络环境差或频繁断网的环境中持续尝试网络加载,或者用户将
allowsCellularAccess
设置为false
。从 iOS 11 开始,我们有了一个新的 API,名为waitsForConnectivity
,还有一个全新的委托,名为URLSession:taskIsWaitingForConnectivity
。如果将waitsForConnectivity
设置为true
,并且无法建立适当的连接,那么将调用委托方法URLSession:taskIsWaitingForConnectivity
,我们可以在其中决定是更新 UI、显示离线消息,还是使用缓存数据更新 UI,然后会话将等待连接。一旦连接可用,会话将继续连接服务器。waitsForConnectivity
只在建立连接的第一次有效,一旦连接可用然后又在数据获取过程中断开,完成处理程序或委托将通知失败条件。let sessionConfig = URLSessionConfiguration.default sessionConfig.waitsForConnectivity = true
- 尝试使用延迟网络 – 我们可以使用延迟网络概念来执行优先级较低的网络事务。这样系统就能找到一个合适的时间来执行任务。可能是在设备充电时或在使用 Wi-Fi 时。优化的方法是将所有优先级较低的事务进行批量处理,创建一个后台会话,并尝试一次性执行。为了延迟网络加载,我们需要设置
NSURLSessionConfiguration.discretionary = true
。let config = URLSessionConfiguration.background(withIdentifier: "com.example.testApp.background") config.isDiscretionary = true
Location
- 高位置精度会导致更多的能量消耗。
- 使用
CLLocationManager.requestLocation()
获取当前位置,而不是使用CLLocationManager.startUpdatingLocation
和CLLocationManager.stopUpdatingLocation
。 - 一旦用户到达目的地或完成任务,立即停止使用位置服务。
- 使用
CLCircularRegion
在用户进入特定区域时收到警报,而不是使用startUpdatingLocation
和stopUpdatingLocation
来跟踪位置。
分析
大多数应用程序会集成分析来了解用户如何使用应用程序。为了实现最佳的节能编码实践,我们可以将分析网络请求移到后台会话。它可以找到上传的最佳时间,并且在失败时系统会重试。为了帮助系统找到执行任务的最佳时间,我们可以设置新的属性开始时间和工作负载大小。
结论
当设备空闲时,消耗的电量较少。当应用程序运行时,设备会比平时消耗额外的能量。从用户的角度来看,应用程序需要对设备消耗的额外能量和固定能量负责。因此,节能编码是为用户提供出色体验的必备条件。有关上述要点的更多详细信息,请参阅 Apple 的节能编码实践。在本文的第二部分,我们将详细讨论节能工具。