Alamofire使用进阶

高级使用说明

Alamofire是建立在 URLSession 和 底层 URL Loading System上的,为了充分利用这个库,建议熟悉底层网络堆栈的概念和功能

推荐阅读

  • URL Loading System Programming Guide
  • URLSession Class Refrence
  • URLCache Class Reference
  • URLAuthenticationChallenge Class Reference

Session Manager

顶层便利方法 像 Alamofire.request 使用了 Alamofire.SessionManager 默认的实例方法 ,该实例使用默认的URLsessionconfiguration配置。

因此,以下两个语句是等效的:

Alamofire.request("https://httpbin.org/get")
let sessionManager = Alamofire.SessionManager.default
sessionManager.request("https://httpbin.org/get")

应用程序可以为后台会话和临时会话创建会话管理器,也可以为自定义默认会话配置的新管理器创建会话管理器,例如为默认头(httpadditionalheaders)或超时间隔(timeoutitervalforrequest)。

Creating a Session Manager with Default Configuration

let configuration = URLSessionConfiguration.default
let sessionManager = Alamofire.SessionManager(configuration: configuration)

Creating a Session Manager with Background Configuration

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)

Creating a Session Manager with Ephemeral Configuration

let configuration = URLSessionConfiguration.ephemeral
let sessionManager = Alamofire.SessionManager(configuration: configuration)

Modifying the Session Configuration

var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"

let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = defaultHeaders

let sessionManager = Alamofire.SessionManager(configuration: configuration)

对于 Authorization 或者 Content-Type headers。不建议这样修改。
相反,请分别使用顶级alamofire.request api、urlrequestconvertible和parameterencoding中的headers参数。

Session Delegate

By default, an Alamofire SessionManager instance creates a SessionDelegate object to handle all the various types of delegate callbacks that are generated by the underlying URLSession. The implementations of each delegate method handle the most common use cases for these types of calls abstracting the complexity away from the top-level APIs. However, advanced users may find the need to override the default functionality for various reasons.

默认情况下,alamofire sessionmanager实例创建一个sessiondelegate对象来处理底层urlsession生成的所有类型的委托回调。每个委托方法的实现处理了这些类型的调用最常见的用例,将复杂性从顶级API中抽象出来。但是,高级用户可能会发现由于各种原因需要覆盖默认功能。

覆盖闭包

定制sessiondelegate行为的第一种方法是使用override闭包。每个闭包都使您能够重写匹配的sessiondelegate api的实现,但仍然对所有其他api使用默认实现。这使得自定义委托功能的子集变得很容易。下面是一些可用的重写闭包的示例:

//
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?

/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?

/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?

/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?

下面是一个简短的示例,说明如何使用taskwillperformhtpredirection来避免后续重定向到任何apple.com域。

let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
let delegate: Alamofire.SessionDelegate = sessionManager.delegate

delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
    var finalRequest = request

    if
        let originalRequest = task.originalRequest,
        let urlString = originalRequest.url?.urlString,
        urlString.contains("apple.com")
    {
        finalRequest = originalRequest
    }

    return finalRequest
}

子类化

重写sessiondelegate的默认实现的另一种方法是对其进行子类化。子类化允许您完全自定义api的行为或为api创建代理,并且仍然使用默认实现。创建代理允许您记录事件、发出通知、提供挂接前和挂接后实现等。下面是一个在发生重定向时子类化sessiondelegate和记录消息的快速示例。

class LoggingSessionDelegate: SessionDelegate {
    override func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        willPerformHTTPRedirection response: HTTPURLResponse,
        newRequest request: URLRequest,
        completionHandler: @escaping (URLRequest?) -> Void)
    {
        print("URLSession will perform HTTP redirection to request: \(request)")

        super.urlSession(
            session,
            task: task,
            willPerformHTTPRedirection: response,
            newRequest: request,
            completionHandler: completionHandler
        )
    }
}

一般来说,默认实现或override闭包都应该提供必需的功能。子类化只能作为最后手段使用。

必须记住,子委托在默认实现中已初始化和销毁。子类化时要小心,不要引入内存泄漏。

Request

请求、下载、上载或流方法的结果是datarequest、download request、uploadrequest和streamrequest,它们都继承自Reqeust。所有请求实例始终由所属会话管理器创建,从不直接初始化。

每个子类都有专门的方法,如authenticate、validate、responsejson和uploadprogress,每个方法都返回调用方实例,以便于方法链式调用。

请求可以挂起,恢复和取消

  • suspend(): 挂起基础任务和调度队列。
  • resume(): 恢复基础任务和调度队列。如果所属管理器未将startRequestSimmediately设置为true,则请求必须调用resume()才能启动。
  • cancel():取消基础任务,生成错误传递给响应注册着

路由请求

随着应用程序规模的增长,在构建网络堆栈时采用通用模式非常重要。该设计的一个重要部分是如何路由您的请求。alamofire urlconvertible和urlrequestconvertible协议以及路由器设计模式都可以帮助您。

URLConvertible

遵守 URLConvertible 协议 的类型 可以用来构建 URLs,然后使用来该类型内部构建请求。默认情况下,字符串、url和urlcomponents符合urlconvertible,允许将其中任何一个作为url参数传递给请求、上载和下载方法。

let urlString = "https://httpbin.org/post"
Alamofire.request(urlString, method: .post)

let url = URL(string: urlString)!
Alamofire.request(url, method: .post)

let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
Alamofire.request(urlComponents, method: .post)

鼓励与Web应用程序进行重要交互的应用程序具有符合URLConvertible的自定义类型,这是将特定于域的模型映射到服务器资源的便捷方法。

类型安全的路由

extension User: URLConvertible {
    static let baseURLString = "https://example.com"

    func asURL() throws -> URL {
    	let urlString = User.baseURLString + "/users/\(username)/"
        return try urlString.asURL()
    }
}
let user = User(username: "mattt")
Alamofire.request(user) // https://example.com/users/mattt

URLRequestConvertible

采用URLRequestConvertible协议的类型可用于构造URL请求。 默认情况下,URLRequest符合URLRequestConvertible,允许将其直接传递到请求,上载和下载方法中(这是为单个请求指定自定义HTTP正文的推荐方法):

let url = URL(string: "https://httpbin.org/post")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"

let parameters = ["foo": "bar"]

do {
    urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
} catch {
    // No-op
}

urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

Alamofire.request(urlRequest)

鼓励以重要方式与Web应用程序交互的应用程序具有符合URLRequestConvertible的自定义类型,以确保请求的终结点的一致性。 这样的方法可用于消除服务器端的不一致性,并提供类型安全的路由,以及管理身份验证凭据和其他状态。

API Parameter Abstraction
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%20bar&offset=50
CRUD & Authorization
import Alamofire

enum Router: URLRequestConvertible {
    case createUser(parameters: Parameters)
    case readUser(username: String)
    case updateUser(username: String, parameters: Parameters)
    case destroyUser(username: String)

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

    var method: HTTPMethod {
        switch self {
        case .createUser:
            return .post
        case .readUser:
            return .get
        case .updateUser:
            return .put
        case .destroyUser:
            return .delete
        }
    }

    var path: String {
        switch self {
        case .createUser:
            return "/users"
        case .readUser(let username):
            return "/users/\(username)"
        case .updateUser(let username, _):
            return "/users/\(username)"
        case .destroyUser(let username):
            return "/users/\(username)"
        }
    }

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest {
    	let url = try Router.baseURLString.asURL()

        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue

        switch self {
        case .createUser(let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        case .updateUser(_, let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        default:
            break
        }

        return urlRequest
    }
}
Alamofire.request(Router.readUser("mattt")) // GET https://example.com/users/mattt

调整和重试请求

如今,大多数Web服务都落后于某种身份验证系统。 今天最常见的一种是OAuth。 通常,这涉及生成访问令牌,以授权您的应用程序或用户调用各种受支持的Web服务。 虽然创建这些初始访问令牌可能很麻烦,但是当您的访问令牌过期并且您需要获取一个新的访问令牌时,这可能会变得更加复杂。 有许多线程安全问题需要考虑。

创建RequestAdapter和RequestRetrier协议是为了使为一组特定的Web服务创建线程安全的身份验证系统更加容易。

RequestAdapter

RequestAdapter协议允许在创建之前对在SessionManager上发出的每个请求进行检查和调整。 使用适配器的一种非常特定的方法是将Authorization标头附加到特定类型的身份验证后面的请求中。

class AccessTokenAdapter: RequestAdapter {
    private let accessToken: String

    init(accessToken: String) {
        self.accessToken = accessToken
    }

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest

        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
        }

        return urlRequest
    }
}
let sessionManager = SessionManager()
sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")

sessionManager.request("https://httpbin.org/get")

RequestRetrier

The RequestRetrier protocol allows a Request that encountered an Error while being executed to be retried. When using both the RequestAdapter and RequestRetrier protocols together, you can create credential refresh systems for OAuth1, OAuth2, Basic Auth and even exponential backoff retry policies. The possibilities are endless. Here’s an example of how you could implement a refresh flow for OAuth2 access tokens.

免责声明:这不是全局OAuth2解决方案。 这只是一个示例,说明如何将RequestAdapter与RequestRetrier结合使用以创建线程安全的刷新系统。

要重申,请勿复制此示例代码并将其放入生产应用程序。 这仅仅是一个例子。 每个身份验证系统必须适合特定的平台和身份验证类型。

class OAuth2Handler: RequestAdapter, RequestRetrier {
    private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void

    private let sessionManager: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

        return SessionManager(configuration: configuration)
    }()

    private let lock = NSLock()

    private var clientID: String
    private var baseURLString: String
    private var accessToken: String
    private var refreshToken: String

    private var isRefreshing = false
    private var requestsToRetry: [RequestRetryCompletion] = []

    // MARK: - Initialization

    public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
        self.clientID = clientID
        self.baseURLString = baseURLString
        self.accessToken = accessToken
        self.refreshToken = refreshToken
    }

    // MARK: - RequestAdapter

    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
            var urlRequest = urlRequest
            urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
            return urlRequest
        }

        return urlRequest
    }

    // MARK: - RequestRetrier

    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        lock.lock() ; defer { lock.unlock() }

        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            requestsToRetry.append(completion)

            if !isRefreshing {
                refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                    guard let strongSelf = self else { return }

                    strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }

                    if let accessToken = accessToken, let refreshToken = refreshToken {
                        strongSelf.accessToken = accessToken
                        strongSelf.refreshToken = refreshToken
                    }

                    strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                    strongSelf.requestsToRetry.removeAll()
                }
            }
        } else {
            completion(false, 0.0)
        }
    }

    // MARK: - Private - Refresh Tokens

    private func refreshTokens(completion: @escaping RefreshCompletion) {
        guard !isRefreshing else { return }

        isRefreshing = true

        let urlString = "\(baseURLString)/oauth2/token"

        let parameters: [String: Any] = [
            "access_token": accessToken,
            "refresh_token": refreshToken,
            "client_id": clientID,
            "grant_type": "refresh_token"
        ]

        sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
            .responseJSON { [weak self] response in
                guard let strongSelf = self else { return }

                if 
                    let json = response.result.value as? [String: Any], 
                    let accessToken = json["access_token"] as? String, 
                    let refreshToken = json["refresh_token"] as? String 
                {
                    completion(true, accessToken, refreshToken)
                } else {
                    completion(false, nil, nil)
                }

                strongSelf.isRefreshing = false
            }
    }
}
let baseURLString = "https://some.domain-behind-oauth2.com"

let oauthHandler = OAuth2Handler(
    clientID: "12345678",
    baseURLString: baseURLString,
    accessToken: "abcd1234",
    refreshToken: "ef56789a"
)

let sessionManager = SessionManager()
sessionManager.adapter = oauthHandler
sessionManager.retrier = oauthHandler

let urlString = "\(baseURLString)/some/endpoint"

sessionManager.request(urlString).validate().responseJSON { response in
    debugPrint(response)
}

一旦将OAuth2Handler用作SessionManager的适配器和重试器,它将通过自动刷新访问令牌并按照失败的顺序重试所有失败的请求来处理无效的访问令牌错误。

上面的示例仅检查401响应代码,该代码不够健壮,但确实演示了如何检查无效的访问令牌错误。 在生产应用程序中,尽管它取决于OAuth2的实现,但还是要检查领域,并且很可能要检查www-authenticate标头响应。

另一个重要说明是,该身份验证系统可以在多个会话管理器之间共享。 例如,对于同一组Web服务,您可能需要同时使用默认会话和临时会话配置。 上面的示例允许在多个会话管理器之间共享同一oauthHandler实例,以管理单个刷新流。

Custom Response Serialization

Alamofire为数据,字符串,JSON和属性列表提供内置的响应序列化:

Alamofire.request(...).responseData { (resp: DataResponse<Data>) in ... }
Alamofire.request(...).responseString { (resp: DataResponse<String>) in ... }
Alamofire.request(...).responseJSON { (resp: DataResponse<Any>) in ... }
Alamofire.request(...).responsePropertyList { (resp: DataResponse<Any>) in ... }

这些响应包装反序列化的值(数据,字符串,任意)或错误(网络,验证错误),以及元数据(URL请求,HTTP标头,状态码,指标等)。

您可以通过多种方式来自定义所有这些响应元素。

Response Mapping

响应映射是产生定制响应的最简单方法。 它可以转换响应的值,同时保留最终的错误和元数据。 例如,您可以将json响应DataResponse 转换为保存应用程序模型(例如DataResponse )的响应。 您可以使用DataResponse.map方法执行响应映射:

Alamofire.request("https://example.com/users/mattt").responseJSON { (response: DataResponse<Any>) in
    let userResponse = response.map { json in
        // We assume an existing User(json: Any) initializer
        return User(json: json)
    }

    // Process userResponse, of type DataResponse<User>:
    if let user = userResponse.value {
        print("User: { username: \(user.username), name: \(user.name) }")
    }
}

当转换可能引发错误时,请改用flatMap:

Alamofire.request("https://example.com/users/mattt").responseJSON { response in
    let userResponse = response.flatMap { json in
        try User(json: json)
    }
}

Handing Errors

响应映射非常适合您的自定义完成处理程序:

@discardableResult
func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
    return Alamofire.request("https://example.com/users/mattt").responseJSON { response in
        let userResponse = response.flatMap { json in
            try User(json: json)
        }

        completionHandler(userResponse)
    }
}

loadUser { response in
    if let user = response.value {
        print("User: { username: \(user.username), name: \(user.name) }")
    }
}

当map / flatMap闭包可能处理大量数据时,请确保在主线程之外执行它:

@discardableResult
func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
    let utilityQueue = DispatchQueue.global(qos: .utility)

    return Alamofire.request("https://example.com/users/mattt").responseJSON(queue: utilityQueue) { response in
        let userResponse = response.flatMap { json in
            try User(json: json)
        }

        DispatchQueue.main.async {
            completionHandler(userResponse)
        }
    }
}

Handing Errors

在实现自定义响应序列化程序或对象序列化方法之前,考虑如何处理可能发生的任何错误非常重要。 有两个基本选项:将现有错误传递给未修改者,以在响应时进行处理; 或者,将所有错误包装在特定于您应用的错误类型中。
例如,这是一个简单的BackendError枚举,将在以后的示例中使用:

enum BackendError: Error {
    case network(error: Error) // Capture any underlying Error from the URLSession API
    case dataSerialization(error: Error)
    case jsonSerialization(error: Error)
    case xmlSerialization(error: Error)
    case objectSerialization(reason: String)
}

Creating a Custom Response Serializer

Alamofire为字符串,JSON和属性列表提供了内置的响应序列化,但是其他可以在Alamofire.DataRequest和/或Alamofire.DownloadRequest的扩展中添加。

extension DataRequest {
    static func xmlResponseSerializer() -> DataResponseSerializer<ONOXMLDocument> {
        return DataResponseSerializer { request, response, data, error in
            // Pass through any underlying URLSession error to the .network case.
            guard error == nil else { return .failure(BackendError.network(error: error!)) }

            // Use Alamofire's existing data serializer to extract the data, passing the error as nil, as it has
            // already been handled.
            let result = Request.serializeResponseData(response: response, data: data, error: nil)

            guard case let .success(validData) = result else {
                return .failure(BackendError.dataSerialization(error: result.error! as! AFError))
            }

            do {
                let xml = try ONOXMLDocument(data: validData)
                return .success(xml)
            } catch {
                return .failure(BackendError.xmlSerialization(error: error))
            }
        }
    }

    @discardableResult
    func responseXMLDocument(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<ONOXMLDocument>) -> Void)
        -> Self
    {
        return response(
            queue: queue,
            responseSerializer: DataRequest.xmlResponseSerializer(),
            completionHandler: completionHandler
        )
    }
}

Generic Response Object Serialization

protocol ResponseObjectSerializable {
    init?(response: HTTPURLResponse, representation: Any)
}

extension DataRequest {
    func responseObject<T: ResponseObjectSerializable>(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<T>) -> Void)
        -> Self
    {
        let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
            guard error == nil else { return .failure(BackendError.network(error: error!)) }

            let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
            let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)

            guard case let .success(jsonObject) = result else {
                return .failure(BackendError.jsonSerialization(error: result.error!))
            }

            guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
                return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
            }

            return .success(responseObject)
        }

        return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}
struct User: ResponseObjectSerializable, CustomStringConvertible {
    let username: String
    let name: String

    var description: String {
        return "User: { username: \(username), name: \(name) }"
    }

    init?(response: HTTPURLResponse, representation: Any) {
        guard
            let username = response.url?.lastPathComponent,
            let representation = representation as? [String: Any],
            let name = representation["name"] as? String
        else { return nil }

        self.username = username
        self.name = name
    }
}
Alamofire.request("https://example.com/users/mattt").responseObject { (response: DataResponse<User>) in
    debugPrint(response)

    if let user = response.result.value {
        print("User: { username: \(user.username), name: \(user.name) }")
    }
}

同样的方法也可以用于处理返回对象集合表示形式的端点:

protocol ResponseCollectionSerializable {
    static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self]
}

extension ResponseCollectionSerializable where Self: ResponseObjectSerializable {
    static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self] {
        var collection: [Self] = []

        if let representation = representation as? [[String: Any]] {
            for itemRepresentation in representation {
                if let item = Self(response: response, representation: itemRepresentation) {
                    collection.append(item)
                }
            }
        }

        return collection
    }
}
extension DataRequest {
    @discardableResult
    func responseCollection<T: ResponseCollectionSerializable>(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self
    {
        let responseSerializer = DataResponseSerializer<[T]> { request, response, data, error in
            guard error == nil else { return .failure(BackendError.network(error: error!)) }

            let jsonSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
            let result = jsonSerializer.serializeResponse(request, response, data, nil)

            guard case let .success(jsonObject) = result else {
                return .failure(BackendError.jsonSerialization(error: result.error!))
            }

            guard let response = response else {
                let reason = "Response collection could not be serialized due to nil response."
                return .failure(BackendError.objectSerialization(reason: reason))
            }

            return .success(T.collection(from: response, withRepresentation: jsonObject))
        }

        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}
struct User: ResponseObjectSerializable, ResponseCollectionSerializable, CustomStringConvertible {
    let username: String
    let name: String

    var description: String {
        return "User: { username: \(username), name: \(name) }"
    }

    init?(response: HTTPURLResponse, representation: Any) {
        guard
            let username = response.url?.lastPathComponent,
            let representation = representation as? [String: Any],
            let name = representation["name"] as? String
        else { return nil }

        self.username = username
        self.name = name
    }
}
Alamofire.request("https://example.com/users").responseCollection { (response: DataResponse<[User]>) in
    debugPrint(response)

    if let users = response.result.value {
        users.forEach { print("- \($0)") }
    }
}

安全

服务进行通信时使用安全的HTTPS连接是保护敏感数据的重要一步。 默认情况下,Alamofire将使用安全框架提供的Apple内置验证来评估服务器提供的证书链。 尽管这可以确保证书链有效,但不能防止中间人(MITM)攻击或其他潜在漏洞。 为了减轻MITM攻击,处理敏感客户数据或财务信息的应用程序应使用ServerTrustPolicy提供的证书或公钥固定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值