Tech Blog

グローバルな家族アプリFammを運営するTimers inc (タイマーズ) の公式Tech Blogです。弊社のエンジニアリングを支える記事を随時公開。エンジニア絶賛採用中!→ https://timers-inc.com/engineering

SwiftでURLのクエリーが簡単に生成出来るライブラリを作った #swift #URLQueryBuilder

WWDC関連のイベントが落ち着いたと思ったらすっかり夏バテ気味のiOSチームのかっくん(@fromkk)です。
気温の変化も大きいので夏風邪などひかない様に気をつけましょうね。

最近はREST API等も増えて来て、サーバーに直接JSONをPOSTしたり、進んでるところはProtocol Buffer等を利用してサーバーとやりとりしているかと思いますが、
未だに旧来のGETやPOSTでクエリー文字列を利用する事もあるかと思います。

iOSでは何故かこの辺りの環境が整備されておらず、力技で解決してきた人も多いのでは無いでしょうか?

これまで

例えば日本郵便を例にすると、

let pref: Int = 13
let url = URL(string: "http://www.post.japanpost.jp/cgi-zip/zipcode.php?pref=\(pref)")!
// get data from url

の様な形でURLを作っていた事も多かったかと思います。
パラメーターが1つ2つ程度ならこれでも十分対応可能ですが、パラメーターが増えたり、ネストが深くなったりすると大変ですよね。

こういう煩雑さを解消する為に作成したライブラリがこちらです。
fromkk/URLQueryBuilder

インストール

今の所 Carthage にのみ対応しています。
Cartfilegithub "fromkk/URLQueryBuilder" を追記して carthage update し、Carthage/Build 内の URLQueryBuilder.framework へのリンクを追加します。

使い方

URLQueryBuilder の生成時に Dictionary を渡して build メソッドを呼ぶだけです。

import URLQueryBuilder

let queryString = URLQueryBuilder(dictionary:["pref": 13]).build()
let url = URL(string: "http://www.post.japanpost.jp/cgi-zip/zipcode.php?\(queryString)")!
// get data from url

URLエンコードしたい事もあるかと思ったので build(with: [.urlEncode]) とすればクエリーをエスケーピングする事が出来ます。

// multibytes
let someDictionary: [String: Any] = ["key": "マルチバイト😇"]
let queryString: String = URLQueryBuilder(dictionary: someDictionary).build(with: [.urlEncoding]) //key=%E3%83%9E%E3%83%AB%E3%83%81%E3%83%90%E3%82%A4%E3%83%88%F0%9F%98%87

複数階層や配列にも対応しています。

// deep hierarchy
let deepDictionary: [String: Any] = ["key": ["subkey": "subvalue"]]
let queryString: String = URLQueryBuilder(dictionary: deepDictionary).build() //key[subkey]=subvalue
// array
let arrayDictionary: [String: Any] = ["array": ["hello", "world"]]
let queryString: String = URLQueryBuilder(dictionary: arrayDictionary).build() //array[0]=hello&array[1]=world

他ライブラリとの連携

元メルカリ石川さん作の ishkawa/APIKitFormURLEncodedBodyParameters では深い階層には対応していない様です。

import APIKit
import URLQueryBuilder

struct DeepFormURLEncodedBodyParameters: BodyParameters {
    let dictionary: [String: Any]
    
    var contentType: String { return "application/x-www-form-urlencoded" }
    func buildEntity() throws -> RequestBodyEntity {
        let post = URLQueryBuilder(dictionary: self.dictionary).build(with: [.urlEncoding])
        return RequestBodyEntity.data(post.data(using: .utf8)!)
    }
}

struct YourRequest: Request {
    typealias Response = YourResponse
    
    var baseURL: URL = ...
    var path: String = ...
    var method: HTTPMethod = ...
    
    var bodyParameters: BodyParameters? {
        return DeepFormURLEncodedBodyParameters(dictionary: ["hello": ["deep": "hierarchy"]])
    }
}

上記の様にする事で複数階層の FormURLEncodedBodyParameters を作成する事が出来ました。

今後

Bool 型の扱いに困ったので今回は非対応にしていますが、その内対応するかもしれません。
(false , true なのか 0 なのか 1 なのか環境によって変わりそうなので。。)

まとめ

これまでもサーバーにアクセスするコードをいくつか書いてきましたが、パラメーターが複数階層に及ぶ事はなかなか無かったのでこれまであまり困る事は無かったんですが、いざ作ってみると凄く便利で今後よく利用する事になりそうな気がします。
もし気に入って頂けた場合リポジトリにスター🌟を頂けたら嬉しいです!
不具合等あった場合もissueやPR待ってます!

宣伝

そういえばiOSDC 2017でLTを話す予定なので聞きに来て頂けたら嬉しいです!
自分が欲しいとアプリを作った 5分(LT) (※盛大にタイトル間違えてますがこのまま突っ走ります)

積極採用中!!

子育て家族アプリFammカップル専用アプリPairyを運営するTimers inc. では、現在エンジニアを積極採用中! 急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください! 採用HP : http://timers-inc.com/engineerings

Timersでは各職種を積極採用中!

急成長スタートアップで、最高のものづくりをしよう。

募集の詳細をみる