5.1さらうどん

@giginetの技術ブログ。ゲーム開発、iOS開発、その他いろいろ

XIBを読み込んで良い感じにViewを生成できるライブラリ作った

Macアプリを作ってて、XIBからNSViewを生成したいだけだったのになかなか面倒だったので簡単にできる奴を作った。 似たようなものはありそうなのに、探してみたら意外とちゃんと使える奴が少なかった。

github.com

自分で定義したCustomViewクラスとCustomView.xibを作っておけば、以下のようにするだけで簡単にViewが生成できる。 Genericsを使っているのでカスタムビューのクラスで取得できる。

let customView: CustomView = try! CustomView.view(withOwner: self)
customView.label.text = "Hello World"
self.view.addSubView(customView)

UIKitにもCocoaにも対応しているので、UIViewでもNSViewでも同じように使える。ついでにtvOSでも使える。

クラスを作るまでもなく、ただUIViewのXIBを読みたいだけという用途の場合、自分でファイル名も指定できる。

let customView: UIView = try! UIView.view(
    fromNibNamed: "MyCustomView", 
    owner: self, 
    bundle: NSBundle(forClass: self.dynamicType)
)

気合入れて大きなライブラリを作らなくても、こういうスニペットレベルのものの方がむしろ汎用性が高くてよさそう。

知見

本当に小さいライブラリだし、Swiftライブラリ作成の知見は前回書いてしまったので、他に特に書くことはない。

giginet.hateblo.jp

一つ言えることは、最初はNibLoaderというライブラリ名だったんだけど、CocoaPodsに既に同名のライブラリがホストされてて、完成段階になって泣きながら名前を変えるハメになって大変面倒だったので、作り始める前は下調べをするべき。

どうぞご利用ください。

SwiftでJSONのテストを良い感じにするJSONMatcher作った

この度、JSONMatcherというSwift向けのテストライブラリを開発しました。

github.com

これは何?

SwiftでJSONのオブジェクトや文字列を検査するマッチャーです。

JSONオブジェクトのテストがこんな感じで簡単に書けます。

import XCTest
import Nimble
import JSONMatcher

class ExampleTestCase: XCTestCase {
    func testComplexExample() {
        expect([
            "name" : "Snorlax",
            "no" : 143,
            "species" : "Sleeping",
            "type" : ["normal"],
            "stats" : [
                "hp" : 160,
                "attack" : 110,
                "defense" : 65,
                "special_attack" : 65,
                "special_defense" : 65,
                "speed" : 30
            ],
            "moves" : [
                ["name" : "Tackle", "type" : "normal", "level" : 1],
                ["name" : "Hyper Beam", "type" : "normal", "level" : NSNull()],
            ]
        ]).to(beJSONAs([
            "name" : "Snorlax",
            "no" : Type.Number, // value type matching
            "species" : try! NSRegularExpression(pattern: "[A-Z][a-z]+", options: []), // regular expression matching
            "type" : ["[a-z]+".regex], // shorthands for NSRegularExpression
            "stats" : [
                "hp" : 160,
                "attack" : 110,
                "defense" : 65,
                "special_attack" : 65,
                "special_defense" : 65,
                "speed" : 30
            ],
            "moves" : [
                ["name" : "Tackle", "type" : "[a-z]+".regex, "level" : Type.Number], // nested collection
                ["name" : "Hyper Beam", "type" : "normal", "level" : NSNull()],
            ]
        ]))
    }
}

JSONMatcherは、Swift製のマッチャーライブラリ、Nimbleの拡張として動作し、与えられたJSON文字列、オブジェクトを簡単に比較できます。

上記のように、複雑なJSONであっても直感的にマッチャーを書くことができます。

マッチャーとして

  • 与えられたオブジェクトや文字列がJSONであることを判定するbeJSON
  • 特定のオブジェクトを含むかを判定するbeJSONIncluding
  • 2つのオブジェクトが一致することを判定するbeJSONAs

の3種類を提供しています。

expect("{\"name\": \"Pikachu\"}").to(beJSON())
expect(["name" : "Pikachu", "no" : 25]).to(beJSONIncluding(["name" : "Pikachu"]))
expect(["name" : "Pikachu", "no" : 25]).to(beJSONAs(["name": "Pikachu", "no" : 25]))

また、上記の例にあるとおり

  • 正規表現NSRegularExpression
  • 型マッチングのようなもの

にも対応していて、かなり柔軟に比較することができます。

このライブラリは、rspec-json_matcherに大変影響を受けてます。

github.com

Swiftでライブラリを作る

今回のモチベーションの一つとして、Swiftのライブラリを最新のエコシステムを用いてしっかり作ってみたいというものがありました。

Swiftのライブラリ作成の知見は枯れておらず、有名OSSを眺めてみてもあまり統一されていなかったので、今回作成した知見をまとめてみました。

f:id:gigi-net:20160521182333p:plain

しっかりテストして、こういう風にバッヂを貼りまくると、ちゃんとしたライブラリっぽくなります。

ライブラリ作成にあたって、3月に行われたtry! Swiftの「Creating a Swift Library」というセッションが大変参考になってオススメです。

以下の書き起こしやリポジトリを参考にしてみてください。

try! Swift ライブラリの開発 #tryswiftconf Day2-8 - niwatakoのはてなブログ

github.com

ディレクトリ構造

まずSwiftのライブラリを作るにあたって、ディレクトリ構造からして悩むのですが、結論から言えば、最小構成で以下のような感じにすれば良さそうです。

実装はSources/LibraryName以下にソースコードTests/LibraryName以下にテストコードを配置するのがベストのようです。

├── Cartfile
├── Carthage
│   └── Checkouts
├── JSONMatcher.xcodeproj
├── Sources
│   └── JSONMatcher
│       ├── *.swift
│       └── Info.plist
└── Tests
       └── JSONMatcher
           ├── *.swift
           └── Info.plist

テスト

f:id:gigi-net:20160521185739p:plain

テストは必ず記述しましょう。XCTestをそのまま使っても良いですが、今回は前述のNimbleを用いて記述しています。

今回は使用していませんが、さらにRspecっぽく書きたい方はQuickを併せて使っても良いでしょう。

テストの実行には簡単なShellスクリプトを用意して、xcodebuild testを実行してます。

マルチプラットフォーム

Swift向けのライブラリは、iOSOSX、tvOS、watchOS、Linuxの5つの環境向けに作ることができます。

今回はiOSOSX、tvOS向けに対応させてみました。

watchOSではそもそもXCTestが使えないため、今回は対応できず、Linux対応は面倒そうなのでやっていません。

マルチプラットフォーム対応は簡単で、以下のようにプラットフォーム別にターゲットとスキーマを作成しましょう。

f:id:gigi-net:20160521191145p:plain

最初はiOS向けのみに実装し、テストがある程度揃ってきたら、テストを実行しつつ、マルチプラットフォーム化をするのが楽でした。

CI

CI as a Serviceとして、Travis CIやCircle CIが使えます。好きなものを使いましょう。

CIでは以下のような事を行っています。

  • 各プラットフォーム向けのテストの実行
  • カバレッジのレポート
  • swiftlintの実行
  • CocoaPodsの検証

また、先日、Travis CIでのPublicリポジトリ向けのキャッシュの提供が開始されました。Swiftのライブラリはビルド時間が長くなりがちなので、依存ライブラリのビルドに時間がかかる場合は利用すると良いでしょう。

カバレッジ

ついでにテストカバレッジを取得しましょう。Xcode7からはテストスキーマのチェックを入れるだけで、簡単にカバレッジを計測できるようになりました。

f:id:gigi-net:20160521191244p:plain

計測したカバレッジは、CIからcodecov.ioというサービスに送っています。codecovはSwift対応が楽で大変便利です。

f:id:gigi-net:20160521191005p:plain

Coverallsでの表示も可能ですが、公式でサポートされていないようでした。

パッケージ管理ツール

現在、Swiftのパッケージ管理ツールとして、CarthageCocoaPodsSwift Package Manager(SPM)の3種類がよく知られています。

対応するための作業はそれほど多くないので、とりあえず全てに対応させてます。

Carthage

基本的に上記の通り、プラットフォーム別にスキーマを作ってあげると、良い感じで対応できます。

今回のように依存関係があるライブラリの場合はCartfileに依存環境を記述しましょう。

Carthageの設計思想は、Xcode標準のスキーマを使ったシンプルなビルドシステムであり、Carthageに対応して作ると良い感じの構造になるので、意識して設計すると良さそう。

CocoaPods

pod spec createなどを実行すると、podspecのひな形ができるので良い感じで記述していきます。

また、CIでpod lib lintなどを回すようにしておくと、podspecの検証ができます。

完成したPodは公式のリポジトリに登録すると良いでしょう。

SPM

Carthage対応ができていればPackage.swiftというファイルを置くだけでなんとなく対応できます。

ほとんどユーザーはいないと思うのでちゃんと動くかどうかはあんまり確認してません。

静的解析

静的解析ツールとして、Realmが提供しているSwiftLintを使って、Travis CI上で回しています。

github.com

SwiftLintはデフォルトの設定だと厳しすぎるので、.swiftlint.ymlという設定ファイルで挙動をカスタマイズすることができます。(参考

どの警告を抑止すべきか、開発元のRealmでの利用例を踏襲しておけば問題ないと思ったので、主にここの設定を参考にしています。

まとめ

ざっくりと開発で培った知見をまとめてみました。参考になれば幸いです。

また、テストケースでJSONを扱う需要がある方は是非JSONMatcherをご利用ください。Pull Requestも歓迎です。

github.com

pecoでrbenvとかpyenvを簡単に切り替える奴作った

peco-anyenv

https://raw.githubusercontent.com/giginet/peco-anyenv/master/images/peco-anyenv.gif

pyenvrbenvのようないわゆるanyenv系のツールが大変便利で常用しているのですが、インタプリタの数が増えてくると切り替えるのが面倒になってくる。

$ rbenv global 2.3.0
rbenv: version `2.3.0' not installed

このようにいちいちバージョンを打たないといけないし、インストールされてると思ったらされてないみたいな事があった。

そこでpecoで現在のインタプリタを簡単に切り替えれる奴を作ってみた。

github.com

使い方は冒頭のアニメーションの通りで、特に説明することはなさそう。

prbenvなどのように、お使いのenvにpプレフィックスをつけて実行するだけ。デフォルトでは現在のディレクトリのみインタプリタが切り替わる(local)が、--globalオプションをつけることで全体に適応することもできる。

特にPythonの場合はvirtualenvを使うことで、かなり頻繁にインタプリタを切り替えることになると思うのでさらに便利だと思う。

現在はPython, Ruby, Perl, Nodeに対応しているけれど、他の言語でも使いたいという方は簡単に増やせるのでPRをお待ちしています。

作ってみて。

元々、半年前にQiitaに記事を書いて個人的に使っていたのですが、便利だったのと、rbenvとかでも使いたくなったので、この機会に汎化してみた。

qiita.com

あまりにもシェルスクリプト力が低すぎて、そらで書くことができないし、シェルスクリプトをもっと学ばねばならないと思った。