Apple iPhone 3GとiPhone SDKを使ってアプリケーションを開発、公開するための情報。

ホーム / Hardware / iPhone SDK

更新:12/07/03 | iPhone SDKの技術情報 | iPhone SDK Tech

まえがき

Xcodeの開発環境ここからは、具体的にコードを示しながら、他でも使えそうな実装方法などを紹介していきます。

 

オーバーライドの使い処

オブジェクト指向のプログラミング言語には、必ずオーバーライド、継承といった考え方が備わっています。
Objective-Cの開発では、クラス毎、画面(UI)毎にファイルが分かれていて、それぞれが個別のオブジェクト・クラス(単にクラスと表します)になっています。

クラスの継承はイモヅル式に繋げていくことが可能ですが、あまり繋げすぎると一体どこからやってきたクラスなのか分からなくなるため注意が必要です。

継承、オーバーライドの使い処

継承を使うのにうってつけのシチュエーションが幾つかあるのでご紹介しておきます。

  1. 新規入力フォーム(UIView)を使い回して、修正ビュー、参照ビューを作成したいような場合。

似たようなフィールドを持っているのに、それぞれにクラスを作成する(コピペかもしれませんが)のは労力が掛かりますし混乱の元です。どんな混乱かというと、殆ど同じなのにまるまる似たようなコードが存在していると、間違い探しのような機能違い探しをしなくてはならないシチュエーションに陥ることがあるのです。

直ぐに思いつく方法として、クラス内にmodeといった独自の判定フラグを用意して、各メソッド内で条件分岐させる方法です。この方法では一つのクラスで複数のモード(新規、編集、参照など)を扱うことが出来ますが、一つのクラス・オブジェクト(単にオブジェクトと表します)内でモードを切り替えながらプログラムの実行が進行するため思わぬ動作をする場合があります。

こんな場合に、継承を利用して新たに編集ビュー、参照ビューを作成するという方法が便利です。
ViewDidLoadedをオーバーライドしてmodeを振り分けておけば、途中でmodeが変更されて動作が不定になるリスクもありません。条件分岐も基本的には不要で、新たなモードが追加されても継承クラスを追加すれば良く見通しも悪くなりません。

 

ソースコードの管理:Subversion

Xcodeは元々ソースコード管理の仕組みとしてSubversionに対応しています。Subversion(サブバージョン、サバージョン、 以降SVN)はプログラムのソースコードなどを管理するバージョン管理システムの一つで現在は多くの言語開発分野で主流を占めていると思われます。

多くのバージョン管理システムがそうであるように、チェックアウトや差分、アップロード、ダウンロードなどの管理機能を備えたサーバー・クライアントシステムとなっています。つまり、XcodeでSubversionによるコード管理を行う場合にもどこかにSubversionのサーバーが必要となります。

インターネットには、無料・有料のSubversionシステムが存在しているので、それらを利用するのも一つですが、個人的には自宅サーバーに構築したVisualSVNサーバー(http://www.visualsvn.com/)を活用しています。VisualSVNServerは個人利用では無料です。個人で使うのに何故コードの管理が必要なのか? 幾つかメリットを挙げてみます。VisualSVN Server

  1. 会社・自宅など複数の環境からコードの修正・管理が可能
  2. 履歴管理が出来るため、特定のバージョンに戻したり、特定のバージョンと際を比較したりすることが容易に可能
  3. ローカルのMacにだけコードが保存されている場合と異なり、サーバーに必然的にバックアップが保持されることになる

VisualSVNなどのサーバーを準備したら、ユーザーの作成やリポジトリの作成などを予め行っておきます。
SVNサーバーの準備が出来たらクライアント=Xcodeの設定です。

  1. プロジェクトを作成したらメニューのSCM(Source Code Management?)からリポジトリを選択します。
  2. SVNのホスト名リポジトリの利用初回のみ、リポジトリ自体を定義しなくてはなりませんので右上の「構成」をクリックします。名前、ホスト(VisualSVNのホストのアドレスやドメイン名など)、パス、ユーザー、パスワードなどを指定して導通チェックが完了すればリポジトリの利用が可能となります。
    ※ ローカル環境、インターネット環境の両方で利用するような場合(ノートMacであればそんな使い方も多い)、DDNSを指定して置いて都度hostsの変更で対応するという一手間が必要になりますのでご注意下さい。詳細は後述。
  3. リポジトリウィンドウが表示されるので、trunkあたりのリポジトリフォルダを選択した状態で、左上の「読み込む」ボタンをクリックします。
    ※ 読み込む際に指定されているリポジトリフォルダにアップロードされるので注意しましょう。
  4. 現在作成中のプロジェクトフォルダを指定します。
  5. アップロードが始まり、リポジトリに指定されたプロジェクトのフォルダが作成されました。
  6. ここからがくせ者です。他に良い方法があるのかも知れませんが、一度Xcodeを終了しアップロードに指定したプロジェクトフォルダの名前を〜bupなどにリネームしておきます。
  7. Xcodeを再度起動し、リポジトリウィンドウから先ほど新たに作成したリポジトリフォルダを指定し、チェックアウトをクリックします。
    所定のプロジェクトフォルダ(のルート)を指定してチェックアウトして完了です。
  8. SVNプロパティSVNプロジェクトの情報(プロパティ)から、「ルートとSCMを構成…」という項目を選択して、リポジトリの箇所に前述のリポジトリが指定されていることを確認します。指定されていなければ明示的に指定します。

Macでhostsの変更

前述の通り、ローカル(LAN)やインターネット環境でLANに置かれたSVNサーバーを利用する場合、LANからアクセスする場合と、インターネットからアクセスする場合でアドレスが異なることになります。

そんな場合には、hostsを書き換えて適宜切り替えてあげる必要があります。SVNの指定IPアドレスを変更すればよいのでは? と思うかも知れませんが、何故かうまくいかず以前に指定されたIPアドレスを使ってアクセスしようとしてエラーとなります。一度でもリポジトリを使っている場合には、過去の更新情報の何処かにSVNサーバーの情報を静的に保持してしまっているように見えます。

MacOSXでのhostsの変更方法は以下の通りです。確認はSnow Leopardのみで行っておりますので、環境に依存してうまくいかない場合があるかも知れません。

  1. ターミナル(ウィンドウ)を起動する。
  2. sudo vi /private/etc/hosts
    と入力(コピー&ペーストも可)して、Enterキーを押下。
  3. この時のアカウント(sudoは管理者特権を得るためのコマンドなのでそれなりのアカウント権限を持ったアカウントが必要)に合ったパスワードを入力して、Enterを押下。
  4. ##
    # Host Database
    #
    # localhost is used to configure the loopback interface
    # when the system is booting. Do not change this entry.
    ##
    192.168.80.80 mysvn.mine.nu
  5. viエディタが起動され、hostsファイルが閲覧モードで表示される。
  6. aキーを押下して編集も度に移行(下段にInsertと表示される)し、カーソルやキーボードを使ってSVNサーバーのドメインに対するIPアドレスを編集する。例えば、ローカルホストにサーバーを立てている場合は127.0.0.1になります。
  7. 編集が完了したら、ESCを押下して編集モードを抜けます。
  8. :w(Enter)と入力して上書き保存します。
  9. :q(Enter)と入力してviエディタを抜けます。

SVNに追加すると「エラー:150002(Entry already exists)already under version control 」エラー

SVNを様々な環境で利用していると、追加時に「エラー:150002(Entry already exists)説明:'/Users/developer/Documents/iPhoneSDK/LocationInformator/AdMob' is already under version control 」という表示によって追加できないことが玉にあります。この場合、追加していないはず(だからこそ、追加のメニューが表示され選択できている)にも関わらず、SVNのデータ上は既に管理下に入っているという矛盾が生じていることになります。

該当するフォルダについて、ターミナルのコマンドラインから

ls -la

として全ての所属ファイルを表示してみます。

272 3 21 20:14 .svn

のように、.svnという名前の隠しファイルがあれば、これを削除して管理下にあるというSVNの認識をクリアすることが出来ます。

rm -rf .svn

として、再度リポジトリへの追加をすれば、正常に処理されるはずです。

生年月日から年齢を算出する

 

iPhone SDK生年月日を引数にして、現時点での年齢を返すメソッドです。恐らく、指定した月の最終日(閏年も意識して)を返すメソッドなどに直ぐに転用できると思います。

引数にはNSDate型のオブジェクトを指定します。戻り値はNSNumber型オブジェクトにInteger型として規定しています。

内容と言えば、dateFormatterなるNSDateFormatterクラスを利用して例えば19731101(生年月日)と20100410(現在)を引き算した後で、10000で割っています。

// 誕生日から年齢を計算するメソッド
-(NSNumber*) getAge:(NSDate*)birthDate {

NSInteger myAge = 0;
NSDate *today = [NSDate date];

myAge = [[dateFormatter stringFromDate:today] integerValue];
myAge = myAge - [[dateFormatter stringFromDate:birthDate] integerValue];
myAge = myAge / 10000;

return [NSNumber numberWithInteger: myAge];

}

そのため、ここには登場しませんが、先にNSDateFormatterクラスのインスタンスメソッドを使って、

[dateFormatter setDateFormat:@"yyyyMMdd"];

などとしておく必要があります。
NSDateFormatterクラスは便利なクラスで、日付を取り出すときのフォーマットを規定できるため各国に対応したアプリケーションで日付を扱うときには必須のクラスでもあり、文字列として日付を取り出してUIに表示させるには重宝します。

コードの中で、アプリケーション名=プロダクト名(info.plistの${PRODUCT_NAME})を取得する方法

一つのコードで、無料版、有料版など複数のバージョンをビルドしたりする場合、アイコンや制限項目をバージョンによって変えたいことがあります。そんな時に、アプリケーション名を利用できるものにはこの方法がとても役に立ちます。

- (NSString *)appName {

return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];

}

私の場合、ApplicationDelegateに用意して、いろいろなViewから活用できるようにしています。メインバンドルの情報を利用しているので、この他にも幾つかの情報を取得することが出来そうですね。

動的にアクションシートの内容を構成する効率的な方法

アクションシートは、アラートビューと並んでユーザーに次のアクションを求める際に便利です。特に3つい上の選択肢がある場合などにはアクションシートの方が圧倒的に有利ですし、どのみちアクションシートを使うシーンがあるのであれば2択の場合であっても最初からアクションシートを使ってしまうと言うのもUIデザインとしてあり得るかも知れません。

一般的にアクションシートの実装は次のようになります。

UIActionSheet *menu = [[UIActionSheet alloc]
initWithTitle:NSLocalizedString(@"Delete Account/Records",@"")
delegate:self
cancelButtonTitle:NSLocalizedString(@"Cancel",@"")
destructiveButtonTitle:nil
otherButtonTitles:
NSLocalizedString(@"Delete All Records", @""),
NSLocalizedString(@"Delete last 50 Records", @""),
NSLocalizedString(@"Delete last 100 Records", @""),
NSLocalizedString(@"Delete last 300 Records", @""),
NSLocalizedString(@"Delete this ACCOUNT", @""),
nil];

これだと、条件によってDelete this ACCOUNTを表示させたりさせなかったりしたくても、容易に変更が出来ません。そんな場合には、メニューを積み上げていく方式をとると柔軟性が増します。

[menu addButtonWithTitle:NSLocalizedString(@"Cancel",@"")];
[menu setCancelButtonIndex:[menu numberOfButtons] - 1];

その際、このようにaddButtonWithTitleでメニューを積み上げるだけでなく、キャンセル属性や強調属性などを都度負荷するようにすると、numberOfButtons - 1と、汎用的に記述できてメニューの数などが変わっても柔軟に対応できます。

勿論、CancelはUIデザイン的には一番最後に追加しましょう。

最初に起動された時だけ決まった処理をする方法

if (isFirstTimeToShow) {
isFirstTimeToShow = FALSE;
[self parseWithParserType:XMLParserTypeNSXMLParser];
}

などとして、普通に変数に入れておけばOKですね。わざわざ書くほどのこともありませんでしたが、何となく気になったので書き残しておきます。
アプリケーション姓名を通して本当に最初に起動された時だけに実行したいのであれば、NSUserDefaultに上記の値を記録しておけば容易に実現できます。

全ての環境下で絵文字を利用可能にする

既に既出のテクニックですが、設定ファイルを書き換えるだけなので非公開APIを使わないという観点から、Appleの審査で物言いが付くこともありません。

NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:PREFS_FILE];
[dict setObject:[NSNumber numberWithBool:YES] forKey:EMOJI_KEY];
[dict writeToFile:PREFS_FILE atomically:NO];

因みに

#define PREFS_FILE @"/private/var/mobile/Library/Preferences/com.apple.Preferences.plist"
#define EMOJI_KEY @"KeyboardEmojiEverywhere"

としてあります。結果的にplistファイルをアプリケーションから読み込んで、書き換えるテクニックでもあります。これの応用で、以下のようなplistファイルを参照することが可能です。

これで、いろいろな技が使えるようになるのでは!? と期待してしまうのが人情です。

WiFi機能をアプリケーションからOn/Off出来るのか?

試しにAPIではアクセスが禁止されているiPhoneのWiFiをOn/Offする機能をアプリケーションから実現してみようと、com.apple.preferences.network.plistの内容を確認してみました。

plistの内容を全て一覧するには次のようなコードが簡単です。

NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:PREFS_WIFI_FILE];

if (!dict) {
NSLog(@"No dict found!");
} else {

for (id key in dict) {
NSLog(@"%@ : %@",key,[dict objectForKey:key]);
}
}

因みに

#define PREFS_WIFI_FILE @"/private/var/mobile/Library/Preferences/com.apple.preferences.network.plist"

です。この結果、次のようにWi-FiやBluetoothのOn/Off設定が反映されていることが分かりました。

2010-07-04 23:20:00.413 minola[11719:307] bluetooth-network : 1
2010-07-04 23:20:00.417 minola[11719:307] wifi-network : 0

早速、wifi-networkを1にしてファイルに上書き保存するコードをテストしてみましたが、のれんに腕押しでした。上書きできないパラメーターもあると言うことですね。この方法で、WiFiをオンにすることは出来ないと言うことが分かりました。

UIRequiresPersistentWiFi=trueの幻想

info.plistにUIRequiresPersistentWiFi=trueを追加すると、

アプリケーションが通信にWi-Fiネットワークを使用していることをシステムに通知するブール値。一定の時間Wi-Fiを使用するアプリケーションは、このキーをtrueに設定する必要があります。そうしないと、デバイスは30分経過すると、節電のためWi-Fi接続を停止します。このフラグを設定することで、現在Wi-Fiを利用していなくても利用可能であれば、ネットワーク選択のダイアログを表示する必要があることもシステムに知らせます。デフォルト値はfalseです。

(出展:http://developer.apple.com/jp/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/ApplicationEnvironment/ApplicationEnvironment.html#//apple_ref/doc/uid/TP40007072-CH7-SW15)

という効能を得られるという触れ込みだったのですが、実際に試してみると飛行機モードの時に「ネットワークに接続して下さい」と自動的に表示される程度。
期待としては最低30分は強制的にWiFiがONになることだったのですが、期待はずれでした。

NSLocalizedStringの引数を一つしか設けなくて、コンパイル時に「error: macro "NSLocalizedString" requires 2 arguments, but only 1 given」と怒られた場合にたくさんある該当箇所を一覧するための正規表現

NSLocalizedString\(@"[^"]*"\)

マルチスレッドでの不可解なエラーメッセージ解消法

2010-10-14 23:11:59.888 ApplicationName[4732:6103] *** __NSAutoreleaseNoPool(): Object 0x703d600 of class UITableView autoreleased with no pool in place - just leaking
2010-10-14 23:11:59.890 ApplicationName[4732:6103] *** __NSAutoreleaseNoPool(): Object 0x6a493f0 of class UIActivityIndicatorView autoreleased with no pool in place - just leaking

iPhoneアプリケーションのデバッグ中に上記のようなエラーメッセージに遭遇することがあるかも知れません。これは、元々マルチスレッドプログラミングの中で発生する問題です。

マルチスレッドプログラミングは、iPhoneの様な非力なデバイス(モバイル端末プログラミング)では特に表示計の処理を別スレッド化して裏処理をしながら視覚的な挙動を実行する場合に用いられます(重宝します)。

メインスレッドから、別のスレッドとして処理を実行させるには、

[self performSelectorInBackground:@selector(startIndicator:) withObject:indexPath];

などのように書きます。簡単ですね。
ここではstartIndicatorというメソッドに引数indexPathを指定してバックグラウンドスレッドで実行させます。(この次の行はメインスレッド上で直ぐに継続して実行され続ける)

呼ばれる側のstartIndicatorというメソッドを何の気無しにコーディングしていると、冒頭のエラーに見舞われてしまいます。スレッドごとにpoolを用意しなさいということなのだと思いますが、次のようにすると問題は解消されます。

- (void)startIndicator:(NSIndexPath *)indexPath {


UIActivityIndicatorView *indicator = (UIActivityIndicatorView*)[self.tableView cellForRowAtIndexPath:indexPath].accessoryView;

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

if (! [indicator isAnimating]) {
[indicator startAnimating];
}

[pool release];
[NSThread exit];

}

UIActivityIndicatorViewを使って、重たい処理中に回転ホイールを表示するだけの物ですが、最初にpoolを確保し、最後にrelease、そしてNSThreadをexitします。
更に上記の例では問題が残っていて、poolの確保の前にindicatorをインスタンス化しています。
当該のメソッドでは、真っ先にpoolの確保をしないと前述の問題は解消されませんので注意が必要です。

iPhoneで正規表現を利用する

驚いたことに、iPhoneSDKには正規表現を実現するライブラリが標準では提供されていないようです。幾つか確認をしたところ、サードパーティーのライブラリがあったので利用してみました。

RegexKitLiteというライブラリで、機能を最小限としてサイズを絞ったいかにもiPhoneSDKと相性の良さそうな正規表現ライブラリです(下記の比較表を参照)。ライブラリをダウンロードしたら、説明に従ってプロジェクトに追加していきます。

  RegexKit.framework RegexKitLite
Regex Library PCRE ICU
ライブラリとしての提供 Yes No
リンクのされ方 フレームワークの中に静的にリンク /usr/lib/libicucore.dylib
に動的リンク
コンパイルサイズ 371KB 16KB—20KB
機能 大きい
様々なクラスが提供される
小さい
NSStringのみ

 

 

 
コメント・フィードバック
ダウンロード
ストリーミング関連
Macintosh関連
Windows関連
メディアなど
 
ハードウェア
ソフトウェア/サービス/開発SDK
デジタル一眼レフカメラ
趣味関連
ゲーム 〜楽しいゲームの紹介や攻略法
RoverMNI(ローバーミニ) 
雑記
その他
Copyright (c)1998-2016 CNXGROUP All Rights Reserved.
このページの全部あるいは一部を無断で利用(コピー)することを禁じます。
>