How to get response right after execution of block in iOS? - ios

I am using AFNetworking framework to call the web service and i have used
POST method to get response from the web service like that :
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setTimeoutInterval:25];
[manager POST:stringURL parameters:param
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"RESPONCE : %#", responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#", error);
NSString *msg = error.localizedDescription;
UIAlertView *alertview = [[UIAlertView alloc]initWithTitle:#"That Shift" message:msg delegate:nil cancelButtonTitle:#"Dissmiss" otherButtonTitles:nil, nil];
[alertview show];
}];
NSLog(#"Right AFter Block Execution RESPONCE : %#", responseObject);
i want my response right after its execution in NSLog(#"Right AFter Block Execution RESPONCE : %#", responseObject); can any one suggest me how can i do that ?
REASON BEHINDE DOING THAT :
The reason behind doing that is i want to use another block right after that code which i have given and i want to use that responce in second block which will be right after that in the plce of NSLog(#"Right AFter Block Execution RESPONCE : %#", responseObject);.

You can use semaphores to synchronize your threads, so the first thread will block and wait for second. But do not do it on main thread since your GUI will freeze.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Inside callback:
dispatch_semaphore_signal(sema);
// Before NSLOG
dispatch_semaphore_wait(sema, <timeout_may_be_forever>);

Related

Calling two NSURLSessionTask completion blocks one after another?

I have the following setup that uses AFNetworking to make calls to my server. I have used an example I found on the internet to include a completion block so I know when the call has finished.
File "FCEngine.m"
- (void)fetchBusinessProfile:(NSString *)userID userAccessToken:(NSString *)userAccessToken completion:(void (^)(NSDictionary *json, BOOL success))completion {
/// Validate the user token again the user id.
NSDictionary *parameters = [[NSDictionary alloc]initWithObjectsAndKeys:userAccessToken,#"user_access_token",
userID,#"user_id",
nil];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
manager.requestSerializer = serializer;
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:#"" parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON Business Profile: %#", responseObject);
fetchBusinessProfileCompletion(responseObject, YES);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
NSMutableDictionary *errorResponse = [[NSMutableDictionary alloc] init];
[errorResponse setObject:#"connection_error" forKey:#"state"];
[errorResponse setObject:[error localizedDescription] forKey:#"description"];
fetchBusinessProfileCompletion(errorResponse, YES);
}];
}
- (void)fetchNotifications:(NSString *)userID userAccessToken:(NSString *)userAccessToken completion:(void (^)(NSDictionary *json, BOOL success))completion {
/// Validate the user token again the user id.
NSDictionary *parameters = [[NSDictionary alloc]initWithObjectsAndKeys:userAccessToken,#"user_access_token",
userID,#"user_id",
nil];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
manager.requestSerializer = serializer;
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
[manager POST:#"" parameters:parameters progress:nil success:^(NSURLSessionTask *task, id responseObject) {
//NSLog(#"JSON: %#", responseObject);
completion(responseObject, YES);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
NSMutableDictionary *errorResponse = [[NSMutableDictionary alloc] init];
[errorResponse setObject:#"connection_error" forKey:#"state"];
[errorResponse setObject:[error localizedDescription] forKey:#"description"];
completion(errorResponse, YES);
}];
}
The following is how I make the call on Main View Controller
- (void)MyMethods {
[self.fcEngine fetchBusinessProfile:userID userAccessToken:userAccessToken completion:^(NSDictionary *json, BOOL success) {
/// Response here
}];
[self.fcEngine fetchNotifications:self.userID userAccessToken:self.userAccessToken completion:^(NSDictionary *json, BOOL success) {
//// Response here
}];
}
Now the problem is that the 2 calls are made one after another and when I fetch the data for one e.g. "fetchBusinessProfile" the competition block on both is called.
Have I set this up wrong? If 2 or more calls I only want the completion to be called for that particular block and not them all.
I don't think you understand asynchronous as well as completion blocks. If you make the 2 network calls as defined above, they can happen in any order. The completion in fetchBusinessProfile and fetchNotifications will be different completion blocks ... unless you make them the same.
For example:
[self.fcEngine fetchBusinessProfile:userID userAccessToken:userAccessToken completion:^(NSDictionary *json, BOOL success) {
/// Handle response
// Note calling the SAME completion block
sameCompletionBlockAlreadyDefined();
}];
[self.fcEngine fetchNotifications:self.userID userAccessToken:self.userAccessToken completion:^(NSDictionary *json, BOOL success) {
//// Handle response
// Note calling the SAME completion block
sameCompletionBlockAlreadyDefined();
}];
In this case, sameCompletionBlockAlreadyDefined() is some already defined block. In this case, the body of the block of each call is indeed, but funnel to the same call via sameCompletionBlockAlreadyDefined. It is possible you are confused because completion happens to be named the same in your first snippet.
Note your question is really poorly phrased so it isn't fully clear on what you mean.
The larger question is what is your goal? Do you want only one completion block to be called at the end? Or you want fully distinct completion blocks? Both require different techniques. Be clear on what your goal is.
The former would be best service with a dispatch_group. The latter requires different completion blocks.
An example of dispatch group would be something like:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self.fcEngine fetchBusinessProfile:userID userAccessToken:userAccessToken completion:^(NSDictionary *json, BOOL success) {
/// Handle response
dispatch_group_leave(group);
];
self.fcEngine fetchNotifications:self.userID userAccessToken:self.userAccessToken completion:^(NSDictionary *json, BOOL success) {
//// Handle response
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// This would be some completion block which means all is done
completion();

Returning from a block - Objective C

I have a class method containing a block, of AFNetworking in which i want to return one dictionary variable, code shown below:
+(NSMutableDictionary*) getFromUrl:(NSString*)url parametersPassed:(NSDictionary*)parameters;
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
__block NSMutableDictionary *resultarray;
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSMutableDictionary *resultarrayTwo = (NSMutableDictionary *)responseObject;
resultarray = resultarrayTwo;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#, %#", error, operation.responseString);
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Message" message:#"Try again" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
}];
return resultArray;
}
How can i return resultArray here, it returns nothing here due to the difference in control flow.
I don't have much knowledge in Objective C block. Waiting for your help.
Thank you.
Change your function design to the following function using Completion Blocks
+(void)getFromUrl:(NSString*)url parametersPassed:(NSDictionary*)parameters completion:(void (^) (NSMutableArray *values))completion;
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSMutableDictionary *resultarray = (NSMutableDictionary *)responseObject;
completion(resultarray);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#, %#", error, operation.responseString);
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Message" message:#"Try again" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
completion(nil);
}];
}
And call the function
[YourClass getFromUrl:url parametersPassed:params completion:^(NSMutableArray *values) {
NSLog(#"%#",values);
}];
Update
Removed the extra array used.
Hope this helps.
The network call is asynchronous in nature, so having this method return its result isn't probably the right way of thinking as it implies you do a synchronous network call.
Have the method take a block parameter instead, and execute that block with the result at a later point of time from the AFNetworking completion block.
Before getting the response for GET call, it will execute the next lines. Thats why you are getting no data into Array.
You can call delegate method in success and failure block. This is the best solution.

Value of NSMutableDictionary is not changing inside the block

I am passing the URL in this method and getting the data as output. i want to assign a new value to nsmutabledictionary but it is not assigning the value.
-(NSDictionary*) getDatafromURL: (NSString*)url{
__block NSMutableDictionary *returnData=[[NSMutableDictionary alloc] init];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
NSLog(#"Data 2: %#",returnData);// it is not printing any data
return returnData;
}
in this above example the Data 1 is showing value successfully
Data 2 gives me empty dictionary.why it is not assigning the new value?
That happens because you get to the line with "Data 2" first and the block is executed only afterwards, since it is an async request. I would suggest that you change your method to something like:
- (void)getDataFromURL:(NSString *)url completionHandler:(void (^)(NSMutableDictionary *returnData, NSError *error))handler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
handler(returnData, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
handler(nil, error);
}];
}
There might be some compile errors in the code I provided.
The other solution would be to do a synchronous request, in which case the block would be executed before the code that is after the block.
EDIT:
If you are choosing the first solution, you have to continue using it asynchronously. So you would call it like:
[self getDataFromURL:#"abc.com" completionHandler:^ (NSMutableDictionary *returnData, NSError *error) {
// process your dictionary and the error object
}];
Please check whether your Data 2 is printing before data 1? If yes, its because, the response object gets downloaded only after a certain delay. Take away the return statements. Pass the data to the dictionary to which you return the method. For eg: like
instead of
self.myDictionary = [self getDatafromURL:someURl];
to
-(void) getDatafromURL: (NSString*)url{
__block NSMutableDictionary *returnData=[[NSMutableDictionary alloc] init];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
self.myDictionary = returnData;
// Continue whatever you want to do
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Or use the dispatch methods instead of the blocks.
like
Or use manager waitUntilFinish method below.

Moving to the next View Controller after Authenticating

I am using AFnetworking for authentication, but after Authenticating user I need to move to the next View controller. It's moving, but also it moves when there's an error too. How can I make use of the responseObject in AFNetworking to my need...... Below is my CODE
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//[operation setCredential:credential];
[operation setResponseSerializer:[AFJSONResponseSerializer alloc]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//This is where if the response is successful it should move
if ([responseObject objectForKey:#"Success"]) {
MainView *home = [self.storyboard instantiateViewControllerWithIdentifier:#"MainViewController"];
[self.navigationController pushViewController:home animated:YES];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", error);
}];
[manager.operationQueue addOperation:operation];
To check if success or not, you are using :
if ([responseObject objectForKey:#"Success"])
I don't know your service and the retrieve Diccionary, however I think you always have an object for the key #"Success", and because #"Success" exists is passing in all the scenarios.
Try to check the value, e.g.:
if ([[responseObject objectForKey:#"Success"] isEqualsToString #"ok"])

How to using AFNetworking 2.0 library synchronously?

The following code using AFNetworking 2.0 is valid to fetch data through internet:
NSString *URLPath = #"http://www.raywenderlich.com/downloads/weather_sample/weather.php?format=json";
NSDictionary *parameters = nil;
[[AFHTTPRequestOperationManager manager] GET:URLPath parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
}];
But I want to test those requests synchronously in the unit test. But it would be blocked when using GCD semaphore like this:
// This code would be blocked.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSString *URLPath = #"http://www.raywenderlich.com/downloads/weather_sample/weather.php?format=json";
NSDictionary *parameters = nil;
[[AFHTTPRequestOperationManager manager] GET:URLPath parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
dispatch_semaphore_signal(sema);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release_ARC_compatible(sema);
How can I fetch data using AFNetworking 2.0 library synchronously (test those code in Kiwi)?
Your semaphore would be blocked because by default AFNetworking runs on the main loop. So if you're waiting on the main loop for the semaphore, AFNetworking's code never gets to run.
In order to fix this, you simply have to tell AFNetworking to use a different dispatch queue. You do that by setting the operationQueue property on AFHTTPRequestOperationManager
You can create your own dispatch queue, or use one of the predefined ones, like so:
// Make sure that the callbacks are not called from the main queue, otherwise we would deadlock
manager.operationQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Resources