最近,我开始使用swift学习iOS应用开发,因此我是新手。我想在Swift&中实现rest api调用发现我们可以使用 URLRequest 来实现这一目标。因此,我编写了通用方法来调用所有类型的rest api(例如 get,put,post ),如下所示。
Recently I have started learning iOS app development using swift so I am new to it. I want to implement rest api call in swift & found that we can achieve this using URLRequest. So I have written generic method to call all type(like get, put, post) of rest api as below.
import Foundation //import Alamofire 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 enum Result<Value> { case success(Value) case failure(Error) } public class apiClient{ private var base_url:String = "api.testserver/" private func apiRequest(endPoint: String, method: RequestMethod, body: JSON? = nil, token: String? = nil, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) { let url = URL(string: (base_url.self + endPoint))! var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method.rawValue urlRequest.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") if let token = token { urlRequest.setValue("bearer " + token, forHTTPHeaderField: "Authorization") } 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 //NSLog(error) completionHandler(data, response, error) } task.resume() } public func sendRequest<T: Decodable>(for: T.Type = T.self, endPoint: String, method: RequestMethod, body: JSON? = nil, token: String? = nil, completion: @escaping (Result<T>) -> Void) { return apiRequest(endPoint: endPoint, method: method, body:body, token: token) { 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)) } } } }这是我从 controller
public func getProfile(userId :Int, objToken:String) -> Void { let objApi = apiClient() objApi.sendRequest(for: ProfileDetails.self, endPoint:"api/user/profile/\(userId)", method: .get, token: objToken, completion: {(userResult: Result<ProfileDetails>) -> Void in switch userResult { case .success(let value): if value.respCode == "01" { print(value.profile) do { //... ddo some taks like store response in local db or else } catch let error as NSError { // handle error print(error) } } else { //do some task } break case .failure(let error): print(error) break } }) }我正在使用以下模型解码服务器响应
I am decoding server response in below model
class ProfileDetails : Response, Decodable { var appUpdate : AppUpdate? var profile : Profile? enum CodingKeys: String, CodingKey { case profile = "profile" case respCode = "resp_code" case respMsg = "resp_msg" } public required convenience init(from decoder: Decoder) throws { self.init() let values = try decoder.container(keyedBy: CodingKeys.self) self.profile = try values.decodeIfPresent(Profile.self, forKey: .profile) self.respCode = try values.decodeIfPresent(String.self, forKey: .respCode)! self.respMsg = try values.decodeIfPresent(String.self, forKey: .respMsg) } }此代码无法处理来自服务器的错误响应,例如 401、404 等。因此,我要寻找的是将此api( URLRequest )请求转换为通用的 Alamofire 请求并进行错误处理,例如 401、404 等。我已经安装了 Alamofire 吊舱。是否有人开发了具有解码和解码功能的通用 Alamofire 请求方法错误处理?
This code is not able to handle error response like 401, 404 etc from server. So what I am looking for, is to convert this api (URLRequest)request to generic Alamofire request with error handling like 401, 404 etc. I have install Alamofire pods. Is there anyone who has developed generic Alamofire request method with decoding & error handling?
预先感谢:)
推荐答案Git链接: github/sahilmanchanda2/wrapper-class-for-alamofire
这是我的版本(使用 Alamofire 5.0.2 ):
Here is my version(Using Alamofire 5.0.2):
import Foundation import Alamofire class NetworkCall : NSObject{ enum services :String{ case posts = "posts" } var parameters = Parameters() var headers = HTTPHeaders() var method: HTTPMethod! var url :String! = "jsonplaceholder.typicode/" var encoding: ParameterEncoding! = JSONEncoding.default init(data: [String:Any],headers: [String:String] = [:],url :String?,service :services? = nil, method: HTTPMethod = .post, isJSONRequest: Bool = true){ super.init() data.forEach{parameters.updateValue($0.value, forKey: $0.key)} headers.forEach({self.headers.add(name: $0.key, value: $0.value)}) if url == nil, service != nil{ self.url += service!.rawValue }else{ self.url = url } if !isJSONRequest{ encoding = URLEncoding.default } self.method = method print("Service: \(service?.rawValue ?? self.url ?? "") \n data: \(parameters)") } func executeQuery<T>(completion: @escaping (Result<T, Error>) -> Void) where T: Codable { AF.request(url,method: method,parameters: parameters,encoding: encoding, headers: headers).responseData(completionHandler: {response in switch response.result{ case .success(let res): if let code = response.response?.statusCode{ switch code { case 200...299: do { completion(.success(try JSONDecoder().decode(T.self, from: res))) } catch let error { print(String(data: res, encoding: .utf8) ?? "nothing received") completion(.failure(error)) } default: let error = NSError(domain: response.debugDescription, code: code, userInfo: response.response?.allHeaderFields as? [String: Any]) completion(.failure(error)) } } case .failure(let error): completion(.failure(error)) } }) } }上面的类使用最新的Alamofire版本(截至2020年2月),该类几乎涵盖了所有HTTP方法,并可以选择以Application / JSON格式或常规格式发送数据。有了此类,您将获得很大的灵活性,它会自动将响应转换为您的Swift对象。
The above class uses latest Alamofire version (as of now Feb 2020), This class covers almost every HTTP Method with option to send data in Application/JSON format or normal. With this class you get a lot of flexibility and it automatically converts response to your Swift Object.
请看它具有的此类init方法:
Look at the init method of this class it has:
数据:[String,Any] =在此,您将放置表单数据。
data: [String,Any] = In this you will put your form data.
标头:[String:String] =在此,您可以发送要与请求一起发送的自定义标头
headers: [String:String] = In this you can send custom headers that you want to send along with the request
url =这里您可以指定完整的url,如果您已经在Class中定义了baseurl,则可以将其保留为空白。当您要使用第三方提供的REST服务时,它非常方便。注意:如果要填充网址,则下一个参数服务应该为nil
url = Here you can specify full url, you can leave it blank if you already have defined baseurl in Class. it comes handy when you want to consume a REST service provided by a third party. Note: if you are filling the url then you should the next parameter service should be nil
service:services =这是NetworkClass本身定义的枚举。这些充当端点。查看init方法,如果url为nil但服务不是nil,则它将附加在基本url的末尾以构成完整的URL,将提供示例。
service: services = It's an enum defined in the NetworkClass itself. these serves as endPoints. Look in the init method, if the url is nil but the service is not nil then it will append at the end of base url to make a full URL, example will be provided.
方法:HTTPMethod =在这里,您可以指定请求应使用的HTTP方法。
method: HTTPMethod = here you can specify which HTTP Method the request should use.
isJSONRequest =默认情况下设置为true。
isJSONRequest = set to true by default. if you want to send normal request set it to false.
在init方法中,您还可以指定公共数据或您希望随每个请求发送的标头,例如您的应用程序版本号,iOS版本等
In the init method you can also specify common data or headers that you want to send with every request e.g. your application version number, iOS Version etc
现在看一下execute方法:这是一个通用函数,如果响应成功,它将返回您选择的swift对象。如果无法将响应转换为swift对象,它将以字符串形式打印响应。如果响应代码不在200-299范围内,则将失败,并为您提供完整的调试说明,以获取详细信息。
Now Look at the execute method: it's a generic function which will return swift object of your choice if the response is success. It will print the response in string in case it fails to convert response to your swift object. if the response code doesn't fall under range 200-299 then it will be a failure and give you full debug description for detailed information.
用法:
说我们有以下结构:
struct Post: Codable{ let userId: Int let id: Int let title: String let body: String }请注意在NetworkClass https://jsonplaceholder.typicode中定义的基本URL。 com /
Note the base url defined in NetworkClass jsonplaceholder.typicode/
示例1:发送内容类型为Application / JSON的HTTP Post
Example 1: Sending HTTP Post with content type Application/JSON
let body: [String : Any] = ["title": "foo", "body": "bar", "userId": 1] NetworkCall(data: body, url: nil, service: .posts, method: .post).executeQuery(){ (result: Result<Post,Error>) in switch result{ case .success(let post): print(post) case .failure(let error): print(error) } }输出:
Service: posts data: ["userId": 1, "body": "bar", "title": "foo"] Post(userId: 1, id: 101, title: "foo", body: "bar")
HTTP 400请求
HTTP 400 Request
NetworkCall(data:[ email: peter @ klaven],URL: reqres.in/api/login ,方法:.post,isJSONRequest:false).executeQuery( ){(结果:结果)在转换结果{ case .success(let post): print(post) case .failure(let error) :打印(错误)} }
NetworkCall(data: ["email":"peter@klaven"], url: "reqres.in/api/login", method: .post, isJSONRequest: false).executeQuery(){ (result: Result) in switch result{ case .success(let post): print(post) case .failure(let error): print(error) } }
输出:
Service: reqres.in/api/login data: ["email": "peter@klaven"] Error Domain=[Request]: POST reqres.in/api/login [Request Body]: email=peter%40klaven [Response]: [Status Code]: 400 [Headers]: Access-Control-Allow-Origin: * Content-Length: 28 Content-Type: application/json; charset=utf-8 Date: Fri, 28 Feb 2020 05:41:26 GMT Etag: W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q" Server: cloudflare Via: 1.1 vegur cf-cache-status: DYNAMIC cf-ray: 56c011c8ded2bb9a-LHR expect-ct: max-age=604800, report-uri="report-uri.cloudflare/cdn-cgi/beacon/expect-ct" x-powered-by: Express [Response Body]: {"error":"Missing password"} [Data]: 28 bytes [Network Duration]: 2.2678009271621704s [Serialization Duration]: 9.298324584960938e-05s [Result]: success(28 bytes) Code=400 "(null)" UserInfo={cf-ray=56c011c8ded2bb9a-LHR, Access-Control-Allow-Origin=*, Date=Fri, 28 Feb 2020 05:41:26 GMT, expect-ct=max-age=604800, report-uri="report-uri.cloudflare/cdn-cgi/beacon/expect-ct", Server=cloudflare, Etag=W/"1c-NmpazMScs9tOqR7eDEesn+pqC9Q", x-powered-by=Express, Content-Type=application/json; charset=utf-8, Content-Length=28, Via=1.1 vegur, cf-cache-status=DYNAMIC}
带有自定义标头
with custom headers
NetworkCall(data:[ username: sahil.manchanda2 @ gmail],标题:[ custom-header-key: custom-header-value],网址: httpbin/post ,方法:.post).executeQuery(){(结果:结果)在切换结果{情况下.success(让数据):打印(数据)情况。失败(出现错误):打印(错误)} }
NetworkCall(data: ["username":"sahil.manchanda2@gmail"], headers: ["custom-header-key" : "custom-header-value"], url: "httpbin/post", method: .post).executeQuery(){(result: Result) in switch result{ case .success(let data): print(data) case .failure(let error): print(error) } }
输出:
Service: httpbin/post data: ["username": "sahil.manchanda2@gmail"] { "args": {}, "data": "{\"username\":\"sahil.manchanda2@gmail\"}", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", "Accept-Language": "en;q=1.0", "Content-Length": "41", "Content-Type": "application/json", "Custom-Header-Key": "custom-header-value", "Host": "httpbin", "User-Agent": "NetworkCall/1.0 (sahil.NetworkCall; build:1; iOS 13.2.2) Alamofire/5.0.2", "X-Amzn-Trace-Id": "Root=1-5e58a94f-fab2f24472d063f4991e2cb8" }, "json": { "username": "sahil.manchanda2@gmail" }, "origin": "182.77.56.154", "url": "httpbin/post" } typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil))在最后一个示例中,您最终可以看到typeMismatch,在executeQuery中传递[String:Any],但是由于Any不能确认可编码,因此我不得不使用String。
In the last example you can see typeMismatch at the end, I tried to pass [String:Any] in the executeQuery but since the Any doesn't confirm to encodable I had to use String.
更多推荐
iOS使用Swift创建通用的Alamofire请求
发布评论