Swift DeveloperiOS学习笔记iOS开发

Alamofire 4.5.0源码解析-AFError与Noti

2017-07-04  本文已影响126人  FlyElephant

最近AFNetWorking的2.X和3.X的源码之后,决定看一下Amalofire网路库的实现,现在最新版本是4.5.0版本.主要代码结构如图所示:

Alamofire.png

文件太多,不太好在一篇文章中阐述,先从AFError和Notification开始吧.

AFError

如果对Swift中的枚举不熟悉,看AFError会感觉有点陌生,AFError将错误分为四大类型,每个类型继续进行枚举:
<pre><code>`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)
}

/// The underlying reason the response validation error occurred.
///
/// - dataFileNil:             The data file containing the server response did not exist.
/// - dataFileReadFailed:      The data file containing the server response could not be read.
/// - missingContentType:      The response did not contain a `Content-Type` and the `acceptableContentTypes`
///                            provided did not contain wildcard type.
/// - unacceptableContentType: The response `Content-Type` did not match any type in the provided
///                            `acceptableContentTypes`.
/// - unacceptableStatusCode:  The response status code was not acceptable.
public enum ResponseValidationFailureReason {
    case dataFileNil
    case dataFileReadFailed(at: URL)
    case missingContentType(acceptableContentTypes: [String])
    case unacceptableContentType(acceptableContentTypes: [String], responseContentType: String)
    case unacceptableStatusCode(code: Int)
}

/// The underlying reason the response serialization error occurred.
///
/// - inputDataNil:                    The server response contained no data.
/// - inputDataNilOrZeroLength:        The server response contained no data or the data was zero length.
/// - inputFileNil:                    The file containing the server response did not exist.
/// - inputFileReadFailed:             The file containing the server response could not be read.
/// - stringSerializationFailed:       String serialization failed using the provided `String.Encoding`.
/// - jsonSerializationFailed:         JSON serialization failed with an underlying system error.
/// - propertyListSerializationFailed: Property list serialization failed with an underlying system error.
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)

}`</code></pre>

我们最熟悉的枚举定义是这样的:
<pre><code>`enum NetError {

case notReachable
case reachable

}`</code></pre>

调用也比较简单:
<pre><code>var netError:NetError = NetError.notReachable switch netError { case .notReachable: print("无网") case .reachable: print("无网") }</code></pre>

Swift中枚举有一个重要的特性是关连值枚举,以图形为例的枚举如下:

<pre><code>`enum Shape {

case Rectangle(CGRect)
case Circle(CGPoint,Int)

}`</code></pre>

枚举使用如下:
<pre><code>` var rect = Shape.Rectangle(CGRect(x: 0, y: 0, width: 100, height: 100))

    var circle = Shape.Circle(CGPoint(x: 50, y: 50),50)
    
    switch(rect) {
        
    case .Rectangle(let rect):
        print("矩形位置:\(rect)")
    case let .Circle(center, radius):
        print("圆心:\(center)--半径:\(radius)")
        
    }
    
    if case let Shape.Rectangle(rect) = rect {
        print("矩形位置:\(rect)")
    }
    
    if case let Shape.Circle(center, radius) = rect {
        print("圆心:\(center)--半径:\(radius)")
    }`</code></pre>

AFError在编码的时候作为异常抛出的使用:
<pre><code>` public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    do {
        let data = try JSONSerialization.data(withJSONObject: parameters, options: options)

        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }

        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }

    return urlRequest
}`</code></pre>

AFError对包含的枚举和自身错误描述都做了扩展:

<pre><code>`extension AFError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidURL(let url):
return "URL is not valid: (url)"
case .parameterEncodingFailed(let reason):
return reason.localizedDescription
case .multipartEncodingFailed(let reason):
return reason.localizedDescription
case .responseValidationFailed(let reason):
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
}
}
}

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)"
}
}
}`</code></pre>

Notification

Notification整个文件的代码扩展了Notification添加了Key结构体,定义了通知的类型状态,继续,完成,取消和暂停.
<pre><code>extension Notification.Name { /// Used as a namespace for allURLSessionTaskrelated notifications. public struct Task { /// Posted when aURLSessionTaskis resumed. The notificationobjectcontains the resumedURLSessionTask`.
public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")

    /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
    public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")

    /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
    public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")

    /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
    public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
}

}

// MARK: -

extension Notification {
/// Used as a namespace for all Notification user info dictionary keys.
public struct Key {
/// User info dictionary key representing the URLSessionTask associated with the notification.
public static let Task = "org.alamofire.notification.key.task"
}
}`</code></pre>

Alamofire实际调用过程如下:

<pre><code>` open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }

    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }

    task.resume()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidResume,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

/// Suspends the request.
open func suspend() {
    guard let task = task else { return }

    task.suspend()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidSuspend,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

/// Cancels the request.
open func cancel() {
    guard let task = task else { return }

    task.cancel()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidCancel,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}`</code></pre>

ParameterEncoding

ParameterEncoding比较好理解就是参数编码,Http请求的八种方法:
<pre><code>public enum HTTPMethod: String { case options = "OPTIONS" case get = "GET" case head = "HEAD" case post = "POST" case put = "PUT" case patch = "PATCH" case delete = "DELETE" case trace = "TRACE" case connect = "CONNECT" }</code></pre>

ParameterEncoding是一个协议,定义了一个Encode方法:
<pre><code>public protocol ParameterEncoding { /// Creates a URL request by encoding parameters and applying them onto an existing request. /// /// - parameter urlRequest: The request to have parameters applied. /// - parameter parameters: The parameters to apply. /// /// - throws: AnAFError.parameterEncodingFailederror if encoding fails. /// /// - returns: The encoded request. func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest }</code></pre>

URLEncoding的实现encode过程:

<pre><code>` public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }

        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else {
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }

        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
    }

    return urlRequest
}`</code></pre>

Swift中的百分比编码,8.3系统提供了简单的处理方式:

<pre><code>`
public func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="

    var allowedCharacterSet = CharacterSet.urlQueryAllowed
    allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

    var escaped = ""

    //==========================================================================================================
    //
    //  Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
    //  hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
    //  longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
    //  info, please refer to:
    //
    //      - https://github.com/Alamofire/Alamofire/issues/206
    //
    //==========================================================================================================

    if #available(iOS 8.3, *) {
        escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
    } else {
        let batchSize = 50
        var index = string.startIndex

        while index != string.endIndex {
            let startIndex = index
            let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
            let range = startIndex..<endIndex

            let substring = string.substring(with: range)

            escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring

            index = endIndex
        }
    }

    return escaped
}`</code></pre>

JSONEncoding的URLRequest关于Content-Type设置:

<pre><code>if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") }</code></pre>

PropertyListEncoding中URLRequest关于Content-Type的设置:
<pre><code>if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type") }</code></pre>

文件末位有个有意思的关于NSNumber是否是Bool类型的扩展:
<pre><code>extension NSNumber { fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) } }</code></pre>

参考资料
Swift 中的枚举,你应该了解的东西

上一篇下一篇

猜你喜欢

热点阅读