Nikka是一个功能强大的Swift HTTP网络库,它提供了众多的扩展,使得它既模块化又强大。
###要求
在99%的情况下,您的应用程序需要与API交互,该API具有某种行为,并且您希望将该行为映射到应用程序中。例如,为了正确处理错误。
Nikka就是基于这个概念设计的。它允许您通过定义一个Provider
来定义API的公用行为。
以下是一个简单的示例:
import Nikka
//Define your provider
class MyProvider:HTTPProvider {
var baseURL = URL(string:"https://my-website.com/api")!
}
...
//This will send a GET request to the endpoint https://my-website.com/api/me/friends
MyProvider().request(Route(path:"/me/friends")).responseJSON { (response:Response<Any>) in
//Parse here the object as an array or dictionary
}
Nikka的优点是它具有高度的扩展性和模块化。您可以在任何位置定义您的端点。如果您希望将它们全部放在一个文件中或喜欢将它们分割到不同服务之中,这取决于您。
以下是展示和 sử dụng end points 的一种好方法
//Endpoints relative to the user
extension Route {
static let me = Route(path:"/me", method: .get}
static let friends = Route(path:"/me/friends") //GET is the default method when it is not specified
static let user = {(id:String) in Route(path:"/user/\(id)")} //You can pass parameters by defining a closure that will return a Route
static let login = {(email:String, password:String) in Route(path:"/login", method:.post, params:["email":email, "password":password])}
}
...
//Then you can simply send a request by passing the route you defined above.
//This will send a POST request to the endpoint https://my-website.com/api/login with a json body `{"email":"[email protected]","password":"bar"}``
MyProvider().request(.login("[email protected]", "bar")).responseJSON { (response:Response<Any>) in
//Parse here the object as an array or dictionary
}
Route
是一个对象,允许您定义一个端点以及您应该如何与之交流。它至少需要一个路径,以定义请求发送的位置。
GET
是默认方法。
您可以将参数和头信息传送给请求。最后,您还可以定义参数应该如何编码。
以下是一些有效的路由示例
Route(path:"/me") //GET request to the relative path /me without any headers or parameters
Route(path:"/login", method:.post, params:["email":"[email protected]", "password":"qwerty12345"]) //POST request that use the default JSON encoding and will pass the the parameters in the request body.
Route(path:"/user/about", method:.put, params:["text":"Hey!!"], headers:["Authorization":"12345"], encoding:.form) //PUT request that sends its parameters using the form encoding
Nikka目前支持3种编码类型,分别是 json
、form
和 url
。
json
将您的参数编码为JSON并将其放入请求体中form
将您的参数编码为查询参数并放入请求体中url
将您的参数编码为查询参数并附加到URL后面路由还支持多媒体表单。以下是一个上传多媒体图片的简单方法
//First you define the Route that will take the image in parameter put it into a Multipart form
extension Route{
static let uploadImage = {(image:UIImage) -> Route in
var form = MultipartForm()
form.append(data: UIImageJPEGRepresentation(image, 0.9)!, forKey: "image", fileName: "image.jpg")
return Route(path:"/profile/picture", method:.post, multipartForm:form)
}
}
...
//Then you just use the route as usual to send the request
let image = UIImage(named:"DSC_0025.JPG")!
MyProvider().request(.uploadImage(image)).uploadProgress { (sent, total) in
print("upload progress: \(sent)/\(total)")
}.responseJSON { (json) in
print("json: \(json)")
}
提供者是一个实现HTTPProvider
的类型,它将映射API的行为。它可以高度定制。
如果您的API要求每个请求都需要某些头信息或参数,您可以在提供者的声明中设置它们。
class MyProvider:HTTPProvider {
var baseURL = URL(string:"https://my-website.com/api")!
var additionalHeaders = ["Locale":"en-US"]
var additionalParameters = ["token":"d71106a0-dd44-4092-a72e"]
}
API有很多不同的方法来处理错误。Nikka允许您在收到不期望的响应时创建自己的错误并将其传播到应用程序中。
以Deezer API为例,当它无法找到给定ID的歌曲时,它会返回HTTP状态码200。在您的初级提供者中,200不会引发错误,但响应内容也无法解析。以下是如何处理这种情况的方法
https://api.deezer.com/track/313555658769 将返回
{
"error": {
"type":"DataException",
"message":"no data",
"code":800
}
}
首先,您应该定义自己的错误,使其符合NikkaError协议
struct DeezerError : NikkaError, Equatable{
var description:String
var code:Int
init(code:Int, description:String) {
self.code = code
self.description = description
}
public static func ==(lhs: DeezerError, rhs: DeezerError) -> Bool {
return lhs.code == rhs.code
}
}
然后,在声明您的提供者时,您可以实现validate
方法,该方法会在接收到响应时被调用
class DeezerProvider:HTTPProvider {
var baseURL = URL(string:"https://api.deezer.com")!
func validate(response: HTTPURLResponse, data: Data, error: Error?) -> NikkaError? {
let jsonError = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [String:Any]
if let error = json?["error"] as? [String:Any], let code = error["code"] as? Int, let desc = error["message"] as? String {
return DeezerError(code:code, description:desc)
}
return nil
}
}
然后,当发送请求时,如果deezer返回HTTP状态码200但其响应体中包含json错误。它将通过验证器并返回错误。
let myProvider = DeezerProvider()
myProvider.request(Route(path:"/track/313555658769").responseJSON { (response:Response<Any>) in
switch response.result{
case .success(let value):
print("success")
case .failure(let error):
print("error: \(error.description)")
}
}
//This will print "error: no data"
在某些情况下,当遇到错误时定义某种行为是有用的。例如,如果您收到HTTP 401错误,您可能想要终止用户会话。这可以通过实现shouldContinue
方法来完成。
class MyProvider:HTTPProvider {
var baseURL:URL { return URL(string:"https://my-website.com/api")!}
func shouldContinue(with error: NikkaError) -> Bool {
if let err = error as? NikkaError , err == NikkaError.http(401){
print("should log out")
return false
}
return true
}
}
在某些情况下,由于您已经有完整的URL,因此不需要定义提供者。您可以使用默认提供者。它允许您通过传递一个带有完整路径的路由来发送请求。
DefaultProvider.request(Route(path:"https://my-website.com/api/user/1")).responseJSON { (response:Response<Any>) in
switch response.result{
case .success(let json):
print("json: \(json)")
case .failure(let error):
print("error: \(error)")
}
}
Nikka非常适合与JSON一起使用,它目前支持以下库来解析您的数据。
使用这些扩展之一,您将能够发送请求并立即获取您的对象
MyProvider().request(Route(path:"/user/1234")).responseObject { (response:Response<User>) in
//You can check the content of the response for a user
let user = response.result.value //This is a User?
//Or you can switch on the response result if you want to manage an error case
switch response.result{
case .success(let user):
print("success: user name is \(user.lastName)")
case .failure(let error):
print("error: \(error)") //Will print an error, if the User cannot be parsed for instance.
}
}
另外,Nikka支持通过添加以下内容到PodFile使用CocoaPods的Futures和RxSwift
pod "Nikka/Futures"
pod "Nikka/Rx"
请注意,当导入模块时,核心依赖项将自动导入,因此您不需要上述之一和Nikka单pod。
Futures在现代编程中非常方便,它允许您整洁地连接请 求。Futures模块允许您在发送请求时返回一个未来。
我鼓励您将Future模块与上面提到的JSON库一起使用。这更加强大。但是,如果您出于某种原因想获得一个包含JSON对象或请求返回的数据的未来,您可以这样做
//With Data and HTTPURLResponse
let loginDataFuture:Future<(HTTPURLResponse,Data)> = myProvider.request(.login("[email protected]", "bar")).response()
loginDataFuture.onComplete { (result:Result<Any>) in
switch result{
case .success(let json):
print("json: \(json)")
case .failure(let error):
print("error: \(error)")
}
}
//With JSON
let loginJSONFuture:Future<Any> = myProvider.request(.login("[email protected]", "bar")).responseJSON()
loginJSONFuture.onComplete { (result:Result<(HTTPURLResponse, Data)>) in
expectation.fulfill()
switch result{
case .success(let response, let data):
print("response code was: \(response.statusCode)")
case .failure(let error):
print("error: \(error)")
}
}
甚至更好,Rx扩展。类似于Future扩展,它将返回可以连接的Rx Observable。
我鼓励您将Rx模块与上面提到的JSON库一起使用。这更加强大。但是,如果您出于某种原因想获得一个包含JSON对象或请求返回的数据的Observable,您可以这样做
//With Data and HTTPURLResponse
let loginDataObservable:Observable<(HTTPURLResponse,Data)> = myProvider.request(.login("[email protected]", "bar")).response()
loginDataObservable.subscribe(onNext: { (response:(HTTPURLResponse, Data)) in
print("response code was: \(response.0.statusCode)")
}).addDisposableTo(bag)
//With JSON
let loginJSONObservable:Observable<Any> = myProvider.request(.login("[email protected]", "bar")).responseJSON()
loginJSONObservable.subscribe(onNext: { json in
print("json is: \(json)")
}).addDisposableTo(bag)
贡献总是受欢迎。请随时提交一个拉取请求来添加新功能或为您的首选JSON库添加支持。
Nikka由Emilien Stremsdoerfer维护,并按Apache 2.0许可证发布。有关详细信息,请参阅LICENSE。