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キーを設定します。この画面で詳しく案内してくれるので特に問題はないでしょう。

どのブランチを監視するのかを決めます。ここで指定したブランチにあるコードを使ってビルドしてくれるようになります。

これまでに設定した内容の確認と、ビルドするプラットフォームを選びます。iOSAndroidを有効にし、必要であればAuto-buildにもチェックしておきましょう。Auto-buildが有効になっていると、ブランチに対する変更を検知して自動的にビルド→配布を行ってくれます。

最後にAndroidiOSの設定を行います。

予め用意しておいたBundle IDやProvisioning Profile、p12などを入力、アップロードします。

長かったですね。これで自動ビルドのための準備ができました。

次のステップに進むと最初のビルドがスタートします。ビルド完了後は配布URL付きのメールが来ます。

運用例

弊社ではCloud Buildが監視するためだけのブランチを作成しており、区切りのいいタイミングでメインブランチからプルリクエストを作成してビルド用ブランチを更新するようにしています。BitbucketのWebインターフェースでプルリクエストを作ってマージするだけで自動的に実行可能なバイナリを作ってくれるわけです。

もっというとこのプルリクエスト作成→マージの部分はChatOpsな感じで動くようにしています。細かい話は後日書くかもしれませんが、予め登録しておいた命令をbot hogehogeのような感じでSlackに投稿すると、あとはBotが勝手にマージしてくれるようにしています。

「そろそろメンバー全員に見せておくか」と思ったらSlackにちょっと書くだけで配布まで完了できるわけです。革命的ですね。

UnityでのXcode設定をUnityEditorのスクリプトだけで自動化する

Unityが出力したiOS用プロジェクトの手動編集をなんとかしたいと考えていたら、同じような人がやはり多いようでいろいろな方法がブログエントリで紹介されていました。

調べた感じではCodeEditor-for-Unityxcodeprojという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の明後日スタイルのブログpythonで散布図アニメーションを試してみた - 株式会社CFlatの明後日スタイルのブログはてなブックマーク - 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'   # 画像処理

また、事前準備で作った画像ファイルをプログラムから使うためにプロジェクトに追加しておきます。

まずは結果から

コード紹介

全体の流れはこんな感じ

  1. 起動時に各画像を読み込んで散布図用の点を計算しておく
  2. スタートボタンでタイマーをスタート
  3. 時間が進むごとにグラフを書き換える

画像の読み込み

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

CPTGraphHostingViewには対応するCPTGraphがあって、そのCPTGraphのなかにはCPTScatterPlotやその他のプロット(棒グラフなど)を複数持てるようになっています。

生成したCPTGraphHostingViewaddSubView:しておきます。

- (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;
}

これでグラフの描画もできました。起動してボタンを押せばアニメーションがスタートします!

ソースコード

CorePlotAnimation — Bitbucket

一挙紹介!動画アプリで使ったCocoaPods

もはやiOS開発に欠かせない存在になったCocoaPods。先日弊社からリリースしたアプリでも、複雑なUIやありがちな機能を簡単に実装するものから実装を少しラクにするようなものまで、いろいろなものを使っています。

スポーツ動画アプリ全10種類 | 株式会社CFlatスポーツ動画アプリ全10種類 | 株式会社CFlat

iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。

このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。


UI/コントロール

MNMBottomPullToRefresh

MNMBottomPullToRefresh for iOS - Cocoa Controls

「上に引っ張ったら更新する」機能を実現するものです。

iOS標準では下に引っ張って更新を実現する方法は用意されていないので自分でやるためには色々頑張る必要がありますが、MNMBottomPullToRefresh利用することで簡単に実現できました。

MMDrawerController

Search results for mmdrawer - Cocoa Controls

メインコンテンツの左右にサイドバーを配置するためのコントロールです。一昔前のFacebookiPhone版がこんな感じのインターフェースでしたね。

安易にサイドバーを配置することの賛否はあるとは思いますが、サイドバーを置くとなったらとても使いやすいライブラリでした。

XCDYouTubeVideoPlayerViewController

XCDYouTubeVideoPlayerViewController for iOS - Cocoa Controls

標準のMPMediaPlayerControllerでは動画のリソースを特定する必要があるため、YouTubeの動画IDだけでは再生できません。

XCDYouTubeVideoPlayerViewControllerは、MPMediaPlayerControllerを継承しているクラスです。YouTubeの動画IDから動画自体のURIを解析してくれる機能をもっているため、動画IDから動画を再生するまでがすごく楽でした。

FontAwesome-iOS

alexdrone/ios-fontawesome

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

kevinlawler/NSDate-TimeAgo

Objective-CNSDateクラスのカテゴリで、相対日時の文字列を返してくれるインターフェースが提供されます。

// 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-CNSArrayNSDictionaryのカテゴリで、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 = ...のところがすごく簡潔に書けていると思います。

  1. peopleが入っているArrayを
  2. nameプロパティのArrayにして(linq_select
  3. ソートして(linq_sort
  4. 重複を取り除いて(linq_distinct
  5. カンマ区切りの文字列にまとめる(linq_aggregate

複数種類のiPhoneアプリを同時開発するときのTips

先日弊社からiPhoneアプリをリリースしました。

スポーツ動画アプリ全10種類 | 株式会社CFlatスポーツ動画アプリ全10種類 | 株式会社CFlat

iPhoneで気軽にスポーツ動画を楽しめるアプリケーションをリリースしました。AppStoreにて配信中です。

このアプリではスポーツのハイライトや試合結果、最新ニュースなどの動画を検索することなく閲覧することができます。スポーツが好きな方、スキマ時間を有効に使いたい方に最適な動画アプリです。


このアプリはあるスポーツについての最新の試合結果などを検索せずに見ることができるというものです。アプリはスポーツ毎に分かれているので、サッカーを見たい人はサッカー動画用のアプリ、野球を見たい人は野球用のアプリを使うことになります。

ここまで言うと大体お分かりいただけると思うのですが、アプリの基本部分の作りはどのアプリにも共通したものになっています。つまりソースコードが共有されているわけです。アプリ間で異なるのはアイコン画像やアプリ名、まとめた動画情報など。また内部ではParse.comのIDや広告のIDなどもそうですね。逆に言うとそれ以外の部分が共通しているということです。

このようなケース、ほとんどのソースコードは一緒だけど一部だけ違うアプリを複数同時にリリースするというケースではいろいろなことが面倒になります。

  • ソース管理はどうするのか
  • テスト配布用ipaファイルをどうやって作るか
  • ipaのアップロードはどうやってやるのか

もちろん10種類分のアプリがあるので手動ではやってられないため、できるところはほとんどを自動化することにしました。

このようなケースはあまりないのかもしれませんが、今日の記事ではどのようなやり方をしたかを紹介してみたいと思います。

ソース管理

ほとんどのソースコードが共通なので同じGitリポジトリで管理することにしています。そして各スポーツに対して一つずつブランチを用意します。

$ git br -a
  01.soccer
  02.cricket
  # ...
  10.icehockey
  master
* work-branch01

こんな風に10個分のブランチを用意しました。

全体に関わる変更

全体に関わるコミットはトピックブランチを経てmasterブランチに適用されます。そしてそれらの変更は$ git rebase masterによって各ブランチに取り込まれることになります。

10個のブランチに対してrebaseコマンドを叩いていくのは非常に面倒です。そこで私はRubyrakeを使うことにしました。

$ 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
  1. 10個分のブランチに対してループを回す
  2. 対象ブランチをチェックアウト
  3. アイコン、表示名、プロビジョニングを変更するタスクを実行
  4. xcodebuildコマンドでxcarchiveを作る
  5. xcrunコマンドでxcarchiveからipaファイルを作る

xcodebuildxcrunがここでの肝です。使い方は上のコードを見てもらえば大体わかってもらえると思います。

リリース(アップロード)の自動化

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の明後日スタイルのブログ60分で始めるiOSアプリのUI自動テスト - 株式会社CFlatの明後日スタイルのブログはてなブックマーク - 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を使うと超簡単にモバイルアプリの通知を発行することができます。

神経衰弱アプリ三部作 | 株式会社CFlat神経衰弱アプリ三部作 | 株式会社CFlat

まだ使ったことはありませんが、モバイルアプリやWebアプリのデータ置き場として使ったりバックグラウンドジョブを走らせたりすることができるようです。自前でサーバーを運用する際のサーバーコストやメンテナンスコストが要らなくなる上に、システム的にも信頼できるので使わない手はなさそうです。

また、Parseにはとてもわかりやすいクイックスタートガイドが付いています。このドキュメントに従ってセットアップを行えば簡単に使いはじめることができます!

と言いたいところなのですが、やはり躓くポイントがいくつかありました。ドキュメントが英語なので大事なところを読み飛ばしてしまったり。

そういうわけで、プッシュ通知をさせるためのセットアップ手順を図を交えて説明したいと思います。

iOS Developer Centerでの準備

基本的に公式チュートリアルの内容をベースにしています。

iOS Push Notifications | Parse

証明書リクエストの用意

iOSアプリケーションに対して通知を行う際には、通知用の証明書を使用する必要があります。これはParseから対象アプリへ通知を行う場合も同じなので、Developer Centerから発行された証明書をParseにアップロードする必要があります。

まずは証明書を発行するためのリクエストを作っておきます。

  1. キーチェーンアクセスを起動する
  2. メニューからキーチェーンアクセス証明書アシスタント認証局に証明書を要求を選ぶ
  3. 名前とメールアドレスを入れる、メールアドレスは多分Developer登録しているアカウント
  4. ディスクに保存を選んで続けるを押し、ファイルを保存する

このファイルは後ほど使います。

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。

通知の設定を行う

  1. Settingsタブを開く
  2. サイドバーからPush Notificationsを選ぶ
  3. Client push enabled?ON
  4. Apple Push Certificateに先ほど作った.p12ファイルをアップロード

Xcodeでの設定・実装

クイックスタートを参照すると簡単です。

Quick Start | Parse

Parseのフレームワークを導入する

CocoaPodsを使う場合

クイックスタートには書かれていませんが、CocoaPodsが使えます。

Podfilepod '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自体のリソースはそれなりに存在するので十分使えそうです。