减少并优先安排工作
在后台少工作
当用户不在使用你的应用时,系统会将其置于后台状态。如果应用程序没有执行重要的工作,例如完成用户启动的任务或以特别声明的后台执行模式运行,则系统最终可能会挂起应用程序。
你的应用不应等待系统挂起。一旦通知该国已发生变化,它应立即开始停止活动。当您的应用程序完成任何剩余任务时,它应通知系统后台活动已完成。否则会导致应用程序保持活动状态,并不必要地消耗能量。
您可以使用Energy organizer查看由从TestFlight用户自动收集的日志信息生成的崩溃和能量报告,并获得应用程序应用商店版本用户的许可。要了解更多信息,请参见能量管理器。
后台应用程序浪费能量的常见原因
应用程序执行不必要的后台活动浪费能源。以下是后台应用程序中能量浪费的一些常见原因:
- 后台活动完成时不通知系统
- 播放无声音频
- 执行位置更新
- 与蓝牙附件交互
- 可以推迟的下载
当应用程序处于非活动状态或移到后台时暂停活动
在应用程序委派中实现UIApplicationDelegate方法,以便在应用程序处于非活动状态或从前台转换到后台时接收调用并挂起活动。
applicationWillResignActive:
applicationWillResignActive:方法在应用程序进入非活动状态时调用,例如当电话或文本消息进入,或者用户切换到另一个应用程序并且您的应用程序开始转换到后台状态时。这是暂停活动、保存数据和为可能的暂停做准备的好地方。请参见清单3-1。
清单3-1应用程序变为非活动状态时的响应
-(void)applicationWillResignActive:(UIApplication *)application {
//停止操作、动画和用户界面更新
}
你的应用不应该依赖状态转换来保存数据。它应该在整个应用程序生命周期的适当位置保存数据,以防止数据丢失。
applicationDidEnterBackground
pplicationIdentiterBackground:方法在应用程序进入后台状态后立即调用。立即停止任何操作、动画和UI更新。见清单3-2应用程序转换为后台应用程序时的响应
-(void)applicationDidEnterBackground:(UIApplication *)application {
//立即停止操作、动画和UI更新
}
iOS只允许运行applicationidenterbackground方法几秒钟。如果你的应用需要更多的时间来完成用户启动的重要任务,它应该请求更多的后台执行时间,系统允许在请求时多用几分钟时间。调用beginBackgroundTaskWithExpirationHandler:方法并向其传递一个处理程序,如果多余的时间用完,将调用该处理程序。接下来,在调度队列或辅助线程上运行剩余的任务。
后台任务完成后,调用endBackgroundTask:方法让系统知道处理完成。如果您不调用此方法,并且后台执行时间耗尽,那么将调用完成处理程序,以给您最后一次包装东西的机会。之后,你的应用程序将被挂起。请参见清单3-3。
// 请求额外的后台执行时间
UIBackgroundTaskIdentifier bgTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// 时间用完时要执行的完成处理程序
}];
// 启动后台任务
// 后台任务完成后通知系统
[[UIApplication sharedApplication] endBackgroundTask:bgTaskID];
应用程序完成后台任务后,不要等待系统调用过期处理程序。调用endBackgroundTask:一旦你的应用完成执行后台任务。
应用程序激活时恢复活动
在应用程序委派中实现UIApplicationDelegate方法,以便在应用程序再次处于活动状态时接收调用并恢复活动
applicationWillEnterForeground
在应用程序从后台应用程序过渡到活动应用程序之前,将立即调用applicationWillEnterForeground:方法。开始恢复操作、加载数据、重新初始化UI,并为用户准备好应用程序。见清单3-4在应用程序转换到前台之前立即响应
- (void)applicationWillEnterForeground:(UIApplication *)application {
// 准备恢复操作、动画和用户界面更新
}
applicationDidBecomeActive
applicationDidBecomeActive:方法在应用程序成为活动应用程序、由系统启动或从后台或非活动状态转换后立即调用。完全恢复所有已停止的操作。见清单3-5。
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 恢复操作、动画和用户界面更新
}
解决失控的后台应用程序崩溃
iOS使用了一个CPU监视器来监视后台应用程序是否有过多的CPU使用,如果它们超出了某些限制,iOS就会终止它们。大多数执行正常后台活动的应用程序都不应该遇到这种情况。但是,如果应用程序达到限制并被终止,则崩溃日志将指示终止的原因。指定了EXC_RESOURCE的异常类型和CPU_FATAL的子类型,以及指示超出限制的消息。见清单3-6。
清单3-6过量CPU使用崩溃日志条目的示例
异常类型:EXC_RESOURCE
异常子类型:CPU_致命
异常消息:(限制80%)观察到89%超过60秒
日志还包括一个堆栈跟踪,它允许您确定应用程序在终止前正在执行的操作。通过分析堆栈跟踪,可以识别失控代码的位置并解决它。
CPU监视器在iOS8及更高版本中可用
优先考虑服务质量类
应用程序和操作竞争使用有限的资源CPU、内存、网络接口等。为了保持响应和效率,系统需要对任务进行优先级排序,并对何时执行这些任务做出智能决策。
直接影响用户的工作(如UI更新)非常重要,并且优先于后台可能发生的其他工作。这种更高优先级的工作通常会消耗更多的能源,因为它可能需要大量和即时的系统资源。
作为开发人员,您可以根据重要性对应用程序的工作进行分类,从而帮助系统更有效地确定优先级。即使您已经实施了其他效率度量,例如将工作推迟到最佳时间,系统仍然需要执行某种级别的优先级排序。因此,对应用程序执行的工作进行分类仍然很重要。
关于服务质量类
服务质量(QoS)类允许您对要由NSOperation、NSOperationQueue、NShread对象、调度队列和PThread(POSIX线程)执行的工作进行分类。通过将QoS分配给工作,您可以指出它的重要性,系统会对其进行优先级排序并相应地进行调度。例如,系统执行由用户启动的工作比后台工作要快,后台工作可以推迟到一个更理想的时间。在某些情况下,系统资源可能会从较低优先级的工作重新分配给较高优先级的工作。
因为高优先级的工作比低优先级的工作执行得更快,资源也更多,所以它通常比低优先级的工作需要更多的能量。准确地为应用程序执行的工作指定适当的QoS类可以确保应用程序响应速度快且节能。
选择服务质量等级
系统使用QoS信息来调整优先级,如调度、CPU和I/O吞吐量以及计时器延迟。因此,所做的工作保持了性能和能源效率之间的平衡。
将QoS分配给任务时,请考虑它如何影响用户以及它如何影响其他工作。如表4
表4-1主要QoS等级(按优先级排列)
QoS类 | 工作类型和服务质量关注点 | 工作期限 |
---|---|---|
用户交互 | 与用户交互的工作,例如在主线程上操作、刷新用户界面或执行动画。如果工作不能很快完成,用户界面可能会冻结。专注于响应和性能。 | 工作几乎是瞬间的 |
用户发起 | 用户已启动并需要立即结果的工作,例如打开保存的文档或在用户单击用户界面中的某个内容时执行操作。这项工作是为了继续用户交互所必需的。专注于响应和性能 | 工作几乎是瞬间的,比如几秒钟或更短的时间。 |
公用事业 | 可能需要一些时间才能完成且不需要立即结果的工作,如下载或导入数据。实用程序任务通常有一个用户可见的进度条。专注于在响应能力、性能和能效之间提供平衡。 | 工作需要几秒钟到几分钟 |
后台 | 在后台操作且用户看不到的工作,如索引、同步和备份。关注能源效率。 | 工作需要很长的时间,如几分钟或几小时。 |
最理想的情况是,在没有用户活动的情况下,至少90%的时间以QoS级别的实用程序运行应用程序。
在iphone上,当启用低功耗模式时,自主操作和后台操作(包括网络)将暂停。请参见对iPhone低功耗模式的反应。
特殊服务质量等级
除了主要的QoS类之外,还有两种特殊的QoS类型(如表4-2所述)。在大多数情况下,您不会接触到这些类,但是知道它们的存在还是有价值的。
如果应用程序使用操作和队列执行工作,则可以为该工作指定QoS。NSOperation和NSOperationQueue都具有类型为NSQualityOfService的qualityOfService属性,该属性可以设置为以下值之一:
- NSQualityOfServiceUserInteractive
- NSQualityOfServiceUserInitiated
- NSQualityOfServiceUtility
- NSQualityOfServiceBackground
清单4-1展示了如何设置操作的QoS。确定一项业务的服务质量NSOperation类的默认QoS为NSQualityOfServiceBackground。
NSOperation *myOperation = [[NSOperation alloc] init];
myOperation.qualityOfService = NSQualityOfServiceUtility;
服务质量推断与提升
注意,QoS不是操作和队列的静态设置,并且可能随时间而波动,具体取决于各种标准。例如,可能出现以下情况:操作的QoS与队列的QoS不匹配、操作与从属操作不匹配或操作未分配QoS。在这些场景中,可以推断QoS。
许多规则控制QoS推断和提升在队列(见表4-3)和操作(见表4-4)方面的发生方式。
NSOperationQueue QoS推理与提升规则
形势 | 结果 |
---|---|
队列没有分配QoS,并且将具有QoS的操作添加到队列中。 | 队列及其其他操作(如果有)不受影响。 |
队列分配了QoS,并将具有QoS的操作添加到队列中。 | 如果新操作的QoS较高,则会提升队列的QoS。队列中任何具有较低QoS的操作也会被提升。将来添加到队列中的任何具有较低QoS的操作都将推断出较高的QoS |
队列的QoS是通过更改队列的qualityOfService属性的值来提高的。 | 具有较低QoS的任何队列操作都会提升到较高QoS。将来添加到队列中的任何具有较低QoS的操作都将推断出较高的QoS |
通过更改队列的qualityOfService属性的值来降低队列的QoS | 队列的任何操作都不受影响。将来添加到队列中的任何操作都将推断较低的QoS,除非它们分配了较高的QoS,在这种情况下,它们将保留分配的QoS级别 |
表4-4NSOperation推理与提升规则
情形 | 结果 |
---|---|
操作没有分配QoS | 该操作推断父操作、队列、 [NSProcessInfo performActivityWithOptions:reason:usingBlock:]块或线程(如果有)的QoS。在主线程上创建操作的情况下,将推断NSQualityOfServiceUserInitiated的QoS。 |
具有QoS的操作被添加到具有更高QoS的队列中。 | 将提升操作的QoS以匹配队列的QoS。 |
提升包含操作的队列的QoS | 如果队列的新QoS高于操作的当前QoS,则该操作将推断该队列的新QoS。 |
另一个操作依赖于该操作(父操作)。 | 如果子操作的QoS更高,父操作将推断该子操作的QoS |
通过更改操作的qualityOfService属性提高操作的QoS | 操作推断新的QoS。如果任何子操作的QoS更高,则将其提升为新的QoS。如果操作队列中位于操作前面的其他操作的QoS更高,则将其提升为新的QoS。 |
通过更改操作的qualityOfService属性降低操作的QoS | 操作推断新的QoS。任何子操作都不受影响。操作队列不受影响。 |
调整正在运行的操作的QoS
操作运行后,可以通过以下方式之一更改其QoS:
更改操作的qualityOfService属性。请注意,这样做也会更改运行该操作的线程的QoS。
将具有更高QoS的新操作添加到正在运行的操作的队列中。这将提升运行操作的QoS以匹配操作的QoS。
使用addDependency:将具有更高QoS的操作作为依赖项添加到正在运行的操作中。
使用waitUntilFinished或waituntillallooperations完成。这将提升正在运行的操作的QoS以匹配调用者的QoS
为调度队列和块指定QoS
如果您的应用程序使用GCD,则可以将QoS类应用于调度队列和块
调度队列
对于调度队列,可以在创建队列时通过调用dispatch_queue_attr_make_with_QoS_类来指定QoS。首先,为QoS创建一个dispatch queue属性,然后在创建队列时提供该属性,如清单4-2所示。
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0);
dispatch_queue_t myQueue = dispatch_queue_create("com.YourApp.YourQueue", qosAttribute);
表4-5显示了GCD QoS类如何映射到基础QoS等价物
GCD QoS类(在sys/QoS.h中定义 | 对应的基础QoS类 |
---|---|
QOS_CLASS_USER_INTERACTIVE | NSQualityOfServiceUserInteractive |
QOS_CLASS_USER_INITIATED | NSQualityOfServiceUserInitiated |
QOS_CLASS_UTILITY | NSQualityOfServiceUtility |
QOS_CLASS_BACKGROUND | NSQualityOfServiceBackground |
QoS是调度队列的一个不可变属性,一旦创建队列就不能更改。要检索分配给调度队列的QoS,请调用dispatch_queue_get_QoS_类。见清单4-3。
qosClass = dispatch_queue_get_qos_class(myQueue, &relative);
全局并发队列
在过去,GCD提供了高、默认、低和后台全局并发队列来确定工作的优先级。现在应该使用相应的QoS类来代替这些队列。表4-6描述了这些队列与其对应的QoS类之间的映射。
表4-6GCD全局并发队列到QoS映射
全局队列 | 对应的QoS类 |
---|---|
Main thread | User-interactive |
DISPATCH_QUEUE_PRIORITY_HIGH | User-initiated |
DISPATCH_QUEUE_PRIORITY_DEFAULT | Default |
DISPATCH_QUEUE_PRIORITY_LOW | Utility |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | Background |
每个QoS类都存在一个全局并发队列。要检索与给定QoS相对应的全局并发队列,请调用dispatch_get_global_queue并将所需的QoS类传递给它。例如,清单4-4检索实用工具QoS类的全局并发队列。
清单4-4为QoS设置全局并发队列
utilityGlobalQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
未分配QoS且未以全局并发队列为目标的队列推断未指定的QoS类
调度块
GCD块API允许在块级别应用QoS类,例如在调用dispatch_async、dispatch_sync、dispatch_after、dispatch_apply或dispatch_once时。在创建块时执行此操作,如清单4-5所示。
清单4-5创建分派块时分配QoS
dispatch_block_t myBlock;
myBlock = dispatch_block_create_with_qos_class(
0, QOS_CLASS_UTILITY, -8, ^{…});
dispatch_async(myQueue, myBlock);
优先权倒置
当高优先级工作依赖于低优先级工作时,或者它成为低优先级工作的结果时,会发生优先级反转。因此,可能会发生阻塞、旋转和轮询。在同步工作的情况下,系统将尝试通过提高较低优先级工作在反转期间的QoS来自动解决优先级反转。这将在以下情况下发生:
- 为串行队列上的块调用dispatch_sync()和dispatch_wait()时。
- 当pthread_mutex_lock()被调用时,而mutex被具有较低QoS的线程持有。在这种情况下,持有锁的线程被提升到调用方的QoS。但是,这种QoS提升不会跨多个锁发生。
- 在异步工作的情况下,系统将尝试解决串行队列上发生的优先级反转。
开发人员应该首先确保优先级反转不会发生,这样系统就不会被迫尝试解决问题。
为线程指定QoS
NSThread拥有一个类型为NSQualityOfService的qualityOfService属性。此类不会基于其执行的上下文推断QoS,因此只能在线程启动之前更改此属性的值。随时读取线程的qualityOfService提供其当前值。
主线程和当前线程
主线程根据其环境自动分配QoS。在应用程序中,主线程以用户交互的QoS级别运行。在XPC服务中,主线程以默认的QoS运行。要检索主线程的QoS,请调用QoS类主函数,如清单4-6所示。
清单4-6检索主线程的QoS
qosClass = qos_class_main();
要检索当前运行线程的QoS,请调用QoS类自身函数,如清单4-7所示。
清单4-7检索当前线程的QoS'
qosClass = qos_class_self();
pthreads
在创建pthread时,可以使用属性来分配QoS类,如清单4-8所示,该属性创建实用程序pthread。
清单4-8创建具有QoS的pthread
pthread_attr_t qosAttribute;
pthread_attr_init(&qosAttribute);
pthread_attr_set_qos_class_np(&qosAttribute, QOS_CLASS_UTILITY, 0);
pthread_create(&thread, &qosAttribute, f, NULL);
要更改pthread的QoS,请调用pthread_set_QoS_class_self_np并将要应用的新QoS传递给它,如清单4-9所示
清单4-9更改pthread的QoS
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND,0);
关于CloudKit和服务质量
如果您的应用程序使用CloudKit框架,那么值得注意的是,某些CloudKit类在默认情况下实现了自定义的QoS行为。
CKOperation是NSOperation类的一个子类。尽管NSOperation类的默认QoS级别为NSQualityOfServiceBackground,但CKOperation对象的默认QoS级别为NSQualityOfServiceUtility。在这个级别上,当你的应用不在使用时,网络请求被视为可自由决定的。在iphone上,当启用低功耗模式时,自主操作将暂停。
CKContainer是NSObject类的一个子类。默认情况下,与CKContainer对象的交互发生在NSQualityOfServiceUserInitiated的QoS级别。
CKDatabase是NSObject类的一个子类。默认情况下,与CKContainer对象的交互发生在NSQualityOfServiceUserInitiated的QoS级别。
有关CloudKit类的信息,请参阅CloudKit Framework Reference。
调试服务类的质量
通过在Xcode中设置断点或在测试时暂停应用程序,可以使用debug navigator中的CPU使用量表检查应用程序,以确认正在应用请求的QoS类