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.
Related
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();
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>);
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.
I am using RestKit and I have following method. I have multiple requests in it and now I am thinking what is best approach (pattern maybe) to do something (for example hide loading alert) after all requests are done. I can set some global boolean values and in every request after it's done change it's own boolean value and check others if are done and then do something. But I am looking for some better solution. Are there some better way?
- (void)loadTypes
{
RKObjectManager *restManager = [RKObjectManager sharedManager];
[restManager getObjectsAtPath:#"remarkGetCategories"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
NSArray* statuses = [mappingResult array];
GetTypesResponse *response = [statuses firstObject];
categoryArray = response.Data;
[_tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
[restManager getObjectsAtPath:#"remarkGetTypes"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
NSArray* statuses = [mappingResult array];
GetTypesResponse *response = [statuses firstObject];
typeArray = response.Data;
[_tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
[restManager getObjectsAtPath:#"remarkGetSubTypes"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
NSArray* statuses = [mappingResult array];
GetTypesResponse *response = [statuses firstObject];
subtypeArray = response.Data;
[_tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
[restManager getObjectsAtPath:#"transactionAccounts"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
NSArray* statuses = [mappingResult array];
GetTypesResponse *response = [statuses firstObject];
NSMutableArray *mutableArray = [[NSMutableArray alloc]init];
for (id object in response.Data) {
BankAccount *bankAccount = [[BankAccount alloc] init];
[bankAccount setValuesForKeysWithDictionary:object];
[mutableArray addObject:bankAccount];
}
accountArray = [NSArray arrayWithArray:mutableArray];
[_tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
Edit:
Using ReactiveCocoa (RAC) for first method (remarkGetCategories). This is my helper class for RAC:
#implementation ReactiveCocoaHelper
+ (RACSignal *)signalGetCategories {
return [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) {
RKObjectManager *restManager = [RKObjectManager sharedManager];
[restManager getObjectsAtPath:#"remarkGetCategories"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
[subscriber sendNext:mappingResult];
[subscriber sendCompleted];
}
failure:^(RKObjectRequestOperation *operation, NSError *error)
{
[subscriber sendError:error];
}];
return nil; // `nil` means there's no way to cancel.
}];
}
#end
This is my code for loading data:
RACSignal *signalCategories = [ReactiveCocoaHelper signalGetCategories];
[[RACSignal
merge:#[ signalCategories ]]
subscribeCompleted:^{
NSLog(#"They're both done!");
[_HUD hide:YES];
}];
It's okay and I guess it would be working the way I want when I implement for all methods but for now I am not sure where and how to map result from request to my categoryArray.
Check out ReactiveCocoa, it has an example especially for cases like this, it's the 6th code chunk in the Introduction section. It may seem complicated first, but it worth the effort of taking the time, it can seriously improve productivity, simplicity and stability.
From a RestKit point of view, the restManager has an operationQueue parameter. So, in the success block you can check the operationCount to determine if all of the download and mapping operations are complete.
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"])