[iOS 8] 新たなタスク優先度管理API – Quality of Service –
GCDとNSOperationのXNUカーネル優先度
iOSで並列処理を行う為の代表的なAPIとしてはGCD(GrandCentralDispatch)とNSOperationの2つがあります。
どちらのAPIも以下のような方法を通じてOSレベルでのスレッド優先度を決定できました。
GCD
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_async(queue, ^{ //タスク });
NSOperation
@interface MYOperation : NSOperation @end @implementation MYOperation - (void)main { @try { @autoreleasepool { //タスク } } @catch (NSException *exception) { } } @end MYOperation *operation = [MYOperation new]; NSOperationQueue *queue = [NSOperationQueue new]; operation.threadPriority = 0.7f; [queue addOperation:operation];
今回 iOS 8 のリリースに伴って、QOS(Quality of Service)と呼ばれる新たな概念の元にスレッド優先度を決定できるようになりました。
Quality of Service
Quality of Serviceはシステムのタスクスケジュール管理に一貫性を持たせる概念です。GCD, NSOperation, NSOperationQueueはこの新しい概念に対応した新しいAPIを提供しています。
QOSにはタスク優先度を示す概念としてサービスレベルがあります。サービスレベルを通じてシステムでのタスク優先度を決定することができます。
各サービスレベルはQOSクラス(通常のプログラミングにおけるクラスの概念とは異なります)として表せ、各クラスにはそれぞれUI更新、バックグラウンドタスクといった用途を明確に振り分けることができます。
開発者はより明示的になったタスク優先度を用いることで、どの用途に用いているタスクかをわかりやすく伝えることもできるようになりました。
早速詳細を確認してみましょう。
5つのTask Policy
サービスレベル(QOSクラス)には次の5つがあります。
- User Interactive
- User Initiated
- Default
- Utility
- Background
各サービスレベルに応じて以下の様なタスクを割り振ることが推奨されています。
User Interactive
インタラクティブなUI実現のためのタスクを割り振ります。例えばUIで起こったタッチイベントやスクリーンへの描画処理などが当てはまります。
User Initiated
ユーザを起点とした、UIに即時反映される必要があるタスクを割り振ります。例えばエンティティのリストを表示した画面からエンティティを選択してエンティティの詳細の表示に必要な情報をロードする処理などが当てはまります。
Default
どのサービスレベルを選ぶかが不明瞭なときに選択します。優先度的にはUser InitiatedとUtilityレベルの中間くらいに位置しています。
Utility
ユーザが結果を即時反映することを期待しないタスクに割り振ります。ユーザを起点とするタスクかシステムで自動的に発火させるタスクかは問いません。ユーザにとっては操作を邪魔せず、かつタスク進行の度合いを示すプログレスバーが表示されるようなタスクがこれに当てはまります。計算リソースが限られている場合は、User Interactiveレベルなどと比べてエネルギー効率のいい方法で処理が走ります。例えば定時的になされるコンテンツのアップデート、もしくはビデオファイルの取り込みなどの処理が当てはまります。
Background
ユーザを起点としないタスクで、ユーザに見えないところで行われるタスクに割り振ります。他のレベルと比較して一番エネルギー効率のいい方法で処理が走ります。例えばコンテンツの事前ダウンロード、バックアップ、外部システムとのデータ同期などが当てはまります。
どのように選択するか
サービスレベルを選択する場合、タスクがどういう性質を持っているかに応じて以下のように割り振ります
- UIのアップデートにすぐに結びつくタスク? -> YES : User Interactive , NO : 次へ
- UIに結果が反映されるべきタスク? -> YES : User Initiated, NO : 次へ
- ユーザがタスクの進行度合いについて認知するタスク? -> YES : Utility, NO : 次へ
- 適切な時間にタスク開始を延期できる? -> YES : Background
Quality of Serviceを用いた新たなAPI
各クラスを表すAPI
dispatch_qos_class_tは各QOSクラスを表す型で、実体はsys/qos.hのqos_class_tです。
FoundationのNSObjCRuntime.hやSwiftにも各QOSクラスに対応する値が提供されています。
表にまとめてみました。
lebel | qos_class_t | Foundation | Swift |
---|---|---|---|
User Interactive | QOS_CLASS_USER_INTERACTIVE | NSQualityOfServiceUserInteractive | UserInteractive |
User Initiated | QOS_CLASS_USER_INITIATED | NSQualityOfServiceUserInitiated | UserInitiated |
Default | QOS_CLASS_DEFAULT | NSQualityOfServiceDefault | Default |
Utility | QOS_CLASS_UTILITY | NSQualityOfServiceUtility | Utility |
Background | QOS_CLASS_BACKGROUND | NSQualityOfServiceBackground | Background |
dispatch_queue_attr_make_with_qos_class
dispatch_queue_createで第二引数にキューの属性を示すdispatch_queue_attr_tを代入できますが、その型の値を返り値として持つAPIとして新たに加わりました
Swift
func dispatch_queue_attr_make_with_qos_class(_ attr: dispatch_queue_attr_t!, _ qos_class: dispatch_qos_class_t, _ relative_priority: Int32) -> dispatch_queue_attr_t!
Objective-C
dispatch_queue_attr_t dispatch_queue_attr_make_with_qos_class ( dispatch_queue_attr_t attr, dispatch_qos_class_t qos_class, int relative_priority );
まず第一引数のdispatch_attr_tには直列キューか並列キューかを指定するDISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENTを代入します。NULLを入れた場合は直列キューが指定されます。
次に第二引数には先程のQOSクラスを指定します。
第三引数には第二引数に与えたQOSクラスからの相対的なスケジュール優先度のオフセットを指定します。0より小さいかまたはMIN_QOS_CLASS_PRIORITYよりも大きな値を指定する必要があります。
dispatch_queue_get_qos_class
既存のキューからQOSクラスを表すdispatch_qos_class_tを取得します。
Swift
func dispatch_queue_get_qos_class(_ queue: dispatch_queue_t!, _ relative_priority_ptr: UnsafeMutablePointer<Int32>) -> dispatch_qos_class_t!
Objective-C
dispatch_qos_class_t dispatch_queue_get_qos_class( dispatch_queue_t queue, int *relative_priority_ptr );
第一引数は既存のキュー、第二引数には取得クラスからの相対的の優先度オフセットがポインタを経由して入ります。
Quality of Service追加に伴うAPIの変更
従来のタスク優先度との対応
従来のdiapatch_get_grobal_queue関数でもキューに入れられたタスクのシステム優先度をDISPATCH_QUEUE_PRIORITY_HIGHなどで指定出来ましたが、iOS8からはGCDの並列キューのタスク管理優先度は従来のものからQOSの概念にマッチするように次のようにマッピングされます。
従来 | Quality of Service |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | QOS_CLASS_USER_INITIATED |
DISPATCH_QUEUE_PRIORITY_DEFAULT | QOS_CLASS_UTILITY |
DISPATCH_QUEUE_PRIORITY_LOW | QOS_CLASS_UTILITY(内部的にはQOS_CLASS_UTILITYよりも更に低い優先度が指定される) |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | QOS_CLASS_BACKGROUND |
Foundation APIでの変更点
NSOperation, NSOperationQueueには新たなプロパティqualityOfServiceが追加されました。こちらにNSQualityOfServiceUserInitiatedなどをいれて優先度を管理できます。
MYOperation *operation = [MYOperation new]; NSOperationQueue *queue = [NSOperationQueue new]; operation.qualityOfService = NSQualityOfServiceDefault; [queue addOperation:operation];
また、これに伴ってNSOperationのthreadPriorityプロパティはDeprecatedされています。
補遺
iOS8から別途追加されたDispatch Block APIにもQOSを指定できますが、別の記事で扱います。