忍者ブログ

[PR]

2024年04月23日
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

そうだ、タイマーを使おう

2009年06月19日
iPhoneアプリケーション開発において、軽い処理のものを書いているうちは問題ないのですが、時間のかかる重い処理をさせる場合、実機単体で実行させた時に、通常のコンピューター用で書くなら特に問題がないはずのコードでも途中で止まってしまう事があります。それはWindowsで言えばマウスカーソルが砂時計になる処理、Macで言えばマウスカーソルが色付きの車輪のようになりくるくる回っているような処理の部分で発生します。

iPhoneやiPod touchの場合、表に見えているのは自分の作ったアプリケーションですが、全部の制御がそのアプリケーションに移っている訳ではなく、裏側で常にシステム側から監視をされています。分かりやすいように書くと、自分のアプリケーションがシステム側から呼ばれた時にお行儀よく返事をしていれば全く問題ないのですが、ついつい重たい処理に夢中になって返事を忘れると、システム側が今動いているプログラムがおかしくなったり、暴走しているのではと疑われる、といったような事が起ります。そのまま返事をしないでいると、お行儀の悪いプログラムは停止してやると、システム側から終了処理をされてしまうというわけですね。

じゃあ長い時間のかかる処理はできないのかというと、システムへの応答を正常に行わせつつループ処理を行わせる方法がちゃんと用意されているので、それを使います。それがNSTimerを使う方法です。以下に一例をあげます。まずは下準備として、NSTimer型の変数をヘッダファイル(hファイル)で定義します。

NSTimer *loadTimer;


プロパティを設定します。

@property (nonatomic, assign) NSTimer *loadTimer;

 
プロパティを設定したら、メソッドファイル(mファイル)に@synthesizeマクロを追加します。

@synthesize loadTimer;

 
タイマーオブジェクトはループが回っている間だけ存在し、使わなくなったらnil、つまり空の状態にするので、deallocが呼ばれた時にreleaseをする必要はありません。次にタイマーの値を変える時に呼び出されるsetterと呼ばれる命令を書いておきます。

- (void)setLoadTimer:(NSTimer *)newTimer {

[loadTimer invalidate];

loadTimer = newTimer;

}


invalidateというのは発動待ち状態のタイマーを止める命令です。今回のような使い方だと自動リピートでは無いので不要の可能性もありますが、念のため書いてあります。そしてループのカウントをとる変数を用意します。

NSInteger loopCount;


ループの開始処理をもともとループを行っていた部分に書きます。例えばloopCountを0にするなどです。その後に、1回目のタイマー発動命令を書きます。


self.loadTimer = [NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(loadRootDictionary:) userInfo:nil repeats:NO];

 

 
1個目の引数がタイマー発動の間隔です(単位は秒です)。0にしてもループの間には上で言うところの「行儀の良いお返事」はちゃんとしてくれますので問題ありません。2番目と3番目の引数でいざタイマーが発動した時、何処にあるどの命令を実行するのかを指定しています。ここでは、自分自身(通常は同じmファイル内です)の中にある、loadRootDictionaryという命令を実行するように指示しています。4番目の引数は特殊なことをするのでなければnilで良いでしょう。5番目の引数でリピートさせるかを指示しますが、無限ループではなく、状況判断をさせて終了条件を満たしたらそこで終わらせるというタイプにしたいのでNOにします。次にタイマーで呼ばれる命令を書きます。

- (void)loadRootDictionary:(NSTimer *)theTimer {
// ここの部分にループでさせたい処理を書きます。
loopCount++;

if(loopCount<10) {

self.loadTimer = [NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(loadRootDictionary:) userInfo:nil repeats:NO]; 

else {

[loadTimer invalidate];

self.loadTimer = nil;

// ここの部分にループ終了後にさせたい処理を書きます。
}

}


上の場合だとloopCountが10より小さければ再度タイマーを発動してこの命令を実行し、10になったらタイマーを停止してタイマーオブジェクトを空にします。これでタイマー処理への移行は終了です。

なお、このようなタイマー処理への移行で気をつけなければならないのが、タイマー発動命令を書いたからといって、そこですぐにタイマーが発動して処理がそちらへと移る訳ではない、ということです。そのあとの行の命令が実行されてゆき、少なくともそのメソッドの単位を抜け出した後になってようやくタイマーが発動し、上の例で言うところのloadRootDictionaryが呼ばれます。ですからもし移行前の状態でタイマーの初回発動命令の直後に、上の例で言えばloadCountが10であることが前提となる命令が書いてあった場合には、当然ながら問題が起きます。それらの処理は上の命令にある、ループ終了後にさせたい処理を書くようにコメントしている部分へと移動させましょう。なお、そこでさらに別のタイマーを発動させれるようにすれば、数個のループ処理を順番で行わせることも出来ます。
 
2010年2月11日追記:
タイマー発動までの間隔が0.0の場合、処理の負荷が強いと画面の更新(プログレスバーの値の変更等)がうまく行われないことがあります。そのような場合は間隔を0以外の値(0.01など)にすると画面の更新処理も無事に行われるようになります。どの程度の値にするかは処理の重さで変わる可能性があるので、実機で試しながらベストな値を探る事になるようです。最も遅いiPhone 3Gや第1世代iPod touchできちんと動けば問題ないでしょう。
PR
Comment
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字
Trackback
トラックバックURL: