Networking

Networking 的诞生源于对一个拥有简单易用的 API,并且开箱即用地支持模拟请求和缓存图像的网络库的需求。

目录

选择配置

初始化 Networking 的实例意味着您必须选择一个 URLSessionConfiguration。可用的类型有 .default.ephemeral.background。如果您不提供任何配置或没有特殊需求,则将使用 default

// Default
let networking = Networking(baseURL: "http://httpbin.org")

// Ephemeral
let networking = Networking(baseURL: "http://httpbin.org", configuration: .ephemeral)

更改请求标头

您可以在任何 networking 对象中设置 headerFields

这将追加(如果未找到)或覆盖(如果找到)NSURLSession 在每个请求上发送的内容。

networking.headerFields = ["User-Agent": "your new user agent"]

身份验证

HTTP 基本身份验证

要使用用户名 "aladdin" 和密码 "opensesame" 进行 基本身份验证,您只需执行以下操作:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(username: "aladdin", password: "opensesame")
let result = try await networking.get("/basic-auth/aladdin/opensesame")
// Successfully authenticated!

Bearer Token 身份验证

要使用 Bearer Token "AAAFFAAAA3DAAAAAA" 进行身份验证,您只需执行以下操作:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(token: "AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

自定义身份验证标头

要使用自定义身份验证标头,例如 "Token token=AAAFFAAAA3DAAAAAA",您需要设置以下标头字段:Authorization: Token token=AAAFFAAAA3DAAAAAA。幸运的是,Networking 提供了一种简单的方法来做到这一点:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerValue: "Token token=AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

提供以下身份验证标头 Anonymous-Token: AAAFFAAAA3DAAAAAA 也是可以的:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerKey: "Anonymous-Token", headerValue: "AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

发起请求

基础知识

发起请求就像调用 getpostputdelete 一样简单。

GET 示例:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
switch result {
case .success(let response):
    let json = response.dictionaryBody
    // Do something with JSON, you can also get arrayBody
case .failure(let response):
    // Handle error
}

POST 示例:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameters: ["username" : "jameson", "password" : "secret"])
 /*
 {
     "json" : {
         "username" : "jameson",
         "password" : "secret"
     },
     "url" : "http://httpbin.org/post",
     "data" : "{"password" : "secret","username" : "jameson"}",
     "headers" : {
         "Accept" : "application/json",
         "Content-Type" : "application/json",
         "Host" : "httpbin.org",
         "Content-Length" : "44",
         "Accept-Language" : "en-us"
     }
 }
 */

您可以在成功的回调中获取响应标头。

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
switch result {
case .success(let response):
    let headers = response.allHeaderFields
    // Do something with headers
case .failure(let response):
    // Handle error
}

NetworkingResult 类型

NetworkingResult 类型是一个枚举,它有两个 case:successfailuresuccess case 有一个 response,failure case 有一个 error 和一个 response,这些都不是可选的。

这是如何使用它的:

// The best way
let networking = Networking(baseURL: "http://fakerecipes.com")
let result = try await networking.get("/recipes")
switch result {
case .success(let response):
    // We know we'll be receiving an array with the best recipes, so we can just do:
    let recipes = response.arrayBody // BOOM, no optionals. [[String: Any]]

    // If we need headers or response status code we can use the HTTPURLResponse for this.
    let headers = response.headers // [String: Any]
case .failure(let response):
    // Non-optional error ✨
    let errorCode = response.error.code

    // Our backend developer told us that they will send a json with some
    // additional information on why the request failed, this will be a dictionary.
    let json = response.dictionaryBody // BOOM, no optionals here [String: Any]

    // We want to know the headers of the failed response.
    let headers = response.headers // [String: Any]
}

这就是我们在 Networking 中处理事情的方式,没有可选值。

选择内容或参数类型

Content-Type HTTP 规范非常不友好,您必须知道它的具体细节,然后才能理解内容类型实际上只是参数类型。因此,Networking 使用 ParameterType 而不是 ContentType。无论如何,希望这能使其更加人性化。

JSON

默认情况下,Networking 使用 application/json 作为 Content-Type。如果您正在发送 JSON,则无需执行任何操作。但是,如果您想发送其他类型的参数,可以通过提供 ParameterType 属性来实现。

发送 JSON 时,您的参数将使用 NSJSONSerialization 序列化为数据。

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameters: ["name" : "jameson"])
// Successfull post using `application/json` as `Content-Type`

URL 编码

如果您想使用 application/x-www-form-urlencoded,只需使用 .formURLEncoded 参数类型。在内部,Networking 将格式化您的参数,以便它们使用 Percent-encodingURL-enconding

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameterType: .formURLEncoded, parameters: ["name" : "jameson"])
// Successfull post using `application/x-www-form-urlencoded` as `Content-Type`

Multipart

Networking 提供了一个简单的模型来使用 multipart/form-data。 multipart 请求包括将一个或多个 FormDataPart 项附加到请求。最简单的 multipart 请求如下所示:

let networking = Networking(baseURL: "https://example.com")
let imageData = UIImagePNGRepresentation(imageToUpload)!
let part = FormDataPart(data: imageData, parameterName: "file", filename: "selfie.png")
let result = try await networking.post("/image/upload", part: part)
// Successfull upload using `multipart/form-data` as `Content-Type`

如果您需要使用多个部分或附加不是文件的其他参数,您可以这样做:

let networking = Networking(baseURL: "https://example.com")
let part1 = FormDataPart(data: imageData1, parameterName: "file1", filename: "selfie1.png")
let part2 = FormDataPart(data: imageData2, parameterName: "file2", filename: "selfie2.png")
let parameters = ["username" : "3lvis"]
let result = try await networking.post("/image/upload", parts: [part1, part2], parameters: parameters)
// Do something

FormDataPart Content-Type:

FormDataPart 使用 FormDataPartType 为每个部分生成 Content-Type。默认的 FormDataPartType.Data,它将 application/octet-stream 添加到您的部分。如果您想使用 Content-Type,但该类型不在现有的 FormDataPartType 中,则可以使用 .Custom("your-content-type)

其他

目前,Networking 开箱即用地支持四种类型的 ParameterTypeJSONFormURLEncodedMultipartFormDataCustom。同时,JSONFormURLEncoded 以某种方式序列化您的参数,Custom(String) 将您的参数作为纯 NSData 发送,并将 Custom 中的值设置为 Content-Type

例如:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/upload", parameterType: .Custom("application/octet-stream"), parameters: imageData)
// Successfull upload using `application/octet-stream` as `Content-Type`

取消请求

使用路径

取消特定路径的任何请求非常简单。请注意,取消请求将导致请求返回错误,状态代码为 URLError.cancelled。

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
// Cancelling a GET request returns an error with code URLError.cancelled which means cancelled request

// In another place
networking.cancelGET("/get")

模拟请求

模拟请求意味着在特定路径上调用此方法后,对该资源的任何调用都将返回您注册为响应的内容。这种技术也称为 mocking 或 stubbing。

模拟成功响应:

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: [["id" : 47333, "title" : "Site Design: Aquest"]])
let result = try await networking.get("/stories")
// JSON containing stories

使用文件内容进行模拟:

如果您的文件不在主 Bundle 中,您必须使用 bundle 参数指定,否则将使用 NSBundle.mainBundle()

let networking = Networking(baseURL: baseURL)
networking.fakeGET("/entries", fileName: "entries.json")
let result = try await networking.get("/entries")
// JSON with the contents of entries.json

使用状态代码进行模拟:

如果您不为此模拟请求提供状态代码,则返回的默认状态代码将为 200(SUCCESS),但如果您提供的状态代码不是 2XX,则 Networking 将返回一个包含状态代码和正确错误描述的 NSError。

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: nil, statusCode: 500)
let result = try await networking.get("/stories")
// error with status code 500

下载和缓存图像

下载:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.downloadImage("/image/png")
// Do something with the downloaded image

取消:

let networking = Networking(baseURL: baseURL)
let result = try await networking.downloadImage("/image/png")
// Cancelling an image download returns an error with code URLError.cancelled which means cancelled request

networking.cancelImageDownload("/image/png")

缓存:

Networking 在下载图像时使用多缓存架构。第一次为特定路径调用 downloadImage 方法时,它会将结果存储在磁盘(Documents 文件夹)和内存(NSCache)中,因此在下次调用时,它将返回缓存的结果而无需访问网络。

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.downloadImage("/image/png")
// Image from network
   
let result = try await networking.downloadImage("/image/png")
// Image from cache

如果您想删除下载的图像,您可以这样做:

let networking = Networking(baseURL: "http://httpbin.org")
let destinationURL = try networking.destinationURL(for: "/image/png")
if let path = destinationURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
   try NSFileManager.defaultManager().removeItemAtPath(path)
}

模拟:

let networking = Networking(baseURL: baseURL)
let pigImage = UIImage(named: "pig.png")!
networking.fakeImageDownload("/image/png", image: pigImage)
let result = try await networking.downloadImage("/image/png")
// Here you'll get the provided pig.png image

记录错误

Networking 捕获的任何错误都将打印在您的控制台中。这非常方便,因为您无论如何都想知道您的网络调用失败的原因。

例如,取消的请求将打印以下内容:

========== Networking Error ==========

Cancelled request: https://api.mmm.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small

================= ~ ==================

一个 404 请求将打印类似以下内容:

========== Networking Error ==========

*** Request ***

Error 404: Error Domain=NetworkingErrorDomain Code=404 "not found" UserInfo={NSLocalizedDescription=not found}

URL: http://httpbin.org/posdddddt

Headers: ["Accept": "application/json", "Content-Type": "application/json"]

Parameters: {
  "password" : "secret",
  "username" : "jameson"
}

Data: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>


*** Response ***

Headers: ["Content-Length": 233, "Server": nginx, "Access-Control-Allow-Origin": *, "Content-Type": text/html, "Date": Sun, 29 May 2016 07:19:13 GMT, "Access-Control-Allow-Credentials": true, "Connection": keep-alive]

Status code: 404 — not found

================= ~ ==================

要禁用错误日志记录,请使用标志 disableErrorLogging

let networking = Networking(baseURL: "http://httpbin.org")
networking.disableErrorLogging = true

安装

Networking 可通过 Swift Package Manager 获取。

作者

这个库是由 @3lvis 带着爱制作的。

许可证

Networking 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件

致谢

Logo 字体感谢 Sanid Jusić