Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alamofire: How to Handle 401 Globally?

I am trying to build a class which manages my network calls for my project. To this time, I mean handling errors globally, everything was fine. I created two functions; for post requests postRequest(:_) and for get requests getRequests(:_). Datamaker functions to return data such as URL, parameters, headers etc, dataparser functions to parse response datas and finally a function to solve errors called errorHandler().

When I call the one of request function, I give a parameter to help function which request it should make. In the function it calls datamakers to get data firstly, then makes request with Alamofire, and at the end if the request was successful it calls dataparser and onSuccess(data:)closure or if it wasn't it calls errorHandler(statusCode:) and onFailure(message:) closure.

I put a switch block in errorHandler and gave statusCode for its parameter. In case 401 I called Token().refresh() and in it's completion called errorHanlder's completion. In the postRequest/errorHandler's completion block I called the postRequest again with the same parameters. It didn't work. I don't why, it went in infinite loop everytime and made requests consecutively.

So I decided to try cnoon's AuthorizationManager class(can be found in this link; Alamofire : How to handle errors globally). I changed it a little bit(added a new parameter as headers and changed NetworkSuccessHandler's type to NSData ). Here is the new form:

public class AuthorizationManager: Manager {
    public typealias NetworkSuccessHandler = (NSData?) -> Void
    public typealias NetworkFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError) -> Void

    private typealias CachedTask = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void

    private var cachedTasks = Array<CachedTask>()
    private var isRefreshing = false

    public func startRequest(
        method method: Alamofire.Method,
        URLString: URLStringConvertible,
        parameters: [String: AnyObject]?,
        encoding: ParameterEncoding,
        headers: [String:String]?,
        success: NetworkSuccessHandler?,
        failure: NetworkFailureHandler?) -> Request?
        {
            let cachedTask: CachedTask = { [weak self] URLResponse, data, error in
                guard let strongSelf = self else { return }

                if let error = error {
                    failure?(URLResponse, data, error)
                } else {
                    strongSelf.startRequest(
                        method: method,
                        URLString: URLString,
                        parameters: parameters,
                        encoding: encoding,
                        headers:  headers,
                        success: success,
                        failure: failure
                    )
                }
            }

            if self.isRefreshing {
                self.cachedTasks.append(cachedTask)
                return nil
            }

            // Append your auth tokens here to your parameters

            let request = self.request(method, URLString, parameters: parameters, encoding: encoding, headers:  headers)

            request.response { [weak self] request, response, data, error in
                guard let strongSelf = self else { return }

                if let response = response where response.statusCode == 401 {
                    strongSelf.cachedTasks.append(cachedTask)
                    strongSelf.refreshTokens()
                    return
                }

                if let error = error {
                    failure?(response, data, error)
                } else {
                    success?(data)
                }
            }

            return request
    }

    func refreshTokens() {
        self.isRefreshing = true

            // Make the refresh call and run the following in the success closure to restart the cached tasks
        Token().refresh { () -> () in
            let cachedTaskCopy = self.cachedTasks
            self.cachedTasks.removeAll()
            cachedTaskCopy.map { $0(nil, nil, nil) }
            self.isRefreshing = false
        }
    }
}

Called it in my postRequest like:

func postRequest(requestType: postRequestType, additionalParameters: [String]?, onSuccess: onSuccessRequest = {_ in }, onFailure: onFailureRequest = {_ in }){
    print("post")
    let requestData = returnStaticDataForPostRequest(requestType, additionalParameters: additionalParameters)
    let Manager = AuthorizationManager()
    Manager.startRequest(method: .POST, URLString: requestData.0, parameters: requestData.2, encoding: requestData.3, headers: requestData.1, success: { (data) -> Void in
        print("Manager")
        let json = JSON(data: data!)
        print(json)
        dataParserForPostRequests(json, parseForWhat: requestType)
        onSuccess(json: json)
        }) { (response, message, error) -> Void in
            print(error)
    }

}

And use of postRequests in the ViewController:

postRequest(.LOGIN, additionalParameters: ["asdasd", "asdasd"], onSuccess: { (json) -> () in
            print(">>>login_try_succeeded")        
            self.performSegueWithIdentifier("LoginToMain", sender: self)
            }) { (errorCode) -> () in
            print(">>>login_try_failed(\(errorCode))")
        }

This is the current state. When I run the code and try to login AuthorizationManager doesn't work. It just prints;

post

And lastly, I don't know if it's relevant but there is yellow warning at this line:

cachedTaskCopy.map { $0(nil, nil, nil) }

says "Result of call to 'map' is unused"

To sum up I need to figure out how I can handle 401's and I know how to use AuthorizationManager in a different way from this.

EDIT:

I tried the run the this code directly from ViewController but it's not working at all. It's like code is invisible.

AuthorizationManager().startRequest(method: .POST, URLString: NSURL(string: "http://server.url/token")!, parameters: ["":""], encoding: .JSON,headers: ["":""], success: { (data) -> Void in
            print(data)
            }) { (response, data, error) -> Void in
                print(error)
                print("asdasd")
        }
like image 281
Faruk Avatar asked Mar 17 '16 13:03

Faruk


1 Answers

It may be the case that your AuthorizationManager is not persisting after its initial attempt to send the request.

Normally it's good practice to avoid the singleton pattern, but this isn't a bad case for it:

public class AuthorizationManager: Manager {
    static let shared = AuthorizationManager()
    // ...the rest of your class
}

And when calling your request, use this singleton instance rather than instantiating a new AuthorizationManager like

AuthorizationManager.shared.startRequest(method: .POST, ...etc...

I'm guessing this may be the issue, since when you create your AuthorizationManager in both cases there's nothing actively retaining that object. The manager may be created, run the request, and then be deallocated before the cachedTask or even before the completion handling, in which case your guard let strongSelf = self else { return } would simply return without running any of your completions or cachedTasks.

Hopefully that helps. If that is the problem, then that singleton solution should be very simple.

like image 193
tbogosia Avatar answered Oct 17 '22 21:10

tbogosia