*

iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1[NSThreadクラスの簡単な利用例]

公開日: : 最終更新日:2014/11/15 Swift , , ,


スポンサードリンク



前回はObjective-CにおけるNSThreadクラスの簡単な利用例を取り扱いましたが今回は、ほぼ同じ内容で言語をSwiftに変えたエントリーとなります。

言語が変わっただけでは面白くないですので、今回は画面上に存在するボタンを押したらスレッドが起動するとの例にしたいと思います。
前回はXCTestフレームワークを利用してマルチスレッドの検証を行ったので、メインスレッドを意識した検証ができませんでしたので、今回はこの点についても記載できたらと思います。

あとはマルチスレッドと言えば排他処理が避けて通れない処理であると言えますので、この点についても記載させていただきたいと思います。

本エントリーではSwift-CにおけるNSThreadクラスの簡単な実装サンプルとその説明を記載させていただきます。
利用しているXcodeは6.1です。

検証を行うためのプロジェクトの作成と準備

今回はiPhoneシュミレーターで動作する検証用のアプリケーションを利用して説明を行います。

プロジェクトの作成

まずプロジェクトを以下の情報で作成します。

templateは「Single view Application」
product nameには「MultiThreadSwiftSample」
Company identifierには「com.example」
LanguageにはSwift

検証を行う前の準備

画面に配置する画面(UI)コンポーネントとして

  • 排他処理でスレッドセースにした値を表示するラベル
  • プログラム内で利用するパラメータを指定するテキストボックスを2つと、そのテキストボックスの見出しのラベルを2つ
  • スレッドを実行するためのボタンを1つ

をそれぞれ用意します。
なお、検証用の画面ですのでオートレイアウト機能はOFFにします。

オートレイアウト機能はOFFに変更

Main.storyboardをエディタエリアに表示した状態でユーティリティエリアの「File Inspector」の「Use Auto Layout」のチェックを外します。
スクリーンショット 2014-11-05 16.36.17

すると以下の確認画面が表示されますので「Disable Size Classes」ボタンをクリックします。
スクリーンショット 2014-11-05 16.39.02

これでオートレイアウト機能が無効になりました。

各画面コンポーネントの配置

まずは排他処理でスレッドセースにした値を表示するラベルを配置します。
Main.storyboardをエディタエリアに表示した状態でユーティリティエリアの「Object Library」からLabelを以下の画像のようにドラッグ&ドロップします。
スクリーンショット 2014-11-05 18.24.32

同様に他のコンポーネントを配置します。

各画面コンポーネントのOutlet接続とイベント登録

プログラムから制御したい画面コンポーネントのOutlet接続と「開始」ボタンのタップ時のイベントハンドラーを登録します。

1つ目のテキストフィールドをOutlet接続してみます。
まずはアシスタントエディタをエディタエリアに表示し、左側にストーリーボード、右側にViewController.swiftを表示します。
その後、1つ目のテキストフィールドを「Control」ボタンを押しながらViewController.swiftにドラッグ&ドロッップします。
スクリーンショット 2014-11-05 18.41.19

すると以下の画面が表示されますので、NameにparamOneを入力し「Connect」ボタンをクリックします。
スクリーンショット 2014-11-05 18.53.16

これでViewController.swiftに以下の1行が追加されました。

同じように一番上のラベルと2つ目のテキストフィールドもOutlet接続してください。

「開始」ボタンのイベント登録は「開始」ボタンを「Control」ボタンを押しながらViewController.swiftにドラッグ&ドロッップします。
ここまではOutlet接続と同じですが、表示された画面の「Connection」をOutletからActionに変更します。
スクリーンショット 2014-11-05 19.01.34

これでViewController.swiftに以下の2行が追加されました。

「Connection」をOutletからActionに変更すると画面が以下のように変わりますので、NameにstartActionを入力して「Connect」ボタンをクリックします。
スクリーンショット 2014-11-05 19.04.15

操作後のUIViewController.swiftは以下のようになりました。

こられの操作を録画した動画は以下にございますので、操作方法上記説明で不明な方はご覧ください。
(画面コンポーネントをドラッグ&ドロップするときに「Control」ボタンを押している事が動画上で認識できませんが・・・)

NSThreadクラスを利用したマルチスレッド処理

NSThreadクラスを利用したスレッド作成の説明をいただきます。
スレッド作成方法として2通りの方法が存在します。

  • NSThreadのdetachNewThreadSelectorメソッドを利用する方法
  • NSThreadのサブクラスを作成する方法

これについてはObjective-CでもSwiftでも同じです。
当然言語が異なりますので、実装の実際は異なります。

detachNewThreadSelectorでスレッドを起動する方法

detachNewThreadSelectorの利用例

detachNewThreadSelector呼び出し時にオブジェクト(レシーバー)とスレッドで実行したいメソッド、及びそのメソッドの引数(id型)を指定します。
(第一引数の文字列がメソッド、toTargetがレシーバー、withObjectが引数)

まずはスレッドを起動し、NSLogでログを出力するだけの例となります。
ソースのコメントにも書いていますがスレッドメソッドの引数にparamOneを渡さなくてもthreadFuncからparamOneが参照できるのですが、あくまで利用例なので気にしないでください。

アプリを起動してパラメータ1に5を指定して「開始」ボタンをタップしたときのログは以下のようになりました。

startActionメソッドが実行されているスレッドのIDは1511813
threadFuncが実行されているスレッドのIDは1511984となっていることがログから分かります。
1511813と1511984のスレッドが個別に動作しているのがログから見て取れると思います。

なお

は以下のコードでも同様の処理となります。

これだけでは少し寂しいので、どのスレッドがメインスレッドで、現在スレッドがマルチで動作しているかをログで出力するようにしてみます。

メインスレッドを判定する前にカレントのスレッドを取得する必要があります。
カレントのスレッドは、NSThreadクラスのクラスメソッドのcurrentThread()->NSThreadを呼び出すことで取得可能です。
取得したNSThreadのインスタンスに対してisMainThread()->Boolを呼び出すことでメインスレッドかどうか判定できます。
(NSLogに出力するときは見やすいように三項演算子を用いてYESかNOを出力した方が見やすいです。)

現在スレッドがマルチで動作しているかはNSThreadクラスのクラスメソッドのisMultiThreaded()->Boolで判定できます。
なお、クラスメソッドとはJavaとC#ではスタティックメソッドと呼ばれており、インスタンス化していないクラスをレシーバーとして呼び出す事が可能なメソッドとなります。

変更後のstartActionとthreadFuncは以下のようになりました。

アプリを起動してパラメータ1に5を指定して「開始」ボタンをタップしたときのログは以下のように変わりました。

このログを見て不思議に思ったことがあります。startActionが実行された時点でもうマルチスレッドなんですね、isMainThreadはYESですから1535429はメインスレッドで間違いないですが、他にもこのアプリを動作させるのに必要なスレッドが動作しているってことなんでしょう!?

threadFuncの方にも追加したログはisMainThread=NO isMultiThreaded=YESとなっていますのでこちらは納得って感じですね。

NSThreadのサブクラスを作成する方法

NSThreadクラスのサブクラスを作成し、mainメソッドをオーバーライドします。
別スレッドとして実行したい処理はmainメソッド内で実装します。この方法に関してもObjective-Cと全く同じです。
mainメソッドから他のメソッドを呼び出してももちろんOKです。

detachNewThreadSelectorの利用例と同じ処理例

まずはdetachNewThreadSelectorのところで記載した利用例と同じ処理結果となるように処理を実装してみます。

・NSThreadのサブクラスを作成
Project navigatarで「MultiThreadSwiftSample」グループを選択し、マウスの右クリックメニューを表示し「New File…」をクリックします。
スクリーンショット 2014-11-06 13.29.14

以下の画面が表示されますので、「Cocoa Touch Class」を選択後に「Next」ボタンをクリックします。
スクリーンショット 2014-11-06 15.53.21

画面の内容が以下のように切り替りますので、ClassにNSThreadEx、Subclass ofにNSThread、LanguageにSwiftをそれぞれ指定し、「Next」ボタンをクリックします。
スクリーンショット 2014-11-06 15.58.33

再び画面が切り替りますので、何も変更せずに「Create」ボタンをクリックします。
スクリーンショット 2014-11-06 16.01.30

これでNSThreadEx.swiftが生成されましたので、NSThreadEx.swiftを以下のように修正します。
といってもViewControllerのthreadFuncメソッドを少し書き換えただけです。
(paramOneの扱いだけ異なります。)

あとはViewController.swiftのstartActionメソッドのスレッド起動部分を変更するだけです。
(paramOneとparamTwoのセット処理も追加)

ViewController.swiftのstartActionメソッドは以下のようになりました。

変更後にパラメータ1に3を指定して「開始」ボタンを押したときのログは以下のようになりました。

パラメータ1の数を減らしたのでfor文が回る回数が減っていますが、NSThreadのdetachNewThreadSelectorメソッドを利用した時の処理結果と一部の文言以外は同じ結果となっています。

パラメータ2の値だけスレッドを起動する例

先ほどの例を変更して、新規の複数のスレッドを起動できるようにします。
起動するスレッドの数はパラメータ2で指定されて値をします。

変更後のstartActionは以下のようになりました。

変更後にパラメータ1を3、パラメータ2に2を指定して「開始」ボタンを押したときのログは以下のようになりました。
メインスレッドが1897175で、新規に起動したスレッドが1897312と1897313となっていることがログから見てとれます。

スレッドの排他処理を含んだ例

Objective-Cだと@synchronizedで排他対象としてオブジェクトを指定して、処理を囲んでスレッドセーフな処理を実現することが可能です。

@synchronized(排他用いるオブジェクト) {
スレッドセーフにしたい処理をここで記述
}

複数のスレッドが@synchronized(排他用いるオブジェクト)の処理を実行したとしても、排他に用いるオブジェクトをロックできた単一のスレッドだけが実際の処理を実行します。
排他に用いるオブジェクトをロックできなかった他のスレッド(複数の可能性もあり)は、ロックが解放されるまで待って、排他に用いるオブジェクトのロックが取得できたスレッドのみが実際の処理を実行できます。これは一般的に良くしられた一番シンプルな排他制御の方法です。

しかし、Swiftでは@synchronizedはまだ利用できません。
変わりにobjc_sync_enterとobjc_sync_exitを利用します。

objc_sync_enter(排他に用いるオブジェクト)でオブジェクトのロック待ちとロックの獲得を、
objc_sync_exit(排他に用いるオブジェクト)でオブジェクトのロックの解放を行います。

排他に用いるオブジェクトですが、これは複数のスレッドかが認識できる同じオブジェクトである必要があります。
各スレッドが異なるオブジェクトに対してロックの取得を行った場合は、当然ですが全てのスレッドでロックの取得が成功してしまい、排他処理とはなりえないからです。

JavaやC#では任意のクラスにスタティックメンバを作成し、これを排他に用いるオブジェクトとして利用します。
こうすることで全てのスレッドが同じオブジェクト(ポインター)を排他の対象として動作することが可能になります。

しかし、SwiftにはJavaやC#のスタティックメンバに相当する定数や変数を宣言することが言語仕様上不可能となっています。
これを解消するために「Read-Only Computed Property」と構造体を併用し、構造体の中にstaticなオブジェクトを定義することでこの問題を解消します。

実際にはNSThreadExクラスに以下のような「Read-Only Computed Property」を実装します。

sharedInstanceにアクセスすることでNSThreadExクラスのスタティックメンバが取得可能となります。
これを排他に用いるオブジェクトとして利用することでマルチスレッドの排他処理が可能となります。

実際の排他制御を加えたNSThreadExクラスのmainメソッドは以下のようになります。

これで13、14行目のログ出力とスリープ処理がクリティカルセクションになります。

変更後にパラメータ1を3、パラメータ2に2を指定して「開始」ボタンを押したときのログは以下のようになりました。
新規に起動したスレッドが1963771と1963772となっており、NSThreadExクラスのmainメソッドの13、14行目が排他処理となっていることが確認できます。

具体的には
18:14:22.209 MultiThreadSwiftSample[20314:1963771] NSThreadEx 0
18:14:23.210 MultiThreadSwiftSample[20314:1963772] NSThreadEx 0
の2行の時刻が1秒以上の間隔があることから確認できます。
(この2行の間にsleep(1)が実行されている)

排他処理を追加する前のログだと
16:34:34.544 MultiThreadSwiftSample[19926:1897312] NSThreadEx 0
16:34:34.544 MultiThreadSwiftSample[19926:1897313] NSThreadEx 0
のように2行の間で時間差が存在していませんでした。
しかし排他処理が追加されたことによって、先にクリティカルセクションに侵入したスレッドでsleep(1)が終わってから、待たされていたスレッドが動き出したことが変更前と変更後のログから確認できます。


スポンサードリンク

Googleアドセンス

Googleアドセンス




関連記事

Xcode6(Swift)のデリゲートとプロトコルの使い方

Xcode(Objective-C)のデリゲートの使い方では利用言語がObjective-Cの時のデ

記事を読む

Swift入門(Xcode6のXCTestフレームワークで学ぶ) 第二回「関数(メソッド)とクロージャーの利用方法」

前回はSwiftの概要をザックリと説明させていただきました。 今回は「メソッドの宣言方法」をよ

記事を読む

2014年の秋のAppleのプレスイベントとXcode6-beta-7のインストール方法

2014年の秋のAppleのプレスイベント いよいよAppleは、米国西海岸時間9月9日午前10時

記事を読む

iOS(Xcode6とSwift)におけるマルチスレッド(非同期)処理の実装方法その2[GCD(Grand Central Dispatch)の利用]

「iOS(Xcode6とSwift)におけるマルチスレッド処理の実装方法その1」ではNSThread

記事を読む

Xcode6-Beta3でSwiftでBDDを行うためにQuickを導入し動作させる手順

Clone the repository Create a using Quick Pro

記事を読む

Swift入門[公式リファレンスのチュートリアルのSteps2の後半]

「Swift入門」では、Steps2の前半まで説明させていただきました。 本エントリーでは、

記事を読む

Xcode6の正式版がリリースされApp Storeからダウンロードしインストール可能になりました。

ついにSwiftが利用できるXcode6の正式版がリリースされました。 ブラウザからMac App

記事を読む

Xcode6_Beta5&SwiftでUITableViewでUINibを使ったカスタムセル(UITableViewCell)を利用する方法

前回は、「Xcode5&Objective-CでUITableViewでUINibを使ったカ

記事を読む

Swift(Xcode6-Beta2)でStringのメソッド(stringByReplacingCharactersInRange:withString:)のコンパイルが通らない時の対処

SwiftでStringのメソッド stringByReplacingCharactersInRa

記事を読む

Swift(Xcode6-Beta2)でMagicalRecord関連の処理を含んだDAOクラスのユニットテスト(UnitTest:XCTestフレームワーク)を実装する時にハマった事

SwiftでMagicalRecord関連の処理を含んだDAOクラスを作成し、ユニットテスト(XCT

記事を読む

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

動画で英語を学習できるiOSの無料アプリCapTubeをリリースいたしました。

個人では初となるiOSアプリをリリースいたしました。 何度もリジ

no image
Ruby on rails4系でBootstrapを利用するためのtips

MacでRuby on rails4系のBootstrapを利用しよう

no image
Java、Eclipse、JUnit関連のエントリーの移行のお知らせ

Java、Eclipse、JUnit関連のエントリーは http:/

iOS8開発者向けお勧め本紹介[詳細! Swift iPhoneアプリ開発 入門ノート Swift 1.1+Xcode 6.1+iOS 8.1対応]

iOS7開発者向けお勧め本紹介を以前に紹介させていただきまいたが、今回

Swift入門(Xcode6のXCTestフレームワークで学ぶ) 第二回「関数(メソッド)とクロージャーの利用方法」

前回はSwiftの概要をザックリと説明させていただきました。 今

→もっと見る

Optimization WordPress Plugins & Solutions by W3 EDGE
PAGE TOP ↑