11

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")
        }
Community
  • 1
  • 1
Faruk
  • 2,269
  • 31
  • 42

1 Answers1

2

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.

tbogosia
  • 315
  • 1
  • 10
  • That was not the my question but your answer will actually work for last part which explained as `EDIT` (or I do not remember what was my question actually is:)). Anyways, since a lot of time passed since I asked this, I had started to use Moya (https://github.com/Moya/Moya) which provides really nice features to develop clean usage of Alamofire. It is discussed how to handle the 401, and the other status codes, in this link; https://github.com/Moya/Moya/issues/379 . However it seems to be that your answer is getting upvotes, I presume that it is helpful to people. So I will accept this. – Faruk Jun 27 '17 at 13:30
  • Well, hope it helps someone! Also, Moya is great, glad you were able to figure out a solution. – tbogosia Jun 27 '17 at 13:59
  • Yeah! Thanks for your interest to help :) – Faruk Jun 27 '17 at 14:37