『基礎からのiOS SDK』の中で間違っている・問題のある部分をまとめました。今までこのブログに書いてきた内容と大半は重複しますが、可能な限り客観的に問題があると証明できるものだけを選び出しています。書籍サイトの問い合わせフォームから同一内容を送っておいたので、いずれは公式に正誤情報としてまとめられるかもしれません(その際にはこのエントリは削除します)。
全文は「続き」からどうぞ。
(続きを読む…)『基礎からのiOS SDK』の中で間違っている・問題のある部分をまとめました。今までこのブログに書いてきた内容と大半は重複しますが、可能な限り客観的に問題があると証明できるものだけを選び出しています。書籍サイトの問い合わせフォームから同一内容を送っておいたので、いずれは公式に正誤情報としてまとめられるかもしれません(その際にはこのエントリは削除します)。
全文は「続き」からどうぞ。
(続きを読む…)『基礎からのiOS SDK』を読み終わったので、全体を通しての感想と、その後の学習方針についてまとめておきたい。
この本は、私にとってはiPhoneアプリ開発を始めるための”1冊目”として絶妙な本だったが、誰にでもオススメできるわけではない。次のような点を理解しておく必要がある:
これは前書きに書いてある通り。C言語やオブジェクト指向についての基礎的な説明は一切ないので、そうした知識がない人は先に他の本を読むべき。一方で、知識がある人にとってはさっさと本題に入ってくれるのでとてもありがたい。Objective-C特有の概念を説明する部分は分かりやすいので、他言語の経験があれば全く無理なく理解できると思う。
XcodeやInterface Builderといったツールの使い方については、本文に沿った最低限の操作しか説明されていない。実際にプログラミングをしていく上では、例えばNSLogでコンソールに出力しながら段階的に実装していくとか、必要に応じてブレークポイントを入れてデバッグするとか、気になることがあったらリファレンスを引くとか、そういうお約束をいろいろ知っている必要があるわけだが、その辺りの説明は一切ない。これまた分かっている人にはありがたいが、「今プログラミングの勉強中です」という人にはたぶん辛いと思う。
iPhoneアプリを作る上で最も重要なView Controllerについての説明が一切なく、必然的にTable View ControllerやNavigation View Controller等についての説明もないので、「よくある画面」を作る方法が全く分からない。また他の書籍にせよネットにせよ、中級者向け以上の情報は普通View Controllerの使用を前提としているので、そうしたコードを読むこともできない。よってさらに先に進むためには、何とかしてこのギャップを埋める必要がある。
この点、日本語の書籍では『iPhone アプリケーションプログラミング』が比較的詳しかった。『基礎からのiOS SDK』を読み終えた後の2冊目としてちょうど良い内容だと思う。ちょっと日本語がわかりにくいところがあるのが難点だが……。
その後は実際に手を動かしながら、Appleが提供している各種ガイドを読んでいくのを強くオススメする。初学者が「こうしたい」と思う程度のことはたいていやり方が載っている。実際のところ、ネット上にはこのガイドの内容すら踏まえていないような情報が散見されるので、注意が必要である。
総じて『基礎からのiOS SDK』は、他言語での開発経験が十分にある人に向けて、iOS SDKおよびObjective-Cの機能を幅広く、ショーケース的に紹介する本だと感じられた。
最後に7章までの範囲で気になった点をまとめておく。8章以降は今のところ興味がない題材なので、ここまで詳しく読むことはしない。
(続きを読む…)『基礎からのiOS SDK』の続き。6章は内容が多岐にわたるためか、ちょっと気になる点…というより間違っている点とかコードのバグとかが多い。本文は前と変わらず分かりやすいだけに、かなり残念。特に後半は厳しい。
正誤表が欲しいところなのだが、今のところ公式サイトには存在しない。そこで私も職業プログラマの端くれとして、勉強も兼ねてできるかぎり正確な情報を調べてみた。かなり長くなったので「続き」からどうぞ。
引続き『基礎からのiOS SDK』を読み進めている。今回は5章まで。メモリ管理→setterの素朴な実装とそれが駄目な理由→正しいsetterの実装→@propertyの利用、という説明の流れは見事で、とても分かりやすかった。
しかしやっぱり気になるところがいろいろあったので、またまとめてみた。
4章のサンプル同様、5章のサンプルでもprivate扱いのメソッドの宣言はない。これはミスではなく、著者の方針と考えた方が良さそうだ。確かにメソッドの順序にさえ気をつけていれば何も問題はないのだが、個人的には気持ちが悪いのでやはりクラス拡張を使って.mファイルで宣言しておくことにする。
@interface ClockAppDelegate() - (void)updateClock:(NSTimer*)theTimer; - (void)setupTimeAnnounce; - (void)playAnnounce:(NSTimer*)theTimer; @end
日付の表示に和暦を選択できないと書いてあるが、iOS 4.1ではシステム設定の「カレンダー」で「和暦」を選択できる。いつから選択できるようになったのかは分からない。
Clockクラスの init と initWithTimeZone のコードは悪い例。全く同じ形のコードを2回繰り返している。こういうときは、必要な初期化処理がもれなく実行されることを保証するためにも、 initWithTimeZone を使って init を実装すべき。
- (id)init { return [self initWithTimeZone: [NSTimeZone systemTimeZone]]; }
この場合 initWithTimeZone がClockクラスの「指示付き初期化子(Designated Initializer)」ということになる。この辺のガイドラインについてはApple提供『The Objective-C Programming Language』の『Allocating and Initializing Objects』の節に詳しく載っている。
4章のサンプルと同じく、動かしてみて違和感があった部分について改善案を提案してみる。
時計の表示を更新する updateClock メソッドはタイマーで1秒に一回実行される。そのためアプリケーション起動後1秒経たないと時計の表示が開始されない。これでは見栄えが良くないので、表示の更新部分は別メソッドに分離して、アプリ起動時にも呼んでおくことにした。最初のサンプル(ClockAppDelegate に全部を実装するパターン)では、次のようになる。
- (void)updateClock:(NSTimer *)theTimer { [self updateLabelText]; } - (void)updateLabelText { //本書内の updateClock の内容 } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //タイマーを開始するときに表示も更新しておく [self updateLabelText]; timer = [NSTimer scheduledTimerWithTimeInterval: 1.0f ...]; }
Clockクラスの作成ではもう少し工夫して、表示を更新するためのpublicなメソッドとして refresh というメソッドを用意することにした。この refresh メソッドは3箇所から呼び出される; まずClockクラスの内部で2箇所、updateClock メソッドおよび awakeFromNib メソッドの中から。
- (void)updateClock:(NSTimer *)theTimer { [self refresh]; } - (void)awakeFromNib { [super awakeFromNib]; [self refresh]; } - (void)refresh { // 本書中の updateClock の内容 }
awakeFromNib はオブジェクトがnibファイルから読み込まれた後、全てのIBOutletが揃ったタイミングで呼び出される。ここで refresh メソッドを呼び出すことで、起動直後の時計表示を行う。
これだけだとタイムゾーンの変更が即座に反映されないので、 もう一箇所 ClockAppDelegate クラスで timeZone プロパティを変更するときにも refresh を呼んでおく。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [window makeKeyAndVisible]; clockGmt.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"]; //タイムゾーンの変更を表示に反映させる [clockGmt refresh]; [self setupTimeAnnounce]; return YES; }
これは本書のコードとは関係ないのだが、時報のサンプルを実際に動かすと、1回目の音声が流れる際に3秒ほどシミュレータが固まって、遅れて再生されるというトラブルが起こった。その際にデバッグコンソールには次のようなメッセージが表示される。
AddRunningClient starting device on non-zero client count
このメッセージでググってみると同様の問題を訴える人は大勢おり、Appleにレポートも送っているが一向に改善されないとのこと。これはiOS 4用シミュレータのバグで、実機では発生しないらしいので、とりあえずは放っておくことにする。
『基礎からのiOS SDK』を買ってiPhoneアプリ開発を始めた。
私はObjective-Cによるプログラミングはもとより、IDEによるGUIアプリ開発自体が初めて。数あるiOS関連書籍をざっと立ち読みした限りでは、この本が一番わかりやすそうだった。操作手順を一つ一つ追いながらも、概念的な説明が要所要所で入っていて、常に頭の中がクリアな状態で読み進められる。さすが帯でバイブルと謳っているだけのことはある。もちろんObjective-C言語の本当に細かい話までは入っていないのだが、そこはAppleが無償で公開しているドキュメントを併読すればいいだろう。
とりあえず4章まで読み終えたので、その中で気になった点をまとめてみた。
アプリケーション起動直後の処理を行うメソッドとして applicationDidFinishLaunching: を使うとあるが、リファレンスによればiOS 3.0以降は application:didFinishLaunchingWithOptions: を使うことが推奨されている。テンプレートを使ってプロジェクトを開始したときに自動的に作成されるのもこちらなので、混乱しないように注意が必要。
UIViewを透明にするための手順として『[View > Opaque]のチェックを外すことで背景を透明にすることができます
』とあるが、それだけでは不十分。[Background] で [Clear Color] を選ばないと透明にはならない。デフォルトでは [White] が選択されているので、白く表示されてしまうはず。
updateWebBrowser メソッドのメソッド宣言(プロトタイプ宣言)が書かれていない。ダウンロードできるサンプルコードにも書かれてないので、本文で説明を省いたわけではなく、書き忘れたのだろうか。
Objective-Cでメソッド宣言を書かなかった場合、C言語同様、ファイル内で後ろに位置するメソッドを参照するとコンパイル時に問題になる。updateWebBrowser メソッドを webViewDidStartLoad: の後に書いたりすると、次のような警告が出るはずである:
warning: 'WebAppDelegate' may not respond to '-updateWebBrowser'
単に .h ファイル内の @interface ブロックに宣言を追加しても良いのだが、 updateWebBrowser メソッドような”プライベートな”メソッドについては、クラス拡張を使って .m ファイル内で宣言するのが行儀良いらしい。
#import "WebAppDelegate.h" @interface WebAppDelegate() - (void) updateWebBrowser; @end @implementation WebAppDelegate //... @end
Best way to define private methods for a class in Objective-Cなど参照。
サンプルアプリに対してあまり細かいことを言っても仕方がないが、実際に動かしてみて違和感があった点について、個人的には次のような改善を提案したい。
サンプルコードの中では webViewDidStartLoad: メソッドの中で UIWebView の request プロパティからURLを取得している。しかしこのタイミングで取得できるURLは、これから読み込もうとしているURLではなく、現在表示中のURLになっている。よって実際にアプリを動かしてみると、ページを移動してもすぐにはURL表示(UITextField)が切り替わらず、コンテンツ全体を読み込み終わって、webViewDidFinishLoad: が呼び出されて、ようやくURLが切り替わる。この動作はウェブブラウザとしては違和感がある。
エラー時に呼び出される webView:didFailLoadWithError: メソッドは、本文中で紹介だけはされているが、どんな処理を書くべきかについては触れられていない(ダウンロードできるサンプルコードの中では単に NSLog でログを取っている)。しかしこのページ読み込みエラーはページ読み込み中に「中止」ボタンを押しても発生するので、割と普通に起こる。実際に webView:didFailLoadWithError: メソッドを空にしたまま読み込み中に「中止」ボタンを押すと、インジケータが止まらなくなる。これは望ましい動作ではない。
よって改善するポイントとしては
のが良いと思う。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { urlField.text = [[request URL] absoluteString]; if([[[request URL] host] isEqualToString: @"www.ng.com"]) { return NO; } return YES; } - (void)webViewDidStartLoad:(UIWebView *)webView { [self updateWebBrowser]; } - (void)webViewDidFinishLoad:(UIWebView *)webView { [self updateWebBrowser]; } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { [self updateWebBrowser]; }