ある三角形の頂点のうち、最後の頂点を求める方法

今日は小ネタです。

三角形ABCの各頂点の座標が、a, b, c であるものとします。
 この三角形ABCのうち、外から渡した座標 d, e のいずれでもない、最後の頂点を求めて下さい。ただし d, e は、必ず a, b, c のうちのいずれかと一致し、また d≠e であるものとします。

答え:a + b + c - d - e

 案外、思いつかないものです。

 ちなみにこの方法は、a〜e が頂点座標ではなく、頂点配列のインデックスの場合にも利用できます。

 似たようなテクニックに、「3つの数字のうち、真ん中の値を求める」のがあります。

template <class T>
T middle(const T& a, const T& b, const T& c)
{
  return a + b + c - std::min(a, std::min(b, c)) - std::max(a, std::max(b, c));
}

☆CSSテクニック☆Borderの継ぎ目

まず最初に、ありがちな次のようなものを作ってみました。

HTMLはだいたいこんな感じ。

<!doctype html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>HTML+CSS3で作る×ボタン</title>
		<style>
		.popup {
			background-color: white;
			color: #888;
			border: solid 1px;
			border-radius: 10px;
			position: absolute;
			top: 5em;
			left: 5em;
			width: 8em;
			height: 10em;
		}

		.popup > .content {
			position: absolute;
			top: 0.5em ;
			bottom: 0.5em ;
			left: 0.5em ;
			right: 0.5em ;
			color: black;
			border: solid 1px;
		}

		.popup > .close {
			border: inherit;
			border-radius: inherit;
			width: 14px;
			height: 14px;
			position: absolute;
			top: -7px;
			right: -7px;
			overflow: hidden;
		}

		.popup > .close > .base {
			border: solid 10px ;
			width: 0px;
			height: 0px;
			position: absolute;
		}

		.popup > .close > .border {
			border: solid 14px transparent;
			width: 0px;
			height: 0px;
			position: absolute;
		}

		.popup > .close:hover {
			color: red;
			border-color: red;
		}

		.popup > .close > .l {
			border-left: solid white 14px;
			top: -7px;
			left: -8px;
		}

		.popup > .close > .r {
			border-right: solid white 14px;
			top: -7px;
			left: -6px;
		}

		.popup > .close > .t {
			border-top: solid white 14px;
			top: -8px;
			left: -7px;
		}

		.popup > .close > .b {
			border-bottom: solid white 14px;
			top: -6px;
			right: -7px;
		}
		</style>
	</head>
	<body style="background-color:#FFC0FF">
		<div class="popup">
			<div class="close">
				<div class="base"></div>
				<div class="border l"></div>
				<div class="border r"></div>
				<div class="border t"></div>
				<div class="border b"></div>
			</div>
			<div class="content">
				<p>Contents</p>
			</div>
		</div>
	</body>
</html>

右上の×印を、borderを用いて作った4つの三角で切り取って作っています。それぞれ、中心から微妙に上下左右に1pxずつずらすことにより、×型の隙間を作っている……という形ですね。
.base なる要素があるのは、わざわざborder-colorとbackground-colorを個別に設定しなくても、colorを設定するだけで一気に色が変わるようにするための工夫です。

これを実際に適用すると、次のようになりました:

WindowsInternet Explorer 10

WindowsIGoogle Chrome 28.0

Windows版IOpera15.0

Windows版IFirefox 22.0

Mac OS XSafari 6.0.5

Mac OS XGoogle Chrome 28.0

Mac OS XOpera 15.0

Mac OS XFirefox 22.0

……なんかFirefoxだけ怪しい。

原因は

1ピクセルずらしをせずに、全く同じ場所に重ねてみると一目瞭然かと思います。

Internet ExplorerGoogle Chrome


Borderの継ぎ目にアンチエイリアスがかかっていることがわかります。

WindowsFirefox

よく見ると、微妙にアンチエイリアスがかかってるような気がしないでもありません。
が……背景色が#7d7d7dだというのに、そのアンチエイリアスっぽいところは#787878だとか#797979だとか。より暗くなっている……何故??
試しに、他の辺のborder-colorをtransparentではなく#888(背景色。実際には#7d7d7dになるのは恐らくOS側が原因)にしてみます。結果は……。


どうやらWindowsFirefox 22.0では、透明borderと不透明borderをブレンドする辺りが上手く行っていないっぽいような予感がしてきます。

Mac OS XFirefox

こちらはそもそも、アンチエイリアスが表示されません。境界線上にあるピクセルは、反時計回りの側のborderの情報をそのまま使っています。
Windows版と同様に、他のborderを#888にしてみましたが、結果は同じ。最初からそういう機能自体がないようです(border-radius等のアンチエイリアスはできるのですが……)。

これらを踏まえて

WindowsFirefoxは少々太い×になってしまうけれども問題はないものとして、MacFirefoxだけ微妙に1ピクセルずつずらした×を作る、ということにすれば、軒並み綺麗な×が作れるような気がします。
JavaScriptFirefox専用のCSSハックを組み合わせて、こんな感じ。

<style>
/* (省略) */

.popup > .close > .l {
	border-left: solid white 14px;
	bottom: -7px;
	left: -8px;
}

.popup > .close > .r {
	border-right: solid white 14px;
	top: -7px;
	right: -8px;
}

.popup > .close > .t {
	border-top: solid white 14px;
	top: -8px;
	left: -7px;
}

.popup > .close > .b {
	border-bottom: solid white 14px;
	bottom: -8px;
	right: -7px;
}
</style>
<script>
jQuery.ready(function() {
	if (navigator.platform.match(/^Mac/i)) {
		jQuery('head').append('<style> @-moz-document url-prefix() { .popup > .close { width: 15px; height: 15px; } } </style>');
	}
}) ;
</script>

上下左右の三角の基準点を反時計回り側の頂点にしておくことで、元は14x14ピクセルの大きさで作っていた×ボタンを15x15にするだけで上手くゆくようになります。

余談

どうやら完璧に見えたGoogle Chrome 28.0でしたが、非transparentなborderが複数になった途端、アンチエイリアスがかからなくなる模様。

もっとも、×印を作る時には各辺バラバラに作るので、関係のないことですが。

今度は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

boost::multi_arrayのランダムアクセスに注意

※下記の内容はVisual Studio2010でコンパイルしました。gcc 4.8.2でコンパイルすると結果が異なります。詳しくはコメント欄をご確認下さい。

便利なboost::multi_array

多次元配列を実装するときに便利なboost::multi_arrayですが、ランダムアクセスが遅いので注意が必要です。 この件について意外と情報が少なかったのでまとめてみました。

サンプルプログラム

#include "stdafx.h"
#include <iostream>
#include <boost/timer.hpp>
#include <boost/multi_array.hpp>

int _tmain(int argc, _TCHAR* argv[])
{
  boost::multi_array<int, 3> multiArray( boost::extents[500][500][500] );

  boost::timer t;

  t.restart();
  for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) {
    for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) {
      for ( int k = 0 ; k < multiArray.shape()[2] ; ++k ) {
        multiArray[i][j][k] = i + j + k;
      }
    }
  }
  std::cout << "ijk time = " << t.elapsed() << std::endl;

  t.restart();
  for ( int k = 0 ; k < multiArray.shape()[2] ; ++k ) {
    for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) {
      for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) {
        multiArray[i][j][k] = i + j + k;
      }
    }
  }
  std::cout << "kji time = " << t.elapsed() << std::endl;

  t.restart();
  int* it = multiArray.origin();
  for ( int i = 0 ; i < multiArray.shape()[0] ; ++i ) {
    for ( int j = 0 ; j < multiArray.shape()[1] ; ++j ) {
      for ( int k = 0 ; k < multiArray.shape()[2] ; ++k, ++it ) {
        *it = i + j + k;
      }
    }
  }
  std::cout << "iterator time = " << t.elapsed() << std::endl;
  
  return 0;
}

実行結果

ijk time = 2.293
kji time = 3.883
iterator time = 0.074

iteratorを使用することによって、ランダムアクセスより10倍以上速くなっています。

一挙紹介!動画アプリで使った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での操作(アプリ追加、バージョン追加、スクリーンショット追加など)もコマンドラインでできるととても助かるのですが。。。

CUDAでプログラミングする前にやっておきたいこと

こちらの記事もご確認下さい。

CUDA6.0用にCPUとGPUの速度比較コードを修正 - 株式会社CFlatの明後日スタイルのブログ

GPUとCPUの速度比較をしたい

以前CUDAでのプログラミングが完成した際に、CPUとの速度比較を行いたいという当たり前の要望が上がりました。 そこでGPUとCPUを切り替えるコードを作成したのですが、どうせなら最初から作っておくべきだったと反省しました。 今回はその際に作成したGPUとCPUの速度比較コードを公開します。

OpenMPの設定

GPUとCPUの速度比較をする際にCPUの方も並列化しないと不公平です。CPUの並列化には設定が簡単なOpenMPを使います。 CUDAプロジェクトでOpenMPを使用できるようにするには、プロジェクトのプロパティを開き、構成プロパティ/CUDA C/C++/Command Lineの追加オプションに-Xcompiler "/openmp"と記載します。 あとは並列化したいfor文の前に#pragma omp parallel forを付け加えるだけです。 CUDAでのプログラミングが初めての方はこの記事も参考にして下さい。

GPUとCPUの切り替えを行うラッピング

下記にあるSwitchableCPUGPU.cuhとTimer.cuh内で宣言されたマクロを呼ぶと、USE_GPUの切り替えでGPUとCPUを切り替えることができます。

SwitchableCPUGPU.cuh

#pragma once
#include "Timer.cuh"

#define USE_GPU

#ifdef USE_GPU

#define SWITCHABLE_DEVICE __device__
#define SWITCHABLE_GLOBAL __global__
#define SWITCHABLE_TIMER CudaEventTimer

#else

#define SWITCHABLE_DEVICE  
#define SWITCHABLE_GLOBAL  
#define SWITCHABLE_TIMER Timer

#endif

template <typename T>
void SwitchableCudaMalloc( T& val, int size )
{
#ifdef USE_GPU
  cudaMalloc( (void**)&val, sizeof(T)*size );
#endif
}

template <typename T>
void SwitchableCudaFree( T* val )
{
#ifdef USE_GPU
  cudaFree( val );
#endif
}

template <typename T>
void SwitchableCudaMemcpyHostToDevice( const T* const host, T* const device, int size )
{
#ifdef USE_GPU
  cudaMemcpy( device, host, sizeof(T)*size, cudaMemcpyHostToDevice );
#endif
}

template <typename T>
void SwitchableCudaMemcpyDeviceToHost( const T* const device, T* const host, int size )
{
#ifdef USE_GPU
  cudaMemcpy( host, device, sizeof(T)*size, cudaMemcpyDeviceToHost);
#endif
}

Timer.cuh

#pragma once
#include <string>
#include <time.h>

class CudaEventTimer
{
public :
  CudaEventTimer( const std::string& message ) : m_message( message )
  {
    cudaEventCreate(&m_start);
    cudaEventCreate(&m_end);
    cudaEventRecord( m_start, 0 );
  }
  ~CudaEventTimer()
  {
    cudaEventRecord( m_end, 0 );
    cudaEventSynchronize( m_end );

    float time;
    cudaEventElapsedTime( &time, m_start, m_end );
    printf("%s = %f sec.\n",m_message.c_str(), time*0.001);

    cudaEventDestroy( m_start );
    cudaEventDestroy( m_end );
  }

private:
  cudaEvent_t m_start;
  cudaEvent_t m_end;
  std::string m_message;
};

class Timer
{
public :
  Timer( const std::string& message ) : m_message( message )
  {
    m_start = clock();
  }
  ~Timer()
  {
    m_end = clock();
    printf("%s = %f sec.\n",m_message.c_str(), (double)(m_end - m_start)/CLOCKS_PER_SEC);
  }

private:
  clock_t m_start;
  clock_t m_end;
  std::string m_message;
};

行列計算でGPUとCPUを比較

下記が行列の掛け算を行うテストコードです。CUDA関連の処理をラッピングされた関数から呼ぶことによって、GPUとCPUの切り替えを容易にしています。

#include <stdio.h>
#include "SwitchableCPUGPU.cuh"

SWITCHABLE_GLOBAL void Calculate( float* matrixA, float* matrixB, float* matrixC, int iLength, int col = 0, int row = 0 )
{
#ifdef USE_GPU
  row = blockIdx.x * blockDim.x + threadIdx.x;
  col = blockIdx.y * blockDim.y + threadIdx.y;

  if ( row > iLength || col > iLength ) return;
#endif

  float target = 0.0f;

  for ( int i = 0 ; i < iLength ; ++i ) {
    target += matrixA[row*iLength + i] * matrixB[i*iLength + col];
  }
  matrixC[row*iLength + col] = target;
}

int main()
{
  // 行列のサイズ決定
  const int iLength = 1024;
  const int iSize = iLength * iLength;

  // CPU側の変数初期化
  float* matrixA = (float*)malloc(sizeof(float)*iSize);
  float* matrixB = (float*)malloc(sizeof(float)*iSize);
  float* matrixC = (float*)malloc(sizeof(float)*iSize);

  for ( int col = 0; col < iLength ; ++col ){
    for ( int row = 0; row < iLength ; ++row ){
      matrixA[col*iLength + row] = rand() % (1000);
      matrixB[col*iLength + row] = rand() % (1000);
      matrixC[col*iLength + row] = 0.0f;
    }
  }

  // ここから時間計測
  SWITCHABLE_TIMER t("time");

  // GPU側の変数初期化
  float* d_matrixA;
  float* d_matrixB;
  float* d_matrixC;
  SwitchableCudaMalloc( d_matrixA, iSize );
  SwitchableCudaMalloc( d_matrixB, iSize );
  SwitchableCudaMalloc( d_matrixC, iSize );

  SwitchableCudaMemcpyHostToDevice( matrixA, d_matrixA, iSize );
  SwitchableCudaMemcpyHostToDevice( matrixB, d_matrixB, iSize );

  // 行列計算
#ifdef USE_GPU
  const int iThread = 16;
  dim3 thread( iThread, iThread );
  const int iBlock = ( iLength + iThread - 1 )/iThread;
  dim3 block( iBlock, iBlock );

  Calculate<<<block, thread>>>( d_matrixA, d_matrixB, d_matrixC, iLength );
  cudaThreadSynchronize();

#else
#pragma omp parallel for
  for ( int i = 0 ; i < iLength ; ++i ) {
    for ( int j = 0 ; j < iLength ; ++j ) {
      Calculate( matrixA, matrixB, matrixC, iLength, i, j );
    }
  }
#endif

  // 後処理
  SwitchableCudaMemcpyDeviceToHost( d_matrixC, matrixC, iSize );

  free( matrixA );
  free( matrixB );
  free( matrixC );
  SwitchableCudaFree( d_matrixA );
  SwitchableCudaFree( d_matrixB );
  SwitchableCudaFree( d_matrixC );

  return 0;
}

実行結果

実行結果は次のようになりました。参考までに並列化しなかったCPUの結果も載せておきます。 GPUには苦手な計算もあるので、OpenMPと比べて明らかに遅い場合はGPUの使用を検討した方が良いかもしれません。

GPU:0.152510s
CPU OpenMP:1.220000s
CPU 並列化無し:7.254000s

GPU計算の高速化

今回のプログラムでは全く行っていませんが、GPU計算には色々な高速化手法があります。 興味のある方は下記の書籍等を参考にして下さい。