JSON
Swift
Generics
Alamofire
swift4

Genericsを用いて安全にJSONをパースする (Alamofire x Swift4)

はじめに

SwiftでAlamofireを利用する際,毎回Modelを利用するのは面倒ですよね。
Codableを利用して簡単に書きましょう!

Alamofireを利用した baseRequest() の作成

ここでは,typealias を利用して,Tに様々な型が入るようにしたクロージャを定義し,
Decodableをクロージャで引き受けつつ通信を行うbaseRequest()を作成しています。

import Alamofire

class Foo {
    /// Success handler
    public typealias SuccessHandler<T> = (_ model: T) -> Void

    /// Failure handler
    public typealias FailureHandler = (_ error: Error) -> Void

    /**
     Alamofire Request

     - Parameters:
         - url: API URL
         - method: HTTP method
         - parameters: Query parameters
         - success: Success handler
         - failure: Failure handler
     */
    func baseRequest<T: Decodable>(_ url: String,
                               method: HTTPMethod = .get,
                               parameters: Parameters? = nil,
                               success: SuccessHandler<T>?,
                               failure: FailureHandler?) {

        Alamofire.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
            .responseData { response in
                switch response.result {
                case .success:
                    guard let success = success, let data = response.data else { return }
                    let decoder: JSONDecoder = JSONDecoder()
                    guard let model = try? decoder.decode(T.self, from: data) else { return }
                    success?(model)
                case .failure(let error):
                    failure?(error)
                }
        }
    }
}

guard let model = try? decoder.decode(T.self, from: data) else { return }部分では,デコード処理を記述していますが,
引数success: SuccessHandler<T>Tの型によってT.selfが変更され,デコード先が様々な型を許容することを示しています。

利用方法

以下のようなModelを定義してみました。

public struct FooModel: Codable {
    public let bar: String
}

ここで新たに SuccessHandlerFooModel を代入した request methodを立てます。

func request(_ url: String,
             method: HTTPMethod = .get,
             parameters: Parameters? = nil,
             success: SuccessHandler<FooModel>?,
             failure: FailureHandler?) {

    self.baseRequest(url, 
                     method: method, 
                     parameters: parameters,
                     success: success,
                     failure: failure)
}

これにより, request() 経由で取得したResponseはFooModelにパースされて処理できるようになります。

呼び出しは以下の通りです。

self.request(url, success: { fooModel in
    print(fooModel.bar)
})

デコードのテストを書く

せっかく便利になったので,Codableなstructがきちんとデコードされるかをテストしましょう。
Codableはしっかり定義しないと全てがnilとなってしまいます。テストは必要ですよね?

import XCTest
@testable import Foo

final class FooTests: XCTestCase {
    var foo: Foo!

    override func setUp() {
        super.setUp()
        self.foo = Foo()
    }

    func decode<T: Decodable>(_ url: String, parameters: Parameters?, model: T.Type) {
        let expect: XCTestExpectation = self.expectation(description: url)

        let success: Foo.SuccessHandler<T> = { model in
            expect.fulfill()
            XCTAssertNotNil(model)
        }
        let failure: Foo.FailureHandler = { error in
            expect.fulfill()
            XCTFail(error.localizedDescription)
        }

        self.foo.baseRequest(url, success: success, parameters: parameters, failure: failure)
        self.wait(for: [expect], timeout: 10)
    }
}

呼び出しは以下のように書きます。

func testDecodeFoo() {
    decode(url, parameters: parameters. model: FooModel.self)
}

これでModelが増えても簡単に書けるようになりました。

それでは良いAlamofireライフを!

参考