[Swift]实现ObjectMapper Rx Swift的备忘录


2019/8/13我写了续集
[快速]使用URLSession结合Codable

创建API客户端

前言

我在一个名为

objective-c swift3的混合项目中编码。
已经确定该应用程序将在不久的将来进行全面更新,并且在重写旧代码的同时,该通信系统被黑盒包装并高度依赖,因此决定重新创建它。

我想移到

swift4,但是我想在将所有objective-c代码转换为swift之后执行此操作,因此我采用ObjectMapper作为可以按原样实现的东西(与Realm的兼容性)。 )。除此之外,还引入了Rx

有很多这类文章,因此我将在此处记录为个人备忘。

现状

Alamofire Curry Argo

时间表

Alamofire RxSwift ObjectMapper(AlamofireObjectMapper)

介绍

实际上,请遵循将其引入项目的过程。

1.安装

使用Carthage安装

库。 (可以引入的任何内容,例如CocoapodsSPM k)

1
2
3
4
github "Alamofire/Alamofire" ~> 4.4
github "ReactiveX/RxSwift"
github "Hearst-DD/ObjectMapper" ~> 2.2
github "tristanhimmelman/AlamofireObjectMapper" ~> 4.0

*如果ObjectMapper是最新的,则可能会发生错误,因此请指定一个较低的

2.创建网络协议

在注释中进行解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import Alamofire
import ObjectMapper
import AlamofireObjectMapper

// MARK: - Base API Protocol
protocol BaseAPIProtocol {
    associatedtype ResponseType

    var method: HTTPMethod { get }
    var baseURL: URL { get }
    var path: String { get }
    var headers: HTTPHeaders? { get }

}

extension BaseAPIProtocol {
    var baseURL: URL {
        return try! "https://~~~".asURL()
    }
    var headers: HTTPHeaders? {
        return nil // 必要であれば個々に設定
    }
}

// MARK: - BaseRequestProtocol
protocol BaseRequestProtocol: BaseAPIProtocol, URLRequestConvertible {
    var parameters: Parameters? { get }
    var encoding: URLEncoding { get }
}

extension BaseRequestProtocol {
    var encoding: URLEncoding {
        // parameter の変換の仕方を設定
        // defaultの場合、get→quertString、post→httpBodyとよしなに行ってくれる
        return URLEncoding.default
    }

    func asURLRequest() throws -> URLRequest {
        // requestごとの pathを設定
        var urlRequest = URLRequest(url: baseURL.appendingPathComponent(path))

        // requestごとの methodを設定(get/post/delete etc...)
        urlRequest.httpMethod = method.rawValue

        // headersを設定
        urlRequest.allHTTPHeaderFields = headers

        // timeout時間を設定
        urlRequest.timeoutInterval = TimeInterval(30)

        // requestごとの parameterを設定
        if let params = parameters {
            urlRequest = try encoding.encode(urlRequest, with: params)
        }

        return urlRequest
    }

}

它用BaseAPIProtocol剪切,以便可以创建用于

上传的协议。

3.创建NetworkManager

Alamofire中包括RxSwift
而且,它是由单例(模拟,严格不同)制成的。

很长,所以解释有点,但是...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import UIKit
import Alamofire
import ObjectMapper
import AlamofireObjectMapper
import RxSwift

// MARK: - API Manager
struct APIManager {

    // MARK: - Static Variables    
    private static let successRange = 200..<300
    private static let contentType = ["application/json"]

    // MARK: - APICallProtocol Methods

    /// API呼び出しメソッド
    static func call<T, V>(_ request: T, _ disposeBag: DisposeBag,
                           onNext: @escaping (V) -> Void, onError: @escaping (Error) -> Void)
        where T : BaseRequestProtocol, V == T.ResponseType, T.ResponseType : Mappable {

            _ = observe(request)
                .observeOn(MainScheduler.instance)
                .subscribe(onNext: { onNext($0) },
                           onError: { onError($0) })
                .disposed(by: disposeBag)
    }

    /// RxでAPIを呼び出すメソッド ※下部に追記あり
    static func observe<T, V>(_ request: T) -> Observable<V>
        where T: BaseRequestProtocol, V: Mappable, T.ResponseType == V {

            return Observable<V>.create { observer in
                let calling = callForJson(request) { response in
                    switch response {
                    case .success(let result): observer.onNext(result as! V)
                    case .failure(let error): observer.onError(error)
                    }
                    observer.onCompleted()
                }

                return Disposables.create() { calling.cancel() }
            }
    }

    /// Json 形式で取得するメソッド
    static func callForJson<T, V>(_ request: T, completion: @escaping (APIResult) -> Void) -> DataRequest
        where T: BaseRequestProtocol, V: Mappable, T.ResponseType == V {

            return customAlamofire(request).responseJSON { response in
                    switch response.result {
                    case .success(let result): completion(.success(mappingJson(request, result as! Parameters)))
                    case .failure(let error): completion(.failure(error))
                    }

            }
    }

    /// Alamofireカスタマイズメソッド
    static func customAlamofire< T >(_ request: T) -> DataRequest
        where T: BaseRequestProtocol {

            return Alamofire
                .request(request)
                .validate(statusCode: successRange)
                .validate(contentType: contentType)
    }

    /// Jsonをレスポンスモデルに mappingするメソッド
    static func mappingJson<T, V>(_ request: T, _ result: Parameters) -> V
        where T: BaseRequestProtocol, V: Mappable, T.ResponseType == V {

        // 必ずmappingされるので「!」を使用
        return Mapper<V>().map(JSON: result)!
    }

}

/* APIレスポンスの結果分岐

 success: ObjectMapperに対応したレスポンスモデルを返す
 failure: サーバーからのエラーログを返す

 */

// MARK: - ResultType
enum APIResult {
    case success(Mappable)
    case failure(Error)
}

4.创建响应模型

必须使swift可以处理从

服务器获得的值。为此创建一个模型。
作为前提,假定它是从服务器以Json格式获得的。
以下是Json的回复。这次,我准备了一个层次结构。

1
2
3
4
5
6
7
8
9
10
11
12
{
    "data": [
        {
            "title": "タイトル",
            "description": "説明文",
            "icon": "https://xxx.jpg",
            "date": "2017-01-01 00:00:00",  
            "id": "00000",
        },
    ],
    "result": true
}

如果以上是与ObjectMapper相对应的响应模型,则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import ObjectMapper

struct xxxResponse: Mappable {

    var data: [xxxDataModel]?
    var result: Bool?

    init?(map: Map) { }

    mutating func mapping(map: Map) {
        data <- map["data"]
        result <- map["result"]
    }

}

struct xxxDataModel: Mappable {

    var title: String?
    var description: String?
    var icon: URL?
    var date: Date?
    var id: Int?

    init?(map: Map) { }

    mutating func mapping(map: Map) {
        title <- map["title"]
        description <- map["description"]  
        icon <- (map["icon"], URLTransform()) // String -> URL 変換
        date <- (map["date"], ISO8601DateTransform()) // String -> Data 変換
        id <- (map["id"], TransformOf<Int, String> // String -> Int 変換
                          (fromJSON: { Int($0!) },toJSON: { $0.map { String($0) } }))
    }

}

层次结构在另一个结构中定义。
map["~~"]进行设置时,可以将其转换为所需的类型。 (由ObjectMapper提供)

5.提出要求

必需的请求参数为

1
2
3
4
[
  "keyword": String,
  "ids": [Int]
]

对于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Alamofire
import ObjectMapper

// MARK: - Request
enum xxxRequest: BaseRequestProtocol {
    typealias ResponseType = xxxResponse    

    case post(keyword: String, ids: [Int])

    var method: HTTPMethod {
        switch self {
        case .post: return .post
        }
    }

    var path: String {
        return "xxx/yyy/zzz" /// 個々に設定する
    }

    var parameters: Parameters? {
        switch self {
        case .post(let keyword, let ids):
            return [
                "keyword": keyword,
                "ids": ids
            ]
        }
    }

}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ViewController: UIViewController {

   private let disposeBag = DisposeBag()

   private func callForXXX() {
      let request = xxxRequest.post(keyword: "キーワード", ids: [00000, 11111, 10101])
      APIManager.call(request, disposeBag, onNext: onNext, onError: onError)
   }  

   private func onNext(with result: Mappable) {
      // do something
   }

   private func onError() {
      // error handling
   }

}

我想知道是否可以集中管理通讯结果,但是我也想即使是同一通讯也要分开处理,因此我决定将成功或失败的方法作为参数。

附录
关于上述网络管理员
由于未特别处理onCompleted,因此仅返回成功/失败,
重写为RxSingle。如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static func observe<T, V>(_ request: T) -> Single<V>
   where T: BaseRequestProtocol, V: Mappable, T.ResponseType == V {

      return Single<V>.create { observer in
            let calling = callForJson(request) { response in
                  switch response {
                     case .success(let result): observer(.success(result as! V))
                     case .failure(let error): observer(.error(error))
                  }
            }

            return Disposables.create() { calling.cancel() }
   }
}

后记

我觉得自己最终像黑匣子一样编写了一个代码,大声笑

由于仅当响应为JSONObject时才支持该响应的JSON格式(因为所涉及的项目采用的是该格式),因此我想考虑在其他地方使用该响应的情况是否应该有点通用。

参考

[2017版本]关于RxSwift Alamofire ObjectMapper领域的Swift实现
如何使用Swift实现API客户端
尝试将RxSwift用于HTTP通信部件