Afnetworking: Handle Error Globally and Repeat Request

AFNetworking: Handle error globally and repeat request

In the AFHTTPClient's init method register for the AFNetworkingOperationDidFinishNotification which will be posted after a request finishes.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(HTTPOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];

In the notification handler check the status code and copy the AFHTTPRequestOperation or create a new one.

- (void)HTTPOperationDidFinish:(NSNotification *)notification {
AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object];

if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
return;
}

if ([operation.response statusCode] == 401) {
// enqueue a new request operation here
}
}

EDIT:

In general you should not need to do that and just handle the authentication with this AFNetworking method:

- (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;

Alamofire: Handle error globally

At the moment you will have to handle that logic in your own custom response implementation. We (the Alamofire TC) are currently working on ways that we can make this process easier, but it's very difficult to get right without complicating the rest of the APIs. We're still a ways off yet.

With that said, I built an OAuth 2.0 system that handles this process in a different non-open-source library. It is possible, it's just difficult to do. You will need to hook into the response closure for all requests that could 401. See my answer here for a full breakdown of how to do this.

Hopefully that helps shed some light. Cheers /p>

Alamofire : How to handle errors globally

Handling refresh for 401 responses in an oauth flow is quite complicated given the parallel nature of NSURLSessions. I have spent quite some time building an internal solution that has worked extremely well for us. The following is a very high level extraction of the general idea of how it was implemented.

import Foundation
import Alamofire

public class AuthorizationManager: Manager {
public typealias NetworkSuccessHandler = (AnyObject?) -> 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,
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,
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)

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

let cachedTaskCopy = self.cachedTasks
self.cachedTasks.removeAll()
cachedTaskCopy.map { $0(nil, nil, nil) }

self.isRefreshing = false
}
}

The most important thing here to remember is that you don't want to run a refresh call for every 401 that comes back. A large number of requests can be racing at the same time. Therefore, you want to act on the first 401, and queue all the additional requests until the 401 has succeeded. The solution I outlined above does exactly that. Any data task that is started through the startRequest method will automatically get refreshed if it hits a 401.

Some other important things to note here that are not accounted for in this very simplified example are:

  • Thread-safety
  • Guaranteed success or failure closure calls
  • Storing and fetching the oauth tokens
  • Parsing the response
  • Casting the parsed response to the appropriate type (generics)

Hopefully this helps shed some light.


Update

We have now released Alamofire 4.0 which adds the RequestAdapter and RequestRetrier protocols allowing you to easily build your own authentication system regardless of the authorization implementation details! For more information, please refer to our README which has a complete example of how you could implement on OAuth2 system into your app.

Full Disclosure: The example in the README is only meant to be used as an example. Please please please do NOT just go and copy-paste the code into a production application.

Detect Time Out Error With AFNetworking

Since AFNetworking is build on top on NSULConnection/NSULRSession you can just NSURLErrorTimedOut to check if the error is a timeout error:

 failure:^(AFHTTPRequestOperation *operation, NSError *error) {

if (error.code == NSURLErrorTimedOut) {
//time out error here
}
}];

You where checking the HTTP status code, but since the connection timed out there is not statuscode.

AFNetworking - no response on all but the final request in a for loop

Your code looks fine, try using this code for downloading images, i'm able to use in loops successfully. Thats what i can help you for now.

NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];

AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];

requestOperation.responseSerializer = [AFImageResponseSerializer serializer];

[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

[imageView setImage: responseObject];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Image error: %@", error);
}];
[requestOperation start];

How to intercept the response of AFNetworking request in Objective-C?

Take a look at the below. In the failure block, retry/resend your query X number of times. Be sure to add logic to end retries at some point, so you don't end up with an infinite loop.

AFHTTPRequestOperationManager *operationManager = [AFHTTPRequestOperationManager manager];

[operationManager POST:url
parameters:object
success:^(AFHTTPRequestOperation *operation, id responseObject) {
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.response.statusCode == 403) {
// retry
}
}
];

This answer may also be helpful.

AFNetworking checking the network available

You can check the reachability by this:

AFNetworkReachabilityManager *reachability = [AFNetworkReachabilityManager sharedManager];
[reachability setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"WWN");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WiFi");
break;
case AFNetworkReachabilityStatusUnknown:
NSLog(@"Unknown");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"Not Reachable");
break;
default:
break;
}
}];

or you can use:

+(BOOL)IsInternet
{
Reachability *networkReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];
if (networkStatus == NotReachable)
{
return NO;
}
else
{
return YES;
}
}


Related Topics



Leave a reply



Submit