2013年9月30日月曜日

CoreData での DISTINCT 取得

CoreData で重複の無いデータを取得する方法が必要だったので調べてみた。
NSFetchRequest の setReturnsDistinctResults に YES を渡せばよい。

具体的なコードは、以下の通り
今回は例として、name が重複しているデータから重複を取り除いてデータを取得する処理を行う。
    // NSFetchRequest は、検索条件などを保持するオブジェクト
    // 後続処理で、このインスタンスに色々と検索条件を設定する。
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    // 検索対象エンティティを指定する。
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Theme" 
                                              inManagedObjectContext:_managedObjectContext];
    [fetchRequest setEntity:entity];
        
    // ソートを設定
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];

    // コードで絞り込んだ値を取得する
    NSDictionary *entityProperties = [entity propertiesByName];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:[entityProperties objectForKey:@"name"]]];
    [fetchRequest setReturnsDistinctResults:YES];   // ← ここで指定する
    
    [fetchRequest setResultType:NSDictionaryResultType];
    
    NSError *error;
    NSArray *fetchResults = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
    if (fetchResults == nil) {
        // Handle the error.
        NSLog(@"fetch error.");
    }
これで、NSArray に重複が除去されたデータが格納されている。

UITabBar のアイテムを選択する

KIF を使用してテストを書こうとしたが、TabBar のアイテム選択の方法が分からない。

UITabBarController を使用してボタンを選択する方法は良くあるけど、UITabBar だけでの選択方法の情報が無かった。

とはいえ、やはり実現している方はおられたので参考にさせていただいた。

【文法】UITabBarのItem(UITabBarItem)の切り替え
http://teapipin.blog10.fc2.com/blog-entry-66.html

現在サンプルで作成しているアプリがナビゲーションコントロールも使っている事もあって、TabBar コントロールを取得するまでが、これまたしんどかったが、こちらも情報が有ったので知識を拝借させて頂いた。

UIViewからUIViewControllerを見る
http://ameblo.jp/pyon-prog/entry-11329857939.html

上記 2 つを合わせて KIF 用のテストを以下のように書いた。
    -(void) testTabBar0Push {
                
        // TabBar の Delegate を発火させるため ViewController まで辿る
        UIApplication *app = [UIApplication sharedApplication];
        UIWindow *window = app.keyWindow;
        
        // rootViewController を取得
        UIViewController *uiview = (UIViewController *)window.rootViewController;
        
        // NavigationController から対象の ViewController を取得
        UINavigationController *navigationController = [uiview.childViewControllers lastObject];
        NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:navigationController.viewControllers];
        ViewController *view = (ViewController *)[viewControllers objectAtIndex:0];
     
        // TabBar
        UITabBar *tabbar = (UITabBar *)[tester waitForViewWithAccessibilityLabel:@"tabBar"];
        UITabBarItem *item = [tabbar.items objectAtIndex:0];
        
        // 選択
        [tabbar setSelectedItem:item];
        [view tabBar:tabbar didSelectItem:item];
        
        // 確認用コードを以下に書く
        ...
    }
どう考えても力業過ぎてコレで良いのか?
と思うがひとまずの動きが出来ているので良しとしようと思う。

今後もっとスマートな方法があれば、また別の記事で書こうと思う。

2013年9月28日土曜日

VIM NeoBundle の通信プロトコルの設定

先日記事にした NeoBundle を前回は Windows の環境だったが、Mac の Vim にも設定しようとしたが、なんだかうまくいかない。

メッセージ云々を見ると、

「...Failed connect to github.com:8080; Connection refused ...」

というメッセージが見える。
コマンドとしては https のプロトコルで通信をしようとしていたのだが、この部分がまずいようだ。

ネットワークの設定としては別段変なことはしている環境ではないのだが、よくわからない。
ブラウザでアクセスすると問題なくページが見えることから、どこかで変な設定が効いているのであろうと思う。

ちなみにと git プロトコルでファイルの取得を行うと、問題なく取得できる。
このことから、ひとまずここでは git プロトコルでの動作方法を考えたところ、
NeoBundle には以下の設定があるとのこと。
    
let g:neobundle_default_git_protocol='git'
この設定通常(指定がなかったら)は git になっているらしいのだが、あえて追加して確認をしてみると上手く動いた。

調査する部分はあるが、ひとまずはこれで動かして行っておこうと思う。

2013年9月27日金曜日

applicationDidBecomeActive が呼ばれた時に ViewController のメソッドを呼ぶ

CoreLocation を使うアプリを作っているが、アプリを閉じる→再開の繰り返しをすると、数回に一度アプリが落ちる現象が出る。

Xcode のデバッガには、「com.apple.CoreLocation.ConnectionClient」と出て落ちているから当たりは間違い無いとは思うが、原因が分からない。

処理としては、CoreLocation の開始を viewDidLoad で行ってた。
そして、バックグラウンドからの復帰時に CoreLocation の開始が行われていないため、無理矢理 applicationDidBecomeActive で、viewController の viewDidLoad を呼ぶようにしていた。
つまり、連続での CoreLocation のスタートをかけているのがまずいのでは?と思い、この部分にあたりを付けてみた。

さて、この場合 1 回だけ Location をスタートするにはどうすれば?

NSLog を張り巡らして見ていると、真っ新からの起動であろうと、バックグラウンドからの起動であろうと、applicationDidBecomeActive を通る事が分かった(公式のドキュメントにも書いてある)。

要するに、applicationDidBecomeActive で、Location の開始処理を行えば良いのだが、applicationDidBecomeActive は、AppDelegate でのメソッドのため処理の関係上 Location のスタートを開始するには不適切だった。
そこで、applicationDidBecomeActive に入ったときに ViewController のメソッドが発行でき、そのメソッドで Location の開始が出来れば良いと思い処理をする方法を調べたところ、以下の実現方法があったので採用した。

これは通知処理を使って、applicationDidBecomeActive が発生したときに発火するメソッドを登録すると言う物。
この登録を viewDidLoad で行う。
当然、いままで有った Location スタートは削除する。
    -(void) viewDidLoad {
        ...
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidBecomeActive)
                                                     name:UIApplicationDidBecomeActiveNotification
                                                   object:nil];
        ...
    }
また、applicationDidBecomeActive 後に発動するメソッドを記述する。
ここに、Location のスタートをする処理を入れる。
    // applicationDidBecomeActive が呼ばれた時に実行する
    - (void)applicationDidBecomeActive {
        // CoreLocation 開始
        [_manager startUpdatingLocation];
    }

これで、立ち上げ→バックグラウンドの連続操作を繰り返してみたところ、アプリが落ちる事が無くなった(とはいえ、安心は出来ないので後日合間を見て検証をしてみる)。
他の遷移の場合を考えたらもう少し手間をかける必要は有ったが、取りかかりとしてはこの線で良かった。

2013年9月25日水曜日

KIF を使ってみる

iOS用の結合テストフレームワーク KIF を使ってみた。
ちなみに KIF は、「Keep It Functional」と言う事らしい。

KIF
https://github.com/kif-framework/KIF

Web で情報を集めてインストール等を行ってみたが、どうやらバージョンが違うらしく、
その通りにやってもエラーが出たので、半ば折れかけになってしまった。
公式の英語を頑張って見れば何とかなったので残しておく。

ちなみに今回インストールするバージョンは、 2.0 系。
なお、ここでは、テスト被対象を、「製品ターゲット」とし、テストユニットのプロジェクトを「テストターゲット」として記述していく。

1. 製品プロジェクト(製品ターゲット)の作成
今回も適当に Single View Application のプロジェクトを作成する。

2. テストターゲットの作成
プロジェクトナビゲータを開き、下部にある「Add Target」ボタンを押す。
ターゲットの選択が出るので、iOS -> Other を選択し、
「Cocoa Touch Unit Testing Bundle」 を選択する。

プロジェクト名は、任意。
今回は、「Integration Tests」とした。

これで作成した名前と同じフォルダがナビゲータに作成されている。
このフォルダ内にあるファイル(.m/.h)は不要なため削除する。

ここで、一旦 Xcode を閉じる。

3. KIF のインストール
KIF のインストールは、前回使い方を学んだ CocoaPods を使用する。
ターミナルを開き、上記で作成したフォルダまで移動する。

ここに、CocoaPods用のファイル「Podfile」を作成する。
    $ vim Podfile
    platform :ios, '6.0'
    target 'Integration Tests', Integration Tests
        pod 'KIF', '~> 2.0.0'
    end
以下のコマンドでインストールする。
    $ pod install
これで、CocoaPods 用のプロジェクトファイルが作成されるので、
作成されたプロジェクトファイルで Xcode で起動する。

4. テスト用プロジェクトの設定
プロジェクトナビゲータから TARGETS で、作成したテストターゲットを選択。

次に、Build Settings をクリックする。
検索窓等を使って Bundle Loader を検索する。
この設定値が空なら、以下の項目を入力設定する。
    $(BUILT_PRODUCTS_DIR)/アプリ名.app/アプリ名
同じく、検索窓等を使用して Target Host に以下の値をセットする。
    $(BUNDLE_LOADER)
次に、Build Phases の Target Dependencies に、テストターゲットを追加する。

最後に、Scheme -> Edit Scheme で、
Build の項目に「+」で、テストターゲットを追加
Test の項目に「+」で、テストターゲットを追加
(この設定で、製品ターゲットを Scheme で選択している状態でも Command + U でテストが走るようになる)

5. テストファイルの作成
ここで、テストを記述するファイルを追加する。
テストターゲットのフォルダに、任意のクラスファイルを作成する。

作成した中でヘッダーファイルには、「KIF/KIF.h」 をインポートする。
    #import <KIF/KIF.h>

    @interface hogeTests : KIFTestCase
    @end
実装ファイルには、以下の内容でメソッドを追加する。
    #import "hogeTests.h"

    // 実行前
    -(void) beforeAll {
    }
    // 都度実行前
    -(void) beforeEach {
    }
    // 都度実行後
    -(void) afterEach {
    }
    // 実行後
    -(void) afterAll {
    }
    // テスト
    -(void) testXXX {
        // Accessibility Label が「SignIn」の UIView をタップ
        [tester tapViewWithAccessibilityLabel:@"SignIn"];
    }
以上で、テストを実行すればテストが動くはず。

ちなみに、1.0 系では KIFTestController を継承して作成するが、2.0 では、このクラスが KIFTestCase になっており、
またシナリオという考えから普通のテストと同じ test~ に変わっている。
シナリオについては、ファイル毎に分ければ良いという事だろうか。

1.0系と、2.0系とのテストケースの記述違いについては、以下を参照

変更点ぽい物
https://github.com/kif-framework/KIF/releases/

あと、UIView などの操作は、「Accessibility Label」 を使用(ラベルを元にコントロールを探す)しているので、
付けていなかった場合はつける事(storyboard で付けるかコードで付ける)。

テストで使えるメソッドについては、インストールされた KIF のソースを見ればメソッドが書かれているので頑張って自分で実装したいメソッドを探し利用する。
また、「SenTestingKit」 も継承しているので、OCUnit での記述はそのまま生かせる。

最後に
感想としては、設定については OCUnit と同じ感じなので普段から利用している人にしたら楽に設定が出来るのではないかと思った。
また、はじめにも書いたがバージョンの違いで全く使い方が違うので迷わない事。
Ver1.0 では、シナリオとか書いてあったのに 2.0 系では全く無くなっていたのは困った。

駆け足で書いて、かつ毎度の事ながらスクリーンショットが無いので把握しづらいとは思う。
とりあえず使える状態に持って行くまでが面倒だが、これも一度設定すれば開発終了まで使えるので、設定の価値は有ると思う。

2013年9月24日火曜日

CocoaPods を使ってみた

ここ最近はテスト関連の情報をよく見る。

そこで、それらのライブラリのインストールには github から取得する方法と、
「CocoaPods」 からインストールする方法が書かれいている。

CocoaPods。。。ここ最近色んな技術の名前を見ているので、消化不良気味で一歩引いていたが、何か良い事有るだろうという事で、調べてみた。

早い話、Xcode でのライブラリ管理が行えると言う物。
Ruby の RubyGem の様な物。

CocoaPods
http://cocoapods.org/

このリンクを見れば、全て書かれてあるが、実際に使ってみないと分からないので、試しに使ってみた。

一緒に参考にしたのは以下のページ

開発レシピ:Objective-Cのライブラリ管理ツール CocoaPods
http://www.iosjp.com/dev/archives/451

これまた、上記のページを見れば終わる(。。。この記事の意味は???)
でも気にしない。
今回は、前回の GHUnit の導入までを試してみる。

1. 前準備
CocoaPods のインストールには ruby(rubygems) を使用する。
ruby(rubygems) を最新の状態でのインストールが良いらしいので、最新の状態にしておく。
$ gem update --system
あと、Xcode の Command Line Tools も必要となるので、インストールする。

2. インストール
以下のコマンドで、CocoaPods をインストールする。
$ gem install cocoapods
インストール完了後は、一旦ターミナルを閉じて再度開き直すと良い。

そのあと、CocoaPods のセットアップコマンドを入力
$ pod setup
余談になるが、以下のコマンドでバージョンの確認が出来る
$ pod --version
3. 対象プロジェクトの作成
CocoaPods でインストールする GHUnit の対象となるプロジェクトを作成する。
今回はテストなので、適当に Xcode から Single View Application を作成する。

次に、テストを格納するプロジェクトを作成
Menu -> File -> New -> Target
作成したプロジェクトから、不要となるファイル(AppDelegate.m/h)を削除する。

プロジェクトナビゲータで、インストールに必要な QuartzCore.framework を追加

ここで、一旦 Xcode を閉じる(不要かも知れない)

4. CocoaPods の設定
上記で作成したフォルダまでターミナルで移動する(xxx.xcodeproj が有るところまで)。
CocoaPods が読み込む 「Podfile」 というファイルを作成する。
$ vim Podfile
platform :ios, '6.0'
pod 'GHUnitIOS', '~> 0.5.5'
5. インストール
Podfile が作成できたら、以下のコマンドによりインストールする。
$ pod install
これで、インストールが始まる。
インストールが完了したら、「xxx.xcworkspace」というファイルが作成されている。
(元々のプロジェクトファイル xxx.xcodeproj とは別に作成されているところに注意) このファイルをダブルクリックして Xcode を起動する。
(ターミナルから 「open xxx.xcworkspace」 とコマンドを打っても開く事が出来る)

6. GHUnit 使用前準備
プロジェクトナビゲータで、Build Settings の Other Linker Flags に 「-ObjC -all_load」 を追加する。

テストプロジェクトに main ファイルを作成する。
    int main(int argc, char *argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate");
        }
    }
7. テストを書く
あとは、テストコードを記述していく。
(CocoaPods のインストールなのでここまでで終了)


注意点:
特に大きくはまった事もなくスムーズに導入できたので良かった。
とはいえ、凡ミスが多少あったので記載します。

引っかかった点としては、
  • Podfile を Profile と間違えていた。
    論外ですな。
    でも、間違ってたんです。。。
  • Podfile の記述で「:」と項目の間にスペースを入れてしまった。
    詰めて記述しないとダメなようです。
    ×「platform : ios」
    ○「platform :ios」
本当にしょうもないミスで恥ずかしい。。。

導入するまでは、また新しい技術かと尻込みしたけど、一度使い方を覚えれば、後の作業に十分対応できてしかも簡単となれば、導入する価値は十分有るなと思いました。