简介
这是一个著名的iOS平台下网络框架,较OC时期Afnetworking有着相似的功能;
原理解析
以下是Alamofire在一个请求发出到执行回调的完整过程。
发起请求
//发出请求
let request = Alamofire.request("https://httpbin.org/get")
//完成回调
request.responseString { response in
requestComplete(response.response, response.result)
}
涉及主要模块:

- 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)
}

高级用法
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)))
}
}
}
网友评论