複数種類のiPhoneアプリを同時開発するときのTips
先日弊社からiPhoneアプリをリリースしました。
iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。
このアプリはあるスポーツについての最新の試合結果などを検索せずに見ることができるというものです。アプリはスポーツ毎に分かれているので、サッカーを見たい人はサッカー動画用のアプリ、野球を見たい人は野球用のアプリを使うことになります。
ここまで言うと大体お分かりいただけると思うのですが、アプリの基本部分の作りはどのアプリにも共通したものになっています。つまりソースコードが共有されているわけです。アプリ間で異なるのはアイコン画像やアプリ名、まとめた動画情報など。また内部ではParse.comのIDや広告のIDなどもそうですね。逆に言うとそれ以外の部分が共通しているということです。
このようなケース、ほとんどのソースコードは一緒だけど一部だけ違うアプリを複数同時にリリースするというケースではいろいろなことが面倒になります。
もちろん10種類分のアプリがあるので手動ではやってられないため、できるところはほとんどを自動化することにしました。
このようなケースはあまりないのかもしれませんが、今日の記事ではどのようなやり方をしたかを紹介してみたいと思います。
ソース管理
ほとんどのソースコードが共通なので同じGitリポジトリで管理することにしています。そして各スポーツに対して一つずつブランチを用意します。
$ git br -a 01.soccer 02.cricket # ... 10.icehockey master * work-branch01
こんな風に10個分のブランチを用意しました。
全体に関わる変更
全体に関わるコミットはトピックブランチを経てmasterブランチに適用されます。そしてそれらの変更は$ git rebase master
によって各ブランチに取り込まれることになります。
10個のブランチに対してrebase
コマンドを叩いていくのは非常に面倒です。そこで私はRubyのrake
を使うことにしました。
$ rake rebase
とすると、ブランチを切り替えながら$ git rebase master
してくれるというスクリプトです。
(ブランチ名の一覧はyaml形式のデータファイルの書いておいてそこから取っています。$ git branch -a
の結果を使って頑張ることもできますが、別の用途で作ったyamlがあったのでそちらを利用。)
desc "Run 'git rebase master' on all sports branches" task :rebase do config = YAML.load_file( File.expand_path("../../data.yml", __FILE__) ) config.keys.each_with_index do |key, i| branch_name = "#{sprintf('%02d', i+1)}.#{key}" system "git checkout #{branch_name}" system "git rebase master" system "git checkout ." end system "git checkout master" end
各ブランチに対する変更
スポーツ毎に異なるものとして次のようなものがあります。
- 各種外部サービスのキーやトークン
- ホーム画面での表示名
- 動画用データの参照先
- アイコン
- ProvisioningProfile(*)
- bundle id(*)
大体こんなところでした。このうち(*)がついていないものは、各スポーツごとのブランチに対して手動でコミットしておきました。残りの(*)で印をした項目はプログラムを書いて動的に変更するようにしました。
というのは、これらのファイルを変更してコミットしてしまうとmasterに対してrebaseを行うときに100%コンフリクトしてしまうために自動処理ができなくなってしまうわけです。具体的にはpbxproj
ファイルとplist
ファイルがそれにあたります。
ProvisioningProfileやbundle idがを変更する必要があるのは各バージョンをビルドするときです。基本的には放っておき、ビルドする際にpbxproj
ファイルとplist
ファイル内の文字列の置換により値を置き換えます。値の対応付けは外部のyamlファイルに書いておきました。
ビルドの自動化
ブランチを切り替えながら10個分のビルドを済ませるのも一苦労です。ここでもrakeを用いた自動化を行っています。
require "yaml" desc "create xcarchive and ipa" task :archive do time = Time.now archive_dir = time.strftime("archive_%Y%m%d%H%M%S") ipa_dir = time.strftime("ipa_%Y%m%d%H%M%S") Dir.mkdir(archive_dir) Dir.mkdir(ipa_dir) profiles = YAML.load_file( File.expand_path("../profiles.yml", __FILE__) ) profiles.each do |k,v| next if k == "master" # change branch system "git co . && git co #{k}" # アイコン、表示名、プロビジョニングを変更するrakeタスク system "rake provision" # create archive system %Q{ xcodebuild \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -sdk iphoneos \ -configuration Release \ archive -archivePath #{archive_dir}/#{k} } ipa_path = File.expand_path("../../#{ipa_dir}", __FILE__) # create ipa system %Q{ xcrun \ -sdk iphoneos \ PackageApplication #{archive_dir}/#{k}.xcarchive/Products/Applications/MyApp.app \ -o #{ipa_path}/#{k}.ipa \ -embed ~/Library/MobileDevice/Provisioning Profiles/#{v['release']}.mobileprovision } end system "git co master" end
- 10個分のブランチに対してループを回す
- 対象ブランチをチェックアウト
- アイコン、表示名、プロビジョニングを変更するタスクを実行
xcodebuild
コマンドでxcarchiveを作るxcrun
コマンドでxcarchiveからipaファイルを作る
xcodebuild
やxcrun
がここでの肝です。使い方は上のコードを見てもらえば大体わかってもらえると思います。
リリース(アップロード)の自動化
iTunesConnectへのアップロードもなかなか骨が折れます。アプリを作って紹介文を書いてスクリーンショットを登録してアップロード待ち状態にして、、、という作業を10個分やる必要があるので。
幸い、そこまでを手動でやっておけばあとは自動でアップロードを行うことができます。
さっきコマンドで作ったipaファイルとxcrun
コマンドを使います。
$ security add-generic-password -s Xcode:itunesconnect.apple.com -a YOUR_ID -w YOUR_PASSWORD -U $ xcrun -sdk iphoneos Validation -online -upload -verbose "path to ipa" # 10種類分.... $ security delete-generic-password -s Xcode:itunesconnect.apple.com -a YOUR_ID
iTuneConnectへのログインが必要になるので、xcrun
コマンドの前後にsecurity
コマンドによる認証情報の追加と削除が必要なことに注意。
スクリーンショット撮影の自動化
10種類分のスクリーンショットを撮るのも一苦労なので、そこも自動化しました。本来はUIのテストに用いるKIFというライブラリを使いました。
60分で始めるiOSアプリのUI自動テスト - 株式会社CFlatの明後日スタイルのブログ
KIFを使うことで自動で画面遷移をさせることができるので、画面遷移をしつつスクリーンショットが欲しいところで画面を画像ファイルに書き出せばいい感じで自動化できます。
表示中の画面を画像に書き出す方法は次の通りです。
- (void)captureScreen:(NSString*)name { NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* dir = [paths objectAtIndex:0]; UIWindow* window = [[UIApplication sharedApplication] keyWindow]; UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); for (UIWindow* win in [[UIApplication sharedApplication] windows]) { [win.layer renderInContext:context]; } UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [UIImagePNGRepresentation(image) writeToFile:[dir stringByAppendingPathComponent:name] atomically:YES]; }
なおこの方法ではステータスバーの部分は描画されませんが、今回はそれで良しとしました。回避策としては、ダミーのステータスバー画像を用意しておいて出力画像に合成させるというものがあります。
KIFを使う=テストの実行ということになります。テストもxcodebuild
で実行できるはずなので、本来はブランチ切替→テストも完全自動化できると思いますが、このときは手動でテストを実行していたので検証はしていません。
最後に
今回のアプリのように複数種類を同時に扱うには自動化が不可欠でした。コマンドラインでいろいろサポートされていて助かりました。
iTunesConnectでの操作(アプリ追加、バージョン追加、スクリーンショット追加など)もコマンドラインでできるととても助かるのですが。。。