Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities.
preface
Yesterday I introduced the JSON model-to-model tool. Today I introduce the ability to add a category to Alamofire version 4 and convert it to a function for the Codable protocol.
Interested diggers can read this post I wrote earlier:
Swift: Network request library — Alamofire
In this article, I mainly explain some new features of Alamofire5, which are not available in Alamofire4.
Why am I still mentioning the code for Alamofire4?
-
Many times, the company’s business code does not update with Swift.
-
Even if the code of Alamofire4 runs on the current Xcode, there is no problem. Developers can still make business requests through familiar ideas and apis.
-
There are many differences between Alamofire4 and Alamofire5 in terms of API or design ideas. Sometimes it is not cost-effective to upgrade the new version.
So it’s normal for some older projects to stay on top of Alamofire4.
Alamofire+Codable classification code
Here’s a taxonomy I wrote for Alamofire4 that extends its transformation approach to Codable protocol compliance models:
import Foundation import Alamofire // MARK: -codable extension Request {/// return Result type for Codable extension: /// /// -parameters: /// -response: // -data: /// - error: AFError /// - keyPath: model keyPath can parse deep JSON data /// - Returns: Result<T> public static func serializeResponseCodable<T: Codable>(response: HTTPURLResponse? , data: Data? , error: Error? , keyPath: String?) -> Result<T> { if let error = error { return .failure(error) } if let response = response, emptyDataStatusCodes.contains(response.statusCode) { do { let value = try JSONDecoder().decode(T.self, from: Data()) return .success(value) } catch { return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))) } } guard let validData = data else { return .failure(AFError.responseSerializationFailed(reason: .inputDataNil)) } if let keyPath = keyPath, ! keyPath.isEmpty { var keyPaths = keyPath.components(separatedBy: "/") return keyPathForCodable(keyPaths: &keyPaths, data: validData) }else { do { let value = try JSONDecoder().decode(T.self, from: validData) return .success(value) } catch { return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))}}} /// /// - Parameters: // -keypaths: /// - Returns: Result<T> private static func keyPathForCodable<T: Codable>(keyPaths: inout [String], data: Data) -> Result<T> { if let firstKeyPath = keyPaths.first, keyPaths.count > 1 { keyPaths.remove(at: 0) if let JSONObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let keyPathJSONObject = (JSONObject as AnyObject?)?.value(forKeyPath: firstKeyPath), let keyPathData = try? JSONSerialization.data(withJSONObject: keyPathJSONObject) { return keyPathForCodable(keyPaths: &keyPaths, data: keyPathData) } }else if let lastKeyPath = keyPaths.last, keyPaths.count == 1 { keyPaths.remove(at: 0) if let JSONObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), let keyPathJSONObject = (JSONObject as AnyObject?)?.value(forKeyPath: lastKeyPath), let keyPathData = try? JSONSerialization.data(withJSONObject: keyPathJSONObject) { do { let value = try JSONDecoder().decode(T.self, from: keyPathData) return .success(value) } catch { return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))) } } } return .failure(AFError.responseSerializationFailed(reason: .inputDatanil))}} Extension DataRequest {/// Create a response Serializer that complies with Codable protocol /// /// -parameter keyPath: Key path / / / - Returns: observe the response of Codable agreement serializer public static func codableResponseSerializer < T: Codable > (keyPath: String?) -> DataResponseSerializer<T> { return DataResponseSerializer { _, response, data, error in return Request.serializeResponseCodable(response: response, data: data, error: error, keyPath: KeyPath)}} /// add a handle to the request completion /// /// -parameters: /// -queue: callback thread /// -keypath: key value path /// -CompletionHandler: handle /// - Returns: DataRequest @discardableResult public func responseCodable<T: Codable>( queue: DispatchQueue? = nil, keyPath: String? = nil, completionHandler: @escaping (DataResponse<T>) -> Void) -> Self { return response( queue: queue, responseSerializer: DataRequest.codableResponseSerializer(keyPath: keyPath), completionHandler: completionHandler ) } } private let emptyDataStatusCodes: Set<Int> = [204, 205]Copy the code
This is a section of less than 110 lines of code, the network request code to save effort is visible.
I wrote this code three years ago, and it looks pretty much the same in Alamofire5 for Codable.
I’m not the first, but to be honest, the Alamofire+ObjectMapper code provides over 90% of the template code for this category.
Alamofire + Codable use
For example, we have JSON like this:
{"code": 0, "list": [{"topicDesc":], "createTime": null, "topicImageUrl": [{"topicDesc":], "topicImageUrl": "http:\\/\\/cdn.timez1.com\\/20180608\\/18626cd2106344078969afd77acf3572.jpg", "id": 12, "topicStatus": 1, "upTime": "2018-06-13 14:46:06", "topicTittle": 21, "topicTittle": 21}, {"topicTittle": "Missed the offline pre-show? Here's a super full UK pre-show for you. ", "createTime": null, "topicImageUrl": "http:\\/\\/cdn.timez1.com\\/20180608\\/d27a7923a24d4cd6878fe08fa338be45.jpg", "id": 10, "topicStatus": 1, "upTime": "2018-06-13 14:46:17", "topicTittle": 20, "topicTittle": 20}]}Copy the code
Using the JSON to Model tool, I generated the following Model:
struct Item: Codable {
var topicOrder: Int?
var id: Int?
var topicDesc: String?
var topicTittle: String?
var upTime: String?
var topicImageUrl: String?
var topicStatus: Int?
}
struct Topics: Codable {
var list: [Item]?
var code: Int?
}
Copy the code
We can get the model by writing a classification method responseCodable by Alamofire and I:
Alamofire. Request (" request url ", method:.post).responsecodable {(response: DataResponse<Topics>) in guard let value = response.value else { return } print(value) }Copy the code
Of course, the underlying model can also be accessed via keyPath:
Request (" request url ", method:.post). ResponseCodable (queue: nil, keyPath: "list") { (response: DataResponse<[Item]>) in guard let list = response.value else { return } print(list) }Copy the code
Does it feel a little like Alamofire5?
Note that the type of the DataResponse must be explicitly declared for this network request, otherwise an error will be reported here.
Reference documentation
Encoding and Decoding Custom Types
Using JSON with Custom Types
Swift: Network request library — Alamofire
AlamofireObjectMapper
The Demo address
AlamofireCodable
conclusion
There is no shame in learning other people’s code and using copies wisely.
I succeeded because I stood on the shoulders of giants.
Great men such as Newton have said so, how much less we brick-moving yards?
I was smirked when I wrote this alamofirecorecodable block for myself, but I immediately found out through a Search on GitHub that millions of people had this idea and thousands of pieces of code have made it available for open source.
The only thing I feel particularly happy about is the process of thinking and refining.
We’ll see you next time.