忍者ブログ

[PR]

2025年01月31日
×

[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

Tiny3D 2.0 マスターアップしました

2009年06月16日
先日ほぼ完成していますと書いたTiny3D最新バージョンである2.0ですが、先ほどアップロードしました。今回のバージョンアップの要点をまとめると以下のようになります。

  • DXF形式ファイルのインポートに対応しました。インポートできるファイルサイズに制限(Tiny3D形式へ変換後のファイルサイズが10メガバイト以内)はありますが、一般的な3Dエディタからの出力で使われる3DFACEと、LINEおよびPOINTオブジェクトの取り込みが可能です。
  • HTTPによるダウンロードを簡易Webブラウザから行うように変更しました。ダウンロード可能なファイル形式(Tiny3D形式、DXF形式)のリンクをタップすると、ダウンロードを行うかを選択するウインドウが開きます。アドレスを直接指定してページを開くことやブックマークを作り保存する機能もあります。ちなみに初めにサンプルのダウンロード用ページが開くようになっています。リンクからDXF形式に変換されたサンプルのインポート用ページへも移動できます。
  • マクロリストの表示(位置と色)を左上に新設したボタンで切り替えるようにしました。
  • 色の選択をACI(AutoCADカラーインデックス)準拠のパレットから行えるようにしました。色設定のマクロへ取り込むことも可能です。

これまでの機能も総合すると、いろいろ細かい制約はあるものの、以下のようなことも出来ます。

  1. インターネット上にあるDXFファイルのリンクをクリックし、データを取り込む。(FTPでも取り込めます。)
  2. データの中にある色をマクロに取り込み、オブジェクト単位などで一気に取り込んで作ったマクロ情報とリンクさせる。
  3. マクロリスト側でリンクした色をACI準拠カラーへ変更することで、オブジェクトの色を変える。(リンクさせる単位をパートにすれば、もとの色が同じでも各レイヤー単位で別の色へと変更することも可能です。)
  4. FTPやメールで変更した色情報を含めた状態でDXFファイルとしてデータを送信する。

急がば回れ?

2009年06月15日
これまで私のiPhoneソフト開発&バージョンアップは、先にソフトを審査してもらい、審査中にHPなどを更新するというスタイルをとってきました。これは審査に時間がかかることや、リジェクトされた場合に修正を早く行える状態にするなど、最終的なリリース日を少しでも早くしようという作戦でした。しかし、前回のバージョンアップではその結果として多くのバグを内包したままリリースをしてしまいました。前回の反省から、今回は先にHPの更新作業を行うためのスクリーンショット回収を行ってきました。これにより、まだアップルへの提出前だというのにバージョン2.0の日本語マニュアルがほぼ完成状態になっています。もともとは順当にバージョン1.7.0にしようかと思っていたのですが、システムの根本的な部分の変更を含む大手術となったため、バージョン番号は一気に2.0まで跳ね上がることになりました。マニュアルに目を通していただけば、機能の追加・強化、インターフェースの変更、ファイル管理システムの変更などかなり広範囲にわたる手入れが行われたことがわかるのではないでしょうか。あとはソフト内部の簡易マニュアルを変更すれば作業終了、アップロードとなりそうです。

http_download4.PNGaci.PNGmanual_save_noneed.PNG









Tiny3D 1.6.0 リリースしましたが…

2009年06月06日
Tiny3Dのバージョン1.6.0がダウンロード可能になりました。今回のバージョンアップの目玉はライティングの追加なのですが、その機能追加のためにいろいろ内部を書き直しました。そのチェックが不十分だったため、細かいところでいろいろ不具合があります。正直な感想としては、よく審査が通ったなと思えるほどです。特に壁面スイッチのあるオブジェクトの描画で問題があり、最悪の場合強制終了になるほどです(汗)。現在開発中のバージョン1.7.0でそれらの不具合は一気に解消できますので少々お待ちください。バージョン1.7.0では完全ではありませんがDXFファイルのインポートが可能になる予定です。現状では三角および四角のポリゴン(3DFACE)、直線(LINE)、点(POINT)に関して取り込み可能となっています。カラーインデックスも認識します。その認識ついでに色の入力でもACI(AutoCADカラーインデックス)準拠のパレットを使えるようにするなど、内部にいろいろ手を加えています。また、時間のかかる処理に関して進捗具合をバーで表示するようにしています。次こそはなるべくバグを減らそうと、動作検証にかなりの時間を割いていますので、今回のような大きなバグは無くなると思われます。

デバッグ中に急に動作が止まる場合に考えられること

2009年05月26日
デバッグ中に急にアプリケーションが反応しなくなった場合、コンソールに出力されるエラーメッセージでエラーの原因がわかる場合が多いです。でも、中にはよくわからないうちにホーム画面に戻ってしまっており、解決のヒントが少ない状態からエラーを探さなければならないこともあります。このような場合で自分が良く遭遇した原因は以下のようなものです。これからiPhone向けソフト開発で初めてObjective-Cに触れるという方の参考になるかもしれないので書き留めておきます。

1.別の画面にスクロールさせようとしたら止まる場合

多くの場合、InterfaceBuilder(以下IBと略します)でスクロール先のView関係の定義を忘れています。俗に紐付けといわれている、コード内の名称(IBOutletで宣言しているもの)とIB側にあるオブジェクトとの関連付けをするのを忘れている場合。nibファイルを作り忘れている場合や、その作ったファイルのクラス設定を忘れている場合などが原因として考えられます。

2.別の画面から戻ったら止まった場合

オブジェクトのメモリ管理ミスが多いです。例えば下の階層のビューへ設定用にオブジェクトを渡した場合、上の階層に戻る時に下のビューの表示が終わると、下のビューのコントローラーは(上の階層のビューコントローラーの変数にでもして組み込んでいなければ)開放されます。この時渡していたオブジェクトを間違って2重に開放してしまっていると、上の階層でそのオブジェクトにアクセスしようとした際に、既にオブジェクトが破棄されてしまっているためエラーになります。

3.時間のかかる処理をさせていて止まった場合

シミュレーターでは動くし、実機でもXcodeから起動させれば問題なく動くのに、実機単独では処理中に止まってホーム画面に戻ってしまうという場合です。数十秒かかるような処理の場合、実機単独の場合にはアプリが応答不能に陥ったと判断され、自動でホーム画面に戻る仕様となっています。起動直後の設定やデータ読み込みなどが複雑で時間がかかる場合などでは、起動させても止まってホーム画面に戻ってしまいます。Xcode経由で止まらないのは、デバッグではブレークポイントで長時間止めたままになることも有り得るため、それらの時間制限が無効化されているからです。あまりに処理時間がかかるような処理は、間にユーザーの応答をさせつつ実行してゆくようにする必要があります。起動後のほうが少し判定間隔が長いようなので、時間のかかる処理は起動時ではなく起動後にするようにするようにしたほうが安全かもしれません。

追記 "デバッグ中に急に動作が止まる場合に考えられること"

Tiny3D 1.5.0 ダウンロード開始しました

2009年05月21日
iTunes Connectでふとアプリケーションのステータスを確認してみたところ、Tiny3Dバージョン1.5.0の承認がおりていて、iTunes Storeでダウンロード可能になっていました。今回のバージョンアップでの機能強化の詳細は以前のP『Tiny3D 1.5.0 アップロードしました』に書かれているとおり、基本図形3種の追加がメインです。先ほどiTunes Storeの画像を更新したばかりなので、まだ向こうはスクリーンショットが更新されていません。一応こちらにも更新後に表示されるはずのスクリーンショットを載せておきます。

tiny3d1.jpgtiny3d2jp.jpgtiny3d3jp.jpgtiny3d4jp.jpgtiny3d5jp.jpg









数種類のセルを使用してもスクロール速度の低下を極力減らす方法

2009年05月19日
以前の記事『テーブルのセルへの参照を2書類以上混在させる』で数種類のセルを混在させる方法を書きましたが、あの方法には実は大きな欠点がありました。その欠点はテーブルが画面に納まりきる場合には全く問題にならないのですが、テーブルのセル数が多くスクロールさせる必要がある場合に大問題になります。そう、スクロール速度がとても遅くなるのです。

最近Tiny3Dで各基本図形の詳細設定をする画面のテーブルスクロールを少しでも高速にしようといろいろ試行錯誤していてわかったのですが、セルの再利用を行えるかの判定部分の処理はかなり重たいようです。スクロール時には画面に表示するセルが次々変化するため、その度に行われるセル再利用判定がスクロールの邪魔をしてしまっていたのです。

つまり、それらの判定を必要最小限に行うようにした方がテーブルのスクロールを高速に出来ます。そのためには、たとえ複数回の重複部分が発生してコードの行数が多くなろうと、セルのセクションや行での分岐後で、そのとき必要なセルに限定した再利用判定をした方が良いということになります。以前のコードをテーブルのスクロール速度も考慮したものに変更すると、以下のように書き換えられます。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

switch (indexPath.row) {

case 0: {

HorizontalSwitchTableViewCell *sCell =
  (HorizontalSwitchTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalSwitchTableViewCell"];

if (sCell == nil) {

sCell = [[[HorizontalSwitchTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalSwitchTableViewCell"]autorelease];

}

sCell.selectionStyle = UITableViewCellSelectionStyleNone;

sCell.myDictionary = self.myDictionary;

sCell.editedKey = @"display";

[sCell.switchCtl setOn:[[myDictionaryobjectForKey:@"display"]boolValue]
animated:NO];

 

sCell.nomalLabel.text = NSLocalizedString(@"Display",@"");

return sCell;

break;

}

case 1: {

UITableViewCell *cell = [tableView
  dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {

cell = [[[UITableViewCell allocinitWithFrame:CGRectZero
reuseIdentifier:@"Cell"autorelease];

}

cell.font = [UIFont systemFontOfSize:20];

cell.text = [NSString stringWithFormat:NSLocalizedString(@"Name
%@",@""),[myDictionary objectForKey:@"name"]];

return cell;

break;

}

case 2: {

HorizontalPositionTableViewCell *pCell =
  (HorizontalPositionTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalPositionTableViewCell"];

if (pCell == nil) {

pCell = [[[HorizontalPositionTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalPositionTableViewCell"]  autorelease];

}

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Center",@"");

pCell.leftValueLabel.text = [NSStringstringWithFormat:@"%f",[[myDictionary
  objectForKey:@"centerx"]floatValue]];

pCell.centerValueLabel.text = [NSString
  stringWithFormat:@"%f",[[myDictionary objectForKey:@"centery"]floatValue]];

pCell.rightValueLabel.text = [NSStringstringWithFormat:@"%f",[[myDictionary
  objectForKey:@"centerz"]floatValue]];

return pCell;

break;

}

case 3: {

HorizontalPositionTableViewCell *pCell = 
(HorizontalPositionTableViewCell *)[tableView
 
dequeueReusableCellWithIdentifier:@"HorizontalPositionTableViewCell"];

if (pCell == nil) {

pCell = [[[HorizontalPositionTableViewCell alloc]
  initWithFrame:CGRectZero
reuseIdentifier:@"HorizontalPositionTableViewCell"]autorelease];

}

pCell.enabledSetting = YES;

pCell.titleLabel.text = NSLocalizedString(@"Rotate",@"");

pCell.leftValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatex"]floatValue]];

pCell.centerValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatey"]floatValue]];

pCell.rightValueLabel.text = [NSString
stringWithFormat:@"%f",[[myDictionary objectForKey:@"rotatez"]floatValue]];

return pCell;

break;

}

case 4: {

UITableViewCell *cell = [tableView
  dequeueReusableCellWithIdentifier:@"Cell"];

if (cell == nil) {

cell = [[[UITableViewCell allocinitWithFrame:CGRectZero
reuseIdentifier:@"Cell"autorelease];

}

cell.font = [UIFont systemFontOfSize:20];

if ([myDictionary objectForKey:@"magnification"] == nil) {

[myDictionary setObject:[NSNumber numberWithFloat:1]
  forKey:@"magnification"];

}

cell.text = [NSStringstringWithFormat:NSLocalizedString(@"Magnification:
  %f",@""),[[myDictionary objectForKey:@"magnification"]floatValue]];

return cell;

break;

}

}

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    if (cell == nil) {

        cell = [[[UITableViewCell allocinitWithFrame:CGRectZero reuseIdentifier:@"Cell"]
        autorelease];

    }

    return cell;

}

 
なんだかぱっと見では複数回同じコードが見えていて簡潔でなくなったように見えます。しかし実機を用いてスクロールさせた場合の速度は、このように記述しておいた方が明らかに速くなります。なお前回も書きましたが、最後の部分は標準設定でビルドしたときの警告回避用なので、実際使われる事はありません。

Tiny3D 1.5.0 アップロードしました

2009年05月09日
久々にTiny3Dをバージョンアップします。今回のバージョンアップ項目は、

基本図形(エレメント)を3種類追加し、全19種類としました。
従来からあるエレメント詳細設定画面の頂点設定部分のレイアウトを変更しました。

バグ修正:
前回のバージョンアップで追加した最大FPS項目がリセットに反応していなかったため修正しました。
四角形(平行四辺形)、三角錘(タイプB)、四角錘、三角柱、四角柱エレメントで辺2の長さが正しく表示されていない不具合を修正しました。

以上です。エレメントの追加に伴い、今回からサンプルのダウンロードリストを別ファイルに分けました。これまでのバージョンとは別のリストを参照するため、1.5.0以上対応のサンプルは1.5.0以上でしかリストに表示されなくなります。

ちなみに今回追加になったエレメントは連続線、連続三角形、三角扇の3種類です。OpenGLのLineStrip、TriangleStrip、TriangleFanに相当します。連続三角形、三角扇には今回もオプションに塗りスイッチが付いているため、ワイヤーフレーム表示への切替もできます。今回追加したエレメントはこれまでのものとは異なり、後から頂点数を自由に変更できます。また、頂点の順番を後から入れ換えることもできます。そのため従来とは異なる専用のエレメント詳細設定画面を用意しました。