在后臺少工作
當用戶不在使用你的應(yīng)用時像寒,系統(tǒng)會將其置于后臺狀態(tài)疼邀。如果應(yīng)用程序沒有執(zhí)行重要的工作丁鹉,例如完成用戶啟動的任務(wù)或以特別聲明的后臺執(zhí)行模式運行妒潭,則系統(tǒng)最終可能會掛起應(yīng)用程序。
你的應(yīng)用不應(yīng)等待系統(tǒng)掛起揣钦。一旦通知該國已發(fā)生變化雳灾,它應(yīng)立即開始停止活動。當您的應(yīng)用程序完成任何剩余任務(wù)時冯凹,它應(yīng)通知系統(tǒng)后臺活動已完成谎亩。否則會導致應(yīng)用程序保持活動狀態(tài),并不必要地消耗能量宇姚。
您可以使用Energy organizer查看由從TestFlight用戶自動收集的日志信息生成的崩潰和能量報告匈庭,并獲得應(yīng)用程序應(yīng)用商店版本用戶的許可。要了解更多信息浑劳,請參見能量管理器阱持。
后臺應(yīng)用程序浪費能量的常見原因
應(yīng)用程序執(zhí)行不必要的后臺活動浪費能源。以下是后臺應(yīng)用程序中能量浪費的一些常見原因:
- 后臺活動完成時不通知系統(tǒng)
- 播放無聲音頻
- 執(zhí)行位置更新
- 與藍牙附件交互
- 可以推遲的下載
當應(yīng)用程序處于非活動狀態(tài)或移到后臺時暫湍а活動
在應(yīng)用程序委派中實現(xiàn)UIApplicationDelegate方法衷咽,以便在應(yīng)用程序處于非活動狀態(tài)或從前臺轉(zhuǎn)換到后臺時接收調(diào)用并掛起活動。
applicationWillResignActive:
applicationWillResignActive:方法在應(yīng)用程序進入非活動狀態(tài)時調(diào)用蒜绽,例如當電話或文本消息進入兵罢,或者用戶切換到另一個應(yīng)用程序并且您的應(yīng)用程序開始轉(zhuǎn)換到后臺狀態(tài)時。這是暫妥仪希活動卖词、保存數(shù)據(jù)和為可能的暫停做準備的好地方。請參見清單3-1。
清單3-1應(yīng)用程序變?yōu)榉腔顒訝顟B(tài)時的響應(yīng)
-(void)applicationWillResignActive:(UIApplication *)application {
//停止操作此蜈、動畫和用戶界面更新
}
你的應(yīng)用不應(yīng)該依賴狀態(tài)轉(zhuǎn)換來保存數(shù)據(jù)即横。它應(yīng)該在整個應(yīng)用程序生命周期的適當位置保存數(shù)據(jù),以防止數(shù)據(jù)丟失裆赵。
applicationDidEnterBackground
pplicationIdentiterBackground:方法在應(yīng)用程序進入后臺狀態(tài)后立即調(diào)用东囚。立即停止任何操作、動畫和UI更新战授。見清單3-2應(yīng)用程序轉(zhuǎn)換為后臺應(yīng)用程序時的響應(yīng)
-(void)applicationDidEnterBackground:(UIApplication *)application {
//立即停止操作页藻、動畫和UI更新
}
iOS只允許運行applicationidenterbackground方法幾秒鐘。如果你的應(yīng)用需要更多的時間來完成用戶啟動的重要任務(wù)植兰,它應(yīng)該請求更多的后臺執(zhí)行時間份帐,系統(tǒng)允許在請求時多用幾分鐘時間。調(diào)用beginBackgroundTaskWithExpirationHandler:方法并向其傳遞一個處理程序楣导,如果多余的時間用完废境,將調(diào)用該處理程序。接下來噩凹,在調(diào)度隊列或輔助線程上運行剩余的任務(wù)。
后臺任務(wù)完成后驮宴,調(diào)用endBackgroundTask:方法讓系統(tǒng)知道處理完成。如果您不調(diào)用此方法堵泽,并且后臺執(zhí)行時間耗盡,那么將調(diào)用完成處理程序落恼,以給您最后一次包裝東西的機會箩退。之后离熏,你的應(yīng)用程序?qū)⒈粧炱稹U垍⒁娗鍐?-3戴涝。
// 請求額外的后臺執(zhí)行時間
UIBackgroundTaskIdentifier bgTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// 時間用完時要執(zhí)行的完成處理程序
}];
// 啟動后臺任務(wù)
// 后臺任務(wù)完成后通知系統(tǒng)
[[UIApplication sharedApplication] endBackgroundTask:bgTaskID];
應(yīng)用程序完成后臺任務(wù)后滋戳,不要等待系統(tǒng)調(diào)用過期處理程序。調(diào)用endBackgroundTask:一旦你的應(yīng)用完成執(zhí)行后臺任務(wù)啥刻。
應(yīng)用程序激活時恢復(fù)活動
在應(yīng)用程序委派中實現(xiàn)UIApplicationDelegate方法奸鸯,以便在應(yīng)用程序再次處于活動狀態(tài)時接收調(diào)用并恢復(fù)活動
applicationWillEnterForeground
在應(yīng)用程序從后臺應(yīng)用程序過渡到活動應(yīng)用程序之前,將立即調(diào)用applicationWillEnterForeground:方法可帽。開始恢復(fù)操作娄涩、加載數(shù)據(jù)、重新初始化UI,并為用戶準備好應(yīng)用程序蓄拣。見清單3-4在應(yīng)用程序轉(zhuǎn)換到前臺之前立即響應(yīng)
- (void)applicationWillEnterForeground:(UIApplication *)application {
// 準備恢復(fù)操作扬虚、動畫和用戶界面更新
}
applicationDidBecomeActive
applicationDidBecomeActive:方法在應(yīng)用程序成為活動應(yīng)用程序、由系統(tǒng)啟動或從后臺或非活動狀態(tài)轉(zhuǎn)換后立即調(diào)用球恤。完全恢復(fù)所有已停止的操作辜昵。見清單3-5。
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 恢復(fù)操作咽斧、動畫和用戶界面更新
}
解決失控的后臺應(yīng)用程序崩潰
iOS使用了一個CPU監(jiān)視器來監(jiān)視后臺應(yīng)用程序是否有過多的CPU使用堪置,如果它們超出了某些限制,iOS就會終止它們张惹。大多數(shù)執(zhí)行正常后臺活動的應(yīng)用程序都不應(yīng)該遇到這種情況舀锨。但是,如果應(yīng)用程序達到限制并被終止诵叁,則崩潰日志將指示終止的原因雁竞。指定了EXC_RESOURCE的異常類型和CPU_FATAL的子類型,以及指示超出限制的消息拧额。見清單3-6碑诉。
清單3-6過量CPU使用崩潰日志條目的示例
異常類型:EXC_RESOURCE
異常子類型:CPU_致命
異常消息:(限制80%)觀察到89%超過60秒
日志還包括一個堆棧跟蹤,它允許您確定應(yīng)用程序在終止前正在執(zhí)行的操作侥锦。通過分析堆棧跟蹤进栽,可以識別失控代碼的位置并解決它恭垦。
CPU監(jiān)視器在iOS8及更高版本中可用
優(yōu)先考慮服務(wù)質(zhì)量類
應(yīng)用程序和操作競爭使用有限的資源CPU番挺、內(nèi)存、網(wǎng)絡(luò)接口等襟衰。為了保持響應(yīng)和效率瀑晒,系統(tǒng)需要對任務(wù)進行優(yōu)先級排序苔悦,并對何時執(zhí)行這些任務(wù)做出智能決策玖详。
直接影響用戶的工作(如UI更新)非常重要蟋座,并且優(yōu)先于后臺可能發(fā)生的其他工作。這種更高優(yōu)先級的工作通常會消耗更多的能源秒拔,因為它可能需要大量和即時的系統(tǒng)資源砂缩。
作為開發(fā)人員庵芭,您可以根據(jù)重要性對應(yīng)用程序的工作進行分類双吆,從而幫助系統(tǒng)更有效地確定優(yōu)先級好乐。即使您已經(jīng)實施了其他效率度量蔚万,例如將工作推遲到最佳時間临庇,系統(tǒng)仍然需要執(zhí)行某種級別的優(yōu)先級排序假夺。因此已卷,對應(yīng)用程序執(zhí)行的工作進行分類仍然很重要悼尾。
關(guān)于服務(wù)質(zhì)量類
服務(wù)質(zhì)量(QoS)類允許您對要由NSOperation闺魏、NSOperationQueue俯画、NShread對象、調(diào)度隊列和PThread(POSIX線程)執(zhí)行的工作進行分類埋虹。通過將QoS分配給工作搔课,您可以指出它的重要性爬泥,系統(tǒng)會對其進行優(yōu)先級排序并相應(yīng)地進行調(diào)度袍啡。例如却桶,系統(tǒng)執(zhí)行由用戶啟動的工作比后臺工作要快颖系,后臺工作可以推遲到一個更理想的時間嘁扼。在某些情況下偷拔,系統(tǒng)資源可能會從較低優(yōu)先級的工作重新分配給較高優(yōu)先級的工作。
因為高優(yōu)先級的工作比低優(yōu)先級的工作執(zhí)行得更快欺旧,資源也更多辞友,所以它通常比低優(yōu)先級的工作需要更多的能量称龙。準確地為應(yīng)用程序執(zhí)行的工作指定適當?shù)腝oS類可以確保應(yīng)用程序響應(yīng)速度快且節(jié)能鲫尊。
選擇服務(wù)質(zhì)量等級
系統(tǒng)使用QoS信息來調(diào)整優(yōu)先級疫向,如調(diào)度、CPU和I/O吞吐量以及計時器延遲谈火。因此糯耍,所做的工作保持了性能和能源效率之間的平衡谍肤。
將QoS分配給任務(wù)時荒揣,請考慮它如何影響用戶以及它如何影響其他工作系任。如表4
表4-1主要QoS等級(按優(yōu)先級排列)
QoS類 | 工作類型和服務(wù)質(zhì)量關(guān)注點 | 工作期限 |
---|---|---|
用戶交互 | 與用戶交互的工作俩滥,例如在主線程上操作贺奠、刷新用戶界面或執(zhí)行動畫儡率。如果工作不能很快完成儿普,用戶界面可能會凍結(jié)眉孩。專注于響應(yīng)和性能浪汪。 | 工作幾乎是瞬間的 |
用戶發(fā)起 | 用戶已啟動并需要立即結(jié)果的工作死遭,例如打開保存的文檔或在用戶單擊用戶界面中的某個內(nèi)容時執(zhí)行操作。這項工作是為了繼續(xù)用戶交互所必需的袁波。專注于響應(yīng)和性能 | 工作幾乎是瞬間的篷牌,比如幾秒鐘或更短的時間枷颊。 |
公用事業(yè) | 可能需要一些時間才能完成且不需要立即結(jié)果的工作夭苗,如下載或?qū)霐?shù)據(jù)题造。實用程序任務(wù)通常有一個用戶可見的進度條界赔。專注于在響應(yīng)能力淮悼、性能和能效之間提供平衡袜腥。 | 工作需要幾秒鐘到幾分鐘 |
后臺 | 在后臺操作且用戶看不到的工作羹令,如索引损痰、同步和備份徐钠。關(guān)注能源效率尝丐。 | 工作需要很長的時間爹袁,如幾分鐘或幾小時。 |
最理想的情況是譬淳,在沒有用戶活動的情況下邻梆,至少90%的時間以QoS級別的實用程序運行應(yīng)用程序浦妄。
在iphone上,當啟用低功耗模式時蠢涝,自主操作和后臺操作(包括網(wǎng)絡(luò))將暫停和二。請參見對iPhone低功耗模式的反應(yīng)儿咱。
特殊服務(wù)質(zhì)量等級
除了主要的QoS類之外混埠,還有兩種特殊的QoS類型(如表4-2所述)钳宪。在大多數(shù)情況下吏颖,您不會接觸到這些類半醉,但是知道它們的存在還是有價值的缩多。
如果應(yīng)用程序使用操作和隊列執(zhí)行工作养晋,則可以為該工作指定QoS绳泉。NSOperation和NSOperationQueue都具有類型為NSQualityOfService的qualityOfService屬性零酪,該屬性可以設(shè)置為以下值之一:
- NSQualityOfServiceUserInteractive
- NSQualityOfServiceUserInitiated
- NSQualityOfServiceUtility
- NSQualityOfServiceBackground
清單4-1展示了如何設(shè)置操作的QoS。確定一項業(yè)務(wù)的服務(wù)質(zhì)量NSOperation類的默認QoS為NSQualityOfServiceBackground方咆。
NSOperation *myOperation = [[NSOperation alloc] init];
myOperation.qualityOfService = NSQualityOfServiceUtility;
服務(wù)質(zhì)量推斷與提升
注意峻呛,QoS不是操作和隊列的靜態(tài)設(shè)置辜窑,并且可能隨時間而波動穆碎,具體取決于各種標準所禀。例如色徘,可能出現(xiàn)以下情況:操作的QoS與隊列的QoS不匹配褂策、操作與從屬操作不匹配或操作未分配QoS颓屑。在這些場景中,可以推斷QoS遍搞。
許多規(guī)則控制QoS推斷和提升在隊列(見表4-3)和操作(見表4-4)方面的發(fā)生方式溪猿。
NSOperationQueue QoS推理與提升規(guī)則
形勢 | 結(jié)果 |
---|---|
隊列沒有分配QoS再愈,并且將具有QoS的操作添加到隊列中翎冲。 | 隊列及其其他操作(如果有)不受影響抗悍。 |
隊列分配了QoS,并將具有QoS的操作添加到隊列中缴渊。 | 如果新操作的QoS較高衔沼,則會提升隊列的QoS指蚁。隊列中任何具有較低QoS的操作也會被提升。將來添加到隊列中的任何具有較低QoS的操作都將推斷出較高的QoS |
隊列的QoS是通過更改隊列的qualityOfService屬性的值來提高的稍坯。 | 具有較低QoS的任何隊列操作都會提升到較高QoS瞧哟。將來添加到隊列中的任何具有較低QoS的操作都將推斷出較高的QoS |
通過更改隊列的qualityOfService屬性的值來降低隊列的QoS | 隊列的任何操作都不受影響勤揩。將來添加到隊列中的任何操作都將推斷較低的QoS陨亡,除非它們分配了較高的QoS数苫,在這種情況下虐急,它們將保留分配的QoS級別 |
表4-4NSOperation推理與提升規(guī)則
情形 | 結(jié)果 |
---|---|
操作沒有分配QoS | 該操作推斷父操作滔迈、隊列燎悍、 [NSProcessInfo performActivityWithOptions:reason:usingBlock:]塊或線程(如果有)的QoS谈山。在主線程上創(chuàng)建操作的情況下,將推斷NSQualityOfServiceUserInitiated的QoS臊诊。 |
具有QoS的操作被添加到具有更高QoS的隊列中抓艳。 | 將提升操作的QoS以匹配隊列的QoS玷或。 |
提升包含操作的隊列的QoS | 如果隊列的新QoS高于操作的當前QoS偏友,則該操作將推斷該隊列的新QoS约谈。 |
另一個操作依賴于該操作(父操作)犁钟。 | 如果子操作的QoS更高涝动,父操作將推斷該子操作的QoS |
通過更改操作的qualityOfService屬性提高操作的QoS | 操作推斷新的QoS炬灭。如果任何子操作的QoS更高,則將其提升為新的QoS米愿。如果操作隊列中位于操作前面的其他操作的QoS更高育苟,則將其提升為新的QoS违柏。 |
通過更改操作的qualityOfService屬性降低操作的QoS | 操作推斷新的QoS漱竖。任何子操作都不受影響馍惹。操作隊列不受影響万矾。 |
調(diào)整正在運行的操作的QoS
操作運行后勤众,可以通過以下方式之一更改其QoS:
更改操作的qualityOfService屬性们颜。請注意窥突,這樣做也會更改運行該操作的線程的QoS阻问。
將具有更高QoS的新操作添加到正在運行的操作的隊列中称近。這將提升運行操作的QoS以匹配操作的QoS凳谦。
使用addDependency:將具有更高QoS的操作作為依賴項添加到正在運行的操作中衡未。
使用waitUntilFinished或waituntillallooperations完成如失。這將提升正在運行的操作的QoS以匹配調(diào)用者的QoS
為調(diào)度隊列和塊指定QoS
如果您的應(yīng)用程序使用GCD送粱,則可以將QoS類應(yīng)用于調(diào)度隊列和塊
調(diào)度隊列
對于調(diào)度隊列竭鞍,可以在創(chuàng)建隊列時通過調(diào)用dispatch_queue_attr_make_with_QoS_類來指定QoS。首先偎快,為QoS創(chuàng)建一個dispatch queue屬性洽胶,然后在創(chuàng)建隊列時提供該屬性,如清單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類如何映射到基礎(chǔ)QoS等價物
GCD QoS類(在sys/QoS.h中定義 | 對應(yīng)的基礎(chǔ)QoS類 |
---|---|
QOS_CLASS_USER_INTERACTIVE | NSQualityOfServiceUserInteractive |
QOS_CLASS_USER_INITIATED | NSQualityOfServiceUserInitiated |
QOS_CLASS_UTILITY | NSQualityOfServiceUtility |
QOS_CLASS_BACKGROUND | NSQualityOfServiceBackground |
QoS是調(diào)度隊列的一個不可變屬性梗搅,一旦創(chuàng)建隊列就不能更改无切。要檢索分配給調(diào)度隊列的QoS,請調(diào)用dispatch_queue_get_QoS_類瘦锹。見清單4-3籍嘹。
qosClass = dispatch_queue_get_qos_class(myQueue, &relative);
全局并發(fā)隊列
在過去,GCD提供了高弯院、默認辱士、低和后臺全局并發(fā)隊列來確定工作的優(yōu)先級。現(xiàn)在應(yīng)該使用相應(yīng)的QoS類來代替這些隊列抽兆。表4-6描述了這些隊列與其對應(yīng)的QoS類之間的映射识补。
表4-6GCD全局并發(fā)隊列到QoS映射
全局隊列 | 對應(yīng)的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類都存在一個全局并發(fā)隊列族淮。要檢索與給定QoS相對應(yīng)的全局并發(fā)隊列辫红,請調(diào)用dispatch_get_global_queue并將所需的QoS類傳遞給它。例如祝辣,清單4-4檢索實用工具QoS類的全局并發(fā)隊列。
清單4-4為QoS設(shè)置全局并發(fā)隊列
utilityGlobalQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
未分配QoS且未以全局并發(fā)隊列為目標的隊列推斷未指定的QoS類
調(diào)度塊
GCD塊API允許在塊級別應(yīng)用QoS類,例如在調(diào)用dispatch_async、dispatch_sync锈嫩、dispatch_after对雪、dispatch_apply或dispatch_once時。在創(chuàng)建塊時執(zhí)行此操作,如清單4-5所示雏赦。
清單4-5創(chuàng)建分派塊時分配QoS
dispatch_block_t myBlock;
myBlock = dispatch_block_create_with_qos_class(
0, QOS_CLASS_UTILITY, -8, ^{…});
dispatch_async(myQueue, myBlock);
優(yōu)先權(quán)倒置
當高優(yōu)先級工作依賴于低優(yōu)先級工作時俏橘,或者它成為低優(yōu)先級工作的結(jié)果時召耘,會發(fā)生優(yōu)先級反轉(zhuǎn)衫贬。因此,可能會發(fā)生阻塞澳窑、旋轉(zhuǎn)和輪詢麻裁。在同步工作的情況下,系統(tǒng)將嘗試通過提高較低優(yōu)先級工作在反轉(zhuǎn)期間的QoS來自動解決優(yōu)先級反轉(zhuǎn)。這將在以下情況下發(fā)生:
- 為串行隊列上的塊調(diào)用dispatch_sync()和dispatch_wait()時。
- 當pthread_mutex_lock()被調(diào)用時,而mutex被具有較低QoS的線程持有苦酱。在這種情況下,持有鎖的線程被提升到調(diào)用方的QoS帝际。但是脯爪,這種QoS提升不會跨多個鎖發(fā)生。
- 在異步工作的情況下,系統(tǒng)將嘗試解決串行隊列上發(fā)生的優(yōu)先級反轉(zhuǎn)。
開發(fā)人員應(yīng)該首先確保優(yōu)先級反轉(zhuǎn)不會發(fā)生,這樣系統(tǒng)就不會被迫嘗試解決問題。
為線程指定QoS
NSThread擁有一個類型為NSQualityOfService的qualityOfService屬性。此類不會基于其執(zhí)行的上下文推斷QoS惠猿,因此只能在線程啟動之前更改此屬性的值趾访。隨時讀取線程的qualityOfService提供其當前值淫半。
主線程和當前線程
主線程根據(jù)其環(huán)境自動分配QoS谣殊。在應(yīng)用程序中肌厨,主線程以用戶交互的QoS級別運行譬圣。在XPC服務(wù)中阔挠,主線程以默認的QoS運行。要檢索主線程的QoS谐腰,請調(diào)用QoS類主函數(shù),如清單4-6所示饱溢。
清單4-6檢索主線程的QoS
qosClass = qos_class_main();
要檢索當前運行線程的QoS状植,請調(diào)用QoS類自身函數(shù)帝簇,如清單4-7所示纸巷。
清單4-7檢索當前線程的QoS'
qosClass = qos_class_self();
pthreads
在創(chuàng)建pthread時祟偷,可以使用屬性來分配QoS類艰管,如清單4-8所示歹鱼,該屬性創(chuàng)建實用程序pthread秧廉。
清單4-8創(chuàng)建具有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,請調(diào)用pthread_set_QoS_class_self_np并將要應(yīng)用的新QoS傳遞給它,如清單4-9所示
清單4-9更改pthread的QoS
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND,0);
關(guān)于CloudKit和服務(wù)質(zhì)量
如果您的應(yīng)用程序使用CloudKit框架,那么值得注意的是骏啰,某些CloudKit類在默認情況下實現(xiàn)了自定義的QoS行為。
CKOperation是NSOperation類的一個子類仙蚜。盡管NSOperation類的默認QoS級別為NSQualityOfServiceBackground,但CKOperation對象的默認QoS級別為NSQualityOfServiceUtility栗涂。在這個級別上角寸,當你的應(yīng)用不在使用時,網(wǎng)絡(luò)請求被視為可自由決定的式矫。在iphone上,當啟用低功耗模式時,自主操作將暫停睁宰。
CKContainer是NSObject類的一個子類肪获。默認情況下,與CKContainer對象的交互發(fā)生在NSQualityOfServiceUserInitiated的QoS級別柒傻。
CKDatabase是NSObject類的一個子類。默認情況下较木,與CKContainer對象的交互發(fā)生在NSQualityOfServiceUserInitiated的QoS級別红符。
有關(guān)CloudKit類的信息,請參閱CloudKit Framework Reference伐债。
調(diào)試服務(wù)類的質(zhì)量
通過在Xcode中設(shè)置斷點或在測試時暫停應(yīng)用程序预侯,可以使用debug navigator中的CPU使用量表檢查應(yīng)用程序,以確認正在應(yīng)用請求的QoS類