Currently I am queueing a simple offline request using AFHTTPRequestOperationManager and it doesn't seem to work in the desired manner:
Here is the responsible code and below are different execution patterns:
#interface ViewController ()
{
AFHTTPRequestOperationManager *manager;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
manager = [AFHTTPRequestOperationManager manager];
NSOperationQueue *operationQueue = manager.operationQueue;
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"Operation: %#", operationQueue.operations);
[operationQueue setSuspended:NO];
NSLog(#"ONLINE");
break;
case AFNetworkReachabilityStatusNotReachable:
default:
NSLog(#"Operation: %#", operationQueue.operations);
[operationQueue setSuspended:YES];
NSLog(#"OFFLINE");
break;
}
}];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"http://www.google.com"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id response){
NSLog(#"success");
}
failure:^(AFHTTPRequestOperation *operation, NSError *failure){
NSLog(#"failure");
}];
}
Pattern 1:
Device on AirPlane mode
Run
Console output:
2015-03-21 16:03:54.486 OfflineSupport[928:227748] Reachability: Not Reachable
2015-03-21 16:03:54.494 OfflineSupport[928:227748] Operation: (
"<AFHTTPRequestOperation: 0x1701d0c20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x170014ab0> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:03:54.494 OfflineSupport[928:227748] OFFLINE
2015-03-21 16:03:54.544 OfflineSupport[928:227748] failure
Wifi Activated
Console output contd.:
2015-03-21 16:04:05.594 OfflineSupport[928:227748] Reachability: Reachable via WiFi
2015-03-21 16:04:05.595 OfflineSupport[928:227748] Operation: (
)
2015-03-21 16:04:05.595 OfflineSupport[928:227748] ONLINE
Pattern 2:
Wifi Active
Run
Console output:
2015-03-21 16:05:43.818 OfflineSupport[934:228478] Reachability: Reachable via WiFi
2015-03-21 16:05:43.826 OfflineSupport[934:228478] Operation: (
"<AFHTTPRequestOperation: 0x1701dde20, state: isExecuting, cancelled: NO request: <NSMutableURLRequest: 0x17001ad10> { URL: http://www.google.com }, response: (null)>"
)
2015-03-21 16:05:43.826 OfflineSupport[934:228478] ONLINE
2015-03-21 16:05:43.960 OfflineSupport[934:228478] success
AirPlane activated
Console output contd.:
2015-03-21 16:05:53.437 OfflineSupport[934:228478] Reachability: Not Reachable
2015-03-21 16:05:53.438 OfflineSupport[934:228478] Operation: (
)
2015-03-21 16:05:53.438 OfflineSupport[934:228478] OFFLINE
In pattern 1, the request results in the failure block as there is no access. But when the device comes online, the request is not executed again. Is there something I am missing here? Do I have to configure something on the operation queue or in the failure block?
Reference: AFNetworking 2.0 queue request when device is offline with setReachabilityStatusChangeBlock does nothing, IOS - best way to queue requests to be sent when connection is reestablished
A few observations:
In pattern 1, you have a bit of a race condition because the reachability status block runs asynchronously, so if you start reachability and immediately add operation, the status may not have been identified as being offline yet, and thus the queue may not have been suspended and thus the operation may start immediately (and fail because you're offline).
The problem is solved if you suspend the queue before starting reachability and before starting any operations. If you're actually offline, the queue will stay offline and any operations that were added will be suspended, too. But if you were really online, the reachability block will be called reasonably quickly and and the queue will be promptly be unsuspended. It eliminates this race condition.
The suspended state of a queue does not affect operations that have already started. In only impacts those operations that have not yet started. So, if the connection goes offline while a network operation was in progress, there is no built in mechanism to pause the operation until the connection is restored, nor restart the operation when the status changes. If you want that functionality, you'd have to implement that yourself.
A few more observations:
It's worth noting, though, that just because reachability says that connectivity is available, it doesn't guarantee that the request will succeed. You still need to gracefully handle failed requests.
To the prior point, if you want a more reliable "can I connect to a particular server", you might consider using managerForDomain rather than sharedManager. Just make sure to keep a strong reference to the resulting AFNetworkReachabilityManager, because unlike the singleton, it won't keep a strong reference to itself.
The AFHTTPRequestOperationManager is from version 2.x, and you might consider upgrading to the latest version (so that you use AFHTTPSessionManager, a NSURLSession based implementation). The NSURLConnection used in 2.x has been deprecated.
The AFHTTPSessionManager is, unfortunately, not NSOperation-based. But if you want to enjoy the "send the requests only when the connection is established" functionality, you can either wrap them in asynchronous NSOperation subclass yourself (see AFNetworking 3.0 AFHTTPSessionManager using NSOperation) you can use a background session (see AFNetworking 2.0 and background transfers, while written for AFNetworking 2.x, outlines the essentials of using AFHTTPSessionManager and background session which still largely applies for version 3).
Related
I'm using CKModifyRecordsOperation to save a set of records and if I have internet connection all works well and completion block is being called. But when I don't have connection the completion block is not being called and I don't get any information that my operations failed.
I'm using the following code in completion block
modifyOperations.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *error)
{
if(error){
NSLog(#"Error: %#", error.localizedDescription);
}
item.creatorRecordId = record.recordID;
};
and then I'm performing operation using
[self.publicDB addOperation:modifyOperations];
Any ideas how can I get an information if the operation failed for example in the case where there is no internet connection?
CloudKit operations have their qualityOfService property set to NSQualityOfServiceUtility by default.
Operations that use NSQualityOfServiceUtility or NSQualityOfServiceBackground may be marked as using discretionary network requests. The system can hold discretionary network requests if network connectivity is poor, so you might not get a response from the server until conditions improve and the system sends the request.
If you'd like your request to be sent immediately in all cases, set CKOperation.qualityOfService to NSQualityOfServiceUserInitiated or NSQualityOfServiceUserInteractive.
This question already has an answer here:
Determine whether iPhone is really connected to the internet or just behind a restricted hotspot
(1 answer)
Closed 7 years ago.
There are loads of questions on here about finding an active internet connection in an app, but none work if you are on a 3g connection and you have no data credit, or if you are on a WiFi network at a hotel that automatically redirects to a log in page and you have yet to enter the password. That kind of situation.
What is the fastest way to check if the internet connection is actually operational?
You can use AFNetworkReachabilityManager class in AFNetworking.
https://github.com/AFNetworking/AFNetworking
above link will help you out in setting things. This will provide you with continuous network check as per your requirement. Here is few line of code:
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
The best way to accomplish this, not using Reachability or any other API, is by creating a NSURL and to try to receive data from it. For the bad connection I added a timeout of 20sec.
Like:
- (void)checkConnection //Run this on a side queue, never on a main queue
{ NSURL *checkURL = [NSURL URLWithString:#"http://www.apple.com"];
NSURLRequest *lRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:checkURL]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0];
NSData *data = [NSData dataWithContentsOfURL:lRequest];
if (data)
{
connected = YES;
}
else
{
connected = NO;
}
}
That would check if a specific website is reachable and set a timeout which indicates a slow network connection.
This code could have errors, as I haven't used/tried it yet! Comment this answer if there are any.
AFNetworkReachabilityManager *mgr=[AFNetworkReachabilityManager sharedManager];
[mgr startMonitoring];
[mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
//NSLog(#"Reachability: %#", AFStringFromNetworkReachabilityStatus(status));
if ([AFNetworkReachabilityManager sharedManager].reachable) {
NSLog(#" ONLINE");
}
else
{
NSLog(#"OFFLINE");
}
}];
This is how I tested reachability through AFNetworking now! How to check reachability in specific domain? And how does AFNetworking uses to test the reachability ?
[AFNetworkReachabilityManager managerForDomain:#"www.google.com"]; didn't work
Couple of things.
First, you really should configure the manager before starting monitoring. In this case this means you should call setReachabilityStatusChangeBlock before calling startMonitoring.
Second, when you're creating new AFNetworkReachabilityManager by using managerForDomain:, you are responsible for managing lifetime of the object. If you use code above with ARC enabled, mgr will be deallocated as soon as it goes out of scope meaning that there will be no manager to monitor reachability. One solution is to make mgr an instance variable of a class, e.g. an application delegate.
/**
Creates and returns a network reachability manager for the specified domain.
#param domain The domain used to evaluate network reachability.
#return An initialized network reachability manager, actively monitoring the specified domain.
*/
+ (instancetype)managerForDomain:(NSString *)domain;
this is from the afnetworking source code,
to monitor a specified domain, just create a reachability manager for that domain using this class method. like this
AFNetworkReachabilityManager *mgr= [AFNetworkReachabilityManager managerForDomain:#"www.google.com"];
We have an "iPhone" environment in our Worklight project that we use for our native iOS client development in XCode 5. We are getting an authentication error, despite having App Authentication disabled in the Worklight console.
The steps we're following are:
Call wlConnectWithDelegate: method on WLClient sharedInstance
On success from wlConnectWithDelegate, we invoke our adapter procedure with invokeProcedure:withDelegate:
The code looks like this:
#import <BlocksKit/A2DynamicDelegate.h>
+ (void) connectToWorklightOnSuccess: (SuccessBlock) success onFailure: (FailureBlock) failure {
WLClient *client = [WLClient sharedInstance];
A2DynamicDelegate *dd = [client dynamicDelegateForProtocol: #protocol(WLDelegate) ];
[dd implementMethod: #selector(onSuccess:) withBlock: success];
[dd implementMethod: #selector(onFailure:) withBlock: failure];
[client wlConnectWithDelegate:(id <WLDelegate>)dd];
}
+ (void) invokeProcedure: (WLProcedureInvocationData *) proc onSuccess: (SuccessBlock) success onFailure: (FailureBlock) failure {
[WJServiceGateway connectToWorklightOnSuccess:^(WLResponse *response) {
NSLog(#"WLClient connect succeeded");
WLClient *client = [WLClient sharedInstance];
A2DynamicDelegate *dd = [client dynamicDelegateForProtocol: #protocol(WLDelegate) ];
[dd implementMethod: #selector(onSuccess:) withBlock: success];
[dd implementMethod: #selector(onFailure:) withBlock: failure];
[client invokeProcedure:proc withDelegate:(id<WLDelegate>) dd];
} onFailure:^(WLFailResponse *failResponse) {
NSLog(#"WLClient connect failed: %#", failResponse);
[failure invoke];
}];
}
The first time we our invokeProcedure:onSuccess:onFailure: method, we get an error response:
Status: 403
InvocationResult: (null)
InvocationContext: (null)
Response text: /*-secure-
{"WL-Authentication-Failure":{"wl_antiXSRFRealm":{"reason":"illegal state"}}}*/
Error code: 0
Error message: (null)
Subsequent calls succeed without error until the App is killed and restarted.
This issue may be related to these issues (in fact, this is a problem happening in the same project):
IBM Worklight - How to enable App Authenticity in a native iOS app?
https://stackoverflow.com/questions/18879189/wl-5-0-6-1-js-vs-ios-native-handlechallenge-queued-in-waitlist-and-only-cal
What seems peculiar though, is that we have App Authentication disabled (with the drop-down option in the Worklight console) and are still seeing an authentication error. Is there some other source for this WL-Authentication-Failure/"illegal state" response?
Are we implementing something with our connect and invoke incorrectly here? Is there a solution such that our first adapter invoke after the app starts up doesn't fail?
As mentioned in the comments, this is not related to Application Authenticity Protection.
You need to first invoke a connect to the Worklight Server, and in its success callback to invoke the adapter procedure
You could also only connect to the Worklight Server on application launch, and later on when required to perform the adapter procedure invocation.
For a working example of connect and invoke in a native iOS app, see the following tutorial and sample: https://developer.ibm.com/mobilefirstplatform/documentation/getting-started-6-3/server-side-development/invoking-adapter-procedures-native-ios-applications/
I'm using AFNetworking in my app to connect / download data from a web service. This app is deployed via Enterprise deployment to users at various locations. At one of the locations where people use our app, the wifi network seems to randomly go down / come back up in a few seconds. In these cases, the requirement was to retry a request thrice before giving up and failing. I've got the retry part of it working fine, but have some problems detecting a network failure. Some code:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation, id responseObject) {
[self parseResponse:operation.responseString];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self messageFailureWithCode:error.code
reason:[NSString stringWithFormat:#"%#", error]];
}
];
The error code I'm retrying on is NSURLErrorTimedOut. But I just got another log file which indicated a failure with the following error: -1003 "A server with the specified hostname could not be found."
I could add this code to my list too, but I wanted to be sure that I'm capturing all the errors and not just handling them as they appear. I was looking through the NSURLError.h file and found the following error codes that vaguely look like they could be caused by a network failure.
Can someone help me figuring out under what conditions each error is triggered and if I'm missing any error codes? The list is below:
NSURLErrorCannotFindHost
NSURLErrorTimedOut
NSURLErrorCannotConnectToHost
NSURLErrorNetworkConnectionLost
NSURLErrorDNSLookupFailed
NSURLErrorResourceUnavailable
NSURLErrorNotConnectedToInternet
Thanks,
Teja.
It sounds like you could safely just retry every failure with an error domain of NSURLErrorDomain (exempting the case where an operation was cancelled). That should cover all of those cases.