プログラマでありたい

おっさんになっても、プログラマでありつづけたい

AWS SDK for iOSでリージョンを指定する

 AWS SDK for iOSを使ってみました。とりあえずS3が使いたいので、サンプル見ながらコーディング。サンプルには、およそS3を使う上で必要なものは殆ど網羅しています。余り困ることは無かったのですが、ダウンロードをするとエラーメッセージが出てくるバケットがありました。エラーの内容は次のとおりです。

The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.


 Rubyでも何回か見たような気がしますが、デフォルト以外のリージョンを使う場合はエンドポイントを指定せよとのことだと思います。では、エンドポイントをどうやって指定したら良いのか?メソッドを見てみると、setEndpointというモノが用意されていました。AmazonS3ClientとS3GetObjectRequestのどちらで指定しても良さそうです。オブジェクトのリクエストなので、S3GetObjectRequestのタイミングごとに指定するのが良いのかなぁと思います。EndPointのリストは、次のページでまとまっています。
Regions and Endpoints - Amazon Web Services Glossary


 次のようなソースを書きました。

    NSString *bucketName = [NSString stringWithFormat:@"your backet name"];
    NSString *keyName    = [NSString stringWithFormat:@"hoge.txt"];

    // Puts the file as an object in the bucket.
    S3GetObjectRequest *getObjectRequest = [[[S3GetObjectRequest alloc] initWithKey:keyName withBucket:bucketName] autorelease];
    [getObjectRequest setEndpoint:@"s3-ap-northeast-1.amazonaws.com"];


 そうすると、次のようなエラーが

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSCFConstantString substringFromIndex:]: Range or index out of bounds'
First throw call stack:

(0x17bf022 0x14becd6 0x1767a48 0x17679b9 0xd27c2 0x24875 0x246fe 0x24132 0x1f567 0x1c933 0x1c07b 0x1b2ee 0x2308 0x17c0e42 0xe69df 0x179394f 0x16f6b43 0x16f6424 0x16f5d84 0x16f5c9b 0x1eb67d8 0x1eb688a 0x41e626 0xdc7d 0x1ef5)


 何故、NSRangeExceptionが???悩むこと1時間以上。
S3/S3Constants.hを見たら、全ての謎が解けました。

// Endpoint
#define kS3ServiceEndpoint                   @"http://s3.amazonaws.com"

http://から始めないと駄目なのです。(もしくはhttps://)
ですので、正しくは下記のソースになります。

    [getObjectRequest setEndpoint:@"https://s3-ap-northeast-1.amazonaws.com"];


 ついでに言うと、単純にS3の東京リージョン使っている場合は、上記のような指定は特に必要ない模様です。 Hosting Websites on Amazon S3を使っている場合のみ、ちゃんと指定が必要な模様です。(付けておくことに越した事はないと思います。)


 こんな些細なことでも、ハマると時間掛かりますよという話でした。

カテゴリ別 App Storeのアプリ登録数

 暇な時に、日本のApp Storeで登録されているアプリ数をカテゴリ別に調べてみました。書きだして眺めてみると、色々な気づきがあったので共有します。ちょっと眺めて見てください。

カテゴリー名 iPhone無料 iPhone有料 iPad無料 iPad有料
Newsstand - - - -
エンターテイメント 25353 26990 6978 9128
カタログ 299 130 73 345
教育 18308 28843 9513 17710
ゲーム - - - -
仕事効率化 6007 7130 2308 3253
辞書/辞典/その他 6756 11578 2347 5568
写真/ビデオ 4856 6220 1692 2978
スポーツ 7493 7417 2006 2449
ソーシャルネットワーキング 7505 2680 1335 919
天気 1077 963 305 405
ナビゲーション 3177 5342 691 2184
ニュース 10088 2337 5656 827
ビジネス 17905 5507 7874 2668
ファイナンス 6558 3565 2016 1127
ブック 10058 40432 7118 21136
ヘルスケア/フィットネス 5487 7867 1375 2552
ミュージック 12352 8832 2300 4077
メディカル 4473 5112 1871 2520
ユーティリティ 16152 16272 3828 4541
ライフスタイル 25345 17075 8077 6031
旅行 11883 16154 2674 5029
合計 201132 220446 70037 95447


 売上/ダウンロード数は、スタート時では口コミや有名サイトでの紹介がモノを言いますが、長期的にみるとApp Storeでのランキングに載りつづけるかが勝負です。となると、必然的に競争が激しくない所の方が有利になります。例えば似たようなカテゴリのビジネスと仕事効率化。iPhoneの無料版だとビジネスカテゴリは18,000弱になります。これに対して仕事効率化は6,000程。逆に有料版だとビジネスは5,500程度なのに仕事効率化は7,100強と逆転します。どちらのカテゴリでもマッチする内容であるのならば、無料版で出す時は仕事効率化でだして、有料版ならばビジネスを選ぶのも一つの手でしょう。
 カテゴリは一度出すと変更出来ないので、慎重に選びましょう。また激戦区を除けば、ランキングに載ることはそこまで難しくありません。リストを見て頂ければ解ると思いますが、iPadアプリなどは特に難易度が低いです。この辺りもコツがあるので、どこかで一度まとめようと思います。


XcodeでMissing fileのWarningが発生した場合の対処法

 iPhoneアプリやiPadアプリの開発でXcode使っていて、大幅にソースの改廃やディレクトリの改変をすると、WarningでMissing fileが出る時があります。これは何かというと、svnやgitの管理ファイル上に残っているのが問題のようです。
 解決方法については色々議論されていますが、XCodeからではなくコンソールから消してしまう方が簡単じゃないかという結論のようです。
 だいぶ改善されたとはいえ、どうもXCodeとソース管理の連携はイマイチな部分が残っていますね。


SVNの場合

svn delete missingFile

Gitの場合

git rm missingFile


参考:
Missing file warnings showing up after upgrade to XCode 4

iOS5のTwitter APIを使って実装してみる

 最近、iOS5に追加されたTwitter APIを作ってTwitterに呟く機能を実装してみました。解説がいらないくらい簡単です。Twitter連携していて採用出来る環境の人は、是非使うべきでしょう。


まずはこれ
Tweeting Sample Code
TWTweetComposeViewController Class Reference


ソースサンプルと設定

まず設定のBuild Phasesで、Link Binary With LibrariesでTwitter.frameworkを追加します。
そのうえで、ViewControllerに下記のような感じで追加します。
TWTweetComposeViewControllerはUIVewControllerの拡張です。

#import <Twitter/Twitter.h>

〜略〜

- (void)_sendTweet:(NSString *)tweet url:(NSURL *)url {
    TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
    NSLog(@"tweet=%@", tweet);
    [tweetViewController setInitialText:tweet];
    if (url != nil) {
        NSLog(@"with url");
        [tweetViewController addURL:url];
    }
    [tweetViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result) {
        switch (result) {
            case TWTweetComposeViewControllerResultCancelled:
                // The cancel button was tapped.
                NSLog(@"Tweet cancelled.");
                break;
            case TWTweetComposeViewControllerResultDone:
                // The tweet was sent.
                NSLog(@"Tweet done.");
                break;
            default:
                break;
        }
        
        // Dismiss the tweet composition view controller.
        [self dismissModalViewControllerAnimated:YES];

    }];
    [self presentModalViewController:tweetViewController animated:YES];
    [tweetViewController release], tweetViewController = nil;
}

メソッドの解説

メソッドも非常にシンプルで解りやすいです。

  • (BOOL)addImage:(UIImage *)image

画像の追加

  • (BOOL)addURL:(NSURL *)url

URLの追加

  • (BOOL)removeAllImages

全ての画像の削除

  • (BOOL)removeAllURLs

全てのURLの削除

  • (BOOL)setInitialText:(NSString *)text

初期に追加する文言の設定

感想

 最近リリースしたアプリをiOS5専用で出したので使ってみました。iOS5限定ということで、堂々とiOSのTwitter APIを利用してみました。これが超絶に楽です。本当に5分くらいでコーディング完了できました。これを実感すると、アプリの性格によっては常に最新のiOSのみ対応するという選択肢もあるんではないかなと思います。また、各種のレポートがiPhone/iPadユーザはかなり高い確率で、最新のOSを使っているというレポートがあります。
 考え方の問題ですが、最新のOSでどんどんリリース出来て新規のユーザが獲得できるのであれば、ある程度旧バージョンのユーザを置き去りにするというのもありなのかもしれません。企業として出しているアプリであれば、その決断はなかなか出来ませんが、個人としては考慮に値するでしょう。

リリース済みのiPhone/iPadアプリのDBの変更の仕方 或いはCoreDataを使ってスキーマの自動マイグレーション

 リリース済みのiPhone/iPadアプリのDBのテーブル定義を変更したい。こんなことって、ありますよね?しかし、サーバサイドのアプリと違って、クライアントサイドで動くiPhone/iPadアプリはどうやってデータの移行をすれば良いのでしょうか?リリース後に気がついて、私は小一時間途方にくれました。
 でも、iPhoneなら出来るんです。簡単に!!
正確に言うとApple謹製のDBのフレームワーク、CoreDataを使っていた場合は簡単に移行(マイグレーション)が実現出来ます。


詳しい情報は、次のリンクをご参照ください。
Core Data Model Versioning and Data Migration
Lightweight Migration


 簡単な手順としては、次のような形になります。

  • 新しいバージョンのモデルを追加する。

  Editor -> Add Model Version
  

  • 追加したモデルに、属性(カラム)を追加する。

  必要なだけ、追加します。

  • マッピングファイルを作成する。

  File -> New File -> Core Data -> Mapping Model
  マッピングファイルは、Source Data Model(移行元)とTarget Data Model(移行先)を指定します。
  注意点としては、どのバージョンからどのバージョンに移行するのかする解るようにすることです。
  どういうことかというと、Version1 -> Version2 -> Version3とこちらの期待通りに移行してくれるとは
  限らないということです。 Version1 -> Version3といった人もいます。
  なので、Version3まである場合は、 ver1->ver3とver2->ver3と2ファイルの用意が必要です。
  ファイル名もV1toV2.xcmappingmodel等、一目でわかる形が良いのではないでしょうか?
  

  • 自動マイグレーションコードを追加する。

  詳細は、下の方に書いておきます。

  • 現在のバージョンを変更する

  該当の.xcdatamodeldを選択して、File InspecterのVersioned Core Data ModelのCurrentを変更します。

自動マイグレーションのコード記述

 自動マイグレーションで、変換(マイグレーション)が起きるタイミングは、NSpersistentCoordinatorでオプションで変換を付けた時になります。データアクセスクラスみたいなので一元化しておけば、そこの部分にオプション追加するだけで対応が可能になります。
 私の場合は、DataManagerという基底クラスを作って、そこで 永続ストアコーディネータの作成やDBファイルの作成を一手に行なっているので、ここだけの修正で済みました。CoreDataを使った開発の詳細は、こちらのエントリーで書いています。
DataManager.m

NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
 
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
                    configuration:<#Configuration or nil#> URL:storeURL
                    options:options error:&error];
if (!success) {
    // Handle the error.
}

 ただ大量データの変換が必要な場合、注意が必要となります。数十件程度のデータであれば、すぐに終わります。が、それが数千件レベルのデータとなりますと、それ相応の時間が必要となります。その場合にアプリ起動時にいきなりマイグレーションが起こると、アプリの起動タイムアウトが発生する可能性があります。またそうではなくても、ユーザは壊れたかと思って中断することも考えられます。その点を考慮した上で、マイグレーションをどこで行うかの設計が必要になります。


 最初にiPhoneアプリでDB(SQLite)を使う時に、CoreDataを使うかサードパーティのFMDBを使うか悩みました。FMDBは慣れ親しんだSQL文で操作出来るし、記述量も少なめとかなり魅力的にみえました。一方、Appleのフレームワークの上という所でバージョンアップ時の対応等で継続性の懸念がありました。悩んだ末に、一度はちゃんとCoreDataを使ってみないとダメだろうと思い、CoreDataを採用した経緯があります。今となっては、良かったなぁと思います。正直、開発している段階ではここまで気が回らなかったので、助かりました。これから開発される方は、是非CoreDataをお使いください。


 あと、iPhone/iPadアプリを効率的にかつ保守性の高いコードで書くには、是非、木下さんのiOS開発におけるパターンによるオートマティズムをお読みください。CoreDataのサンプルコード等も入手出来て、本当に参考になります。


enjoy!!

XcodeでGitのリモートリポジトリ(remote repository)を追加する方法

 Xcode4.2系になって、文句なくよくなったのがバージョン管理ツールとの連携。今までが酷すぎたのですが、4.2になって随分と改善されています。まずデフォルトのバージョン管理ツールがGitになっています。GJ!! そして、それ以上に素晴らしいのが、Xcodeからリポジトリを自動で生成出来るようになっていることです。iOSアプリの開発者は、昔からバリバリやってましたという人以上にプログラミングってよく解らないけど挑戦してみましたという人の方が多いんじゃないかなと思います。そんな人達にじゃぁGitのサーバ用意してってのは酷な話です。でも、Appleなら大丈夫。ローカルリポジトリならすぐ作れてバージョン管理が出来ますよという形にXcodeはなっています。素晴らしい!!(ただ、バージョン管理の概念があるのかは難しいところですが。)
 ということで、Xcode4.2ならプロジェクト始めたその瞬間からローカルレポジトリを作成出来てバージョン管理が出来ます。ただ残念ながら、リモートリポジトリを作るところまではサポートしてくれていません。なので、同一PC内でリモートレポジトリを作る為のチップスを紹介します。ググッてみたら、一度リポジトリを作ってからcloneする例が多かったので、一助になればと思います。ちなみに今回の例は、既にgitのモジュールを入れていること前提です。


コマンドラインで、空のリポジトリを作成

$ mkdir REPOSITORY_NAME
$ cd REPOSITORY_NAME
$ sudo git init --bare --shared=true


こっからが肝です。
Xcodeでプロジェクトの作成
File -> Source Control-> Commit
File -> Soruce Control-> Repositoriesを選択。プロジェクト名を選んで、Remotesを選択状態でAdd Remote
Remote Nameを適当につける。 Masterとかが一般的。
次にLocationで先程作成したPathを指定します。(もちろん別サーバのURLも指定出来ます。)
そして、プロジェクトをアクティブにした上で、File -> Source Control -> Push
これだけです。


簡単ですよね。ちなみにリモートリポジトリの保管先をDropBoxやS3にすると更に便利です。
詳しくは、このあたりも読んでください。
-- 週末プログラマにお薦め!!Subversion+DropBoxで似非分散型バージョン管理
Enjoy!!


熟練のプログラマが、iPhone/iPadアプリ作成に苦戦する理由

 何回かiPhone/iPadアプリの作り方をレクチャーしていたのですが、わりと優秀なプログラマでも苦戦することが何回かありました。反面、経験の浅いプログラマがすんなりと習得している例もありました。
 どうしてなんだろうなぁと考えていたのですが、最近少しだけ理由が解りました。サーバサイドプログラムに慣れているか、JavaScriptのようなクライアントサイドプログラムに慣れているか、この違いが大きいです。もう少し言うと、イベント駆動型プログラミングの考え方があるかないかです。
 私自身も、JavaScript書くよりサーバサイドのプログラミングを書くことが圧倒的に多かったこともあり、最初にiPhoneアプリを作った時はかなり戸惑いました。理由としては、書いた処理がどの順番で呼ばれているか、また何故呼ばれているのかさっぱり解らなかったからです。暫く触っていてJavaやRuby,Pythonではなく、イベント駆動で呼ばれるJavaScriptに近いんだと解ってから理解が急速に進みました。今から考えると当たり前の話なんですが、一番最初の時は全然気が付きませんでした。


 ということで上記過程を経て一つのシンプルな原則を編み出しました。


クラスの役割と、イベントのライフサイクル、レスポンスをまず覚える


 上記の原則を抑えながら開発していくと、かなり上達のスピードがあがります。例えば、UIViewControllerのライフサイクルでいうと、最初にinit(init,initWithNibName,initWithCoder)処理が走り、loadView -> viewDidLoad -> viewWillAppear -> viewDidAppearと順番に処理が進みます。画面が非表示になる際は、viewWillDisappear -> viewDidDisappearとなります。また画面回転の際にはshouldAutorotateToInterfaceOrientation等のイベントが走りますし、メモリ不足の場合にはdidReceiveMemoryWarningのイベントが発生します。
 またどのようなライフサイクル・イベントがあるかを覚えていくだけで、クラスの性格も解ってきます。例えばUIViewとUIScrolleViewのイベントの違いが解れば、使い方も自ずと理解できます。後は習うより慣れろですね。Enjoy!!