忍者ブログ

[PR]

2024年04月20日
×

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

アップル社のイベントで分かった事

2009年09月10日
今回のイベントで判明したことのうち、iPhoneデベロッパに関係しそうなことは

1.iPhone OS 3.1本日より提供(初掲載時、提供が近いと書きましたが既に提供されています)

第3世代iPod touchは始めからOS 3.1搭載になる。
第2世代以前でOS 3.0 にアップデートしているユーザーへは無料で提供される。

2.iTunes 9本日より提供

アプリケーションの管理に変更あり。OS 3.1ではホーム画面編集を母艦で行える。

3.第3世代iPod touchは2グレードへ

8GB版と32および64GB版とでは機能に差がある。
音声コントロールやOpenGL ES2.0対応などは32および64GB版のみ対応。
噂になっていたカメラ搭載はされず。コンパスや無線のn対応もなし。
BluetoothはiPhone 3GS同等へ。32および64GB版でも本体にはマイクなし。
8GB版にマイク付きイヤホンを刺した場合などにマイクが使えるのかは不明。
(ボイスレコーダーは8GB版でも使える?)

こんなところでしょうか。カメラなしと64GB版投入、2グレード制導入については予想が外れましたね。機能面では容量以外はカメラ、コンパス、内蔵マイクなどでiPhone 3GSにアドバンテージありというところでしょうか。

2009年9月10日14:35追記
アップル社が今回発表した機種群をiPod touch 2G 2009と呼んでいるらしいという情報があるようです。それをもとに考えると8GB版はこれまでの第2世代iPod touchにOS 3.1を搭載したもので、32および64GB版はCPUなどをiPhoneの3G→3GSのようにパワーアップしたものと、シンプルに説明できるなと思いつきました。

32および64GB版での最大+50%の高速化、の比較対象は当然iPhone 3Gではなく、それよりちょっと高速なこれまでの第2世代iPod touchになるものと考えると、CPU速度はiPhone 3GSと同等程度のようです。第2世代iPod touchの時のようにiPhone 3GSより高速なCPUとはいかないかもしれません。
(初掲載時高速化の割合を間違えていました。iPhone 3GSとの速度比較予想を含め修正しています。CPUの動作クロック、システムバスクロックはiPhone 3GSと変わらないとの情報も出てきています。)

ちなみにこれら3機種を比較すると、8GB→32GBでは価格はおよそ1.5倍ですが、2倍速のCPUと4倍の容量が手に入ります。それでバッテリーの持ちは変わりません。付加機能も64GBと変わりません。でも32GB→64GBでは価格はおよそ1.3倍ですが容量が2倍になるだけです。このように考えると、1台目として買うなら32GBモデルがおすすめなのかなと思います。第1世代iPod touchの32GBモデルが登場時におよそ6万円していたことを考えると、ずいぶんと安くなったものだなと思います。
PR

自分のアプリケーションのデータを標準のメールで送る

2009年06月29日
これまでは自分のアプリケーションで作ったデータをiPhoneから外へ出そうとすると、様々な困難がありました。標準のメール機能へファイルを受け渡す事が出来なかったからです。画像については標準の写真アプリにファイルを送る事が出来るため、写真アプリへデータを渡し、写真アプリを開いて画像を表示、さらにアクションからメールへ添付することで一応外へ出す事が出来ましたが、それもかなり回り道をしている状態でした。

私も自分のアプリケーションが持つデータを外へ出すために四苦八苦し、自前でSMTPやFTPのクライアント機能を実装してなんとか出口を作ってきました。ですがこれからはそんな苦労をしなくても簡単にメール添付でのデータ送信ができます。それは3.0ではMessageUIが追加されたからです。実際の利用方法はアップル提供のサンプル「MailComposer」を詳しく見れば分かると思います。簡単に説明すると、まずメール送信のビューへ進みたいビューのコントローラーのヘッダファイルへ

#import <MessageUI/MessageUI.h>

#import <MessageUI/MFMailComposeViewController.h>

を追加し、さらにMFMailComposeViewControllerDelegateに反応させるようにします。あとはメソッドファイル側でMFMailComposeViewController*型の変数を作り、必要なら宛先やCC、BCCの情報をそれぞれNSArrayにして渡します。さらに添付データをNSDataで、本文をNSStringで渡します。最後にこのメール編集用のコントローラーを表示させます。

メールを送信したか等の結果は

- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error


で受け取ります。当たり前ですがMessageUI.frameworkの追加は忘れずに行いましょう。MailComposerのプロジェクトを開いておいて、自分のプロジェクトへドラッグアンドドロップしてしまうのが簡単で良いと思います。

巨大化したメソッドファイルをカテゴリを使って分離する

2009年06月20日
アプリケーションの処理でついつい代表クラスへ丸投げをしていると、代表クラスがどんどん肥大してゆきます。Tiny3Dは代表クラスのメソッドが8千行を超えていた時期もありました。ここまで肥大化させてしまうとXcodeの動作が不安定になりやすいようで、コード補完が妙な動きをしてレイアウトが崩れ、編集メニューの「取り消す」でも取り消せずさらに崩れるような状態になったりします。そのような場合に有効な解決策が、カテゴリの導入です。

まず、新規で分離するファイルと同じ名前に、何か付け足したメソッドファイルを作ります。ヘッダファイルは作らないようにチェックは外しておきます。@implementationの部分に以下のように括弧でくくってカテゴリ名を付け足します。先ほどのファイル名に付け足すものというのはカテゴリ名にしておくと分かりやすいでしょう。


@implementation
Tiny3DAppDelegate (dxf)


そして、その前の部分に移動するメソッドで必要なインポートや定義を書き加えます。そしてヘッダファイルの最後にある@endのさらに後ろにそのカテゴリに移動させるメソッドを宣言しておきます。書き方はこれまでにヘッダファイルでやっていたメソッドの宣言と同じです。違うのはその宣言部分の前後を

@interface Tiny3DAppDelegate (dxf)


@end


で挟むということだけです。括弧の中は先ほど書いたカテゴリ名と同じにします。あとは元からあったメソッドファイルから、新しく作ったメソッドファイルへとメソッドを移動します。これで巨大になったメソッドファイルを分割することができます。同じようにしてカテゴリを複数作れば、メソッドファイルを複数のファイルに分散させる事も出来ます。メッセージ類は今までと全く同じままで良く、オブジェクトの内外ともに書き換える必要はありません。ちゃんとカテゴリ内のメソッドへもメッセージが届きます。まあ、そのためにヘッダ側でメソッドを宣言しているわけですが。なお、オブジェクト内部の変数はこれまで通りヘッダファイルの元々のインターフェース内で宣言、@synthesizeも元からあるメソッドファイルへと書くようにしましょう。
 

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

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できちんと動けば問題ないでしょう。

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

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

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

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

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

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

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

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

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

アラートを出す その2

2009年02月27日
前回の記事では、単純にOKボタンのみを表示するアラートを出しました。今回は2個目のボタンを付けて、どちらのボタンが押されたかで動作を変えられるようにする方法についてです。まず、2個目のボタンは、前回nilだったotherButtonTitlesにボタン用の文字列を追加すれば簡単に追加できます。以下に例を示します。


alertType1;

UIAlertView *alert = [[UIAlertView alloc]

initWithTitle:NSLocalizedString(@"Tiny3D",@"title")

message:NSLocalizedString(@"Reset all settings?",@"message")
delegate:self cancelButtonTitle:NSLocalizedString(@"Cancel",@"")
otherButtonTitles:NSLocalizedString(@"Reset",@""),nil];

[alert show];

[alert release];


これはツールの設定をリセットする項目を選んだ時に実行するコードです。alertTypeというのはヘッダーで宣言したNSInteger型の変数で、これは別に他の名前でも構いません。同じビューコントローラから複数のアラートを表示するときの場合分けをするために作っています。これで表示されるアラートには、日本語環境でキャンセルとリセットという2個のボタンが表示されます。なお、otherButtonTitlesの引数カンマ区切りで列挙し最後にnilがきます。では次にボタンが押された時の動作を追加しましょう。そのためにはまず、このビューコントローラがアラートからのメッセージを受信するようにする必要があります。具体的にはまずヘッダーファイルの@Interface部分を変更します。

@interface SettingTableViewController : UITableViewController <UIAlertViewDelegate> {

 
この<UIAlertViewDelegate>を追加しておく事で、ビューコントローラがアラート表示終了時のメッセージを受信できます。そこで次にメッセージを受信したときに実行される命令をメソッドファイルに追加します。

- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

{

if(buttonIndex == 1) {
// 押されたのは2番目のボタンです。

if(alertType == 1){

// リセット用のアラートでリセットボタンが押されています。

// ここに具体的なリセット用のコードを書きます。

}

}

}

 
buttonIndexは一番左(3個以上ならボタンは縦配列になるので一番上)のボタンが0で順番に1増えます。上の例ではキャンセルが0でリセットが1です。キャンセルの場合は特に何もする必要がないので分岐を用意していません。リセットする場合はbuttonIndexが1になるのでそれを条件に分岐します。他のアラート(マクロの全削除の確認用などで出しているアラート)と区別するため、さらにalertTypeの値で分岐します。ここにリセットに必要なコードを記述します。別のアラートでもボタンによりアクションが必要なら、別のalertTypeの値を作っておいて分岐させれば良いでしょう。

2010年4月28日追記:
UIAlertViewはUIViewの派生クラスなのでメンバ変数でtagという値を持っています。それを使うことでも分岐を行わせることが出来ます。例えば上のアラートの表示設定でalert.tag = 1;としておき、分岐判定コードではalertTypeのかわりにactionSheet.tag==1などの条件にすれば同じような動作をさせられます。

アラートを出す

2009年02月27日
ユーザーへの通知方式として、一番確実なのはアラートの表示でしょう。これを使うと、画面が暗くなって、中央にメッセージを表示します。OKボタンを押すと元の画面に戻ります。今回はTiny3DのFTPアップロードが終わった時を例にして説明します。まず、アップロードが終わるとコールバック関数から代表クラス(Tiny3DAppDelegeteという名前になっています)にメッセージを送ります。

objc_msgSend(appDelegate,@selector(finishUpload));


この命令でのappDelegateが代表クラスのid値です。特に引数を付けていないのでセレクタのfinishUploadには右に:が付きません。

-(void)finishUpload {

NSLog(@"call appDelegate finishUpload");

if([tabBarController selectedIndex]==3) {

id viewController = [(UINavigationController *)[tabBarController

selectedViewController]topViewController];

objc_msgSend(viewController,@selector(uploadFinished));

}

}


上がTiny3DAppDelegateのfinishUploadです。Tiny3Dの場合はタブバーのボタンが入れ替わる事はないので、必ずツールのインデックス番号は3(一番左が0なので、左から4番目)になります。そのツールのトップ画面のビューコントローラーへ向けて、メッセージを送ります。

-(void)uploadFinished {

NSLog(@"call FTPUpload uploadFinished");

[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Tiny3D",@"title")
message:NSLocalizedString(@"Upload completed.",@"message")

delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];

[alert show];

[alert release];

}


そして上にあるのがそのビューコントローラー(FTPUploadViewControllerと勝手に命名してますが)にあるuploadFinishedです。ちなみに2行目の命令で画面の左上に通信中などに表示される回転するアニメーションの表示を消しています。表示の開始の時はNOの部分がYESになります。その後にあるのがアラートの表示内容を決めているところです。英語では"Upload completed."と表示しますが、日本語だと「アップロードに成功しました。」と出るようになっています。実際に表示するのは[alert show]の部分です。alertは自分でallocで作ったオブジェクトなので、表示させたらreleaseで解放します。当たり前ですがリリースしたからといって表示が消える事はありません。OKが押されない限りは表示したままになります。なお、このときに表示されるボタンでいろいろと凝った事もできるのですが、それは次の機会に書きます。





戻り先のビューへメッセージを送る

2009年02月17日
TableViewでpushViewControllerを呼び出してスクロールさせていく場合には、スクロールする前にいろいろとデータを渡してやるのに苦労することはまずありません。なぜならpushViewControllerで次のビューを表示するために使うViewControllerの参照を書いているからです。それをもとに関数を呼ぶなり変数を変更するなり好きにすれば良いのです。ここまでは当たり前な話です。

では、その逆にpopViewControllerで戻る側にデータを渡したい場合はどうでしょう。代表クラスに全部データを持たせておき、そちらを通じてやりとりする手もあります。渡したいデータは代表クラスに渡しておき、戻り先のViewControllerにあるViewWillAppearで代表クラスへデータを取りにいくようにさせれば、更新内容を反映させる事はできます。でも、プログラムが終了したら破棄するようなデータなどにもいちいち代表クラスを橋渡しに使う必要はあるのかと思う事も多々あります。かといって今表に見えているViewControllerまでの道のりが常に同じとは限らないこともあります。同じような値の設定をする場合には再利用した方が効率が良いだろうと、設定用のViewControllerが数種類の呼び出し元を持つ事もあります。では、そんな場合で直接データを渡したい時はどうするかですが、まずは以下のコードを見てください。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

[myTableView deselectRowAtIndexPath:indexPath animated:YES];

NSArray *naviArray = [self.navigationController viewControllers];

NSInteger nowIndex = [naviArray count];

id aViewController = [naviArray objectAtIndex:nowIndex-2];

if([aViewController isKindOfClass:[FTPUploadViewController class]]) {

FTPUploadViewController *controller = (FTPUploadViewController *)[naviArray
objectAtIndex:nowIndex-2];

NSString *aString = [[listContent objectAtIndex:indexPath.row]
objectForKey:@"url"];

[controller setDirectoryTextFieldText:aString];

aString = [[listContent objectAtIndex:indexPath.row]
objectForKey:@"username"];

[controller setUsernameTextFieldText:aString];

} else if([aViewController isKindOfClass:[FTPDownloadViewController class]]){

FTPDownloadViewController *controller = (FTPDownloadViewController *)
[naviArray
objectAtIndex:nowIndex-2];

NSString *aString = [[listContent objectAtIndex:indexPath.row]
objectForKey:@"url"];

[controller setDirectoryTextFieldText:aString];

aString = [[listContent objectAtIndex:indexPath.row]
objectForKey:@"username"];

[controller setUsernameTextFieldText:aString];

}

[self.navigationController popViewControllerAnimated:YES];

}


FTP用のアドレスとユーザー名の組み合わせ(Tiny3Dではこいつをブックマークと呼んでます)をテーブルでリスト表示させていて、選択されたら元の設定用のビューにデータを渡そうという部分です。FTPはダウンロードとアップロードの両方で使いますが、同じFTP用なのにわざわざ2種類のブックマークリストを持つ必要もあるまいと、どちらからも同じTableViewControllerをpushViewControllerを使って表示しています。そのため、選択後の戻り先はアップロードの設定用かダウンロードの設定用のどちらかのViewControllerになります。この場合にはnaviArrayという配列を作ってその中にnavigationControllerが持っているこれまでに通過してきたViewControllerのリストを放り込んでいます。配列はインデックス0から始まるので、countで返ってくる配列の個数から2を引いたインデックス値が、今のViewControllerの呼び出し元になります。そして呼び出し元クラスがアップロードかダウンロードかで場合分けを行い、データを変更するための関数へメッセージを送っています。この当時はまだobjc_msgSendの使い方を知らなかったためにこんなコードになっていますが、どちらでもメッセージを受け取る関数は同じなので、型キャストをせずにid値だけを得て、objc_msgSendでメッセージを送るだけで済ませてしまえば分岐する必要が無くなります。ただ、何か問題が起きた場合のデバックをする場合は上のように書いてある方がやりやすいのかもしれません。