Pra*_*bhu 2 generics rest swift
我正在实现一个 API 客户端,它将调用我的后端 API,并返回适当的对象或错误。
这是我到目前为止:
public typealias JSON = [String: Any]
public typealias HTTPHeaders = [String: String]
public enum RequestMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
public class APIClient {
public func sendRequest(_ url: String,
method: RequestMethod,
headers: HTTPHeaders? = nil,
body: JSON? = nil,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
let url = URL(string: url)!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
if let headers = headers {
urlRequest.allHTTPHeaderFields = headers
urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
}
if let body = body {
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: body)
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: urlRequest) { data, response, error in
completionHandler(data, response, error)
}
task.resume()
}
}
Run Code Online (Sandbox Code Playgroud)
好的,所以我想要做的是这样的事情:
apiClient.sendRequest("http://example.com/users/1", ".get") { response in
switch response {
case .success(let user):
print("\(user.firstName)")
case .failure(let error):
print(error)
}
}
apiClient.sendRequest("http://example.com/courses", ".get") { response in
switch response {
case .success(let courses):
for course in courses {
print("\(course.description")
}
case .failure(let error):
print(error)
}
}
Run Code Online (Sandbox Code Playgroud)
因此, apiClient.sendRequest() 方法必须将响应 json 解码为所需的 swift 对象,并返回该对象或错误对象。
我有这些结构:
struct User: Codable {
var id: Int
var firstName: String
var lastName: String
var email: String
var picture: String
}
struct Course: Codable {
var id: Int
var name: String
var description: String
var price: Double
}
Run Code Online (Sandbox Code Playgroud)
我也定义了这个 Result 枚举:
public enum Result<Value> {
case success(Value)
case failure(Error)
}
Run Code Online (Sandbox Code Playgroud)
我被卡住的地方是,我不确定如何在 sendRequest() 中调整我的 completionHandler,以便我可以将它与 User 对象或 Course 对象或任何其他自定义对象一起使用。我知道我必须以某种方式使用泛型来实现这一点,并且我已经在 C# 中使用了泛型,但我在 Swift 4 中还不是很舒服,所以我们感谢任何帮助。
编辑:另外,我想知道如何将 sendRequest() 的响应向上冒泡到 ViewController 中的调用代码的一级,以便 ViewController 可以访问成功和失败的结果(以异步方式) .
这是您可以使用的方法,它将实际的 HTTP 工作转发到现有方法,并仅处理 json 解码:
public func sendRequest<T: Decodable>(for: T.Type = T.self,
url: String,
method: RequestMethod,
headers: HTTPHeaders? = nil,
body: JSON? = nil,
completion: @escaping (Result<T>) -> Void) {
return sendRequest(url, method: method, headers: headers, body:body) { data, response, error in
guard let data = data else {
return completion(.failure(error ?? NSError(domain: "SomeDomain", code: -1, userInfo: nil)))
}
do {
let decoder = JSONDecoder()
try completion(.success(decoder.decode(T.self, from: data)))
} catch let decodingError {
completion(.failure(decodingError))
}
}
}
Run Code Online (Sandbox Code Playgroud)
,可以这样调用:
apiClient.sendRequest(for: User.self,
url: "https://someserver.com",
method: .get,
completion: { userResult in
print("Result: ", userResult)
})
Run Code Online (Sandbox Code Playgroud)
,或者像这样:
apiClient.sendRequest(url: "https://someserver.com",
method: .get,
completion: { (userResult: Result<User>) -> Void in
print("Result: ", userResult)
})
Run Code Online (Sandbox Code Playgroud)
, 通过指定完成签名并省略第一个参数。无论哪种方式,我们都让编译器推断其他东西的类型,如果我们提供足够的信息来这样做的话。
在多个方法之间拆分职责使它们更可重用,更易于维护和理解。
假设您将 api 客户端包装到另一个公开一些更通用方法的类中,这些方法隐藏了 api 客户端的复杂性,并允许通过仅传递相关信息从控制器调用,您最终可能会得到一些像这样的方法:
func getUserDetails(userId: Int, completion: @escaping (Result<User>) -> Void) {
apiClient.sendRequest(for: User.self,
url: "http://example.com/users/1",
method: .get,
completion: completion)
}
Run Code Online (Sandbox Code Playgroud)
,它可以简单地从控制器调用,如下所示:
getUserDetails(userId: 1) { result in
switch result {
case let .success(user):
// update the UI with the user details
case let .failure(error):
// inform about the error
}
}
Run Code Online (Sandbox Code Playgroud)
通过在 上添加另一个重载sendRequest(),也可以轻松添加对解码数组的更新支持,以下是从答案开头开始的代码的小型重构版本:
private func sendRequest<T>(url: String,
method: RequestMethod,
headers: HTTPHeaders? = nil,
body: JSON? = nil,
completion: @escaping (Result<T>) -> Void,
decodingWith decode: @escaping (JSONDecoder, Data) throws -> T) {
return sendRequest(url, method: method, headers: headers, body:body) { data, response, error in
guard let data = data else {
return completion(.failure(error ?? NSError(domain: "SomeDomain", code: -1, userInfo: nil)))
}
do {
let decoder = JSONDecoder()
// asking the custom decoding block to do the work
try completion(.success(decode(decoder, data)))
} catch let decodingError {
completion(.failure(decodingError))
}
}
}
public func sendRequest<T: Decodable>(for: T.Type = T.self,
url: String,
method: RequestMethod,
headers: HTTPHeaders? = nil,
body: JSON? = nil,
completion: @escaping (Result<T>) -> Void) {
return sendRequest(url: url,
method: method,
headers: headers,
body:body,
completion: completion) { decoder, data in try decoder.decode(T.self, from: data) }
}
public func sendRequest<T: Decodable>(for: [T].Type = [T].self,
url: String,
method: RequestMethod,
headers: HTTPHeaders? = nil,
body: JSON? = nil,
completion: @escaping (Result<[T]>) -> Void) {
return sendRequest(url: url,
method: method,
headers: headers,
body:body,
completion: completion) { decoder, data in try decoder.decode([T].self, from: data) }
}
Run Code Online (Sandbox Code Playgroud)
现在你也可以做这样的事情:
func getAllCourses(completion: @escaping (Result<[Course]>) -> Void) {
return apiClient.sendRequest(for: User.self,
url: "http://example.com/courses",
method: .get,
completion: completion)
}
// called from controller
getAllCourses { result in
switch result {
case let .success(courses):
// update the UI with the received courses
case let .failure(error):
// inform about the error
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2364 次 |
| 最近记录: |