AHSuperNetwork
示例
要运行示例项目,请克隆存储库,然后首先从Example目录运行 pod install
。
需求
安装
AHSuperNetwork可以通过CocoaPods获得。要安装,只需将以下行添加到Podfile中:
pod 'AHSuperNetwork'
作者
engsulta, [email protected]
许可
AHSuperNetwork 在 MIT 许可下可用。更多信息请参阅 LICENSE 文件。
支持的平台
.iOS(11) 及更新版本
用法
1- 在您的文件中导入 AHFoundation 模块
import AHFoundation
2- 创建 AHRequestProtocol 的具体实现
首先,您需要定义您的端点请求,并符合 AHRequestProtocol。此协议将包含配置请求所需的所有信息。
端点是什么?本质上,它是一个包含所有组成组件(如路径、HTTP 方法、头、查询参数和正文参数)的 URLRequest。EndPointType 协议是我们 AHNetwork 层实现的基石。
以下是您需要符合的协议:
public protocol AHRequestProtocol {
/// The relative Endpoint path added to baseUrl
var path: String {get}
/// The HTTP request method
var httpMethod: HTTPMethod {get}
/// Http task create encoded data sent as the message body of a request, such as for an HTTP POST request or
/// inline in case of HTTP GET request (default: .request)
var httpTask: HTTPTask {get}
/// A dictionary containing all the request’s HTTP header fields related to such endPoint(default: nil)
var headers: HTTPHeaders? {get}
/// authentication Flag if exist in protocol network client may ask auth layer to provide new auth token
var isAuthenticationNeededRequest: Bool? {get}
///The constants enum used to specify interaction with the cached responses.
/** Specifies that the caching logic defined in the protocol implementation, if any, is
used for a particular URL load request. This is the default policy
for URL load requests.*/
var cachePolicy: CachePolicy { get }
}
按照协议进行,创建您的请求
public struct AHRequestTest: AHRequestProtocol {
}
}
通过配置参数,按照以下步骤操作以使您的 AHRequestTest 可执行
以下是配置AHRequestTest的步骤
设置你的路径
public struct AHRequestTest: AHRequestProtocol {
public var path: String { return "/posts" }
}
配置HTTPMethod
此枚举将用于设置我们请求的HTTP方法。
public enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
case options = "OPTIONS"
}
现在,您可以在您的请求中设置HTTP方法。
public struct AHRequestTest: AHRequestProtocol {
var path: { return "/posts"}
public var httpMethod: HTTPMethod { return .get }
}
HTTPHeaders
HTTPHeaders 实际上是一个类型别名,您可以在HTTPTask文件顶部创建此类型别名。
public typealias HTTPHeaders = [String: String]
现在,您可以在请求中设置HTTP请求头。
public struct AHRequestTest: AHRequestProtocol {
public var path: String { return "/posts" }
public var httpMethod: HTTPMethod { return .get }
var headers : { return [:] }
}
HTTPTask
HTTPTask 负责配置特定endPoint请求的参数。您可以根据网络层需求添加适用的任何多个情况。
.request
:如果没有参数。.requestParameters
:如果您有body和/或url参数。.requestParametersAndHeaders
:如果您有额外的头。
public enum HTTPTask {
case request
case requestParameters(bodyParameters: Parameters?,
bodyEncoding: AHParameterEncoder,
urlParameters: Parameters?)
case requestParametersAndHeaders(bodyParameters: Parameters?, bodyEncoding: AHParameterEncoder,
urlParameters: Parameters?,
extraHeaders: HTTPHeaders?)
}
现在,您可以在请求中设置httpTask变量。
public struct AHRequestTest: AHRequestProtocol {
public var path: String { return "/posts" }
public var httpMethod: HTTPMethod { return .get }
public var headers: HTTPHeaders? { return [:] }
var httpTask: { return .request }
}
关于如何选择httpTask的更多理解请参阅以下参数与编码部分。
参数 & 编码
在选择你的httpTask时,你会发现自己需要选择AHParametersEncoder,因为它将负责根据参数类型进行编码。
参数是别名,别名为让我们的代码更简洁。 public typealias Parameters = [String: Any]
AHParameterEncoder 是编码组内部的一个枚举,使用协议ParameterEncoderProtocol,其中一个静态函数 encode。
public protocol ParameterEncodingProtocol {
func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws
}
encode方法接收两个参数:一个inout URLRequest和参数。通常,变量作为值类型传递给函数。
ParameterEncoderProtocol将由我们的 JSONParameterEncoder 和 URLPameterEncoder 实现。
AHParameterEncoder执行一个函数 encode,即编码参数。这个方法可能失败,所以它抛出了自定义错误,我们需要处理它。
现在,如果您有请求参数,您可以在请求中将httTask替换为这个
public struct AHRequestTest: AHRequestProtocol {
public var path: String { return "/posts" }
public var httpMethod: HTTPMethod { return .get }
public var headers: HTTPHeaders? { return [:] }
public var httpTask: HTTPTask { return .requestParameters(
bodyParameters: nil,
bodyEncoding: .urlEncoding,
urlParameters: ["userId": "1"]) }
}
注意,如果您的请求不需要认证令牌,您应该确保设置
isAuthenticationNeededRequest = false
您还可以为这个请求设置您想要的缓存策略
public struct AHRequestTest: AHRequestProtocol {
public var path: String { return "/posts" }
public var httpMethod: HTTPMethod { return .get }
public var headers: HTTPHeaders? { return [:] }
public var httpTask: HTTPTask { return .requestParameters(
bodyParameters: nil,
bodyEncoding: .urlEncoding,
urlParameters: ["userId": "1"]) }
public var isAuthenticationNeededRequest: Bool? = false
public var cachePolicy: CachePolicy = .reloadIgnoringLocalCacheData
}
现在您已经完成了AHRequestTest的构建。让我们进入下一步
注意,您还可以使用已从AHRequestProtocol中实现的AHRequest结构体,并保持默认值,或者根据您自己的业务逻辑进行更改。
let inlineRequest = AHRequest(path: "/posts",
httpMethod: .get,
httpTask: .requestParameters(
bodyParameters: nil,
bodyEncoding: .urlEncoding,
urlParameters: ["userId": "1"]),
headers: nil,
isAuthenticationNeededRequest: true,
cachePolicy: .reloadIgnoringLocalCacheData)
3- 使用AHNetworkClient执行AHRequestProtocol
AHNetworkClient 是AHNetworkClient组内部的主类,它遵从AHNetworkClientProtocol协议。
public typealias AHNetworkCompletion = ( Codable?, Error? ) -> Void
public typealias AHNetworkProgressClosure = ((Double) -> Void)?
public typealias AHNetworkDownloadClosure = ( URL?, URLResponse?, Error? ) -> Void
public protocol AHNetworkClientProtocol {
/// The URL of the EndPoint at the server.
var baseUrl: String { get }
/// A dictionary containing all the Client Related HTTP header fields (default: nil)
var headers: [String: String]? { get }
/// The session used during HTTP request (default: URLSession.shared)
var session: URLSessionProtocol { get }
/// the authClientProvider module may be injected
var authClientProvider: AuthTokenProvider? { get set }
/// The HTTP request timeout.
var timeout: TimeInterval { get }
///start network execution to start http request
func execute<T: Codable>(request: AHRequestProtocol,
model: T.Type,
progressClosure: AHNetworkProgressClosure?,
completion: @escaping AHNetworkCompletion)
func upload<T: Codable, U: Codable>(request: AHRequestProtocol,
responseModel: T.Type,
uploadModel: U,
completion: @escaping AHNetworkCompletion)
func download(url: String,
progressClosure: AHNetworkProgressClosure?,
completion: @escaping AHNetworkDownloadClosure)
func cancel(request: AHRequestProtocol, completion: @escaping () -> Void)
}
此协议将包含配置网络客户端(基础URL、与客户端相关的标题、超时和会话)所需的所有信息。您已注意到这种配置是按相同的网络客户端共享的。
您需要创建实例 AHNetworkClient 来开始执行请求。
let myNetworkClient = AHNetworkClient(baseURL: "https://jsonplaceholder.typicode.com", session: session)
或
let myNetworkClient = AHNetworkClient(baseURL: "https://jsonplaceholder.typicode.com")
在这种情况下使用 URLSession.shared
您有责任用您想要的任何URL会话配置创建URL会话,并创建一个随与服务器通信而变化的客户端基础URL。您可以有具有不同基础URL的不同客户端。
现在,您可以使用预定义的函数 execute、cancel、upload,它们包含您需要的所有实现,可以将请求转换为URL请求,并允许您取消正在运行的帮助请求。我们还提供了上传功能。
现在我们可以开始获取用户帖子数据
如果我们需要通过网络消费用户帖子JSON文件,我们应该创建可编码的用户帖子对象,该对象将代表用户帖子API JSON响应。您可以在这里查看响应
[
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
]
现在,创建可编码的用户帖子结构体
struct UserPosts : Codable {
let body : String?
let id : Int?
let title : String?
let userId : Int?
}
在您的调用侧获取响应
func sampleExecuteDataTask() {
myNetworkClient.execute(request: AHRequestTest(), model: user [UserPosts].self) { model, error in
if error != nil {
// switch on error and handle each case
} else {
// you have your model as Codable model object cast to your type and easily use it
if let model = model as? [UserPosts] {
print(model[0].title)
}
}
}
}
输出
sunt aut facere repellat provident occaecati excepturi optio reprehenderit
如何下载文件任务
您可以使用AHNetworkLayer下载任何扩展名的文件。以下是一个示例,演示如何使用AHNetworkClient的download方法下载 audioFile,只需将文件URL传递给download方法即可
let fileURL = "https://audio-ssl.itunes.apple.com/itunes-assets/Music6/v4/68/34/f1/6834f1f8-8fdb-4247-492a-c0caea580082/mzaf_3920281300599106672.plus.aac.p.m4a"
func testDownloadFile() {
let client = AHNetworkClient(baseURL: "", session: URLSession.shared)
client.download(url: fileURL) { location, response, error in
// you can access the location throw file manager for demo perpos we will just print it
print(location ?? "")
}
}
如何上传文件任务
您可以使用AHNetworkLayer上传任务。以下是一个示例,演示如何上传一个名为"MockModel"的类型为Encodable的对象,只需将文件模型传递给AHNetworkClient的upload方法即可,您可以试试看
func testUploadFile() {
let exp = expectation(description: #function)
mockClient = AHNetworkClient(baseURL: "https://www.test.com", session: session)
let request = AHRequest(path: "/uploaded", httpMethod: .post, isAuthenticationNeededRequest: false)
mockClient.upload(request: request, responseModel: [String].self, uploadModel: MockModel()) { responseModel, error in
XCTAssertNotNil(responseModel)
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
}
如果您想上传非 Encodable 对象,则需要手动将其转换为 Encodable 类型
以下是一个示例,说明如何上传 UIImage,因为默认情况下 UIImage 不可编码。
extension UIImage: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
guard let data = self.jpegData(compressionQuality: 1.0) else { return }
try container.encode(data)
}
}
func testUploadImage() {
let exp = expectation(description: #function)
let bundle = Bundle.unitTest
let image = UIImage(named: "cloud", in: bundle, compatibleWith: nil) ?? UIImage()
mockClient = AHNetworkClient(baseURL: "https://www.test.com", session: session)
let request = AHRequest(path: "/uploaded", httpMethod: .post, isAuthenticationNeededRequest: false)
mockClient.upload(request: request, responseModel: [String].self, uploadModel: image) { responseModel, _ in
XCTAssertNotNil(responseModel)
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
}
如何查看数据任务进度
//step 1: create an instance of AHNetworkProgressDelegate
let delegate: AHNetworkProgressDelegateMock? = AHNetworkProgressDelegateMock()
// step 2: create session configuration with the delegate of type AHNetworkProgressDelegate
let sessionWithDelegate = URLSession(configuration: URLSessionConfiguration.default, delegate: delegate, delegateQueue: .main)
// step 3: create your own progressClosure of type AHNetworkProgressClosure
let progressClosure: (Double?) -> Void = { progressValue in
print(progressValue ?? "0")}
// create your request just like normal request but add a progress closure to .execute method
myNetworkClient.execute(request: AHRequestTest(),
model: user [UserPosts].self,
progressClosure: progressClosure) { model, error in
if error != nil {
// switch on error and handle each case
} else {
// you have your model as Codable model object cast to your type and easily use it
if let model = model as? [UserPosts]{
print(model[0].title)
}
}
}
您还可以按照相同步骤查看下载文件任务的进度。
如何取消任务
var sampleRequest = AHRequestTest()
func testCancelTask() {
let exp = expectation(description: #function)
myNetworkClient.execute(request: sampleRequest, model: [UserPosts].self) { _, _ in
}
myNetworkClient.cancel(request: sampleRequest) {
XCTAssertTrue(self.session.urlSessionDataTaskMock.isCancelledCalled)
exp.fulfill()
}
wait(for: [exp], timeout: 1.0)
}
AHNetworkError
AHNetwork层可能会抛出这些自定义错误之一,以便调用方执行任何特殊处理。
public enum AHNetworkResponse: String, Error {
case success
case authenticationError = "You need to be authenticated first."
case badRequest = "Bad request"
case serverError = "server encountered an unexpected condition"
case outdated = "The url you requested is outdated."
case failed = "Network request failed."
case noData = "Response returned with no data to decode."
case unableToDecode = "We could not decode the response."
}
public enum AHNetworkError: String, Error {
case parse = "Unable to parse response"
case network = "Network operation failed"
case empty = "Empty response"
case missingURL = "missing URL"
case encodingFailed = "encodingFailed"
case unknown = "Unknown"
case authFailed = "Authentication token missed"
case noInternetConnection = "no Internet Connection"
}
现在,您可以在您的completion Handler中切换这些自定义错误。