スレッド管理
OS X または iOS の各プロセス (アプリケーション) は、1 つ以上のスレッドで構成されており、それぞれのスレッドは、アプリケーションのコードを通じて単一の実行パスを表します。すべてのアプリケーションは、アプリケーションの main 関数を実行する単一のスレッドで開始します。アプリケーションは追加のスレッドを生成することができ、それぞれのスレッドは特定の関数のコードを実行します。
アプリケーションが新しいスレッドを生成すると、そのスレッドはアプリケーションのプロセス空間内の独立したエンティティ (実体) になります。各スレッドは独自の実行スタックを持ち、カーネルによってランタイムに別々にスケジュールされます。スレッドは、他のスレッドや他のプロセスと通信したり、I/O 操作を実行したり、必要な他の処理を行うことができます。ただし、それらは同じプロセス空間内にあるため、1 つのアプリケーション内のすべてのスレッドは同じ仮想メモリ空間を共有し、プロセス自体と同じアクセス権を持っています。
この章では、OS X および iOS で利用可能なスレッドテクノロジの概要と、アプリケーションでこれらのテクノロジーを使用する方法の例を示します。
スレッドのコスト
スレッド化は、メモリ使用量とパフォーマンスの点で、プログラム (およびシステム) にとって実際のコストです。各スレッドは、カーネルメモリ空間とプログラムのメモリ空間の両方にメモリを割り当てる必要があります。スレッドを管理しスケジュールを調整するために必要なコア構造は、有線メモリを使用してカーネルに格納されます。スレッドのスタック空間とスレッド単位のデータは、プログラムのメモリ空間に格納されます。これらの構造のほとんどは、スレッドを最初に作成するときに作成され、初期化されます。これは、カーネルとのやりとりが必要なために比較的高価なプロセスです。
表 2-1 に、アプリケーションで新しいユーザーレベルのスレッドを作成する際のおおよそのコストを示します。セカンダリスレッドに割り当てられるスタック空間の量など、これらのコストの一部は構成可能です。スレッドを作成するための時間的コストはおおよその近似値であり、お互いの相対的な比較にのみ使用する必要があります。スレッドの作成時間は、プロセッサの負荷、コンピュータの速度、および使用可能なシステムとプログラムのメモリ量によって大きく異なります。
表 2-1 スレッド作成コスト
項目 | おおよそのコスト | ノート |
カーネルデータ構造 | およそ 1 KB | このメモリはスレッドデータ構造と属性を格納するために使用され、その多くは有線メモリとして割り当てられているため、ディスクに割り当てることはできません。 |
スタック空間 | 512 KB (二次スレッド) 8 MB (OS X メインスレッド) 1 MB (iOS メインスレッド) | 二次スレッドの最小許容スタックサイズは 16 KB で、スタックサイズは 4 KB の倍数でなければなりません。このメモリー空間は、スレッド作成時にプロセス・スペースに保管されますが、そのメモリーに関連付けられた実際のページは必要になるまで作成されません。 |
作成時間 | およそ 90 マイクロ時間 | この値は、スレッドを作成する最初の呼び出しと、スレッドのエントリポイントルーチンが実行を開始した時刻との間の時間を反映します。この数値は、Intel ベースの iMac で 2 GHz Core Duo プロセッサと1 GB の RAM (OS X v10.5) を使用してスレッド作成時に生成された平均値と中央値を分析して求めました。 |
スレッドコードを書くときに考慮すべきもう一つのコストは、製造コストです。スレッド化されたアプリケーションを設計する際には、アプリケーションのデータ構造を整理するための基本的な変更が必要になることがあります。これらの変更を行うことは、同期の使用を避けるために必要であり、そのため、設計が不適切なアプリケーションに対して非常に大きなパフォーマンス上のペナルティが課せられます。これらのデータ構造を設計し、スレッド化されたコードで問題をデバッグすると、スレッド化されたアプリケーションを開発するのにかかる時間が長くなる可能性があります。しかし、これらのコストを避けると、実行時に大きな問題が発生する可能性がありますが、それはスレッドがロックを待ったり、何もしないで過ごす時間が長すぎる場合です。
スレッドの作成
低レベルのスレッドを作成するのは比較的簡単です。どの場合でも、スレッドのメイン・エントリポイントとして機能する関数またはメソッドが絶対に必要で、使用可能なスレッドルーチンの 1 つを使用してスレッドを開始しなければなりません。以下のセクションでは、より一般的に使用されるスレッドテクノロジーの基本的な作成プロセスを示します。これらのテクニックを使用して作成されたスレッドは、使用するテクノロジによって決定されるデフォルトの属性セットを継承します。スレッドの構成方法については、スレッド属性の構成 を参照してください。
NSThread の使用
NSThread クラスを使用してスレッドを作成するには、2 つの方法があります。
- 新しいスレッドを生成するには、detachNewThreadSelector:toTarget:withObject: クラスメソッドを使用します。
- 新しい NSThread オブジェクトを作成し、その start メソッドを呼び出します。(iOS および OS X v10.5 以降でのみサポートされています)
どちらの手法でも、アプリケーションに分離スレッドを作成します。分離されたスレッドとは、スレッドが終了したときに、スレッドのリソースがシステムによって自動的に再生されることを意味します。また、コードは後でスレッドと明示的に結合する必要はない事をも意味します。
detachNewThreadSelector:toTarget:withObject: メソッドは OS X のすべてのバージョンでサポートされているため、スレッドを使用する既存の Cocoa アプリケーションでしばしば見られます。新しいスレッドを切り離すには、スレッドのエントリポイントとして使用したいメソッドの名前 (セレクタとして指定します)、そのメソッドを定義するオブジェクト、および起動時にスレッドに渡したい全てのデータを指定するだけです 。以下の例は、現在のオブジェクトのカスタムメソッドを使用してスレッドを生成する、このメソッドの基本的な呼び出し方法を示しています。
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
OS X v10.5 以前では、NSThread クラスを主に使用してスレッドを生成していました。NSThread オブジェクトを取得していくつかのスレッド属性にアクセスすることはできますが、実行した後はスレッド自体からのみ行うことができます。OS X v10.5 では、対応する新しいスレッドをただちに生成することなく、 NSThread オブジェクトを作成するためのサポートが追加されました。(このサポートは iOS でも利用できます。) このサポートにより、スレッドを開始する前にさまざまなスレッド属性を取得して取得することができます。また、そのスレッドオブジェクトを使用して、実行中のスレッドを後で参照することも可能になりました。
OS X v10.5 以降で NSThread オブジェクトを初期化する簡単な方法は、initWithTarget:selector:object: メソッドを使用することです。このメソッドは、detachNewThreadSelector:toTarget:withObject: メソッドとまったく同じ情報を受け取り、新しい NSThread インスタンスを初期化するためにこのメソッドを使用します。ただし、スレッドは開始しません。スレッドを開始するには、以下の例のように、スレッドオブジェクトの start メソッドを明示的に呼び出します。
NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil]; [myThread start]; // Actually create the thread
現在スレッドが実行されている NSThread オブジェクトがある場合、そのスレッドにメッセージを送信できる方法の 1 つは、アプリケーションのほぼすべてのオブジェクトの performSelector:onThread:withObject:waitUntilDone: メソッドを使用することです。OS X v10.5 で、スレッド (メインスレッド以外の) で selector (セレクタ) のサポートが導入されており、スレッド間の通信に便利です。 (このサポートは iOS でも利用できます。) このテクニックを使用して送信するメッセージは、通常の実行ループ処理の一部として他のスレッドによって直接実行されます。(もちろん、これはターゲットスレッドが実行ループで実行されていなければならないという意味です。実行ループ を参照してください。) このように通信するには何らかの形式の同期が必要かもしれませんが、スレッド間の通信ポートを設定するよりは簡単です。
他のスレッドの通信オプションの一覧については、スレッドの切り離された状態の設定 を参照してください。
POSTIX スレッドの使用
OS X と iOS では、POSIX スレッド API を使用してスレッドを作成するための C を基本としたサポートを提供します。このテクノロジーは、実際にあらゆる種類のアプリケーション (Cocoa や Cocoa Touch アプリケーションを含む) で使用でき、複数のプラットフォーム用のソフトウェアを書く場合に便利です。スレッドを作成するために使用する POSIX ルーチンは、ほぼ pthread_create と呼ばれます。
リスト 2-1 に、POSIX 呼び出しを使用してスレッドを作成するための 2 つのカスタム関数を示します。 LaunchThread 関数は、メインルーチンが PosixThreadMainRoutine 関数で実装されている新しいスレッドを作成します。POSIX はデフォルトで結合できるスレッドを作成するため、この例ではスレッドの属性を変更して、切り離されたスレッドを作成しています。スレッドを切り離されたものとしてマークすると、そのスレッドが終了したときに即座にそのスレッドのリソースを再利用する機会をシステムに与えます。
リスト 2-1 C でスレッドを作成
#include <assert.h> #include <pthread.h> void* PosixThreadMainRoutine(void* data) { // Do some work here. return NULL; } void LaunchThread() { // Create the thread using POSIX routines. pthread_attr_t attr; pthread_t posixThreadID; int returnVal; returnVal = pthread_attr_init(&attr); assert(!returnVal); returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); assert(!returnVal); int threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL); returnVal = pthread_attr_destroy(&attr); assert(!returnVal); if (threadError != 0) { // Report an error. } }
上記のリストのコードをソースファイルの 1 つに追加して LaunchThread 関数を呼び出すと、アプリケーションに新しい切り離されたスレッドを作成します。もちろん、このコードを使って作成された新しいスレッドは全く役に立たないでしょう。スレッドは起動し、すぐに終了します。物事をより面白くするために、いくつかの実際の作業を行うために PosixThreadMainRoutine 関数にコードを追加する必要があります。スレッドが何をすべきかを知るためには、作成時にいくつかのデータへのポインタを渡します。このポインタは、pthread_create 関数の最後のパラメータとして渡して下さい。
新しく作成したスレッドからアプリケーションのメインスレッドに情報を伝達するには、ターゲットスレッド間に通信パスを確立する必要があります。C を基本としたアプリケーションでは、ポート、条件、共有メモリの使用などを含めた、スレッド間で通信するいくつかの方法があります。長い寿命のスレッドの場合、アプリケーションのメインスレッドにスレッドの状態を確認する方法や、アプリケーションが終了したときにスレッドを正常に終了させる方法など、スレッド間通信メカニズムを常に設定する必要があります。
POSIX スレッド関数の詳細については、pthread のマニュアルページを参照してください。
NSObject を使ってスレッドを生成
iOS および OS X v10.5 以降では、すべてのオブジェクトが新しいスレッドを生成し、それを使用してメソッドの 1 つを実行できます。performSelectorInBackground:withObject: メソッドは、新しい切り離されたスレッドを作成し、指定されたメソッドを新しいスレッドのエントリポイントとして使用します。たとえば、(変数 myObj で表される) オブジェクトがあり、そのオブジェクトにバックグラウンド・スレッドで実行する doSomething というメソッドがある場合は、以下のコードを使用してそれを行うことができます。
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
このメソッドを呼び出す効果は、現在のオブジェクト、セレクタ、およびパラメータ・オブジェクトをパラメータとして NSThread の detachNewThreadSelector:toTarget:withObject: メソッドを呼び出した場合と同じです。新しいスレッドは、デフォルトの構成を使用してすぐに生成され、実行を開始します。セレクタの内部では、他のスレッドと同じようにスレッドを構成すしなければなりません。たとえば、自動解放プール (ガベージコレクションを使用していない場合) を設定し、スレッドの実行ループを使用する予定がある場合は、そのループを構成する必要があります。新しいスレッドを構成する方法については、スレッド属性の構成 を参照してください。
POSIX スレッドを Cocoa アプリで使用
NSThread クラスは、Cocoa アプリケーションでスレッドを作成するためのメイン・インターフェイスですが、POSIX スレッドを使用すると便利です。たとえば、POSIX スレッドを使用するコードをすでに持っていて、そのコードを書き換えたくない場合は、POSIX スレッドを使用できます。Cocoa アプリケーションで POSIX スレッドを使用する予定がある場合は、引き続き Cocoa とスレッド間のやりとりを認識し、以下のセクションのガイドラインに従ってください。
Cocoa フレームワークの保護
マルチスレッドアプリケーションの場合、Cocoa フレームワークはロックや他の形式の内部同期を使用して、正しく動作するようにします。ただし、シングルスレッドの場合、これらのロックがパフォーマンスを低下させないようにするため、NSThread クラスを使用してアプリケーションが最初の新しいスレッドを生成するまで、Cocoa はロックを作成しません。POSIX スレッドルーチンのみを使用してスレッドを生成すると、Cocoa はアプリケーションがマルチスレッド化されていることを知らせる通知を受け取りません。そのようなことが起こると、Cocoa フレームワークを含む操作は、アプリケーションを不安定にするかクラッシュさせる可能性があります。
Cocoa に複数のスレッドを使用することを知らせるためには、NSThread クラスを使用して単一のスレッドを生成し、そのスレッドをただちに終了させるだけです。あなたのスレッド・エントリポイントは何もする必要はありません。NSThread を使ってスレッドを生成するだけで、Cocoa フレームワークが必要とするロックが確実に行われるようになります。
Cocoa があなたのアプリケーションがマルチスレッドであると思っているかどうか分からない場合は、NSThread の isMultiThreaded メソッドを使ってチェックすることができます。
POSIX と Cocoa のロックの混在
同じアプリケーション内で POSIX と Cocoa のロックを混在させ使用しても安全です。Cocoa のロックおよび条件オブジェクトは、本質的に POSIX の mutex および条件のラッパーです。ただし、特定のロックでは、そのロックを作成および操作するために常に同じインタフェースを使用しなければなりません。言い換えると、 pthread_mutex_init 関数を使用して作成した mutex を操作するために Cocoa の NSLock オブジェクトを使用することはできませんし、逆も同様です。
スレッド属性の構成
スレッドを作成した後は、スレッド環境のさまざまな部分を構成したいことがあります。以下のセクションでは、変更したい時に行うことができる変更のいくつかを説明します。
スレッドのスタックサイズの構成
作成した新しいスレッドごとに、システムはそのスレッドのスタックとして機能するように、プロセス空間内の特定の量のメモリーを割り当てます。スタックはスタックフレームを管理し、スレッドのローカル変数が宣言されている所でもあります。スレッドに割り当てられるメモリの量は、スレッドのコスト にリストされています。
特定のスレッドのスタックサイズを変更したい場合は、そのスレッドを作成する前に実行しなければなりません。 NSThread を使用してスタックサイズを設定することは、iOS と OS X v10.5 以降でのみ可能ですが、すべてのスレッド化技術はスタックサイズを設定する方法をいくつか提供します。表 2-2 に、各テクノロジーのさまざまなオプションを示します。
表 2-2 スレッドのスタックサイズの設定
テクノロジー | オプション |
Cocoa | iOS および OS X v10.5 以降では、NSThread オブジェクトを割り当てて初期化します (detachNewThreadSelector:toTarget:withObject: メソッドは使用しないでください)。スレッドオブジェクトの start メソッドを呼び出す前に、setStackSize: メソッドを使用して新しいスタックサイズを指定してください。 |
POSIX | 新しい pthread_attr_t 構造体を作成し、pthread_attr_setstacksize 関数を使用してデフォルトのスタックサイズを変更します。スレッドを作成するときは、属性を pthread_create 関数に渡します。 |
マルチプロセッササービス | スレッドを作成するときに、適切なスタックサイズの値を MPCreateTask 関数に渡します。 |
スレッドローカルな格納の構成
各スレッドは、スレッド内のどこからでもアクセスできるキー値のペアの辞書を保持しています。この辞書を使用して、スレッドの実行中全体を通して保持したい情報を格納することができます。たとえば、スレッドの実行ループの複数の反復を通して永続化したい状態情報を格納するために使用することができます。
Cocoa と POSIX はスレッドの辞書をさまざまな方法で格納するため、2 つのテクノロジーへの呼び出しを混在させることはできません。ただし、スレッドのコード内に 1 つのテクノロジーを適用する限り、最終的な結果は似ています。Cocoa では、NSThread オブジェクトの threadDictionary メソッドを使用して NSMutableDictionary オブジェクトを取得し、このオブジェクトには、スレッドで必要なすべてのキーを追加できます。POSIX では、pthread_setspecific 関数と pthread_getspecific 関数を使用して、スレッドのキー値を設定し取得して下さい。
スレッドの切り離された状態の設定
ほとんどの高水準スレッドテクノロジーは、デフォルトで切り離されたスレッドを作成します。ほとんどの場合、切り離されたスレッドは、スレッドの完了時にシステムがスレッドのデータ構造を直ちに解放できるため、優先されます。切り離されたスレッドは、プログラムとの明示的な対話も必要としません。スレッドから結果を取得する方法は、あなたの裁量に委ねられます。これに対して、他のスレッドがそのスレッドと明示的に結合するまで、システムは結合可能なスレッドのリソースを再利用しません。これは、結合を実行するスレッドをブロックするプロセスです。
結合可能なスレッドは、子スレッドの同族のように考えることができます。それらは依然として独立したスレッドとして実行されますが、結合可能なスレッドは、そのリソースがシステムによって再利用される前に、別のスレッドによって結合されなければなりません。結合可能なスレッドは、既存のスレッドから別のスレッドにデータを渡す明示的な方法も提供します。それが終了する直前に、結合可能なスレッドはデータポインタまたは他の戻り値を pthread_exit 関数に渡すことができます。別のスレッドは、 pthread_join 関数を呼び出してこのデータを要求できます。
結合可能なスレッドを作成したい場合は、POSIX スレッドを使用するしかありません。POSIX はデフォルトで結合可能としてスレッドを作成します。スレッドを detached (切り離し) または joinable (結合可能) とマークするには、スレッドを作成する前に pthread_attr_setdetachstate 関数を使用してスレッド属性を変更します。スレッドが開始したら、pthread_detach 関数を呼び出して結合可能なスレッドを切り離されたスレッドに変更することができます。これらの POSIX スレッド関数の詳細については、pthread のマニュアルページを参照してください。スレッドとの結合方法については、pthread_join のマニュアルページを参照してください。
スレッドの優先度の設定
作成した新しいスレッドには、デフォルトの優先度が関連付けられています。カーネルのスケジュール化アルゴリズムは、実行するスレッドを決定する際にスレッドの優先度を考慮に入れ、優先度の高いスレッドは優先度の低いスレッドよりも実行される可能性が高くなります。より高い優先度はスレッドの実行時間の特定の量を保証するものではなく、優先度の低いスレッドと比較してスケジューラによって選択される可能性が高いということだけです。
スレッドの優先順位を変更したい場合、Cocoa と POSIX の両方でそうする方法を提供しています。Cocoa スレッドの場合は、NSThread の setThreadPriority: クラスメソッドを使用して、現在実行中のスレッドの優先度を設定できます。POSIX スレッドの場合は、pthread_setschedparam 関数を使用します。詳細は、NSThread クラスリファレンス または pthread_setschedparam のマニュアルページを参照してください。
スレッドのエントリルーチンの作成
ほとんどの場合、スレッドのエントリポイントルーチンの構造は OS X では他のプラットフォームと同じです。データ構造を初期化したり、何か作業をしたり、オプションで実行ループを設定したり、スレッドのコードが完了したらクリーンアップしたりすることができます。設計によっては、エントリルーチンを記述する際にいくつかの追加ステップが必要になることがあります。
自動開放プールの作成
Objective-C フレームワークでリンクするアプリケーションは、通常、それぞれのスレッドに少なくとも 1 つの自動解放プールを作成しなければなりません。アプリケーションがオブジェクトの保持と解放を処理する管理モデルを使用する場合、自動解放プールはそのスレッドから自動開放されたオブジェクトをキャッチします。
アプリケーションが管理メモリモデルの代わりにガベージコレクションを使用する場合、自動解放プールの作成は厳密には必要ではありません。ガベージコレクションされたアプリケーションに自動解放プールが存在することは有害ではなく、ほとんどの場合単に無視されます。コードモジュールがガベージコレクションと管理メモリモデルの両方をサポートしなければならない場合には許可されます。そのような場合、管理メモリモデルのコードをサポートするために自動解放プールが存在しなければならず、ガベージコレクションを有効にしてアプリケーションを実行すると、単に無視されます。
あなたのアプリケーションが管理メモリモデルを使用している場合、自動解放プールを作成することは、スレッド・エントリルーチンで最初に行うべきことです。同様に、この自動解放プールを破棄することは、スレッドで最後に行うべきことです。このプールは、自動解放されたオブジェクトがキャッチされることを保証しますが、スレッド自体が終了するまで解放しません。リスト 2-2 に、自動解放プールを使用する基本スレッド・エントリルーチンの構造を示します。
リスト 2-2 スレッド・エントリポイント・ルーチンの定義
- (void)myThreadMainRoutine { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool // Do thread work here. [pool release]; // Release the objects in the pool. }
スレッドが終了するまで最上位レベルの自動解放プールはオブジェクトを解放しないため、長い寿命のスレッドは、オブジェクトをより頻繁に解放するための追加の自動解放プールを作成する必要があります。たとえば、実行ループを使用するスレッドは、実行ループごとに自動解放プールを作成したり解放する可能性があります。オブジェクトを頻繁に解放すると、アプリケーションのメモリ占有量が大きくなりすぎてパフォーマンス上の問題が発生する可能性を防ぎます。パフォーマンス関連の動作と同様に、コードの実際のパフォーマンスを測定し、自動解放プールの使用状況を適切に調整する必要があります。
メモリ管理と自動解放プールの詳細については、高度なメモリ管理プログラミングガイド を参照してください。
例外ハンドラの設定
アプリケーションが例外を catch して処理する場合は、発生する可能性がある例外をすべて carch するようスレッドコードを準備する必要があります。例外が発生する場所で例外を処理することが最善ですが、スレッド内で throw された例外を catch するのに失敗すると、アプリケーションは終了します。スレッド・エントリルーチンに最終的な try/catch をインストールすると、未知の例外を catch して適切な応答ができます。
Xcode でプロジェクトをビルドするときは、C++ または Objective-C の例外処理スタイルを使用できます。Objective-C で例外を発生させ catch する方法の設定については、例外プログラミングのトピックス を参照してください。
実行ループの設定
別のスレッドで実行したいコードを書くときは、2 つのオプションがあります。最初のオプションは、スレッドのコードを、ほとんどまたはまったく中断なしに実行される 1 つの長いタスクとして記述し、終了時にスレッドを終了させることです。2 番目のオプションはスレッドをループに入れ、到着時に動的に要求を処理させる方法です。最初のオプションではコードを特別に設定する必要はありません。あなたはやりたい仕事をやり始めます。しかし、2 番目のオプションは、スレッドの実行ループを設定することがあります。
OS X と iOS は、すべてのスレッドで実行ループを実装するための組み込みサポートを提供します。アプリのフレームワークは、アプリケーションのメインスレッドの実行ループを自動的に開始します。セカンダリスレッドを作成する場合は、実行ループを構成して手動で起動しなければなりません。
実行ループの使用と構成の詳細については、実行ループ を参照してください。
スレッドの終了
スレッドを終了させるお勧めの方法は、エントリーポイント・ルーチンを正常に終了させることです。Cocoa、POSIX、および マルチプロセッサ・サービスはスレッドを直接中止するためのルーチンを提供しますが、このようなルーチンの使用は全くお勧めしません。スレッドを中止すると、そのスレッドはそれ以降クリーンアップされません。スレッドによって割り当てられたメモリがリークする可能性があり、スレッドによって現在使用されている他のリソースが適切にクリーンアップされず、後で問題が発生する可能性があります。
操作の途中でスレッドを終了する必要がある場合は、キャンセルまたは終了メッセージに応答するために、最初からスレッドを設計する必要があります。長時間実行する操作では、定期的に作業を停止し、そのようなメッセージが到着したかどうかを確認することを意味します。スレッドが終了するように求めるメッセージが表示された場合、スレッドは必要なクリーンアップを実行して正常に終了する機会を得ます。それ以外の場合は、次のデータのまとまりに取り組み、処理するだけです。
キャンセルメッセージに応答する 1 つの方法は、実行ループ入力ソースを使用してそのようなメッセージを受信することです。リスト 2-3 は、このコードがスレッドのメイン・エントリルーチンでどのように見えるかの構造を示しています。(この例では、メインループ部分のみを示し、自動解放プールの設定や実際の作業の構成手順は含まれていません。) この例では、実行ループにカスタム入力ソースをインストールし、実行ループはあなたのスレッドの他の 1 つからおそらくメッセージを受け取ります。入力ソースの設定の詳細については、実行ループソースの構成 を参照してください。総作業量の一部を実行した後、スレッドは実行ループを短時間実行して、メッセージが入力ソースに到着したかどうかを確認します。そうでない場合、実行ループは直ちに終了し、ループは次の作業のまとまりで継続されます。ハンドラは exitNow ローカル変数に直接アクセスできないため、終了条件はスレッドの辞書のキー値のペアを通じて伝達されます。
リスト 2-3 長いジョブ中に終了条件を確認する
- (void)threadMainRoutine
{
BOOL moreWorkToDo = YES;
BOOL exitNow = NO;
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
// Add the exitNow BOOL to the thread dictionary.
NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
[threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
// Install an input source.
[self myInstallCustomInputSource];
while (moreWorkToDo && !exitNow)
{
// Do one chunk of a larger body of work here.
// Change the value of the moreWorkToDo Boolean when done.
// Run the run loop but timeout immediately if the input source isn't waiting to fire.
[runLoop runUntilDate:[NSDate date]];
// Check to see if an input source handler changed the exitNow value.
exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
}
}