【Alamofire源码解析】16- Validation

这里面主要写了如何验证请求是否有效。

1. 验证Request

extension Request {

    // 为了代码简洁,把ResponseValidationFailureReason定义为ErrorReason
    fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason

    // 用于表示验证结果
    public enum ValidationResult {
        case success
        case failure(Error)
    }

    // MIMEType类型
    fileprivate struct MIMEType {
        let type: String
        let subtype: String

        // 是否是通配符
        var isWildcard: Bool { return type == "*" && subtype == "*" }

        init?(_ string: String) {
            let components: [String] = {
                let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)

                #if swift(>=3.2)
                    let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
                #else
                    let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
                #endif

                return split.components(separatedBy: "/")
            }()

            if let type = components.first, let subtype = components.last {
                self.type = type
                self.subtype = subtype
            } else {
                return nil
            }
        }

        // 判断与另外一个给定的MIMEType是否匹配
        func matches(_ mime: MIMEType) -> Bool {
            switch (type, subtype) {
            case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
                return true
            default:
                return false
            }
        }
    }
    
    // 请求成功的状态码
    fileprivate var acceptableStatusCodes: [Int] { return Array(200..<300) }

    // 接受的ContentType类型
    fileprivate var acceptableContentTypes: [String] {
        if let accept = request?.value(forHTTPHeaderField: "Accept") {
            return accept.components(separatedBy: ",")
        }

        return ["*/*"]
    }

    // 验证响应的状态码是否匹配给定的状态码
    fileprivate func validate<S: Sequence>(
        statusCode acceptableStatusCodes: S,
        response: HTTPURLResponse)
        -> ValidationResult
        where S.Iterator.Element == Int
    {
        if acceptableStatusCodes.contains(response.statusCode) {
            return .success
        } else {
            let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
            return .failure(AFError.responseValidationFailed(reason: reason))
        }
    }

    // 验证响应的ContentType是否匹配给定ContentTypes
    fileprivate func validate<S: Sequence>(
        contentType acceptableContentTypes: S,
        response: HTTPURLResponse,
        data: Data?)
        -> ValidationResult
        where S.Iterator.Element == String
    {
        // 如果有数据,说明请求成功了,直接返回
        guard let data = data, data.count > 0 else { return .success }

        guard
            let responseContentType = response.mimeType,
            let responseMIMEType = MIMEType(responseContentType)
            else { // 响应中没有mimeType
            
                // 如果给定的ContentTypes是通配符,则验证成功
                for contentType in acceptableContentTypes {
                    if let mimeType = MIMEType(contentType), mimeType.isWildcard {
                        return .success
                    }
                }

                let error: AFError = {
                    let reason: ErrorReason = .missingContentType(acceptableContentTypes: Array(acceptableContentTypes))
                    return AFError.responseValidationFailed(reason: reason)
                }()

                return .failure(error)
        }
    
        // 如果响应的contentType匹配给定的ContentTypes,则验证成功
        for contentType in acceptableContentTypes {
            if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
                return .success
            }
        }

        let error: AFError = {
            let reason: ErrorReason = .unacceptableContentType(
                acceptableContentTypes: Array(acceptableContentTypes),
                responseContentType: responseContentType
            )

            return AFError.responseValidationFailed(reason: reason)
        }()

        return .failure(error)
    }
}

2. 验证DataRequest

extension DataRequest {
    // 验证请求是否有效的closure
    public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult

    // 使用给定的Validation验证
    @discardableResult
    public func validate(_ validation: @escaping Validation) -> Self {
        let validationExecution: () -> Void = { [unowned self] in
            if
                let response = self.response,
                self.delegate.error == nil,
                case let .failure(error) = validation(self.request, response, self.delegate.data)
            {
                self.delegate.error = error
            }
        }
        
        // 把验证添加到一个数组中,待请求完成后执行所有验证
        validations.append(validationExecution)

        return self
    }

    // 验证响应状态码是否与给定的状态码匹配
    @discardableResult
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
        return validate { [unowned self] _, response, _ in
            // 直接调用父类的验证方法
            return self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }

    // 验证响应的contentType是否与给定的contentTypes匹配
    @discardableResult
    public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
        return validate { [unowned self] _, response, data in
            // 直接调用父类的验证方法
            return self.validate(contentType: acceptableContentTypes, response: response, data: data)
        }
    }

    // 默认的验证方法:
    // 1)验证状态码是否在200...299区间;2)验证contentType是否与请求头中给定的"Accept"相同
    @discardableResult
    public func validate() -> Self {
        return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
    }
}

2. 验证DownloadRequest

这里基本上跟验证DataRequest的一样。

extension DownloadRequest {
    // 验证请求是否有效的closure
    public typealias Validation = (
        _ request: URLRequest?,
        _ response: HTTPURLResponse,
        _ temporaryURL: URL?,
        _ destinationURL: URL?)
        -> ValidationResult

    // 使用给定的Validation验证
    @discardableResult
    public func validate(_ validation: @escaping Validation) -> Self {
        let validationExecution: () -> Void = { [unowned self] in
            let request = self.request
            let temporaryURL = self.downloadDelegate.temporaryURL
            let destinationURL = self.downloadDelegate.destinationURL

            if
                let response = self.response,
                self.delegate.error == nil,
                case let .failure(error) = validation(request, response, temporaryURL, destinationURL)
            {
                self.delegate.error = error
            }
        }
        
        // 把验证添加到一个数组中,待请求完成后执行所有验证
        validations.append(validationExecution)

        return self
    }

    // 验证响应状态码是否与给定的状态码匹配
    @discardableResult
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
        return validate { [unowned self] _, response, _, _ in
            // 直接调用父类的验证方法
            return self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }

    // 验证响应的contentType是否与给定的contentTypes匹配
    @discardableResult
    public func validate<S: Sequence>(contentType acceptableContentTypes: S) -> Self where S.Iterator.Element == String {
        return validate { [unowned self] _, response, _, _ in
            let fileURL = self.downloadDelegate.fileURL

            guard let validFileURL = fileURL else {
                return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
            }

            do {
                let data = try Data(contentsOf: validFileURL)
                // 直接调用父类的验证方法
                return self.validate(contentType: acceptableContentTypes, response: response, data: data)
            } catch {
                return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
            }
        }
    }

    // 默认的验证方法:
    // 1)验证状态码是否在200...299区间;2)验证contentType是否与请求头中给定的"Accept"相同
    @discardableResult
    public func validate() -> Self {
        return validate(statusCode: self.acceptableStatusCodes).validate(contentType: self.acceptableContentTypes)
    }
}

有任何问题,欢迎大家留言!

欢迎加入我管理的Swift开发群:536353151,本群只讨论Swift相关内容。

原创文章,转载请注明出处。谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,198评论 19 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 174,155评论 25 709
  • iOS网络架构讨论梳理整理中。。。 其实如果没有APIManager这一层是没法使用delegate的,毕竟多个单...
    yhtang阅读 5,321评论 1 23
  • 朋友跟我吐槽,新招的助理让他无时无刻不想开掉,做事完全没有脑子,说一就做一,绝对不会主动想到二,甚至连一都做不好。...
    爱上西瓜的皮球阅读 286评论 0 0
  • 不知不觉的离开《起航》已经有1个月之久了,忘了离开那天是什么时候,但是还记得那天的天气很好。尽管离开报社这么长时间...
    夏目心叶阅读 181评论 0 5