2019/8/13我写了续集
[快速]使用URLSession结合Codable
创建API客户端
前言
我在一个名为
已经确定该应用程序将在不久的将来进行全面更新,并且在重写旧代码的同时,该通信系统被黑盒包装并高度依赖,因此决定重新创建它。
我想移到
有很多这类文章,因此我将在此处记录为个人备忘。
现状
时间表
介绍
实际上,请遵循将其引入项目的过程。
1.安装
使用
库。 (可以引入的任何内容,例如
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 } } |
它用
上传的协议。
3.创建NetworkManager
在
而且,它是由单例(模拟,严格不同)制成的。
很长,所以解释有点,但是...
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.创建响应模型
必须使
服务器获得的值。为此创建一个模型。
作为前提,假定它是从服务器以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 } |
如果以上是与
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) } })) } } |
层次结构在另一个结构中定义。
用
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 } } |
我想知道是否可以集中管理通讯结果,但是我也想即使是同一通讯也要分开处理,因此我决定将成功或失败的方法作为参数。
附录
关于上述网络管理员
由于未特别处理
重写为
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通信部件