[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を指定できますが、別の記事で扱います。

参考サイト