Networking 的诞生源于对一个拥有简单易用的 API,并且开箱即用地支持模拟请求和缓存图像的网络库的需求。
初始化 Networking 的实例意味着您必须选择一个 URLSessionConfiguration。可用的类型有 .default
、.ephemeral
和 .background
。如果您不提供任何配置或没有特殊需求,则将使用 default
。
.default
:默认会话配置使用持久的基于磁盘的缓存(除非结果下载到文件),并将凭据存储在用户的钥匙串中。
.ephemeral
:临时会话配置对象类似于默认会话配置对象,但相应的会话对象不会将缓存、凭据存储或任何会话相关数据存储到磁盘。相反,会话相关数据存储在 RAM 中。临时会话写入数据到磁盘的唯一时间是当您告诉它将 URL 的内容写入文件时。使用临时会话的主要优点是隐私。通过不将潜在的敏感数据写入磁盘,可以降低数据被拦截并在以后使用的可能性。因此,临时会话非常适合 Web 浏览器和其他类似情况下的隐私浏览模式。
.background
:此配置类型适合在应用在后台运行时传输数据文件。使用此对象配置的会话将传输的控制权交给系统,系统在单独的进程中处理传输。在 iOS 中,此配置使传输即使在应用本身暂停或终止时也能继续进行。
// 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"]
要使用用户名 "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 "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!
发起请求就像调用 get
、post
、put
或 delete
一样简单。
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 类型是一个枚举,它有两个 case:success
和 failure
。 success
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
。无论如何,希望这能使其更加人性化。
默认情况下,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`
如果您想使用 application/x-www-form-urlencoded
,只需使用 .formURLEncoded
参数类型。在内部,Networking 将格式化您的参数,以便它们使用 Percent-encoding
或 URL-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`
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 开箱即用地支持四种类型的 ParameterType
:JSON
、FormURLEncoded
、MultipartFormData
和 Custom
。同时,JSON
和 FormURLEncoded
以某种方式序列化您的参数,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ć。