了解 AlamoFire OAuth 示例

Posted

技术标签:

【中文标题】了解 AlamoFire OAuth 示例【英文标题】:Understanding AlamoFire OAuth Example 【发布时间】:2017-06-24 01:37:06 【问题描述】:

我能够获得 AlamoFire 提供的 OAuth 示例的有效实现。不过,我希望了解某些代码行及其工作原理。

完整示例:

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
            
    

问题:

[weak self] succeeded, accessToken, refreshToken in
                    guard let strongSelf = self else  return 

    [weak self]guard for strongSelf 的用途是什么?

        requestsToRetry.append(completion)
    
        if !isRefreshing 
            refreshTokens  [weak self] succeeded, accessToken, refreshToken in
                guard let strongSelf = self else  return 
    
                //Implementation
    
                strongSelf.requestsToRetry.forEach  $0(succeeded, 0.0) 
                strongSelf.requestsToRetry.removeAll()
            
        
    

    此请求重试如何工作? requestsToRetry 只是 RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) 的数组,它如何知道要重试的请求?

strongSelf.lock.lock()

    NSLock 是否不允许在此方法执行时任何其他线程访问 self (OAuth2Handler)?

【问题讨论】:

1 是为了让您获得对self 的强引用,这样如果self 为零,则以下代码不会尝试使用它,因为self 在该上下文中很弱,强引用将在范围结束时释放,因此不会发生保留周期。不熟悉 alamofire,所以不能确定其余的是什么。 【参考方案1】:

1) 正如 Fonix 所评论的那样,您强烈引用 self 以避免如果 self 为 nil 您开始收集保留周期..

我指的是:

[weak self] ... in
guard let strongSelf = self else  return 

由于self 将被捕获在异步分派的块中,self 将被隐式保留并在块完成后再次释放,换句话说,self 将一直延伸到块完成后.编写此代码,您可以避免延长 self 的生命周期,并在 self 等于 nil 时决定不执行该块

2)根据您提到的行:

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

有一个名为requestsToRetry 的数组包含您需要重新启动的所有请求。在此代码中,您将所有具有 401 状态代码的请求附加到数组(当服务器返回状态代码 401 时) 使用代码forEach 循环遍历requestToRetry 数组:

strongSelf.requestsToRetry.forEach  $0(succeeded, 0.0) 
strongSelf.requestsToRetry.removeAll()

并启动所有项目。循环结束后,您删除所有项目。

事实上,消息来源报道:

public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void

public protocol RequestRetrier 
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)

您可以找到更多详情here

3)正如您所说,经常面临的并发问题是与从不同线程访问/修改共享资源有关的问题。 lock.lock() 是在修改项目时锁定其他执行块的解决方案。 defer 中的代码在离开函数解锁块之前被调用。

【讨论】:

以上是关于了解 AlamoFire OAuth 示例的主要内容,如果未能解决你的问题,请参考以下文章

Xcode:Alamofire 源代码中的 Swift Dropbox 错误

如何在 Alamofire 4.0 中添加带有上传进度百分比的标签的进度条

Swift Alamofire 上的 Django oauth2 令牌请求失败

Alamofire 5 调整和重试请求

Alamofire 重试请求 - 反应方式

URLSessions 或 AlamoFire 用于下载每天更新一次的网页?