I'm trying to create a function that will return a value based on the response from an AFNetworking POST request.
However, I can't figure out a way to do this, as the function is asynchronous, so the value is returned before the response is actually received.
int didLogin;
__block NSString *response;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
NSDictionary *parameters = #{#"username":username, #"password":password};
[manager POST:loginUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
//json "response" object from server response
response = [responseObject objectForKey:#"response"];
NSLog(#"Response: %#", response);
[HUD hide:YES];
[HUD removeFromSuperViewOnHide];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[HUD hide:YES];
[HUD removeFromSuperViewOnHide];
NSLog(#"Login Error: %#", error);
}];
if ([response compare:#"1"] == NSOrderedSame || [response compare:#"2"] == NSOrderedSame)
{
//successful login/1-new device/2-existing device
didLogin = 1;
}
else if ([response compare:#"0"] == NSOrderedSame)
{
//unsuccessful login/invalid password
didLogin = 0;
}
else{
//unsuccessful login
didLogin = 2;
}
At which point it would return didLogin.
Is there any way to make this work, or do I need to use a synchronous request?
Your method should take a callback block as a parameter and then you should call that block with didLogin when the asynchronous process is complete. You need to embrace the asynchronous nature of what you're trying to do throughout your code.
Related
I am sending array as a parameter in AFNetworking GET Request.
My code is as follows:
- (void)getProductSearchResult:(NSString *)locale andSearchDict:(NSDictionary *)dictSearch{
NSString *strURL = [NSString stringWithFormat:#"%#/%#/search?%#",BASEURL,locale,APIFORMAT];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager GET:strURL parameters:dictSearch success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *jsonDict = (NSDictionary *)responseObject;
if ([jsonDict isKindOfClass:[NSDictionary class]] || [jsonDict isKindOfClass:[NSMutableDictionary class]]) {
if (self.delegate && [self.delegate respondsToSelector:#selector(API_ProductSearch_didSuccess:)]) {
[self.delegate API_ProductSearch_didSuccess:jsonDict];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (self.delegate && [self.delegate respondsToSelector:#selector(api_ProductSearch_didFailWithError:)]) {
[self.delegate api_ProductSearch_didFailWithError:[NSError description]];
}
}];
}
The dictionary which I pass as parameter is as follows:
{
"brand_filter" = (
1
);
"category_filter" = (
438
);
"max_price" = "47.37188";
"min_price" = "1.95";
"price_currency" = USD;
"supplier_filter" = (
"Aakron Line"
);
}
URL which created is shown like
http://demo.aakronline.ca/app_dev.php/api/v1/en_us/search?_format=json&brand_filter[]=1&category_filter[]=438&max_price=48.04479&min_price=2.622917&price_currency=USD&supplier_filter[]=Aakron%20Line
The problem area in URL is array is not passed in proper format i.e.
brand_filter[]=1&category_filter[]=438 instead of brand_filter=[1]&category_filter=[438]
Can anyone tell me how to solve this mistake?
But I am not getting the successful response.
In afnetworking 3.0, instead of AFHTTPRequestOperationManager use AFHTTPSessionManager
NSString *strUrl = [NSString stringWithFormat:#"%#/%#/search?%#",BASEURL,locale,APIFORMAT];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:strUrl parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"value: %#",responseObject);
//other code as it is
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"Error: %#",error);
}];
I have this class that performs a REST call .
- (NSString*)cerca:(NSString*)email{
if ([[Utility sharedUtility] connected]) {
HttpClient *self = [HttpClient sharedHTTPClient];
[self.requestSerializer setValue:[self getUserAgent] forHTTPHeaderField:#"User-Agent"];
[self.requestSerializer setValue:NST_CODE forHTTPHeaderField:#"Nst-Code"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"nst_id"] forHTTPHeaderField:#"Nst-Id"];
[self.requestSerializer setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"api_key"] forHTTPHeaderField:#"Api-Key"];
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
parameters[#"email"] = email;
__block NSString *result;
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
result = responseObject;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
return result;
}
The call returns a JSON . My problem is that when I do return result ; Nothing is passed to the calling method .
Can you tell me why ??
The calling method is
HttpClient *client = [HttpClient alloc];
NSString *result = [client cerca:email];
That method you are calling is an asynchronous call, meaning the result comes after you have returned. You need to change your method to accept a block argument and return the result in the callback.
- (void)cerca:(NSString*)email callback:(void (^)(id result)) callback {
if ([[Utility sharedUtility] connected]) {
...
[self POST:#"get_info" parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
if (callback) {
callback(responseObject)
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
}
}
To call the method you would do:
[client cerca:email completion:^(NSString *response) {
// Do what you want with the response.
}];
Its because the self POST call is asynchronous and you are returning the result before it has had time to be assigned. You need to rebuild your - (NSString*)cerca:(NSString*)email method so it somehow can handle the asynchronousness of this. Easiest way is usually to change the method so it takes a completion block as in parameter. Maybe something like:
- (void)cerca:(NSString *)email completion:(void (^)(NSString *res))completion {
[self POST:#"get_info" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
if (completion) {
completion(responseObject);
}
}failure:^(NSURLSessionDataTask *task, NSError *error) {
if (completion){
completion(nil);
}
}];
}
I changed my method
- (NSString*)cerca:(NSString*)email completion:(void (^)(NSString *res))completion {
But I do not understand what to call it . Indications ?
[client cerca:email completion:nil];
I got a async problem with my code. I got all of my webrequests in 1 class. One of my requests needs to return an NSMutableArray that another class needs to use. My webRequest code is here:
- (NSMutableArray*) getTournamentsInClub:(NSString *)clubGUID withDelegateViewController:(UIViewController *)viewController {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableArray *responseArray = [[[NSMutableArray alloc] init] autorelease];
NSString *URL = [[NSString alloc]initWithFormat:#"SomeURL=%#",clubGUID];
[manager POST:URL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
for (id obj in responseObject){
//NSLog(#"obj: %#",[obj valueForKey:#"CustomerName"]);
[responseArray addObject:obj];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
return responseArray;
}
I call the method from a viewController like this:
[self handleClubTournaments:[[TournamentsWebService sharedToursWS] getTournamentsInClub:
//Show load screen. (hide in handler function)
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
[xdelegate showLoadingScreen:self.clubToursTableView andStatus:NSLocalizedString(#"loadTours", #"")];
And my handleClubTournaments function looks like this:
-(void) handleClubTournaments:(id)result {
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
if([result isKindOfClass: [NSError class]]) {
// If an error has occurred, handle it
[xdelegate hideLoadingScreen];
[[TournamentsWebService sharedToursWS] showErrorMessageAccordingToFault:result];
return;
}
if([result isKindOfClass: [SoapFault class]]) {
[xdelegate hideLoadingScreen];
// If a server error has occurred, handle it
[[TournamentsWebService sharedToursWS] showErrorMessageAccordingToFault:result];
return;
}
//Do something with result...
if ([result count] > 0) {
NSLog(#"Antal klubturneringer: %d", [result count]);
//Start by removing excisting tours
[self.tournamentsSourceArray removeAllObjects];
NSMutableArray *tempArray=[NSMutableArray array];
for (GGTournamentData *t in result) { //cast object in result list and add them to array
[tempArray addObject:t];
}
self.tournamentsSourceArray = [self sortByStringDate:tempArray]; //sort by date
[tempArray release];
NSLog(NSLocalizedString(#"tourLoadet", #""));
}
[self.clubToursTableView reloadData];
[xdelegate hideLoadingScreen];
//Scroll view
if (self.tournamentsSourceArray.count > 0) { //hvis det er turneringer..
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:[self findIndexOfMonthClosestToDate]];
[self.clubToursTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
So my problem is that the NSMutableArray gets returned before my async task is done. I know a async task behaves like that, but how do i make sure that my handleClubTournaments function don't run before my webrequest(getTournamentsInClub) got some data for it?
Thanks in advance.
I don't think you know how Asynchronous operations work. The NSMutableArray will never be set, because it is returned synchronously.
In your case, I suggest you to work with delegates.
- (void)getTournamentsInClub:(NSString *)clubGUID withDelegateViewController:(UIViewController *)viewController completionBlock:(void (^)(NSMutableArray *result))completionBlock {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableArray *responseArray = [[[NSMutableArray alloc] init] autorelease];
NSString *URL = [[NSString alloc]initWithFormat:#"SomeURL=%#",clubGUID];
[manager POST:URL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
for (id obj in responseObject) {
[responseArray addObject:obj];
}
// Request finished. Call the block.
completionBlock(responseArray);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)handleClubTournaments {
GP_MobilAppDelegate *xdelegate = [[UIApplication sharedApplication] delegate];
[[TournamentsWebService sharedToursWS] getTournamentsInClub:^(NSMutableArray *result)
{
// Hide the Loading Indicator. Do something with the Result.
}];
// You can't access the result synchronously, therefore it's impossible to depend on it synchronously.
}
another way to return the data asynchronously would be blocks, similar to the AFNetworking solution.
You can read more about getting started with blocks here and how to use delegates here.
I am trying to pull data from the Twitter Streaming API using the SLRequest class.
When I use the endpoint and parameters documented in the code below the program "hangs"
and no JSON data is printed. I am using an endpoint based on an example from the twitter dev
website https://dev.twitter.com/docs/streaming-apis/parameters#with I am requesting
tweets at a certain location.
When I use this code to query my timeline using the REST API (the code and request is included but
commented out) the program does not hang and I get a valid response.
Is there something else in the code that I need to implement to access the data using the streaming API? What additional modifications or changes need to be made?
ACAccountStore * accountStore = [[ACAccountStore alloc] init];
ACAccountType * twitterAccountType =
[accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Ask the user permission to access his account
[accountStore requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted, NSError *error) {
if (granted == NO) {
NSLog(#"-- error: %#", [error localizedDescription]);
}
if (granted == YES){
/***************** Create request using REST API*********************
***************** This URL is functional and returns valid data *****
NSURL * url = [NSURL URLWithString:#"https://userstream.twitter.com/1.1/user.json"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:#{#"screen_name": #"your_twitter_id"}];
***************************************************************/
// Create request using Streaming API Endpoint
NSURL * url = [NSURL URLWithString:#"https://stream.twitter.com/1.1/statuses/filter.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"track" forKey:#"twitter&"];
[params setObject:#"locations" forKey:#"-122.75,36.8,-121.75,37.8"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url parameters:params];
NSArray * twitterAccounts = [accountStore accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] == 0) {
(NSLog(#"-- no accounts available"));
} else if ([twitterAccounts count] >0){
[request setAccount:[twitterAccounts lastObject]];
NSLog([request.account description]);
NSLog(#"Twitter handler of user is %#", request.account.username);
// Execute the request
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSError * jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&jsonError];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// NSLog(#"-- json Data is %#", json);
NSLog([json description]);
}];
}];
}
}
}];
SLRequest doesn't play well with the streaming API.
Here is how to do with STTwitter:
self.twitter = [STTwitterAPI twitterAPIOSWithAccount:account];
[_twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
NSLog(#"-- access granted for %#", username);
[_twitter postStatusesFilterUserIDs:nil
keywordsToTrack:#[#"twitter"]
locationBoundingBoxes:#[#"-122.75,36.8,-121.75,37.8"]
delimited:nil
stallWarnings:nil
progressBlock:^(id response) {
NSLog(#"-- %#", response);
} stallWarningBlock:^(NSString *code, NSString *message, NSUInteger percentFull) {
NSLog(#"-- stall warning");
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
Internally, STTwitter builds a NSURLConnection instance with the request from -[SLRequest preparedURLRequest]. You can replicate this trick in your code if you wish.
I'm testing a method using OCMock. The method is as follows:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
block:(void (^)(id responseObject, NSError *error))block
{
return [self GET:URLString parameters:parameters success:^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
} failure:^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
}];
}
This test fails with "expected method was not invoked":
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
void (^successBlock)(NSURLSessionDataTask *, id) = ^(NSURLSessionDataTask * __unused task, id responseObject) {
block(responseObject, nil);
};
void (^failureBlock)(NSURLSessionDataTask *, NSError *) = ^(NSURLSessionDataTask * __unused task, NSError *error) {
block(nil, error);
};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:successBlock failure:failureBlock];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
But this passes:
id sessionManagerPartialMock = [OCMockObject partialMockForObject:[FOOHTTPSessionManager manager]];
NSString *URLstring = #"test";
NSDictionary *parameters = nil;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {};
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
[sessionManagerPartialMock stopMocking];
Why does the first test fail and how can I make it pass?
I've put an example project on GitHub demonstrating the issue: https://github.com/paulyoung/OCMockExample
As others have said, blocks are compared by comparing their addresses. Two different blocks - even though they do the exact same thing - are not equal.
You can still make your test more specific by actually invoking the success and failure block, and check if they behave as expected.
As far as I understand you want to test that
the method calls another method with two blocks, that when invoked, invoke the original block with
some response object as the first and nil as the second argument in case of success
nil as the first, an NSError* as the second argument in case of failure
Here is the test for the success case:
id responseMock = #"responseObject";
__block BOOL blockCalled = NO;
void (^block)(id, NSError *) = ^(id responseObject, NSError *error) {
XCTAssertNil(error, #"");
XCTAssertEqualObjects(responseObject, responseMock, #"");
blockCalled = YES;
};
[[[sessionManagerPartialMock expect] andDo:^(NSInvocation *invocation) {
void (^successBlock)(NSURLSessionDataTask *, id);
[invocation getArgument:&successBlock atIndex:4];
successBlock((id)#"task", responseMock);
}] GET:URLstring parameters:parameters success:[OCMArg isNotNil] failure:[OCMArg isNotNil]];
[sessionManagerPartialMock GET:URLstring parameters:parameters block:block];
[sessionManagerPartialMock verify];
XCTAssert(blockCalled, #"");
I checked your repo and I think now I can give you good answer.
What you are trying to do in your test is blocks comparing and currently this is not possible in Obj-c world. Even if you block from test and block created inside singleton looks identical they are not the same blocks.
You can fix this by using you fail and success block as private properties and use them in your OCMock expect. Like :
[[sessionManagerPartialMock expect] GET:URLstring parameters:parameters success:sessionManager.successBlock failure:sessionManager.failureBlock];