Mobile,Jasmine,JavaScript,Java,テスト駆動開発等)など扱っています。マルチスレッド処理スポンサードリンク前回はObjective-CにおけるNSThreadクラスの簡単な利用例を取り扱いましたが今回は、ほぼ同じ内容で言語をSwiftに変えたエントリーとなります。言語が変わっただけでは面白くないですので、今回は画面上に存在するボタンを押したらスレッドが起動するとの例にしたいと思います。前回はXCTestフレームワークを利用してマルチスレッドの検証を行ったので、メインスレッドを意識した検証ができませんでしたので、今回はこの点についても記載できたらと思います。あとはマルチスレッドと言えば排他処理が避けて通れない処理であると言えますので、この点についても記載させていただきたいと思います。本エントリーではSwift-CにおけるNSThreadクラスの簡単な実装サンプルとその説明を記載させていただきます。利用しているXcodeは6.1です。今回はiPhoneシュミレーターで動作する検証用のアプリケーションを利用して説明を行います。プロジェクトの作成まずプロジェクトを以下の情報で作成します。画面に配置する画面(UI)コンポーネントとして排他処理でスレッドセースにした値を表示するラベルプログラム内で利用するパラメータを指定するテキストボックスを2つと、そのテキストボックスの見出しのラベルを2つスレッドを実行するためのボタンを1つをそれぞれ用意します。検証用の画面ですのでオートレイアウト機能はOFFにします。Layout」のチェックを外します。Classes」ボタンをクリックします。これでオートレイアウト機能が無効になりました。各画面コンポーネントの配置まずは排他処理でスレッドセースにした値を表示するラベルを配置します。Library」からLabelを以下の画像のようにドラッグ&ドロップします。同様に他のコンポーネントを配置します。各画面コンポーネントのOutlet接続とイベント登録プログラムから制御したい画面コンポーネントのOutlet接続と「開始」ボタンのタップ時のイベントハンドラーを登録します。1つ目のテキストフィールドをOutlet接続してみます。アシスタントエディタをエディタエリアに表示し、左側にストーリーボード、右側にViewController.swiftを表示します。その後、1つ目のテキストフィールドを「Control」ボタンを押しながらViewController.swiftにドラッグ&ドロッップします。すると以下の画面が表示されますので、NameにparamOneを入力し「Connect」ボタンをクリックします。これでViewController.swiftに以下の1行が追加されました。UITextField!同じように一番上のラベルと2つ目のテキストフィールドもOutlet接続してください。「開始」ボタンのイベント登録は「開始」ボタンを「Control」ボタンを押しながらViewController.swiftにドラッグ&ドロッップします。ここまではOutlet接続と同じですが、表示された画面の「Connection」をOutletからActionに変更します。これでViewController.swiftに以下の2行が追加されました。{}「Connection」をOutletからActionに変更すると画面が以下のように変わりますので、NameにstartActionを入力して「Connect」ボタンをクリックします。操作後のUIViewController.swiftは以下のようになりました。不明な方はご覧ください。(画面コンポーネントをドラッグ&ドロップするときに「Control」ボタンを押している事が動画上で認識できませんが・・・)NSThreadクラスを利用したマルチスレッド処理NSThreadクラスを利用したスレッド作成の説明をいただきます。スレッド作成方法として2通りの方法が存在します。NSThreadのdetachNewThreadSelectorメソッドを利用する方法NSThreadのサブクラスを作成する方法これについてはObjective-CでもSwiftでも同じです。言語が異なりますので、実装の実際は異なります。detachNewThreadSelectorでスレッドを起動する方法detachNewThreadSelectorの利用例detachNewThreadSelector呼び出し時にオブジェクト(レシーバー)とスレッドで実行したいメソッド、及びそのメソッドの引数(id型)を指定します。(第一引数の文字列がメソッド、toTargetがレシーバー、withObjectが引数)まずはスレッドを起動し、NSLogでログを出力するだけの例となります。ソースのコメントにも書いていますがスレッドメソッドの引数にparamOneを渡さなくてもthreadFuncからparamOneが参照できるのですが、あくまで利用例なので気にしないでください。i)sleep(1)}}}アプリを起動してパラメータ1に5を指定して「開始」ボタンをタップしたときのログは以下のようになりました。4startActionメソッドが実行されているスレッドのIDは1511813threadFuncが実行されているスレッドのIDは1511984となっていることがログから分かります。1511813と1511984のスレッドが個別に動作しているのがログから見て取れると思います。SwiftNSThread.detachNewThreadSelector("threadFunc:",toTarget:self,withObject:paramOne)1NSThread.detachNewThreadSelector("threadFunc:",toTarget:self,withObject:paramOne)は以下のコードでも同様の処理となります。object:paramOne)thread.start()これだけでは少し寂しいので、どのスレッドがメインスレッドで、現在スレッドがマルチで動作しているかをログで出力するようにしてみます。メインスレッドを判定する前にカレントのスレッドを取得する必要があります。カレントのスレッドは、NSThreadクラスのクラスメソッドのcurrentThread()->NSThreadを呼び出すことで取得可能です。取得したNSThreadのインスタンスに対してisMainThread()->Boolを呼び出すことでメインスレッドかどうか判定できます。(NSLogに出力するときは見やすいように三項演算子を用いてYESかNOを出力した方が見やすいです。)現在スレッドがマルチで動作しているかはNSThreadクラスのクラスメソッドのisMultiThreaded()->Boolで判定できます。クラスメソッドとはJavaとC#ではスタティックメソッドと呼ばれており、インスタンス化していないクラスをレシーバーとして呼び出す事が可能なメソッドとなります。変更後のstartActionとthreadFuncは以下のようになりました。i)sleep(1)}}アプリを起動してパラメータ1に5を指定して「開始」ボタンをタップしたときのログは以下のように変わりました。4このログを見て不思議に思ったことがあります。isMultiThreaded=YESとなっていますのでこちらは納得って感じですね。NSThreadのサブクラスを作成する方法NSThreadクラスのサブクラスを作成し、mainメソッドをオーバーライドします。別スレッドとして実行したい処理はmainメソッド内で実装します。この方法に関してもObjective-Cと全く同じです。mainメソッドから他のメソッドを呼び出してももちろんOKです。detachNewThreadSelectorの利用例と同じ処理例まずはdetachNewThreadSelectorのところで記載した利用例と同じ処理結果となるように処理を実装してみます。File...」をクリックします。Class」を選択後に「Next」ボタンをクリックします。ofにNSThread、LanguageにSwiftをそれぞれ指定し、「Next」ボタンをクリックします。画面が切り替りますので、何も変更せずに「Create」ボタンをクリックします。これでNSThreadEx.swiftが生成されましたので、NSThreadEx.swiftを以下のように修正します。いってもViewControllerのthreadFuncメソッドを少し書き換えただけです。(paramOneの扱いだけ異なります。i)sleep(1)}}}あとはViewController.swiftのstartActionメソッドのスレッド起動部分を変更するだけです。(paramOneとparamTwoのセット処理も追加)ViewController.swiftのstartActionメソッドは以下のようになりました。i)sleep(2)}}変更後にパラメータ1に3を指定して「開始」ボタンを押したときのログは以下のようになりました。2パラメータ1の数を減らしたのでfor文が回る回数が減っていますが、NSThreadのdetachNewThreadSelectorメソッドを利用した時の処理結果と一部の文言以外は同じ結果となっています。先ほどの例を変更して、新規の複数のスレッドを起動できるようにします。起動するスレッドの数はパラメータ2で指定されて値をします。変更後のstartActionは以下のようになりました。i)sleep(2)}}変更後にパラメータ1を3、パラメータ2に2を指定して「開始」ボタンを押したときのログは以下のようになりました。メインスレッドが1897175で、新規に起動したスレッドが1897312と1897313となっていることがログから見てとれます。2スレッドの排他処理を含んだ例Objective-Cだと@synchronizedで排他対象としてオブジェクトを指定して、処理を囲んでスレッドセーフな処理を実現することが可能です。{スレッドセーフにしたい処理をここで記述}複数のスレッドが@synchronized(排他用いるオブジェクト)の処理を実行したとしても、排他に用いるオブジェクトをロックできた単一のスレッドだけが実際の処理を実行します。排他に用いるオブジェクトをロックできなかった他のスレッド(複数の可能性もあり)は、ロックが解放されるまで待って、排他に用いるオブジェクトのロックが取得できたスレッドのみが実際の処理を実行できます。これは一般的に良くしられた一番シンプルな排他制御の方法です。Swiftでは@synchronizedはまだ利用できません。変わりにobjc_sync_enterとobjc_sync_exitを利用します。objc_sync_enter(排他に用いるオブジェクト)でオブジェクトのロック待ちとロックの獲得を、objc_sync_exit(排他に用いるオブジェクト)でオブジェクトのロックの解放を行います。排他に用いるオブジェクトですが、これは複数のスレッドかが認識できる同じオブジェクトである必要があります。各スレッドが異なるオブジェクトに対してロックの取得を行った場合は、当然ですが全てのスレッドでロックの取得が成功してしまい、排他処理とはなりえないからです。JavaやC#では任意のクラスにスタティックメンバを作成し、これを排他に用いるオブジェクトとして利用します。することで全てのスレッドが同じオブジェクト(ポインター)を排他の対象として動作することが可能になります。SwiftにはJavaやC#のスタティックメンバに相当する定数や変数を宣言することが言語仕様上不可能となっています。Property」と構造体を併用し、構造体の中にstaticなオブジェクトを定義することでこの問題を解消します。Property」を実装します。Static.instance}sharedInstanceにアクセスすることでNSThreadExクラスのスタティックメンバが取得可能となります。これを排他に用いるオブジェクトとして利用することでマルチスレッドの排他処理が可能となります。実際の排他制御を加えたNSThreadExクラスのmainメソッドは以下のようになります。i)sleep(1)//ロックを解放objc_sync_exit(NSThreadEx.sharedInstance)}}これで13、14行目のログ出力とスリープ処理がクリティカルセクションになります。変更後にパラメータ1を3、パラメータ2に2を指定して「開始」ボタンを押したときのログは以下のようになりました。新規に起動したスレッドが1963771と1963772となっており、NSThreadExクラスのmainメソッドの13、14行目が排他処理となっていることが確認できます。0の2行の時刻が1秒以上の間隔があることから確認できます。0のように2行の間で時間差が存在していませんでした。排他処理が追加されたことによって、先にクリティカルセクションに侵入したスレッドでsleep(1)が終わってから、待たされていたスレッドが動き出したことが変更前と変更後のログから確認できます。動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。個人では初となるiOSアプリをリリースいたしました。Storeからダウンロードしインストール可能になりました。Swiftが利用できるXcode6の正式版がリリースされました。Test)をXCTestフレームワークで試してみる。]!が発生するそろそろQuickを利用しようと思いまたまたハマりました。Dispatch)の利用]「iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1」ではNSThread記事を読むSwift入門[公式リファレンスのチュートリアルのSteps2の後半]「Swift入門」では、Steps2の前半まで説明させていただきました。コメントをキャンセルメールアドレスが公開されることはありません。ENTRY動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。個人では初となるiOSアプリをリリースいたしました。前回はSwiftの概要をザックリと説明させていただきました。Mobile,Jasmine,JavaScript,Java,テスト駆動開発等)など扱っています。