[PR]
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
アップル社のイベントで分かった事
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万円していたことを考えると、ずいぶんと安くなったものだなと思います。
自分のアプリケーションのデータを標準のメールで送る
私も自分のアプリケーションが持つデータを外へ出すために四苦八苦し、自前でSMTPやFTPのクライアント機能を実装してなんとか出口を作ってきました。ですがこれからはそんな苦労をしなくても簡単にメール添付でのデータ送信ができます。それは3.0ではMessageUIが追加されたからです。実際の利用方法はアップル提供のサンプル「MailComposer」を詳しく見れば分かると思います。簡単に説明すると、まずメール送信のビューへ進みたいビューのコントローラーのヘッダファイルへ
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
を追加し、さらにMFMailComposeViewControllerDelegateに反応させるようにします。あとはメソッドファイル側でMFMailComposeViewController*型の変数を作り、必要なら宛先やCC、BCCの情報をそれぞれNSArrayにして渡します。さらに添付データをNSDataで、本文をNSStringで渡します。最後にこのメール編集用のコントローラーを表示させます。
メールを送信したか等の結果は
で受け取ります。当たり前ですがMessageUI.frameworkの追加は忘れずに行いましょう。MailComposerのプロジェクトを開いておいて、自分のプロジェクトへドラッグアンドドロップしてしまうのが簡単で良いと思います。
巨大化したメソッドファイルをカテゴリを使って分離する
まず、新規で分離するファイルと同じ名前に、何か付け足したメソッドファイルを作ります。ヘッダファイルは作らないようにチェックは外しておきます。@implementationの部分に以下のように括弧でくくってカテゴリ名を付け足します。先ほどのファイル名に付け足すものというのはカテゴリ名にしておくと分かりやすいでしょう。
@implementation Tiny3DAppDelegate (dxf)
そして、その前の部分に移動するメソッドで必要なインポートや定義を書き加えます。そしてヘッダファイルの最後にある@endのさらに後ろにそのカテゴリに移動させるメソッドを宣言しておきます。書き方はこれまでにヘッダファイルでやっていたメソッドの宣言と同じです。違うのはその宣言部分の前後を
@interface Tiny3DAppDelegate (dxf)
@end
で挟むということだけです。括弧の中は先ほど書いたカテゴリ名と同じにします。あとは元からあったメソッドファイルから、新しく作ったメソッドファイルへとメソッドを移動します。これで巨大になったメソッドファイルを分割することができます。同じようにしてカテゴリを複数作れば、メソッドファイルを複数のファイルに分散させる事も出来ます。メッセージ類は今までと全く同じままで良く、オブジェクトの内外ともに書き換える必要はありません。ちゃんとカテゴリ内のメソッドへもメッセージが届きます。まあ、そのためにヘッダ側でメソッドを宣言しているわけですが。なお、オブジェクト内部の変数はこれまで通りヘッダファイルの元々のインターフェース内で宣言、@synthesizeも元からあるメソッドファイルへと書くようにしましょう。
そうだ、タイマーを使おう
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できちんと動けば問題ないでしょう。
デバッグ中に急に動作が止まる場合に考えられること
1.別の画面にスクロールさせようとしたら止まる場合
多くの場合、InterfaceBuilder(以下IBと略します)でスクロール先のView関係の定義を忘れています。俗に紐付けといわれている、コード内の名称(IBOutletで宣言しているもの)とIB側にあるオブジェクトとの関連付けをするのを忘れている場合。nibファイルを作り忘れている場合や、その作ったファイルのクラス設定を忘れている場合などが原因として考えられます。
2.別の画面から戻ったら止まった場合
オブジェクトのメモリ管理ミスが多いです。例えば下の階層のビューへ設定用にオブジェクトを渡した場合、上の階層に戻る時に下のビューの表示が終わると、下のビューのコントローラーは(上の階層のビューコントローラーの変数にでもして組み込んでいなければ)開放されます。この時渡していたオブジェクトを間違って2重に開放してしまっていると、上の階層でそのオブジェクトにアクセスしようとした際に、既にオブジェクトが破棄されてしまっているためエラーになります。
3.時間のかかる処理をさせていて止まった場合
シミュレーターでは動くし、実機でもXcodeから起動させれば問題なく動くのに、実機単独では処理中に止まってホーム画面に戻ってしまうという場合です。数十秒かかるような処理の場合、実機単独の場合にはアプリが応答不能に陥ったと判断され、自動でホーム画面に戻る仕様となっています。起動直後の設定やデータ読み込みなどが複雑で時間がかかる場合などでは、起動させても止まってホーム画面に戻ってしまいます。Xcode経由で止まらないのは、デバッグではブレークポイントで長時間止めたままになることも有り得るため、それらの時間制限が無効化されているからです。あまりに処理時間がかかるような処理は、間にユーザーの応答をさせつつ実行してゆくようにする必要があります。起動後のほうが少し判定間隔が長いようなので、時間のかかる処理は起動時ではなく起動後にするようにするようにしたほうが安全かもしれません。
アラートを出す その2
alertType = 1;
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> {
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 1) {
// 押されたのは2番目のボタンです。
if(alertType == 1){
// リセット用のアラートでリセットボタンが押されています。
// ここに具体的なリセット用のコードを書きます。
}
}
}
2010年4月28日追記:
UIAlertViewはUIViewの派生クラスなのでメンバ変数でtagという値を持っています。それを使うことでも分岐を行わせることが出来ます。例えば上のアラートの表示設定でalert.tag = 1;としておき、分岐判定コードではalertTypeのかわりにactionSheet.tag==1などの条件にすれば同じような動作をさせられます。
アラートを出す
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が押されない限りは表示したままになります。なお、このときに表示されるボタンでいろいろと凝った事もできるのですが、それは次の機会に書きます。
戻り先のビューへメッセージを送る
では、その逆に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];
}