プログラマーだけん、がんばる

神の国、島根のプログラマー。サーバ、Rubyまわりの技術(Ruby on Rails, Rhodes etc..)やiOS, Androidなどの開発を行っていくうえで、役だったことなどを共有できればいいなと思います。

「xcode-install」を使用してXcodeもRubyで管理する

xcode-install」とは

少し前までGitHubRubyカテゴリのトレンドにランクインしていて、
気になってはいたのですが導入してみたらすごく簡単にXcodeを管理することが出来ました。

今まで、Xcode複数バージョン管理しようと思うと、
ブラウザを立ち上げ、
開発者ポータルへログインし、
Xcodeを選び、
最新のXcodeが大きく表示され、
それは要らない!となり、
「View All」をクリック、やっと辿り着いた....

マウスいらーん!!!!! (ノ ゚Д゚)ノ ==== ┻━━┻

xcode-install」を使用すると、
rbenvのように、バージョンの異なったXcodeを管理することができます。

つまり、コマンドベースで管理できるようになるのです。

前提条件

  • Mac
  • Rubyが使える
  • Appleの開発者ライセンスを取得している

ご存知のように任意のXcodeのバージョンを使用するためには毎年Apple様にお金を払う必要があります。

導入

Gemなのでいつも通りです。

$ gem install xcode-install --no-ri --no-rdoc

Fetching: multipart-post-2.0.0.gem (100%)
Successfully installed multipart-post-2.0.0
Fetching: faraday-0.9.2.gem (100%)
Successfully installed faraday-0.9.2
Fetching: faraday_middleware-0.10.0.gem (100%)
Successfully installed faraday_middleware-0.10.0
Fetching: plist-3.1.0.gem (100%)
Successfully installed plist-3.1.0
Fetching: multi_xml-0.5.5.gem (100%)
Successfully installed multi_xml-0.5.5
Fetching: security-0.1.3.gem (100%)
Successfully installed security-0.1.3
Fetching: colored-1.2.gem (100%)
Successfully installed colored-1.2
Fetching: highline-1.7.8.gem (100%)
Successfully installed highline-1.7.8
Fetching: credentials_manager-0.9.0.gem (100%)
Successfully installed credentials_manager-0.9.0
Fetching: spaceship-0.3.4.gem (100%)
Successfully installed spaceship-0.3.4
Fetching: claide-0.9.1.gem (100%)
Successfully installed claide-0.9.1
Fetching: xcode-install-0.9.6.gem (100%)
/Users/ryota/.rbenv/rbenv.d/exec/gem-rehash/rubygems_plugin.rb:6: warning: Insecure world writable dir /usr in PATH, mode 040777
Successfully installed xcode-install-0.9.6
12 gems installed

次に、ログイン情報を環境変数にexportします。
ここで値を設定しないと何も動作しません。
ファイルに記述した方は再読込も忘れずに。。

export XCODE_INSTALL_USER=YOUR_USER_VALUE
export XCODE_INSTALL_PASSWORD=PASSWORD_VALUE

以上で導入完了です。rbenvを使用した方は必要時応じて、rehashをしておきましょう。
下記のようにヘルプが表示されたら大丈夫です。

$ xcode-select help

Usage:

    $ xcode-install COMMAND

      Xcode installation manager.

Commands:

    + cleanup     Cleanup cached downloads.
    + install     Install a specific version of Xcode.
    + installed   List installed Xcodes.
    + list        List Xcodes available for download.
    + select      Select installed Xcode via `xcode-select`.
    + selected    Show version number of currently selected Xcode.
    + uninstall   Uninstall a specific version of Xcode.
    + update      Update cached list of available Xcodes.

Options:

    --version     Show the version of the tool
    --verbose     Show more debugging information
    --no-ansi     Show output without ANSI codes
    --help        Show help banner of specified command

使い方(代表的なもを抜粋)

Xcodeをインストールする
$ xcode-install install 7.0.1
Xcodeをアンインストールする
$ xcode-install uninstall 7.0.1
インストール済みのXcodeを確認する
$ xcode-install installed
インストール可能なXcodeを確認する
$ xcode-install list
パスを通すXcodeを変更する(xcode-selectと同等です)
$ xcode-install select 7.1
現在パスが通っているXcodeの確認
$ xcode-install selected

まとめ

この手のツールは使い方も統一されていて簡単に使えますね。
注意しないといけないのはXcode自体のダウンロードの都合でインストールプロセスが長いので、
万が一のためにtmux等の上で実行することをお勧めします。

Xcode 6.3.2(Objective-C) + Quick + Nimble + KIF がうまく動かなかったハナシ

いつもはSpectaでテストをすることが多いのですが、
ミーハーな私は、巷でじわじわきているQuickを導入してみたが、うまくいかなかったので記します。

環境はこちら

  • Xcode 6.3.2 (訳あってObjective-C)
  • KIF (3.3.0):
  • KIF/Core (= 3.3.0)
    • KIF/Core (3.3.0)
  • Nimble (0.4.2)
  • Quick (0.3.1)

QuickとNimbleを用いてモデルのユニットテストを記述していた際にはうまく動作していたのですが、
いざUIテストの記述をしていた際におかしな動きになりました

なにが起こったか?

「KIFを使用してテストを行った際にきちんとエラーがキャッチされず、テストが失敗したことにならない」

なにが原因か?

そもそもKIFはテストのフレームワークに依存せずしない作りになっており、エラーの発生はフレームワーク側へ移譲する実装をしています。

If you want to use KIF with a test runner that does not subclass XCTestCase, your runner class just needs to implement the KIFTestActorDelegate protocol which contains two required methods.

(void)failWithException:(NSException *)exception stopTest:(BOOL)stop;
(void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop;

In the first case, the test runner should log the exception and halt the test execution if stop is YES. In the second, the runner should log all the exceptions and halt the test execution if stop is YES. The exceptions take advantage of KIF's extensions to NSException that include the lineNumber and filename in the exception's userData to record the error's origin.

https://github.com/kif-framework/KIF#use-with-other-testing-frameworks

つまり、Quick側の以下のメソッドの中身が怪しい

(void)failWithException:(NSException *)exception stopTest:(BOOL)stop;
(void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop;

さっそくQuickのソースコードを読んでみる。

/**
 This method is used to record failures, whether they represent example
 expectations that were not met, or exceptions raised during test setup
 and teardown. By default, the failure will be reported as an
 XCTest failure, and the example will be highlighted in Xcode.
 */
- (void)recordFailureWithDescription:(NSString *)description
                              inFile:(NSString *)filePath
                              atLine:(NSUInteger)lineNumber
                            expected:(BOOL)expected {
    if (self.example.isSharedExample) {
        filePath = self.example.callsite.file;
        lineNumber = self.example.callsite.line;
    }
    [super recordFailureWithDescription:description
                                 inFile:filePath
                                 atLine:lineNumber
                               expected:expected];
}


テストを失敗させ、XCode上のファイルにハイライトをつける処理を実装している。
一見間違ってなさそうに見えるので、かなり混乱しましたが、
「recordFailureWithDescription:inFile:atLine:expected:」を呼び出すのはselfに対してではなく、現在実行されているテストのXCTestCaseのインスタンスというのが正解です。

使用しているv0.3.1より上のバージョンにコミットが積まれていました。
ただしこの修正が含まれているバージョンを使用するには、Xcode 7 / Swift 2.0 である必要があります。

つまり詰んだ...orz

解決法

カテゴリで拡張するしかない?

そもそも自分の使い方が悪いんじゃないか、導入の仕方がまずい?など色々悩みましたが、
Spectaでやっていた際には起きなかった事象なので無理やりカテゴリで解決しました。。

#import <Quick/QuickSpec.h>
#import <Quick/Quick-Swift.h>
#import <Quick/QuickConfiguration.h>
#import <Quick/NSString+QCKSelectorName.h>
#import <objc/runtime.h>

@interface QuickSpec (WithObjC)
@property (nonatomic, strong) XCTestRun *testRun;
@property (nonatomic, strong) Example *example;
@end

@implementation QuickSpec (WithObjC)

static QuickSpec *currentSpec = nil;

@dynamic testRun, example;

- (void)setTestRun:(XCTestRun *)testRun {
  objc_setAssociatedObject(self, _cmd, testRun, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (XCTestRun *)testRun {
  return objc_getAssociatedObject(self, @selector(setTestRun:));
}

- (void)performTest:(XCTestRun *)run {
  self.testRun = run;
  [super performTest:run];
}

+ (SEL)addInstanceMethodForExample:(Example *)example {
  IMP implementation = imp_implementationWithBlock(^(QuickSpec *self){
    currentSpec = self;
    [example run];
  });
  const char *types = [[NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)] UTF8String];
  SEL selector = NSSelectorFromString(example.name.qck_selectorName);
  class_addMethod(self, selector, implementation, types);
  
  return selector;
}

- (void)recordFailureWithDescription:(NSString *)description
                              inFile:(NSString *)filePath
                              atLine:(NSUInteger)lineNumber
                            expected:(BOOL)expected {
  if (self.example.isSharedExample) {
    filePath = self.example.callsite.file;
    lineNumber = self.example.callsite.line;
  }
  [currentSpec.testRun recordFailureWithDescription:description
                               inFile:filePath
                               atLine:lineNumber
                             expected:expected];
}

- (void)failWithException:(NSException *)exception stopTest:(BOOL)stop
{
  self.continueAfterFailure = !stop;
  
  NSException *copyException = [exception copy];
  [self recordFailureWithDescription:copyException.description
                              inFile:copyException.userInfo[@"SenTestFilenameKey"]
                              atLine:[copyException.userInfo[@"SenTestLineNumberKey"] integerValue]
                            expected:YES];
}

- (void)failWithExceptions:(NSArray *)exceptions stopTest:(BOOL)stop
{
  NSException *lastException = exceptions.lastObject;
  for (NSException *exception in exceptions) {
    [self failWithException:exception stopTest:(exception == lastException ? stop : NO)];
  }
}

@end

多分(間違いなく)もっといい方法がある気がしますが、
発生していたエラーは解決し、KIFでテストが失敗した際にもうまく動作するようになりました。
しばらくこれで様子をみます。

最適な解決法があればツッコミをお願いします。

iPhone6・iPhone6 Plusまとめ情報

おはようございます。朝起きて知った方も多いんじゃないでしょうか?
開発者の多くの方は注目していたと思いますが、
現地時間9月9日午前10時(日本時間9月10日午前2時)に待ちに待ったApple発表会が米カリフォルニア・クパチーノで開催されました。
そこで発表された事を、まとめてみたいと思います。

続きを読む

RailsからRpushを使用して、スマートフォンにプッシュ通知を送る

サーバサイドの開発をしていて、プッシュ通知をクライアントに送るという機能を実装する事が多くなったのと、日本語の情報が少なかったので書きます。

私がよくRailsからプッシュ通知を送る為に使用しているのが、「Rpush」というgemです。
その他、Rubyからプッシュ通知を送る為のgemというのはあるのですが、最近はもっぱら「Rpush」です。

続きを読む

iOSアプリ【PictMemo】リリースしました。(してましたw)

明けましておめでとうございます。
20歳になりました、友村です。

題名の通り、iOSアプリをリリースしてました。
英語と、日本語でリリースしてます。

【PictMemo】公式サイト

なぜ'してました'かというと、リリースしたのは少し前で、19歳のうちに何かを残したかったからです。
ただ、修正したい箇所があったので、修正して、今「AppStore」で出ているのはバージョン1.1.0です。

アプリの名前は【PictMemo】です。
PictMemoではないです。
【PictMemo】です。
俗に言う"隅付き括弧"というやつが付いてます!←重要!!

何をするアプリか、画像にメモを残すアプリです。

◯メモとは?

単純にキーボードで入力して残すメモと、声を録音して残すメモと、おおまかに分けて2つあります。
f:id:famtom:20140207160957p:plainf:id:famtom:20140207160927p:plain

あと、上級者向けですが、画像というのは見えないデータ(メタデータ)というものを持ってます。
それは、位置情報だったり、カメラの機種だったり、フラッシュの情報だったり、..etc
実は画像一枚に色んな情報が隠されています。

PictMemoでは、画像の見えないデータにもメモを埋め込むことが出来ます。(PictMemo内で撮影した画像のみ)

また、画像のメタデータを分かりやすく表示してくれます(撮影地等のすべてのメタデータ)。
個人的には、この機能が結構お気に入りです。

他にも機能はありますが、紹介はこれくらいにするので、iOSユーザの方は是非使用してみてください。
現在、バージョン2.0.0を申請中で、更に使いやすくなってます。

【PictMemo】のテーマカラーは、「ミッドナイト・ブルー」という色で、流行りのフラットUIを作成するために推奨されている色の一部です。
下記のURLのサイトを参考にしました。
http://flatuicolors.com/

【PictMemo】公式サイトも作成しました。
Ruby製のソフトウェアであるnanocで、さくっと作成しました。

このアプリ開発で得たノウハウも後日まとめて紹介できたらいいなと思います。


以下、「AppStore」でも見れますが、アプリの紹介文です。

【PictMemo】 - 写真にメモを残して整理 -

【PictMemo】は"写真管理を楽にしてメモを残す"アプリです

■PictMemoはあなたがお持ちになっている写真に簡単にメモを残す事ができます

iPhoneに搭載されている写真アプリのように簡単に写真を選択して、メモを残す事ができます。
これはとても簡単で、後で一覧表示することが出来ます。

■手書きメモだけでなく、ボイスメモを残せる

PictMemoでは手入力のメモだけでなく、ボイスメモを残す事ができます。
こちらもとても簡単でかつ、手入力を行う画面と同じ画面で行うことが出来ます。
ボイス録音ボタンを押せば、あなたは端末に向かって声を発するだけです。

■お気に入り写真メモ機能

PictMemoで作成した画像メモに、お気に入りを登録することが出来ます。
登録はとても簡単で、一度登録してしまえば、後はお気に入り一覧表示機能から簡単に閲覧可能です。

■見やすいメモ一覧画面

PictMemoで登録した画像メモは一覧表示で見る事ができます。
とてもシンプルで分かりやすく、ボイスメモを残した場合には音声の再生を行うことも出来ます。

■充実した画像メモ共有機能

PictMemoで登録した画像メモは共有することが出来ます。
例えばメールであれば、メモを本文にして、画像を添付したメールを自動的に作成します。
後は、あなたが宛先を選んで、「送信」するだけです。
他にも、Twitter, Facebook, AirDrop等があります。

もちろん共有した後にも画像メモは残るので、後でPictMemo内で見る事ができます。

■他のアプリで撮影した写真へのメモの書き込みもできる

PictMemoは、iPhoneのカメラロールを使用しますので、iPhoneに保存してある画像であればどの画像にもメモを残すことが出来ます。

■写真の撮影地・住所の確認が出来る

PictMemoは、画像に付属しているメタデータ(見えないデータ)を解析して、
撮影地の地図表示、住所表示などを行い表示してくれます。
その他、シャッタースピード、フラッシュ等の詳しい情報も閲覧可能です。

■PictMemo内で写真を撮影することが出来る

もちろんPictMemo内で写真を撮影することも出来ます。
その場合はとてもスムーズに、メモ入力まで行うことが出来ます。
最初からメモを残す目的での撮影の場合には、PictMemoからの撮影を推奨します。
その場合にも、画像はカメラロールに保存するので、他の写真管理アプリかも参照することが出来ます。

■写真にメモデータを埋め込むことが出来る

PictMemoでは、画像に埋め込まれているメタデータにメモを埋め込むことが出来ます。

◯上級者向け,PictMemo内で写真を撮影した場合のみ


◯本アプリケーションは、カメラロールに依存しているため、写真データを保存していません。標準の写真アプリで写真を削除すると、本アプリケーションでも閲覧することが出来なくなるので、ご注意ください。


ActiveRecordを継承しないモデルオブジェクトで検索機能の実装

こんにちは。今回はRailsについてです。

ActiveRecordを継承しないモデルオブジェクト

Railsを開発するうえで必須になってくるModelオブジェクト。
今回はActiveRecord::Baseを継承せずに、モデルと同じような動きをするRubyオブジェクトを作成して、検索機能を実装します。

作成したRubyオブジェクトにモデルと同じような動きを持たせるメリットとして、

  • バリデーション機能が使える
  • モデルオブジェクトとして振る舞うので、form_forの引数に渡せる
  • バリデーション時のエラーメッセージを簡単に他言語対応できる

上記の機能を持たせることによって、大きくその恩恵をうけることが出来るのが、フォームなどを使用する、検索機能などです。

今回は、productsリソースに基づいて、検索機能を実装します。
productsテーブルのカラムは1つで、"name"属性、タイプは"string"です。

続きを読む

Objective-Cのカテゴリによる既存クラスの拡張とRubyのオープンクラスによる既存クラスの拡張

こんにちは。今回は題名通り、Objective-Cのカテゴリによる既存クラスの拡張とRubyのオープンクラスによる既存クラスの拡張のやり方を紹介します。

今回はObjective-CのNSStringクラスへ@"sample string"を返す、sampleというクラスメソッドと、
RubyのStringクラスへ'sample string'を返す、sampleというクラスメソッドを作成します。

まずObjective-cによる既存クラスの拡張です。

Objective-Cによる既存クラスの拡張

Objective-Cによる既存クラスの拡張を行うにはカテゴリという機能を使用します。
カテゴリとは、クラスの持つメソッドをカテゴリごとに分類するメソッドです。
この機能を使用することによって、メソッドが沢山ある大きなクラスを分かりやすく分類することが出来ます。
カテゴリの名前は自由につけることが出来ますが、用途によって分かりやすい名前がいいと思います。
今回は「Sample」というカテゴリ名にします。

まずiPhoneアプリの新しいプロジェクトを作成します。
Xcodeを起動して、「Create a new Xcode project」をクリックして作成します。
テンプレートは「empty project」でプロジェクト名は「SampleCategory」にします。
f:id:famtom:20131012193703j:plain

次に、フォルダツリーの上で、右クリックして、「New File」-> 「Objective-C class」を選択します。
その次に下記の画面で、classに「Sample」、subclass ofに「NSString」を選択します。
後で修正するので、実際のところなんでもいいです。
f:id:famtom:20131012194326p:plain
ファイルを作成したら 、
「Sample.h」を「NSString+Sample.h」に、
「Sample.m」を「NSString+Sample.m」にリネームします。
ファイルの名前はなんでもいいのですが慣習的に、「拡張するクラス名+カテゴリ名」とするのが一般的のようです。

この2つのファイルにカテゴリ拡張のコードを記述します。

まず、NSString+Sample.hを下記のようにします。

・NSString+Sample.h

#import <Foundation/Foundation.h>

@interface NSString (Sample) //拡張するクラス名(カテゴリ名)
+(NSString*) sample; //作成するクラスメソッドの宣言
@end

ヘッダファイルの実装はこれで終わりです。

次に、NSString+Sample.mを下記のように実装します。


・NSString+Sample.m

#import "NSString+Sample.h"

@implementation NSString (Sample)
+(NSString*)sample{
  return @"sample string";
}

@end

これで実装は終了です。

最後に結果を確認するにAppDelegate.mにメソッドの返り値をアラート表示するプログラムをつけたします。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    // ここからアラート表示プログラムの追加
    UIAlertView *alert =[[UIAlertView alloc]
                         initWithTitle: @"タイトル"
                         message: [NSString sample] // 拡張したメソッドを実行
                         delegate:nil
                         cancelButtonTitle:nil
                         otherButtonTitles:@"OK", nil
                         ];
    [alert show];
    //ここまで

    return YES;
}

まぁ、結果が確認できればOKなので、NSLogでもいいんですけどね。
実行結果が下記のようになります。

f:id:famtom:20131012195927p:plain

結果が確認出来ました。
注意点とては、クラスのインスタンス変数は追加できません。
以上が、Objective-Cのカテゴリによる既存クラスの拡張です。
非常に強力で、魅力的な機能です。

Rubyのオープンクラスによる既存クラスの拡張

Rubyのオープンクラスによる既存クラスの拡張は、Objective-Cのカテゴリによる既存クラスの拡張に比べ、
非常に簡単に実装できます。(個人的に)

まずstring.rbというファイルを作成し、下記の実装をします。
今回は、メソッドの定義と同じファイルに結果を出力するコードを書きます。

class String
  def self.sample
    'sample string'
  end
end

puts String.sample

実装はこれで終わります。
これで、既存のStringというクラスにsampleというクラスメソッドが追加できました。
最後に実行して、

$ ruby string.rb
#=> sample string

という結果になればOKです。

最後に

以上がObjective-CRubyによる、既存クラス拡張例です。
非常に強力ですが、使い所や、間違った使い方をしてしまうと、解決しにくいバグを作ってしまう可能性があるので注意して下さい。
とはいえ、開発をしていればこういったことをしたいと思う時が必ずくると思うので、
頭の隅にでも記憶しておいてもらえれば幸いです。

今回の作成したサンプルのソースです。GitHubで確認できます。

  • Objective-Cのカテゴリによる既存クラスの拡張

https://github.com/tomomura/objective-c-sample-category

  • Rubyのオープンクラスによる既存クラスの拡張

https://github.com/tomomura/ruby-open-class-sample