LoginSignup
3
0

More than 5 years have passed since last update.

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

Posted at

はじめに

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

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0