yujiro's blog

「インターネット上で正しい答えを得る最善の方法は、質問することではない。間違った答えを投稿することだ」by ウォード・カニンガム

【Alamofire 実践】Router の使い方とその問題点、URLRequestConvertible について

今回はAlamofire の実践編ということで、もう少し踏み込んだAlamofire の使い方を書いていこうと思います。

前回記事 : 【Swift】Alamofire を使って色んなリクエストを投げてみる - yujiro's blog

Alamofire を実際に使う際、エンドポイントの管理にはURLRequestConvertible というprotocolを継承した、Router という名前の enum を作って管理することが多いです。

このRouter はどう使うんでしょうか


まず以下を見てください。

前回の記事でご紹介した get リクエストを送信する簡単な例です。

let urlString: String = "http://localhost:3000/articles"

let parameters: Parameters = ["foo": "bar"]

Alamofire.request(urlString, method: .get, parameters: parameters)
...

これだとAPIリクエストをする部分とエンドポイントを定義している部分が一緒になってしまっています。

リクエストはviewModel でやるのか Repository でやるのか、そのアプリケーションごとに設計が違うと思いますが、いずれにしろリクエストをするオブジェクトがエンドポイントのhttpメソッドやパラメータ等を知っている、というのは責務過多です。

それを解決するのがRouter(URLRequestConvertible) です。

import Alamofire

enum Router: URLRequestConvertible {

    static let baseURL = "http://localhost:3000"

    case articles

    var method: Alamofire.HTTPMethod {
        switch self {
        case .articles:
            return .get
        }
    }

    var path: String {
        switch self {
        case .articles:
            return "/articles"
        }
    }

    func asURLRequest() throws -> URLRequest {
        let url = URL(string: Router.baseURL)!
        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue

        switch self {
        case .articles:
            return try Alamofire.JSONEncoding.default.encode(urlRequest)
        }
    }
}

case には各エンドポイントの識別子を記載します。

それで method でそのエンドポイントごとのhttp method を返し、path でそのエンドポイントごとのURLを返しています。

メソッドは asURLRequest というURLRequestConvertible でprotocol 定義されているメソッドを実装します。 このメソッド内で最終的にリクエストの内容を組み立てる、といった具合です。

このRouter を使った上でのリクエストの送り方は

Alamofire.request(Router.articles)

でOK。

リクエスト部分が非常にスッキリしますね。


でも、これってエンドポイントがめちゃめちゃ膨大だと、このRouterファイルの見通しがすっごく悪くなります。

例えばエンドポイントが30個になったら、var method, var path, func asURLRequest の中でそれぞれ30個ぐらいずつcase 式を書くってことになります。(まぁdefault でまとめられるところもあるでしょうが。)

なので、別にこの enum Router: URLRequestConvertible にこだわらなくてもいいのではっていうのが僕の持論です。

お気づきの方も多いと思いますが、Alamofire のrequest メソッドはオーバーロードされていて、2通りの実行の仕方があります。 上記のRouter のほうでは

public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    return SessionManager.default.request(urlRequest)
}

こっちを使っています。 引数に URLRequestConvertible 型を入れる必要がありますが、こいつはAlamofire 内で、extension 定義されたFoundation の URLRequest に継承されています。

extension URLRequest: URLRequestConvertible {
    /// Returns a URL request or throws if an `Error` was encountered.
    public func asURLRequest() throws -> URLRequest { return self }
}

つまり、Alamofire.request にわたすパラメータは URLRequest でもOKってことです。

なので、例えば下記のように書くことができます。

import Alamofire

class Router {

    static let baseURL = "http://localhost:3000"

    static var articles: URLRequest {
        var urlRequest = URLRequest(url: URL(string: "\(Router.baseURL)/articles")!)
        urlRequest.httpMethod = HTTPMethod.get.rawValue
        return urlRequest
    }
}

リクエスト部分 :

Alamofire.request(Router.articles)
...

僕はこれで良いんじゃね?って思います。