Unity Cloud BuildでUnityのビルドやバイナリ配布を自動化しよう
お金を払ってProライセンスを使っている人に朗報です。Unityがベータ版として提供する Unity - Cloud Build を使えば、いわゆる継続的インテグレーション(CI)が簡単に行えます。
弊社では「レポジトリの特定のブランチを監視させ、変更があったらiOS版とAndroid版をビルドして配布」という用途のために使用してします。
そうです、TestFlightのような配布機能まで備えており大変便利です。ProユーザーはJenkinsやTestFlightを個別にセットアップする必要がなくなります。
下準備
iOS用ビルドやAndroid用ビルドをするためにはいくつか下準備が必要となります。
identifierの用意
Bundle ID的なものを用意しておく必要があります。iOS Developer CenterでApp IDを作ったりしてiOS向け、Android向けの設定を終えます。あとで必要になるのでビルド用のProvisioning等を用意しておきましょう。つまるところ、手元でビルドするのに必要な準備と大差はないと思います。
手作業による変更が要らない状態に
Xcodeプロジェクトの書き出し後に手作業による設定変更をしている人もいるかもしれませんが、Cloud Buildではそのようなことはできません。プロジェクトの書き出しからビルドまでを完全に自動で通す必要があります。
そこで前回のエントリーUnityでのXcode設定をUnityEditorのスクリプトだけで自動化する につながります。Xcode のプロジェクトを変更する必要がある人はこちらを参考に色々頑張ってみましょう。
Cloud Buildでプロジェクトの設定
Unity - Cloud Build ←まずここからProライセンスを支払っているアカウントでログインし、上部ナビゲーションバーからAdd New
を選択します。
するとこのような画面になるので、監視したいリポジトリのURLを入力しましょう。弊社ではbitbucketを利用しているのでgit@bitbucket:account/repository.git
のようになりました。
次にSSHキーを設定します。この画面で詳しく案内してくれるので特に問題はないでしょう。
どのブランチを監視するのかを決めます。ここで指定したブランチにあるコードを使ってビルドしてくれるようになります。
これまでに設定した内容の確認と、ビルドするプラットフォームを選びます。iOSとAndroidを有効にし、必要であればAuto-build
にもチェックしておきましょう。Auto-build
が有効になっていると、ブランチに対する変更を検知して自動的にビルド→配布を行ってくれます。
予め用意しておいたBundle IDやProvisioning Profile、p12などを入力、アップロードします。
長かったですね。これで自動ビルドのための準備ができました。
次のステップに進むと最初のビルドがスタートします。ビルド完了後は配布URL付きのメールが来ます。
運用例
弊社ではCloud Buildが監視するためだけのブランチを作成しており、区切りのいいタイミングでメインブランチからプルリクエストを作成してビルド用ブランチを更新するようにしています。BitbucketのWebインターフェースでプルリクエストを作ってマージするだけで自動的に実行可能なバイナリを作ってくれるわけです。
もっというとこのプルリクエスト作成→マージの部分はChatOpsな感じで動くようにしています。細かい話は後日書くかもしれませんが、予め登録しておいた命令をbot hogehoge
のような感じでSlackに投稿すると、あとはBotが勝手にマージしてくれるようにしています。
「そろそろメンバー全員に見せておくか」と思ったらSlackにちょっと書くだけで配布まで完了できるわけです。革命的ですね。
UnityでのXcode設定をUnityEditorのスクリプトだけで自動化する
Unityが出力したiOS用プロジェクトの手動編集をなんとかしたいと考えていたら、同じような人がやはり多いようでいろいろな方法がブログエントリで紹介されていました。
- UnityでXcodeのプロジェクト設定を自動化したい(c#でPostProcessBuildを書く場合) - Qiita
- A-Liaison BLOG: Unity の PostprocessBuildPlayer を使って Weak Framework を追加する方法
調べた感じではCodeEditor-for-Unity
やxcodeproj
というRubyのgemを使う方法が多くみられました。前者のライブラリは最近はメンテされていないようですし、後者の場合Rubyが必要になるということで広く導入しにくい(関係者全員にxcodeproj gem入れろとはいえない)ので別の方法がない限りできないかなーと思っていたら、なんとBitbucketにリポジトリを発見しました。
Unity-Technologies / XcodeAPI — Bitbucket https://bitbucket.org/Unity-Technologies/xcodeapi
Unity-Technologiesというアカウント名から察するに公式のツールだと考えて良さそうです。
このコードを使ってXcodeプロジェクト出力後にフレームワークを追加したり設定を変更するような処理を書いてみようと思います。
なお他の手段でできることがこのXcodeAPI
ではまだできないということがあるかもしれないのでその点は注意してください。
導入
準備はほとんど要らなくて、リポジトリからダウンロードしたファイルをUnityのプロジェクト内、Assets
以下の好きなところに突っ込んでおけば大丈夫です。
ビルド後の処理を書く
Assets/Editor
あたりのディレクトリに、例えばModifyXcodeProject.cs
という感じのスクリプトを配置します。
using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; // ←さっきいれたXcodeAPI using System.Collections; using System.IO; public class XcodeProjectMod : MonoBehaviour { // ちょっとしたユーティリティ関数(http://goo.gl/fzYig8を参考) internal static void CopyAndReplaceDirectory(string srcPath, string dstPath) { if (Directory.Exists(dstPath)) Directory.Delete(dstPath); if (File.Exists(dstPath)) File.Delete(dstPath); Directory.CreateDirectory(dstPath); foreach (var file in Directory.GetFiles(srcPath)) File.Copy(file, Path.Combine(dstPath, Path.GetFileName(file))); foreach (var dir in Directory.GetDirectories(srcPath)) CopyAndReplaceDirectory(dir, Path.Combine(dstPath, Path.GetFileName(dir))); } [PostProcessBuild] public static void OnPostprocessBuild(BuildTarget buildTarget, string path) { if (buildTarget == BuildTarget.iPhone) { string projPath = PBXProject.GetPBXProjectPath(path); PBXProject proj = new PBXProject(); proj.ReadFromString(File.ReadAllText(projPath)); string target = proj.TargetGuidByName("Unity-iPhone"); // システムのフレームワークを追加 proj.AddFrameworkToProject(target, "AssetsLibrary.framework", false); // 自前のフレームワークを追加 CopyAndReplaceDirectory("Assets/Lib/mylib.framework", Path.Combine(path, "Frameworks/mylib.framework")); proj.AddFileToBuild(target, proj.AddFile("Frameworks/mylib.framework", "Frameworks/mylib.framework", PBXSourceTree.Source)); // ファイルを追加 var fileName = "my_file.xml"; var filePath = Path.Combine("Assets/Lib", fileName); File.Copy(filePath, Path.Combine(path, fileName)); proj.AddFileToBuild(target, proj.AddFile(fileName, fileName, PBXSourceTree.Source)); // Yosemiteでipaが書き出せないエラーに対応するための設定 proj.SetBuildProperty(target, "CODE_SIGN_RESOURCE_RULES_PATH", "$(SDKROOT)/ResourceRules.plist"); // フレームワークの検索パスを設定・追加 proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)"); proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks"); // 書き出し File.WriteAllText(projPath, proj.WriteToString()); } } }
iOSプロジェクト書き出し後に[PostProcessBuild]
属性を付けたメソッドが呼ばれます。そこでXcodeプロジェクトの本体である.pbxproj
を編集するためのコードを書けばOKです。
上のコードでは
これらのことをやっています。
このコードもXcodeAPIもソース管理に入れてしまえば誰でも使うことができるので他の方法よりも良いかもしれません。
今度はiOSで散布図アニメーションを試してみた
セルフインスパイアな記事。先日Pythonによる散布図アニメーションを取り上げました。
pythonで散布図アニメーションを試してみた - 株式会社CFlatの明後日スタイルのブログ
これと同じようなことをiPhoneでやってみました。
事前準備
アニメーションに使う動画を適当に入手し、フレームを画像にして出力しておきます。
$ ffmpeg -i input.mp4 -f image2 frame%03d.jpg
開発環境
iOS開発なのでXcodeを使います。XcodeでSingle View Applicationあたりで新規プロジェクトを作ります。
また、オープンソースのライブラリも必要となります。
CocoaPodsを導入し(CocoaPods.org - The Dependency Manager for Objective C.)、
依存するライブラリを$ pod install
します。
プロジェクトのルートディレクトリにPodfile
を作り次の内容を記載、からの$ pod install
でインストールが完了するのでxcworkspace
のほうを開きます。
# Podfile pod 'CorePlot' # グラフ描画 pod 'OpenCV' # 画像処理
また、事前準備で作った画像ファイルをプログラムから使うためにプロジェクトに追加しておきます。
まずは結果から
コード紹介
全体の流れはこんな感じ
- 起動時に各画像を読み込んで散布図用の点を計算しておく
- スタートボタンでタイマーをスタート
- 時間が進むごとにグラフを書き換える
画像の読み込み
Single View Applicationのサンプルアプリケーションなので細かいことは気にせず、viewDidLoad
で画像の読み込みを行います。
ここでは100個分を読み込み対象とし、OpenCVのFastFeatureDetectorで抽出した特徴点(X,Y)のvectorを得て、それを1コマ分として_plots
というvectorに保持しておきました。
// ViewController.mm <- C++を使うために.mではなく.mmに変更 #import "ViewController.h" #import <opencv2/opencv.hpp> #import <opencv2/highgui/ios.h> #import <vector> std::vector< std::vector<cv::KeyPoint> > _plots; - (void)viewDidLoad { // ... // あらかじめ画像を読み込んで特徴量の検出を済ませておく [self loadFrames]; } - (void)loadFrames { for(NSUInteger i=1; i<=100; ++i){ UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"frame%03d.jpg", i]]; cv::Mat mat; UIImageToMat(image, mat); cv::FastFeatureDetector det = cv::FastFeatureDetector(); std::vector<cv::KeyPoint> points; det.detect(mat, points); // グラフのY軸と画像のY軸が反対なので値を反転する for(size_t j=0; j<points.size(); ++j){ points[j].pt.y = 360 - points[j].pt.y; } _plots.push_back(points); } }
タイマー処理
続いてアニメーションに必要なタイマー処理の部分です。最初の処理で100枚分の画像データを読み込みました。アニメーションをするには時間経過にしたがって表示するデータを少しずつ変えていけばいいのでタイマーを使います。
タイマーは標準のNSTimer
、0.1秒ごとにstepAnimation
を呼び出し、インデックスをインクリメントしています。このインデックスは表示データの配列を指すインデックスで、タイマーによってこのインデックスを変えていくことで描画するデータを切り替えていくということをやっています。
インデックスを増やしたら(または最初に戻したら)、グラフの再描画を明示的に呼び出します。[self.graph reloadData]
というのがそれに該当します。グラフについては次の節で。
NSUInteger _currentIndex; // 0に初期化しておく - (IBAction)start:(UIButton*)sender { if ([self.timer isValid] ){ [self.timer invalidate]; [sender setTitle:@"Start" forState:UIControlStateNormal]; }else{ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(stepAnimation) userInfo:nil repeats:YES]; [sender setTitle:@"Stop" forState:UIControlStateNormal]; } } - (void)stepAnimation { _currentIndex++; if( _currentIndex >= _plots.size() ){ _currentIndex = 0; } [self.graph reloadData]; }
グラフ描画
Graphの描画にはCorePlotというライブラリを使用しました。汎用性の高いOSSのライブラリです。
core-plot - Cocoa plotting framework for OS X and iOS - Google Project Hosting
ビューやグラフを作る
グラフ表示周りのコードを紹介するにあたり、見た目上の細かい設定がかなり多くて煩雑になるので、その部分を省略して掲載します(最後にコード全文のリンクを紹介します)。
- (UIView*)createGraphView { CPTGraphHostingView *graphView = [[CPTGraphHostingView alloc] initWithFrame:rect]; CPTGraph *graph = [[CPTXYGraph alloc] initWithFrame:rect]; graphView.hostedGraph = graph; CPTScatterPlot *scatterPlot = [CPTScatterPlot new]; scatterPlot.dataSource = self; [graph addPlot:scatterPlot]; self.graph = graph; return graphView; }
イメージ的にはこんな感じの階層で参照を持つようになっています。
- CPTGraphHostingView
- CPTGraph
- CPTScatterPlot
- CPTGraph
CPTGraphHostingView
には対応するCPTGraph
があって、そのCPTGraph
のなかにはCPTScatterPlot
やその他のプロット(棒グラフなど)を複数持てるようになっています。
生成したCPTGraphHostingView
はaddSubView:
しておきます。
- (void)viewDidLoad { // ... // グラフ描画の設定 UIView* graphView = [self createGraphView]; [self.view addSubview:graphView]; }
表示データの取り扱い
CorePlotで表示するデータはDataSource
プロトコルを実装することでCorePlot側に教えてあげる仕組みになっています。UITableView
とほぼ同じ仕組みなのでiOS開発者であれば問題ないはず。
今回はViewController
をそのままDataSourceにします。
// ViewController.h #import <UIKit/UIKit.h> #import <CorePlot/CorePlot-CocoaTouch.h> @interface ViewController : UIViewController<CPTPlotDataSource> @end
あとはDataSourceのインターフェースを実装してあげればいいのですが、そのときにタイマーによって変化するインデックスに応じたデータを返すことでアニメーションを実現しているわけです。
// プロットするデータの数を返す - (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot { return _plots[_currentIndex].size(); } // idx番目に関する値を返す // XなのかYなのかはfieldNumに応じて変える - (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx { NSNumber *num; std::vector<cv::KeyPoint>& points = _plots[_currentIndex]; switch (fieldEnum) { case CPTScatterPlotFieldX: num = [NSNumber numberWithFloat:points[idx].pt.x]; break; case CPTScatterPlotFieldY: num = [NSNumber numberWithFloat:points[idx].pt.y]; default: break; } return num; }
これでグラフの描画もできました。起動してボタンを押せばアニメーションがスタートします!
ソースコード
一挙紹介!動画アプリで使ったCocoaPods
もはやiOS開発に欠かせない存在になったCocoaPods。先日弊社からリリースしたアプリでも、複雑なUIやありがちな機能を簡単に実装するものから実装を少しラクにするようなものまで、いろいろなものを使っています。
iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。
UI/コントロール系
MNMBottomPullToRefresh
MNMBottomPullToRefresh for iOS - Cocoa Controls
「上に引っ張ったら更新する」機能を実現するものです。
iOS標準では下に引っ張って更新を実現する方法は用意されていないので自分でやるためには色々頑張る必要がありますが、MNMBottomPullToRefresh利用することで簡単に実現できました。
MMDrawerController
Search results for mmdrawer - Cocoa Controls
メインコンテンツの左右にサイドバーを配置するためのコントロールです。一昔前のFacebookのiPhone版がこんな感じのインターフェースでしたね。
安易にサイドバーを配置することの賛否はあるとは思いますが、サイドバーを置くとなったらとても使いやすいライブラリでした。
XCDYouTubeVideoPlayerViewController
XCDYouTubeVideoPlayerViewController for iOS - Cocoa Controls
標準のMPMediaPlayerController
では動画のリソースを特定する必要があるため、YouTubeの動画IDだけでは再生できません。
XCDYouTubeVideoPlayerViewControllerは、MPMediaPlayerController
を継承しているクラスです。YouTubeの動画IDから動画自体のURIを解析してくれる機能をもっているため、動画IDから動画を再生するまでがすごく楽でした。
FontAwesome-iOS
Webでよく使われているFontAwesomeをiOSでも使えるようにするライブラリです。ちょっとしたアイコンなら画像なしで表現できるようになるのでとても楽です。
iRate
iRate for iOS - Cocoa Controls
iRateはアプリ起動時に「アプリを評価してね〜」のダイアログを出すための拡張です。この機能はユーザーに負荷をかけることになるのでいい感じのタイミングで出さないと評価を下げることになってしまうのですが、iRateは起動回数や経過日数などから使ってくれているユーザーに対してのみダイアログを出してくれるという仕組みになっています。
ユーザービリティを多少損なうことにはなりますが、レビューへの導線も大切なので入れておきたいところです。
SVProgressHUD
Search results for svprogress - Cocoa Controls
SVProgressHUDを使うと標準のActivityIndicatorよりもちょっとかっこ良くて使いやすいインジケータが使えるようになります。
ライブラリ/拡張
MWFeedParser
MWFeedParser for iOS - Cocoa Controls
RSSフィードを解析するライブラリの中では一番使われている(気がする)ライブラリです。RSSのパースは自分では絶対に書きたくない処理(DRY!)なのでこれを使うのが今のところ一番いいのかなと思います。
NSDate+TimeAgo
Objective-CのNSDate
クラスのカテゴリで、相対日時の文字列を返してくれるインターフェースが提供されます。
// githubより転載 NSDate *date = [[NSDate alloc] initWithTimeIntervalSince1970:0]; NSString *ago = [date timeAgo]; NSLog(@"Output is: \"%@\"", ago); 2011-11-12 17:19:25.608 Proj[0:0] Output is: "41 years ago"
LinqToObjectiveC
ColinEberhardt/LinqToObjectiveC
Objective-CのNSArray
やNSDictionary
のカテゴリで、Linqのようなインターフェースが使えるようになるライブラリです。
内部ではforループを回しているだけなのですが、場合によってはより読みやすいコードにすることができるので使用しました。
// githubより転載 LINQSelector surnameSelector = ^id(id person){ return [person name]; }; LINQAccumulator csvAccumulator = ^id(id item, id aggregate) { return [NSString stringWithFormat:@"%@, %@", aggregate, item]; }; NSArray* surnamesList = [[[[people linq_select:surnameSelector] linq_sort] linq_distinct] linq_aggregate:csvAccumulator];
上の例のNSArray* surnamesList = ...
のところがすごく簡潔に書けていると思います。
- peopleが入っているArrayを
- nameプロパティのArrayにして(
linq_select
) - ソートして(
linq_sort
) - 重複を取り除いて(
linq_distinct
) - カンマ区切りの文字列にまとめる(
linq_aggregate
)
複数種類の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での操作(アプリ追加、バージョン追加、スクリーンショット追加など)もコマンドラインでできるととても助かるのですが。。。
MBaaSのParseでiOSにPush通知を送るチュートリアル
弊社の神経衰弱アプリシリーズではPush通知にMBaaSで有名なParseを使っています。Parseを使うと超簡単にモバイルアプリの通知を発行することができます。
まだ使ったことはありませんが、モバイルアプリやWebアプリのデータ置き場として使ったりバックグラウンドジョブを走らせたりすることができるようです。自前でサーバーを運用する際のサーバーコストやメンテナンスコストが要らなくなる上に、システム的にも信頼できるので使わない手はなさそうです。
また、Parseにはとてもわかりやすいクイックスタートガイドが付いています。このドキュメントに従ってセットアップを行えば簡単に使いはじめることができます!
と言いたいところなのですが、やはり躓くポイントがいくつかありました。ドキュメントが英語なので大事なところを読み飛ばしてしまったり。
そういうわけで、プッシュ通知をさせるためのセットアップ手順を図を交えて説明したいと思います。
iOS Developer Centerでの準備
基本的に公式チュートリアルの内容をベースにしています。
iOS Push Notifications | Parse
証明書リクエストの用意
iOSアプリケーションに対して通知を行う際には、通知用の証明書を使用する必要があります。これはParseから対象アプリへ通知を行う場合も同じなので、Developer Centerから発行された証明書をParseにアップロードする必要があります。
まずは証明書を発行するためのリクエストを作っておきます。
- キーチェーンアクセスを起動する
- メニューから
キーチェーンアクセス
→証明書アシスタント
→認証局に証明書を要求
を選ぶ - 名前とメールアドレスを入れる、メールアドレスは多分Developer登録しているアカウント
- ディスクに保存を選んで
続ける
を押し、ファイルを保存する
このファイルは後ほど使います。
App IDを作成・更新する
続いて通知機能を有効にするアプリのためのIDを発行します。ほぼいつもどおりでいいですが、通知機能を有効にしておく必要があります。
すでに作成済みのApp IDを通知に対応させるためには、App IDを選んでEdit
からPush Notifications
にチェックを入れて保存します。
また、アプリのIDにはExplicit App ID
を指定しておきましょう。com.cflat.hogehoge
みたいなアレです。
証明書を作ってParse登録用のファイルを生成する
次のステップでは、最初のステップで証明書アシスタントを使って作った証明書リクエストをDev Centerにアップロードして証明書を作ります。
先ほど作ったApp IDのEdit
画面に移ります。すると、Push Notifications
下にアップロード用ボタンがあるのでそこをクリックして最初のステップで作ったリクエストを指定します。
完了するとダウンロードできるようになります。
ダウンロードしたファイルをダブルクリックするとキーチェーンアクセスに登録されます。登録された証明書を右クリックし、p12
ファイルを適当な名前でエクスポートします。これはあとはParseに登録します。
このアプリ用のプロビジョニングプロファイルを取得
先ほど用意したApp ID用のプロビジョニングプロファイルを作っておきます。通知機能を有効にするにはプロファイルでビルドする必要があります。
ここはほぼいつもどおりなので省略。
Parseで設定
アプリケーションを作る
ログイン後ダッシュボードの右上にあるボタンからアプリを作ります。識別のための名前を入れるだけでOK。
通知の設定を行う
Settings
タブを開く- サイドバーから
Push Notifications
を選ぶ Client push enabled?
をON
にApple Push Certificate
に先ほど作った.p12
ファイルをアップロード
Xcodeでの設定・実装
クイックスタートを参照すると簡単です。
Parseのフレームワークを導入する
CocoaPodsを使う場合
クイックスタートには書かれていませんが、CocoaPodsが使えます。
Podfile
にpod 'Parse'
と書いて$ pod install
すればOK。こちらのほうが楽です。
自分で追加する場合
ここからiOS用のSDKをダウンロードして、展開したらできるParse.framework
をプロジェクトに追加します。
Downloads & Changelogs | Parse
また、Link Binary With Libraries
に依存しているライブラリを追加してあげる必要があります。追加するのは次ののフレームワーク・ライブラリです。
- AudioToolbox.framework
- CFNetwork.framework
- CoreGraphics.framework
- CoreLocation.framework
- libz.dylib
- MobileCoreServices.framework
- QuartzCore.framework
- Security.framework
- StoreKit.framework
- SystemConfiguration.framework
実装を書く
AppDeletege.m
にクイックスタートにあるコードを書きます。
application:didFinishLaunchingWithOptions:
には次のコードを。
[Parse setApplicationId:@"YOUR_APPLICATION_ID" clientKey:@"YOUR_CLIENT_KEY"]; [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge| UIRemoteNotificationTypeAlert| UIRemoteNotificationTypeSound];
また、次の2つのメソッドを追加します。
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { // Store the deviceToken in the current installation and save it to Parse. PFInstallation *currentInstallation = [PFInstallation currentInstallation]; [currentInstallation setDeviceTokenFromData:deviceToken]; [currentInstallation saveInBackground]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { [PFPush handlePush:userInfo]; }
実装はこれで終わり。
Bundle IDとプロビジョニングの指定
iOS Dev CenterでPush通知を有効にしたアプリのIDを指定しておきます。
ビルド設定を開いて、プロビジョニングプロファイルを先ほど作ったものに設定しておきます。
実際に通知を投げてみる
ここまでで設定が全て終わりました。このアプリをビルドして実機に転送し起動しておきます。
Parseのダッシュボードを開きPush Notifications
を選択、+ Send a push
を押します。
そうすると次のような画面になります。1 recipientとなっていれば先ほど実機に転送したアプリが正しく設定できています。
- 通知先
- 通知時刻
- 期限
- バッジをどうするか
などを決めつつ、通知のメッセージを入れてSend Notification
すれば通知が届くはずです!
長かったけれどお疲れさまでした。
通知メッセージはリアルタイムにプレビューできます。これはおまけかな。
まとめ
- Parseで通知をやってみた
- 証明書周りが少し面倒
- 実装は簡単!
- Webインターフェースから通知を送れるよ
数値計算ライブラリLAPACKはiOS標準で使えた
数値計算用ライブラリで有名なLAPACKですが、iOSではSDKのフレームワークをリンクするだけで使えるようになります。個人的には数値計算はまったくのノーマークだったので驚きました。
準備
プロジェクトにAccelerate.frameworkを追加。
で、インポート。
#import <Accelerate/Accelerate.h>
準備終わりです。
最小二乗問題を解く
動くことを確認するために適当な場所にLAPACKを呼び出すコードを書いてみましょう。ここでは例として3点(1,1)(2,4)(3,7)についての最小二乗問題を問いています。結果は当然y = 3 x - 2
となるので、a = 3
b = -2
となるはずです。
- (void)leastSquare { const int M = 3; const int N = 2; __CLPK_complex A[M*N], b[M], work[M+N]; long m=M,n=N,lda=M,ldb=M,info,ldw=M+N, nrhs=1; A[0].r = 1.0f, A[3].r = 1.0f; A[1].r = 2.0f, A[4].r = 1.0f; A[2].r = 3.0f, A[5].r = 1.0f; b[0].r = 1.0f; b[1].r = 4.0f; b[2].r = 7.0f; cgels_("N",&m, &n, &nrhs, A, &lda, b, &ldb, work, &ldw, &info); NSLog(@"a=%f, b=%f\n",b[0].r,b[1].r); }
CLAPACKと異なるのは__CLPK_complex
を使って複素数を与えることができるようです。今回は使っていませんが。
LAPACKなどのライブラリを使うときによくあるのは、コンパイルがうまくいかないとかリンクがうまくいかないといった導入時点でのつまずきですが、OSが標準でサポートしてくれているためそういった点でつまずくことはほぼありません。
インターフェース自体は従来のCLAPACKとほとんど変わりませんし、LAPACK自体のリソースはそれなりに存在するので十分使えそうです。