美文网首页
Alamofire源码解析(一)

Alamofire源码解析(一)

作者: akak | 来源:发表于2018-07-05 11:14 被阅读38次

简介

这是一个著名的iOS平台下网络框架,较OC时期Afnetworking有着相似的功能;

原理解析

以下是Alamofire在一个请求发出到执行回调的完整过程。

发起请求

     //发出请求
     let request = Alamofire.request("https://httpbin.org/get")
     //完成回调
     request.responseString { response in        
                requestComplete(response.response, response.result)
            }

涉及主要模块:

111.jpeg
  • SessionManager 负责初始化session,提供对外请求api
  • SessionDelegate 处理所有网络回调,分发
  • Request 请求的封装,解析队列管理,提供了对请求的resume,suspend,cancel操作

SessionManger创建request,当 startRequestsImmediately =true(默认true)时立即发出请求,否则是需要request.resume()手动发出请求。
SessionDelegate模块,处理网络请求所有的回调。当urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?),调用对应request.delegate.queue.isSuspended=false,即开启队列操作。

获取返回数据的闭包实质上将被当成operation添加入request.data.queue中。当请求完成后数据解析队列随即开始操作。

   request.responseObject { (response: DataResponse<APIResult<T>>) in
    print(response)
 }
222.jpeg

高级用法

1. protocol RequestAdapter

func adapt(_ urlRequest: URLRequest) throws -> URLRequest

只要实现该协议,就可以在内部实现加密,请求过滤,动态调整Header等等,在请求发送之前,有个机会可以让你修改URLRequest

举个🌰

 private enum AuthenticationError: Error {
        case expiredAccessToken
    }

    private class AuthenticationAdapter: RequestAdapter {
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            throw AuthenticationError.expiredAccessToken  //所有request将被拦截
        } 
    }
    func testHttp() {
        sessionManager = SessionManager()
        sessionManager.adapter = AuthenticationAdapter()
        let request = sessionManager.request("https://httpbin.org/")
        XCTAssertNotNil(request.request)
        XCTAssertNil(request.response)
    }
    
    
    
    //用于统计所有request的Adapter
    private class CountAdapter:RequestAdapter {
        var adaptedCount = 0
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        adaptedCount += 1 
        return urlRequest
    }
    
    

2. emun 掩码方式

在SDWebImage中也是常见的枚举方式

使得枚举也像集合一样可以多选项共存,增删该选项。



struct EnjoyOptions: OptionSet {
  public let rawValue: UInt
  public static let Movies = EnjoyOptions(rawValue: 1 << 1)//10
  public static let Sing = EnjoyOptions(rawValue: 2 << 1)//100
  public static let Basketball = EnjoyOptions(rawValue: 3 << 1)//1000
  public static let Football = EnjoyOptions(rawValue: 4 << 1)//10000
}

var myOpt1: EnjoyOptions = [EnjoyOptions.Movies , EnjoyOptions.Sing]
//
if myOpt1.contains(.Movies) {
  print("Movies")
}
if myOpt1.contains(.Sing) {
  print("Sing")
}
//Output
//Movies
//Sing

myOpt1.insert(.Basketball)//插入类型
if myOpt1.contains(.Basketball) {
  print("Basketball")
}

var myOpt2: EnjoyOptions = [EnjoyOptions.Movies]

let opt2Removed = myOpt2.remove(.Movies)//opt2Removed = .Movies  移出类型

if !myOpt2.contains(.Movies) {
  print("not contain Movies")
}

myOpt2.insert(.Movies)
let replaced = myOpt2.update(with: [.Movies,.Basketball,.Football]) //补充类型
print(replaced)//.Movies
print(myOpt2)//[.Movies,.Basketball,.Football]


左移运算符 <<,1<<1 = 二进制(10) = 十进制(2);
2<<1 = 二进制(100)= 十进制(4)以此类推。因此前两个类型同时存在的时,二进制(110) = 十进制(6)
所以多选项的rawvalue单独存在,相互不影响

3. 错误封装

Alamofire中将AFError归类成四大类,并且提供了区分不同错误类别的方法

public enum AFError: Error {
    //参数编码错误
    public enum ParameterEncodingFailureReason {
        case missingURL
        case jsonEncodingFailed(error: Error)
        case propertyListEncodingFailed(error: Error)
    }
   
     //表单编码错误
    public enum MultipartEncodingFailureReason {
        case bodyPartURLInvalid(url: URL)
        case bodyPartFilenameInvalid(in: URL)
        case bodyPartFileNotReachable(at: URL)
        case bodyPartFileNotReachableWithError(atURL: URL, error: Error)
        case bodyPartFileIsDirectory(at: URL)
        case bodyPartFileSizeNotAvailable(at: URL)
        case bodyPartFileSizeQueryFailedWithError(forURL: URL, error: Error)
        case bodyPartInputStreamCreationFailed(for: URL)

        case outputStreamCreationFailed(for: URL)
        case outputStreamFileAlreadyExists(at: URL)
        case outputStreamURLInvalid(url: URL)
        case outputStreamWriteFailed(error: Error)

        case inputStreamReadFailed(error: Error)
    }
   
     //返回值验证错误
    public enum ResponseValidationFailureReason {
        case dataFileNil
        case dataFileReadFailed(at: URL)
        case missingContentType(acceptableContentTypes: [String])
        case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
        case unacceptableStatusCode(code: Int)
    }
    
    //返回值序列化错误
    public enum ResponseSerializationFailureReason {
        case inputDataNil
        case inputDataNilOrZeroLength
        case inputFileNil
        case inputFileReadFailed(at: URL)
        case stringSerializationFailed(encoding: String.Encoding)
        case jsonSerializationFailed(error: Error)
        case propertyListSerializationFailed(error: Error)
    }

    case invalidURL(url: URLConvertible)
    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
    case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
    case responseValidationFailed(reason: ResponseValidationFailureReason)
    case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}

扩展AFError区分不同错误类别,方便外界区分


extension AFError { 
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }
 
    public var isParameterEncodingError: Bool {
        if case .parameterEncodingFailed = self { return true }
        return false
    }
 
    public var isMultipartEncodingError: Bool {
        if case .multipartEncodingFailed = self { return true }
        return false
    } 

    public var isResponseValidationError: Bool {
        if case .responseValidationFailed = self { return true }
        return false
    } 
    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false
    }
}

作者不仅对错误有纵向的分类,横向根据错误属性也进行分类


extension AFError {

    public var urlConvertible: URLConvertible? {
        switch self {
        case .invalidURL(let url):
            return url
        default:
            return nil
        }
    }
//  如果当前错误类型是InvalidURLError(无效地址),则打印错误url
//    if error.isInvalidURLError {
//        print(error.urlConvertible)
//    }

 //若是潜在错误,输出不同类型的潜在错误
    public var underlyingError: Error? {
        switch self {
        case .parameterEncodingFailed(let reason):
            return reason.underlyingError
        case .multipartEncodingFailed(let reason):
            return reason.underlyingError
        case .responseSerializationFailed(let reason):
            return reason.underlyingError
        default:
            return nil
        }
    }
}

//不同类型的潜在错误又做了细分输出
extension AFError.ParameterEncodingFailureReason {
    var underlyingError: Error? {
        switch self {
        //当前类型中,jsonEncodingFailed和propertyListEncodingFailed是有潜在错误的
        case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
            return error
        default:
            return nil
        }
    }
    
}


最后,为每个错误提供人性化的输出

extension AFError.ParameterEncodingFailureReason {
    var localizedDescription: String {
        switch self {
        case .missingURL:
            return "URL request to encode was missing a URL"
        case .jsonEncodingFailed(let error):
            return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
        case .propertyListEncodingFailed(let error):
            return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
        }
    }
}

一个完善的框架有错误体系,好让接入方更全面把握风险。不过目前我们框架中throw的error较少

举个🌰

PPError
|-PPFoundation
|-Environment
|-Network
|-Navigator
|-...
|-PPModuleError
|-PPDBError
|-PPBusError
|-...


public enum PPFoundationError: Error {
  public enum Environment {
    case missingPlist(at: URL)
    case missingWxKey(key: String)
    case missingTencentKey(key: String)
  }
  
  public enum Network {
    case baseURLIsEmpty
  }

}
extension PPFoundationError.Environment {
  var infoKey: String {
    switch self {
    case .missingWxKey:
      return "weixin"
    case .missingTencentKey:
      return "tencent"
    default: return ""
    }
  }
}

4. URLRequestConvertible

协议内容如下

/// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
public protocol URLRequestConvertible {
    /// Returns a URL request or throws if an `Error` was encountered.
    ///
    /// - throws: An `Error` if the underlying `URLRequest` is `nil`.
    ///
    /// - returns: A URL request.
    func asURLRequest() throws -> URLRequest
}

可根据协议调整请求模块,自定义构造URLRequest。

举个🌰,通过enum指定请求路径与参数.

  enum Router: URLRequestConvertible {
    case search(query: String, page: Int)

    static let baseURLString = "https://example.com"
    static let perPage = 50

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest {
        let result: (path: String, parameters: Parameters) = {
            switch self {
            case let .search(query, page) where page > 0:
                return ("/search", ["q": query, "offset": Router.perPage * page])
            case let .search(query, _):
                return ("/search", ["q": query])
            }
        }()

        let url = try Router.baseURLString.asURL()
        let urlRequest = URLRequest(url: url.appendingPathComponent(result.path))

        return try URLEncoding.default.encode(urlRequest, with: result.parameters)
    }
}

发送请求

Alamofire.request(Router.search(query: "foo bar", page: 1)) // https://example.com/search?q=foo bar&offset=50

5. ResponseSerializer

面对业务所需数据类型的多样性(JSON、Dic、String、XML、Model...),如何可扩展性的支持序列化呢?

不同序列化模块都需要遵循DataResponseSerializerProtocol协议,DataResponseSerializerProtocol协议内容:


public protocol DataResponseSerializerProtocol {
    
    associatedtype SerializedObject
    
    var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<SerializedObject> { get }
}

//struct内部存着一个闭包,不同序列化模块实现不同的闭包内容,
//相当于变化封装在var serializeResponse中
public struct DataResponseSerializer<Value>: DataResponseSerializerProtocol {
    public typealias SerializedObject = Value

    public var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>//不同模块,内部实现不同

    public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>) {
        self.serializeResponse = serializeResponse
    }
}

但是不同的序列化流程,最终都在队列中被执行了

public func response<T: DataResponseSerializerProtocol>(
        queue: DispatchQueue? = nil,
        responseSerializer: T,
        completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
        -> Self
    { 
        delegate.queue.addOperation {//加入request.delegate.queue,网络请求完成后queue开启
            
            let result = responseSerializer.serializeResponse(
                self.request,
                self.response,
                self.delegate.data,
                self.delegate.error
            )//序列化数据
            
            var dataResponse = DataResponse<T.SerializedObject>(
                request: self.request,
                response: self.response,
                data: self.delegate.data,
                result: result,
                timeline: self.timeline
            )//组装response

            dataResponse.add(self.delegate.metrics)

            (queue ?? DispatchQueue.main).async { completionHandler(dataResponse) } //回调
        }

        return self
    }

举个🌰
JSON的struct内部实现

extension DataRequest {

  //step1 获取响应JSON数据
  public func responseJSON(
        queue: DispatchQueue? = nil,
        options: JSONSerialization.ReadingOptions = .allowFragments,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.jsonResponseSerializer(options: options),
            completionHandler: completionHandler
        )
    }
  
  //step2 返回”内部带有JSON解析程序的“的struct
  public static func jsonResponseSerializer(
        options: JSONSerialization.ReadingOptions = .allowFragments)
        -> DataResponseSerializer<Any>
    {
        return DataResponseSerializer { _, response, data, error in
            return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
        }
    }
  }
  
//具体的json解析程序
extension Request {
    public static func serializeResponseJSON(
        options: JSONSerialization.ReadingOptions,
        response: HTTPURLResponse?,
        data: Data?,
        error: Error?)
        -> Result<Any>
    {
        guard error == nil else { return .failure(error!) }

        if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }

        guard let validData = data, validData.count > 0 else {
            return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
        }

        do {
            let json = try JSONSerialization.jsonObject(with: validData, options: options)
            return .success(json)
        } catch {
            return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
        }
    }
} 

相关文章

网友评论

      本文标题:Alamofire源码解析(一)

      本文链接:https://www.haomeiwen.com/subject/hznuuftx.html