Posted at

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ライフを!


参考