Tag Archives: Objective-c

Objective-CではURLエンコード/デコードをするのがちょっと面倒です。

JavaScriptのencodeURIComponent()と同じように使えるC関数を提供するライブラリを書きました

使い方は単純です。

AZEncodeURIComponent(@"日本語"); // -> "%E6%97%A5%E6%9C%AC%E8%AA%9E"
AZDecodeURIComponent(@"%E6%97%A5%E6%9C%AC%E8%AA%9E"); // 日本語

CocoaPods経由でインストールできます。

pod "AZEncodeURIComponent"

単純なライブラリですが、何故かこれだけを提供するものがなかったので書きました。。

NewImage

よく見かけるようなUITableViewを使った”このアプリについて”といった画面を簡単に作れるようにしたいなーと思ったので、簡単に作ってみました。
ソースコードはGithubにおいてあります

実装した機能

  • datasourceは別のクラスで作ってセットできる
    • 選択した際の挙動についてもdatasourceにblockで書ける
  • Cell 背景とかを少しカスタマイズ
  • ライセンス表示用のWebViewがセット
  • 問い合わせ用にMFMailComposeViewControllerでメールを立ち上げる機能

簡単な解説を書いていくと、TableViewController(ソースではAboutAppTableViewController)にdatasourceやタップしたらといったものは書かないようにして、
外からインスタンス化した時などにdatasourceを渡せるような構造にしてみました。

サンプルなので、datasourceを渡す部分もAppDelegate.mにまとめて書いちゃってますが、この辺もそれ用のクラスを用意したほうがいいのかもしれない。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Section Names
    NSArray *sectionTitles = [NSArray arrayWithObjects:@"About App", @"Other", nil];

// make data for each section
    NSString *appNameString = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *) kCFBundleNameKey];
    NSString *versionString = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *) kCFBundleVersionKey];
    NSArray *section1;
    section1 = [NSArray arrayWithObjects:
                            [NSDictionary dictionaryWithObjectsAndKeys:
                                              @"App Name", kCellTextKey,
                                              appNameString, kCellDetailTextKey,
                                              nil],
                            [NSDictionary dictionaryWithObjectsAndKeys:
                                              @"Version", kCellTextKey,
                                              versionString, kCellDetailTextKey,
                                              nil],
                            nil];
    NSArray *section2;
    section2 = [NSArray arrayWithObjects:
                            [NSDictionary dictionaryWithObjectsAndKeys:
                                              @"License", kCellTextKey,
                                              Block_copy(^(NSIndexPath *indexPath) {
                                                  [self pushWebView];
                                              }), kDidSelectBlock,
                                              [NSNumber
                                                  numberWithInteger:UITableViewCellAccessoryDetailDisclosureButton],
                                              kCellAccessoryType,
                                              nil],
                            [NSDictionary dictionaryWithObjectsAndKeys:
                                              @"Question?", kCellTextKey,
                                              Block_copy(^{
                                                  [self showMailComposeView];
                                              }), kDidSelectBlock,
                                              @"mail", kCellAccessoryType,
                                              nil],
                            nil];
    NSArray *sections = [NSArray arrayWithObjects:section1, section2, nil];


    self.viewController = [[[AboutAppTableViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
    self.viewController.title = @"About";
    self.viewController.dataSource = sections;
    self.viewController.sectionTitles = sectionTitles;
    // Navigation Controller
    self.navigationController = [[[UINavigationController alloc]
                                                          initWithRootViewController:self.viewController] autorelease];
    // window
    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    self.window.rootViewController = self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

という感じで、sectionやその中のcellについての情報を作ってself.viewController.dataSource = sections;で渡すような感じにしています。
TableViewを表示した後でも、同じようにdataSourceを渡してreloadDataをすればTableViewの内容が更新できます。

    self.viewController.dataSource = sections;
    self.viewController.sectionTitles = sectionTitles;
    // TableView更新
    [self.viewController.tableView reloadData];

それぞれ、SectionとそのSectionの中にNSDictionaryの配列を書いていく感じで、TableViewの内容を決めていけるようにしています。
NSDictionaryのkeyでいくつかの要素を指定できる感じです。

今の所指定できるようにしたのは次の要素です。

  • kCellTextKey
    • Cellの左部分のテキスト
  • kCellDetailTextKey
    • Cellの右部分のテキスト
  • kCellAccessoryType
    • CellのUITableViewCellAccessoryType
    • 数字はkeyに入れられないので、NSNumberでラップしたものを渡す。
    • @”mail” という文字列を指定することで、最初のスクリリーンショットみたいなメールアイコンが出るのも加えた
  • kDidSelectBlock 
    • 選択した際の挙動についてBlockで書く
    • 書かなかった場合は何もおきない

という感じです。

kDidSelectBlockでcellをdidSelectRowAtIndexPathした時の挙動をBlockで書けますが、渡すBlockはそのままだとスコープを抜けた時に破棄されてしまうのでBlock_copy()を使って、
ヒープへコピーしてから渡すようにしています。(あんまり自信ない)

こういう書き方で合ってるのかはよくわかりませんが、タップした時の処理もCellの表示内容と一緒にかけるととても楽です。
これを利用して、LicenseWebViewControllerで用意したライセンスについて書いたhtmlを表示する機能やメールについて動作を書いています。

UITableViewなものを改めて考えて書いてみると意外と勉強になる部分が多かった。
UITableViewの書き方は iOS開発におけるパターンによるオートマティズム に沿った感じの書き方をしています。

最近だと、UITableViewの使い方については以下の記事が面白そう。

TableViewについてだけで一冊本がでるぐらい

UITableViewはいろいろ使い道があって便利ですが、もっと楽に書きたいです。

前回のcedarに引き続き、BDDスタイルのiOSテスティングフレームワークのKiwiについての紹介。

Kiwiについては以下のサイトがとてもよくまとめてくれているので、そこを見るだけでもいい気がしますが、
最近になってKiwiの導入方法等も変わったので、新しくなったKiwiの導入方法についてです。
どちらにしても下記のサイトは参考になるのでKiwiを使うには読んでおくべきでしょう。

公式にもインストール方法のページGuide: Up and Running with Kiwi – GitHubがあるので、そちらも読むといいです。
とても細かくかいてあります。(絶対パスで指定したりする部分があるのが微妙ですが)

今回作成したサンプルプロジェクトは以下に置いてあります

まずはOCUnit環境の構築から始めます。(既にその環境ができてるなら飛ばしても大丈夫です)
ちょっと詳しくは知りませんが、OCUnit環境の上にKiwiの環境を重ねて作るというようなイメージで。

プロジェクトの作成

NewImage

既にできてるプロジェクトに追加する場合は、途中からOCUnitを追加するのと同じでtargetにTest bundleを加えてやればいい。とりあえずここまでやればOCUnitのテストはできてる状態。

NewImage

手動でターゲットにTest Bundleから追加した時はSchemeにそのターゲットのSchemeが追加されるが、
メインとなってるSchemeを選んでいるときにもTestを実行できるようにするには、メインSchemeの編集で、
Testの部分にTestターゲットを関連付けすればメインSchemeからTestを実行できるようになる。(プロジェクト作成でInclude Unit Testした場合はこれが自動で行われている)

NewImage

ここからがKiwiの導入

まずはKiwiを入れたいプロジェクトがあるディレクトリまでコンソールで移動しておく

➜  KiwiSample git:(master) ls
KiwiSample           KiwiSample.xcodeproj KiwiSampleTests
➜  KiwiSample git:(master) git submodule add https://github.com/allending/Kiwi.git Kiwi
Cloning into Kiwi...
remote: Counting objects: 1305, done.
remote: Compressing objects: 100% (453/453), done.
remote: Total 1305 (delta 821), reused 1288 (delta 807)
Receiving objects: 100% (1305/1305), 12.59 MiB | 1.19 MiB/s, done.
Resolving deltas: 100% (821/821), done.
➜  KiwiSample git:(master) ✗ ls                                                                                                                              [~/Dropbox/workspace/iOS/project/KiwiSample]
Kiwi                 KiwiSample           KiwiSample.xcodeproj KiwiSampleTests
➜  KiwiSample git:(master) ✗ git submodule update --init # Kiwiをアップデートしておく                                                                                         [~/Dropbox/workspace/iOS/project/KiwiSample]
Submodule 'Kiwi' (https://github.com/allending/Kiwi.git) registered for path 'Kiwi'
➜  KiwiSample git:(master) ✗ open Kiwi                                                                                                                       [~/Dropbox/workspace/iOS/project/KiwiSample]

このようにして、Kiwiをプロジェクトのgitサブモジュールとして追加しておきます。(まだプロジェクト自体には入っていない)こうしておけば、以降はgit submodule updateとかでKiwiがアップデートできるようになると思います。

そして、最後にopen KiwiでKiwiディレクトリを開くと、Kiwi.xcodeprojがあるので、それをプロジェクトに追加します。

NewImage

 

Kiwiの導入 – 設定

まずはのBuild Phasesタブ設定から

そして、プロジェクトに作ったテストターゲット(画面ではKiwiSampleTest)のBuild Phases -> Target Dependiciesから先ほど追加したKiwiを依存関係に加えておきます。

NewImage

NewImage

次に、同じくテストターゲットのBuild Phase->Link Binary With LibraiesからWorkspaceにあるlibKiwi.aを追加します。

NewImage

次はBuild Settingタブ

テストターゲットのBuild Setting -> “User Header Search Paths”に、追加したKiwi.xcodeproj内のソースを参照するように、Kiwi/Kiwi とパスを追加します。

NewImage

最後にテストターゲットのBuild Setting -> “Other Linker Flags“におなじみの-all_loadを追加します。

NewImage

これで、Kiwiの準備は完了で後はテストケースを書いていくだけです。
KiwiSampleTests.m という次のようなファイルをテストターゲットに追加していけばテストケースを書いていけます。

#import "Kiwi.h"

SPEC_BEGIN(MathSpec)

describe(@"Math", ^{
    it(@"is pretty cool", ^{
        NSUInteger a = 21;
        NSUInteger b = 21;
        [[theValue(a + b) should] equal:theValue(42)];
    });
});

SPEC_END

Kiwiのいい所はデフォルトでOCUnitと同じようにTestからテストを実行できるようなっている所。

NewImage

KiwiはBlocksを使ったBDDライクな書き方ができようになっていて、この辺はCedarと同じです。(語弊ありますが)
また、Kiwi自体にMockとStubの機能が含まれていたりするようです。

Kiwiについてのドキュメントですが、以前はkiwi-lib.infoがあってそこにまとまっていましたが、今はKiwi WikiというGithub上に集めているみたいなので(まだ未完成なのでドキュメントが行方不明です…)、そちらを参照するのがいいでしょう。

一応まだサイト上には過去のドキュメントが残ってるのでそれを見るのがいいかも。

導入方法の参考

おまけ

自分はAppCodeを普段使っているので、TestをAppCodeで走らせたいと思います。

Edit Configuration から OCUnitとしてターゲット:KiwiSampleTestsを指定します。

NewImage

そして、Configを作ったOCunit(画面だとKiwiTest)に切り替えて実行するとテストが実行できます。
(#import “Kiwi.h”でWarningが出てしまいますが、ちゃんと動作します)
OCUnitをAppCodeで実行するのと同じでグリーン、レッドバーで結果が表示されます。

NewImage

補完とか整形が若干おかしいのが残念ですが、構文自体は結構好きです。

 

GH-Unit導入方法とAppCodeを使ったコンソール実行についての動画です。
(画質が悪くてとても見にくいです…)

GHUnitの導入については下記の記事の手順そのままです。

後半は(上記の設定と一緒にしていますが)、GH-Unitをコマンドラインから実行できるようにして、動画ではAppCodeから呼び出して実行結果をAppCode内のコンソールで見るという感じの動画になっています。

コマンドラインから実行させる手順について動画の補足をしておきます。
コマンドライン用の設定はguide_command_line Documentに書かれているので、これをよく読んでおくと動画やっていることがわかると思います。

箇条書きで手順を書くと

  1. GH-Unitをアプリとして実行できるようにプロジェクトの設定をしておく
  2. 最初にcloneしてきたレポジトリ内にあるRunTests.shを、プロジェクトファイルと同じディレクトリに置く
  3. Build Phasesで、Add Run Scriptから先ほどのRunTests.shを実行するため
    sh RunTests.sh
    と設定する。
  4. コマンドラインからビルドコマンドを叩いて実行する

コマンドライン用の設定はこれだけで、後はguide_command_line Documentを見てわかるように、コマンドラインから叩くだけ実行できるようになります。
iOS向けのプロジェクトの場合は以下のコマンドをプロジェクトファイルと同じディレクトリでコマンドラインから入力する事で、テスト結果を表示できます。

GHUNIT_CLI=1 xcodebuild -target {{Tests}} -configuration Debug -sdk iphonesimulator build

{{Tests}}の部分は各自プロジェクトでGH-Unitのテストコードを置いてあるTarget名を入力します。(使いまわせるようにTestsなどに固定したほうが楽かもしれません)
レポジトリにもサンプルMakefileが入っていますが、実際にCIなどで回す場合はMakefileのようなものを書いたほうが楽になると思います
動画ではGHUnit-CLI.shというファイルに上記のコマンドを書いて、AppCodeのExternal Toolsから呼び出してAppCodeのコンソールに表示させています。

この動画でやっている事自体は余り実用性は無いですが、GH-Unitはコマンドラインでヘッドレステストも行えることがわかったかと思います。
コマンドラインでの実行結果を処理するスクリプト等を書けば、GH-Unitをもっと便利に使えるようになる気がします。
実際にJenkinsなどでCI していく場合には、自分の撮影したものではないですが以下の動画が参考になると思います。