[PR]
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
そうだ、タイマーを使おう
iPhoneやiPod touchの場合、表に見えているのは自分の作ったアプリケーションですが、全部の制御がそのアプリケーションに移っている訳ではなく、裏側で常にシステム側から監視をされています。分かりやすいように書くと、自分のアプリケーションがシステム側から呼ばれた時にお行儀よく返事をしていれば全く問題ないのですが、ついつい重たい処理に夢中になって返事を忘れると、システム側が今動いているプログラムがおかしくなったり、暴走しているのではと疑われる、といったような事が起ります。そのまま返事をしないでいると、お行儀の悪いプログラムは停止してやると、システム側から終了処理をされてしまうというわけですね。
じゃあ長い時間のかかる処理はできないのかというと、システムへの応答を正常に行わせつつループ処理を行わせる方法がちゃんと用意されているので、それを使います。それがNSTimerを使う方法です。以下に一例をあげます。まずは下準備として、NSTimer型の変数をヘッダファイル(hファイル)で定義します。
NSTimer *loadTimer;
プロパティを設定します。
@property (nonatomic, assign) NSTimer *loadTimer;
@synthesize loadTimer;
- (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];
- (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できちんと動けば問題ないでしょう。
Tiny3D 2.0 マスターアップしました
- DXF形式ファイルのインポートに対応しました。インポートできるファイルサイズに制限(Tiny3D形式へ変換後のファイルサイズが10メガバイト以内)はありますが、一般的な3Dエディタからの出力で使われる3DFACEと、LINEおよびPOINTオブジェクトの取り込みが可能です。
- HTTPによるダウンロードを簡易Webブラウザから行うように変更しました。ダウンロード可能なファイル形式(Tiny3D形式、DXF形式)のリンクをタップすると、ダウンロードを行うかを選択するウインドウが開きます。アドレスを直接指定してページを開くことやブックマークを作り保存する機能もあります。ちなみに初めにサンプルのダウンロード用ページが開くようになっています。リンクからDXF形式に変換されたサンプルのインポート用ページへも移動できます。
- マクロリストの表示(位置と色)を左上に新設したボタンで切り替えるようにしました。
- 色の選択をACI(AutoCADカラーインデックス)準拠のパレットから行えるようにしました。色設定のマクロへ取り込むことも可能です。
これまでの機能も総合すると、いろいろ細かい制約はあるものの、以下のようなことも出来ます。
- インターネット上にあるDXFファイルのリンクをクリックし、データを取り込む。(FTPでも取り込めます。)
- データの中にある色をマクロに取り込み、オブジェクト単位などで一気に取り込んで作ったマクロ情報とリンクさせる。
- マクロリスト側でリンクした色をACI準拠カラーへ変更することで、オブジェクトの色を変える。(リンクさせる単位をパートにすれば、もとの色が同じでも各レイヤー単位で別の色へと変更することも可能です。)
- FTPやメールで変更した色情報を含めた状態でDXFファイルとしてデータを送信する。
急がば回れ?
Tiny3D 1.6.0 リリースしましたが…
デバッグ中に急に動作が止まる場合に考えられること
1.別の画面にスクロールさせようとしたら止まる場合
多くの場合、InterfaceBuilder(以下IBと略します)でスクロール先のView関係の定義を忘れています。俗に紐付けといわれている、コード内の名称(IBOutletで宣言しているもの)とIB側にあるオブジェクトとの関連付けをするのを忘れている場合。nibファイルを作り忘れている場合や、その作ったファイルのクラス設定を忘れている場合などが原因として考えられます。
2.別の画面から戻ったら止まった場合
オブジェクトのメモリ管理ミスが多いです。例えば下の階層のビューへ設定用にオブジェクトを渡した場合、上の階層に戻る時に下のビューの表示が終わると、下のビューのコントローラーは(上の階層のビューコントローラーの変数にでもして組み込んでいなければ)開放されます。この時渡していたオブジェクトを間違って2重に開放してしまっていると、上の階層でそのオブジェクトにアクセスしようとした際に、既にオブジェクトが破棄されてしまっているためエラーになります。
3.時間のかかる処理をさせていて止まった場合
シミュレーターでは動くし、実機でもXcodeから起動させれば問題なく動くのに、実機単独では処理中に止まってホーム画面に戻ってしまうという場合です。数十秒かかるような処理の場合、実機単独の場合にはアプリが応答不能に陥ったと判断され、自動でホーム画面に戻る仕様となっています。起動直後の設定やデータ読み込みなどが複雑で時間がかかる場合などでは、起動させても止まってホーム画面に戻ってしまいます。Xcode経由で止まらないのは、デバッグではブレークポイントで長時間止めたままになることも有り得るため、それらの時間制限が無効化されているからです。あまりに処理時間がかかるような処理は、間にユーザーの応答をさせつつ実行してゆくようにする必要があります。起動後のほうが少し判定間隔が長いようなので、時間のかかる処理は起動時ではなく起動後にするようにするようにしたほうが安全かもしれません。
Tiny3D 1.5.0 ダウンロード開始しました
数種類のセルを使用してもスクロール速度の低下を極力減らす方法
最近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 alloc] initWithFrame: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 alloc] initWithFrame: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 alloc] initWithFrame:CGRectZero reuseIdentifier:@"Cell"]
autorelease];
}
return cell;
}
Tiny3D 1.5.0 アップロードしました
基本図形(エレメント)を3種類追加し、全19種類としました。
従来からあるエレメント詳細設定画面の頂点設定部分のレイアウトを変更しました。
バグ修正:
前回のバージョンアップで追加した最大FPS項目がリセットに反応していなかったため修正しました。
四角形(平行四辺形)、三角錘(タイプB)、四角錘、三角柱、四角柱エレメントで辺2の長さが正しく表示されていない不具合を修正しました。
以上です。エレメントの追加に伴い、今回からサンプルのダウンロードリストを別ファイルに分けました。これまでのバージョンとは別のリストを参照するため、1.5.0以上対応のサンプルは1.5.0以上でしかリストに表示されなくなります。
ちなみに今回追加になったエレメントは連続線、連続三角形、三角扇の3種類です。OpenGLのLineStrip、TriangleStrip、TriangleFanに相当します。連続三角形、三角扇には今回もオプションに塗りスイッチが付いているため、ワイヤーフレーム表示への切替もできます。今回追加したエレメントはこれまでのものとは異なり、後から頂点数を自由に変更できます。また、頂点の順番を後から入れ換えることもできます。そのため従来とは異なる専用のエレメント詳細設定画面を用意しました。