AHSuperNetwork 0.1.0

AHSuperNetwork 0.1.0

Ahmed Sultan 维护。



  • engsulta

AHSuperNetwork

CI Status Version License Platform

示例

要运行示例项目,请克隆存储库,然后首先从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的步骤

width=100%

设置你的路径

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将由我们的 JSONParameterEncoderURLPameterEncoder 实现。

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的不同客户端。

现在,您可以使用预定义的函数 executecancelupload,它们包含您需要的所有实现,可以将请求转换为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中切换这些自定义错误。