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")
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With