Async Operations when using AFNetworking - afnetworking

I was trying to use AFNetworking Framework to test network connectivity to website "www.abc.org"
Code:
- (void)connect
{
NSLog(#"1");
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(#"2");
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
{
NSLog(#"3");
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.requestSerializer.timeoutInterval = self.timeoutAccumulator; // timeout
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:#"http://www.abc.org/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"4");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
break;
case AFNetworkReachabilityStatusNotReachable:
default:
break;
}
}];
[[AFNetworkReachabilityManager sharedManager] stopMonitoring];
NSLog(#"5");
}
The console output is:
1
5
2
3
4
It looks like the Operations are Async. How can I make the operations in the main queue, or the main thread. so the 2nd operation will wait for the 1st operation to complete. What I want is the Output in the order of:
1
2
3
4
5

One possible way is using block with completion.

Related

Wait for a request to succeed \ fail before firing up the next request (one request queue)

I have a few requests that needs to fire one by one while depending on the previous response.
That was pretty straight forward with NSOperation and trying to figure out
what's the best approach here with Sessions & AFNetworking >= 3.0
-(void)startGet
{
NSString *urlStr = [NSString stringWithFormat:#"https://test.com/test?%ld",(long)test];
NSURL *URL = [NSURL URLWithString:urlStr];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
for (int i=0; i<10; i++)
{
test = i;
[self startGet];
}
}
The log I want to get is:
https://test.com/test?0
https://test.com/test?1
https://test.com/test?2
https://test.com/test?3
https://test.com/test?4
https://test.com/test?5
https://test.com/test?6
...
Things I've tried:
...
dispatch_group_t serviceGroup = dispatch_group_create();
...
-(void)startGet
{
NSString *urlStr = [NSString stringWithFormat:#"https://test.com/test?%ld",(long)test];
NSURL *URL = [NSURL URLWithString:urlStr];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
dispatch_group_enter(serviceGroup);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
dispatch_group_leave(serviceGroup);
} failure:^(NSURLSessionTask *operation, NSError *error) {
dispatch_group_leave(serviceGroup);
//NSLog(#"Error: %#", error);
}];
dispatch_group_wait(serviceGroup,DISPATCH_TIME_FOREVER);
}
Although the request came out in the right order I would still get mixed responses like so:
https://test.com/test?4
https://test.com/test?7
https://test.com/test?1
https://test.com/test?3
I'm not sure if something is wrong with the code or I totally misunderstood the purpose of dispatch_group_t in that case.
I've digged around and saw a comment by matt from AFNetworking about integrating a simple solution using Operations & session in AF and it is soon to be public but it was over 2 years ago.
I'm trying to solve this without using nested requests or NSOperations
Thanks
You can try out 2 approaches
Semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
dispatch_semaphore_signal(semaphore);
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
WaitUntillFinished
[operation waitUntilFinished];
You can check for detailed implementation at below link:
https://github.com/AFNetworking/AFNetworking/issues/1804

svprogresshud not showing on main thread

On viewdidload of my tableviewcontroller i have the following
[SVProgressHUD showWithStatus:#"Loading"];
[self getData];
[SVProgressHUD dismiss];
On the getData method, i am using AFNetworking to get the data from my backend api. Since that is an asynchronous call, i would expect my SVProgressHUD to show.
-(void) getData {
AFHTTPRequestOperationManager * reqManager =AFHTTPRequestOperationManager manager];
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
//set auth token..etc
[reqManager GET:urlString parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code for success....
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//code for failure
}];
}
But it's not showing at all. So obviously i am working on my main thread. Where am i going wrong ?
The problem is not that it's not showing on the main thread, but rather that it's showing and being dismissed so quickly that you're not seeing it. This is because GET is an asynchronous method.
The solution is to adopt a completion handler pattern, namely a block of code that getData will call when the asynchronous method finishes:
- (void)getDataWithCompletionHandler:(void (^)(NSError *))completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
//set auth token..etc
[manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code for success....
if (completionHandler) {
completionHandler(nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//code for failure
if (completionHandler) {
completionHandler(error);
}
}];
}
And
[SVProgressHUD showWithStatus:#"Loading"];
[self getDataWithCompletionHandler:^(NSError *error) {
[SVProgressHUD dismiss];
}];
This pattern allows you to defer the calling of dismiss until the asynchronous process is done, but it also keeps the SVProgressHUD code together in a single area (rather than scatter it about and burying UI related code inside your networking methods).
The problem is you are showing hud immediately after method call like
[self getData];
[SVProgressHUD dismiss];
This is the problem. Move dismiss code to
-(void) getData {
AFHTTPRequestOperationManager * reqManager =AFHTTPRequestOperationManager manager];
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
//set auth token..etc
[reqManager GET:urlString parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//code for success....
[SVProgressHUD dismiss];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//code for failure
[SVProgressHUD dismiss];
}];
}
You can see the dismiss call in success and failure of webservice call, and call the getData method by
[SVProgressHUD showWithStatus:#"Loading"];
[self getData];
remove the [SVProgressHUD dismiss] from here.

Does it necessary to go back to main thread to update UI?

I was using AFNetworking to deal with the http request.And here is my code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:URL_LOGIN parameters:parames success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.tableview reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request failed");
}];
or:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:URL_LOGIN parameters:parames success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableview reloadData];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request failed");
}];
Which one is right,does it necessary to use dispathc_get_main_queue(),or the AFNetworking fix everything? Anybody knows?Thanks in advance.
On one hand, AppKit and UIKit is not thread safe, so you have to do any UI-related work on the main thread.
But as for AFNetworking, it automatically makes sure that the callbacks (success or failure) is executed on the main thread (unless you set otherwise). So normally you do not have to explicitly use dispatch_get_main_queue to dispatch your work to the main thread.
To check whether the callback is on the main queue:
[manager POST:someURL parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (operation.completionQueue == NULL) {
// is main queue
}
}...
Documentation
UI update always happens in main thread.So your tableview needs to be reloaded in main thread.
So the second one is true.

Objective-C: How to return response from POST method?

I have a POST method that looks like
+ (id) post {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
id response = nil;
[manager POST:#"https://mapp.com/oauth/register"
parameters:#{#"email" : #"e", #"memberExternalId" : #"m"}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
response = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// (todo) alert on failure
NSLog(#"Error: %#", error);
}];
return response;
}
I thought to abstract this method so that I could reuse it, so I created id response.
But when I assign
response = responseObject;
I get compilation error as
Variable is declared outside the block and is not assignable
Question
How can I preserve the response so that I can return it later.
You're calling an asynchronous method that uses a completion block pattern, so you should do the same. So
Change the return type to void;
Add block parameter to your method;
In the POST completion block, call your completionHandler if there was one supplied;
Thus:
+ (void) postWithCompletionHandler:(void (^)(id responseObject, NSError *error))completionHandler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
// these lines not needed
//
// [manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[manager POST:#"https://mapp.com/oauth/register"
parameters:#{#"email" : #"e", #"memberExternalId" : #"m"}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
}
Thus, you'd call it like so:
[MyClass postWithCompletionHandler:^(id responseObject, NSError *error) {
if (!responseObject) {
NSLog(#"failed: %#", error);
return;
}
// use the `responseObject` here
}];
// Note, because the above is asynchronous, don't try to use `responseObject` here.
// You can only use it inside the above `completionHandler` block.
As an aside, you probably don't want to instantiate a new AFHTTPRequestOperationManager for every request. So I'd personally (a) make the AFHTTPRequestOperationManager a property of this class; (b) move the instantiation of the operation manager into some initialization method for the class; and (c) make this "perform request" method an instance method, not a class method. It's not so critical here, but as you contemplate future functionality where you're issuing many requests, it's inefficient to be instantiating new request operation managers all over the place.

SVProgressHud waits too long to dismiss

I want to display SVProgressHUD during login process. But SVProgressHUD still waits after login. It's dismissed ~15seconds after "Login success" message.
Here is my code:
[SVProgressHUD show];
__block BOOL result;
dispatch_async(queue, ^{
result = [self autanticate];
NSLog(#"autantication result = %d", result);
result = [self getCSRFToken];
NSLog(#"Login success result = %d", result);
[SVProgressHUD dismiss];
});
autanticate and getCSRFToken functions are like below:
- (BOOL) getCSRFToken
{
__block BOOL success = false;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// Make sure that the callbacks are not called from the main queue, otherwise we would deadlock
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *parameters = #{#"username": self.username.text,
#"password": self.password.text};
NSLog(#"Creating CSRF semaphore");
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// CSRF Token bilgisini alalım
NSString *Url = [NSString stringWithFormat:#"%#%#", BaseURLString, #"?q=services/session/token"];
[manager GET:Url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"CSRF Token: %#", responseObject);
success = true;
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
success = false;
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"Waiting CSRF semaphore");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return success;
}
- (BOOL)autanticate
{
__block BOOL isAutanticated = false;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
NSDictionary *parameters = #{#"username": self.username.text,
#"password": self.password.text};
// Session bilgilerini alalım
NSLog(#"Creating autantication semaphore");
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSString *Url = [NSString stringWithFormat:#"%#%#", BaseURLString, #"?q=rest/mserv/signin"];
[manager POST:Url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
NSDictionary *result = [responseObject objectForKey:#"result"];
NSString *sessid = [result objectForKey:#"sessid"];
NSString *session_name = [result objectForKey:#"session_name"];
NSLog(#"sessid = %#\nsession_name=%#", sessid, session_name);
isAutanticated = true;
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
isAutanticated = false;
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"Waiting autanticatio semaphore");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return isAutanticated;
}
You must dispatch [SVProgressHUD dismiss] to the main thread
[SVProgressHUD show];
__block BOOL result;
dispatch_async(queue, ^{
result = [self autanticate];
NSLog(#"autantication result = %d", result);
result = [self getCSRFToken];
NSLog(#"Login success result = %d", result);
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
})
});
All user interface elements must only be modified on the main thread.
Remark: Note that the dispatch_async() returns
(almost) immediately, so that the result is not set
when dispatch_async() returns.
And don't be tempted to use a semaphore to wait for the block to finish.
That is fine on a background thread, but will most probably give a deadlock
on the main thread.

Resources