I have the following code inside a class (static method) which I call to get data from an API. I decided to make this a static method just so I can reuse it on some other parts of the app.
+ (NSArray*) getAllRoomsWithEventId:(NSNumber *)eventId{
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"http://blablba.com/api/Rooms/GetAll/e/%#/r?%#", eventId, [ServiceRequest getAuth]]];
NSMutableArray *rooms = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:urlRequest];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Response of getall rooms %#", JSON);
NSArray *jsonResults = (NSArray*)JSON;
for(id item in jsonResults){
Room* room = [[Room alloc]init];
if([item isKindOfClass:[NSDictionary class]]){
room.Id = [item objectForKey:#"Id"];
room.eventId = [item objectForKey:#"EventId"];
room.UINumber = [item objectForKey:#"RoomUIID"];
[rooms addObject:room];
}
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"Error");
}];
[operation start];
[operation waitUntilFinished];
return rooms;
}
Now my issue is, whenever I call this in a ViewController (ViewDidLoad method). The static method will run till the end and will return null on the rooms, but the Nslog will display the "Success" block Nslog a few seconds after. Now I understand that this is asynchronous so it doesn't wait for the success block to execute before it reaches the "return rooms;" line. With all that said, I need some advice as to how to handle this, like maybe a progress bar or something like that? Or something that delays it? I'm not really sure if that's the reight way or if it is, I am not sure how to do it.
Any advice is very much appreciated. Thank you!
AFNetworking is built around asynchronicity—starting a request, and then executing some piece of code once that request has finished.
waitUntilFinished is an anti-pattern, which can block the user interface.
Instead, your method should have no return type (void), and have a completion block parameter that returns the serialized array of rooms:
- (void)allRoomsWithEventId:(NSNumber *)eventId
block:(void (^)(NSArray *rooms))block
{
// ...
}
See the example app in the AFNetworking project for an example of how to do this.
You can write your method following way:
+ (void) getAllRoomsWithEventId:(NSNumber *)eventId:(void(^)(NSArray *roomArray)) block
{
NSURL *urlRequest = [NSURL URLWithString:[NSString stringWithFormat:#"http://blablba.com/api/Rooms/GetAll/e/%#/r?%#", eventId, [ServiceRequest getAuth]]];
NSMutableArray *rooms = [[NSMutableArray alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:urlRequest];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Response of getall rooms %#", JSON);
NSArray *jsonResults = (NSArray*)JSON;
for(id item in jsonResults){
Room* room = [[Room alloc]init];
if([item isKindOfClass:[NSDictionary class]]){
room.Id = [item objectForKey:#"Id"];
room.eventId = [item objectForKey:#"EventId"];
room.UINumber = [item objectForKey:#"RoomUIID"];
[rooms addObject:room];
}
}
block(rooms);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON){
NSLog(#"Error");
block(nil); //or any other error message..
}];
[operation start];
[operation waitUntilFinished];
}
you can call this method like followings:
[MyDataClass getAllRoomsWithEventId:#"eventid1":^(NSArray *roomArray) {
NSLog(#"roomArr == %#",roomArray);
}];
Related
I have two methods that are running their code in background, and method1 triggers method2 as follows:
+(void)insertAllDataInDatabase{
NSLog(#"1");
NSString *url=#"http://localhost/kalimat/get_all_artists.php";
//NSLog(#"url %#",url);
NSURL *urlChannels= [ NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:urlChannels];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
NSMutableArray *arrayOfJson=JSON;
for (int i=0; i<[arrayOfJson count]; i++) {
NSLog(#"2");
NSMutableDictionary *songDico=[arrayOfJson objectAtIndex:i];
NSString *artist=[songDico objectForKey:#"artist"];
[self getArtistSongs:artist];
}
});
NSLog(#"6");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response,
NSError *error, id JSON) {
//DLog(#"Request Failure Because %#",[error userInfo]);
}];
[operation start];
}
+(void)getArtistSongs:(NSString*)artist {
NSLog(#"3");
LKDBHelper* globalHelper = [LKDBHelper getUsingLKDBHelper];
NSMutableArray *arrayOfSongs=[[NSMutableArray alloc]init];
artist = [artist stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//DLog(#"artisttt %#",artist);
NSString *url=[NSString stringWithFormat:#"%#?artist=%#", #"http://localhost/kalimat/get_kalimat.php",artist];
url = [url stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet];
//NSLog(#"url %#",url);
NSURL *urlChannels= [ NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:urlChannels];
[LKDBHelper clearTableData:[Song class]];
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON) {
NSMutableArray *arrayOfJson=JSON;
for (int i=0; i<[arrayOfJson count]; i++) {
NSLog(#"4");
NSMutableDictionary *songDico=[arrayOfJson objectAtIndex:i];
DCKeyValueObjectMapping *parser = [DCKeyValueObjectMapping mapperForClass: [Song class]];
Song *song = [parser parseDictionary:songDico];
song.artist=artist;
[arrayOfSongs addObject:song];
//DLog(#"inserting...");
[globalHelper insertToDB:song];
//DLog(#"getting lyrics");
//[self getLyricsWhereArtist:artist andSong:song.song];
//[[NSNotificationCenter defaultCenter] postNotificationName:#"AllArtistsSongs" object:arrayOfSongs];
}
NSLog(#"5");
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response,
NSError *error, id JSON) {
DLog(#"Request Failure Because %#",[error userInfo]);
}];
[operation start];
});
}
Basing on the NSLogs, I want to have :
1
2
3
4
4
4
4
...
5
6
But I'm having:
1
6
2
3
2
3
2
3
2
3
...
Is there a way to order the execution of those methods?
Thank you very much for your help.
You're already calling getArtistSongs: from a background thread. If you want those to run serially, just remove the dispatch_async call from that method. You'd also need to make those requests synchronously; I haven't used AFNetworking so I don't know if that's available or how to do it.
This will work without blocking the main thread, because your calls to getArtistSongs: come from the block that's running on the background thread:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// All this code runs in the background.
NSMutableArray *arrayOfJson=JSON;
for (int i=0; i<[arrayOfJson count]; i++) {
NSLog(#"2");
NSMutableDictionary *songDico=[arrayOfJson objectAtIndex:i];
NSString *artist=[songDico objectForKey:#"artist"];
[self getArtistSongs:artist];
}
// All code above runs in the background.
});
The 6, of course, will still print immediately after the 1. If you need code to run last of all, it goes inside the block, after the for loop of songDiscos, possibly wrapped in a dispatch_async to the main thread.
I develop an app and I use the AFNetworking to comunicate with the server.Everything is oky except one call which return status code: 500; second time when I call the WS is oky.
My method :
-(IBAction)quizzSubmit:(id)sender {
BusinessLogic *bl = [BusinessLogic sharedManager];
[AlertDFF alertWithTitle:#"Loading ..." owner:self];
NSString *plm = [NSString stringWithFormat:#"[{\"nid\":%d,\"score\":%d,\"time\":%d,\"options\":{\"cid1\":\"%#\",\"cid2\":\"%#\",\"cid3\":\"%#\",\"cid4\":\"%#\",\"cid5\":\"%#\",\"cid6\":\"%#\",\"cid7\":\"%#\",\"cid8\":\"%#\",\"cid9\":\"%#\",\"cid10\":\"%#\"}}]", self.quizzId, self.score, self.time, [self.quizzAnswersDic objectForKey:#"cid1"], [self.quizzAnswersDic objectForKey:#"cid2"], [self.quizzAnswersDic objectForKey:#"cid3"], [self.quizzAnswersDic objectForKey:#"cid4"], [self.quizzAnswersDic objectForKey:#"cid5"], [self.quizzAnswersDic objectForKey:#"cid6"], [self.quizzAnswersDic objectForKey:#"cid7"], [self.quizzAnswersDic objectForKey:#"cid8"], [self.quizzAnswersDic objectForKey:#"cid9"], [self.quizzAnswersDic objectForKey:#"cid10"]];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
bl.currentUser.hash, #"hash",
plm,#"quizzes",
nil];
ApiClientBlitzApp *client = [ApiClientBlitzApp sharedClient];
NSURLRequest *request = [client requestWithMethod:#"POST" path:getQuizSubmit parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest
*request, NSHTTPURLResponse *response, id resultObjectsJSON)
{
if (resultObjectsJSON count]>0){
[AlertDFF dismissAlertInController:self];
[Utils showAlert:#“Your Quiz Result was sent ! ” andDelegate:nil];
}else{
[AlertDFF dismissAlertInController:self];
[Utils showAlert:#“Problems ! Please try again later! ” andDelegate:nil];
}
}failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"ERROR response = %# /n", response);
NSLog(#"error = %#", error);
[AlertDFF dismissAlertInController:self];
}];
[operation start];
} }
The WS team told me that my request doesn't reah to the server.
If I press second time the button and call again the metod everthing it's oky.
I need some help with this problem please.
I test the request on Chrome - Postman and everything it's oky.
I am showing google street view from my ios app for a perticular location using Google Maps SDK for iOS version: 1.4.0.4450.
It works fine if street view is available.
My question is if street view is not available how to check it?
There is a class GMSPanoramaService. It contains a public member method. I think this can be useful.
- requestPanoramaNearCoordinate:callback:
Retrieves information about a panorama near the given coordinate.
But how to use it?
Thanks in advance!
You may use this rude method
-(void)isStreetViewAvailable:(CLLocationCoordinate2D)location completionBlock: (NWisStreetViewCompletionBlock)completionBlock
{
NSString *loc = [NSString stringWithFormat:#"%.10f,%.10f&", location.latitude, location.longitude];
NWisStreetViewCompletionBlock completeBlock = [completionBlock copy];
NSString *connectionString = [NSString stringWithFormat:#"http://cbk0.google.com/cbk?output=json&ll=%#", loc];
NSLog(#"connect to: %#",connectionString);
NSURL *url = [NSURL URLWithString:connectionString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//NSLog(#"%#", JSON);
NSLog(#"%#", JSON);
if([JSON objectForKey:#"Location"] == nil)
completeBlock(#"", nil);
//NSLog(#"panoId: %#",[[json objectForKey:#"Location"] objectForKey:#"panoId"]);
completeBlock([[JSON objectForKey:#"Location"] objectForKey:#"panoId"], nil);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:[error description] forKey:NSLocalizedDescriptionKey];
// populate the error object with the details
NSError *err = [NSError errorWithDomain:#"world" code:200 userInfo:details];
completeBlock(NO, err);
}];
[operation start];
}
I'm new to using blocks in iOS and I am thinking that's probably the crux of my problem.
I just want to build a simple static DataManager class whose sole job is to fetch data from my Restful service.
I would call this from all my various UIViewControllers (or collectionview/table controllers)
In my class i have a function that looks like this
+ (NSArray *) SearchByKeyword: (NSString*) keyword {
__block NSArray* searchResults = [[NSArray alloc] init];
NSString *baseURL = #"http://someURL.com/api/search";
NSString *requestURL = [baseURL stringByAppendingString:keyword];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:requestURL
parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
searchResults = [JSON valueForKeyPath:#""];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
return searchResults;
}
However, this keeps returning zero data. Can someone suggest the right way of doing this?
You are trying to use the results of an asynchronous task (the JSON operation) as the return value for a synchronous method call, so that is why you get no data.
You could provide your view controllers with an API that takes completion blocks and failure blocks, similar to the AF networking one. View controllers can then do what they need to do with the results when they are passed into the block.
Modifying your code from your question:
typedef void (^SearchCompletionBlock)(NSArray *results);
typedef void (^SearchFailureBlock)(NSError *error);
+ (void)searchByKeyword:(NSString*)keyword completionBlock:(SearchCompletionBlock)completionBlock failureBlock:(SearchFailureBlock)failureBlock;
{
NSString *baseURL = #"http://someURL.com/api/search";
NSString *requestURL = [baseURL stringByAppendingString:keyword];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET"
path:requestURL
parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
if (completionBlock) {
completionBlockc([JSON valueForKeyPath:#""]);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
if (failureBlock) {
failureBlock(error);
}
}];
[operation start];
}
Then clients could pass completion blocks that stored the results and reloaded their views. Something like:
^ (NSArray *results) {
self.results = results;
[self.tableView reloadData];
}
Your JSON request operation is asynchronous, meaning that it will kick off the request ([operations start], then immediately return your results, which will be empty. When the completion block runs, it assigns your data but nothing is done with it. Your search method can't return an object unless it waits for the request to complete.
You've got a few options:
Pass in a completion block to the search method which does something with the results. The completion block is called in the completion block of the request, once all the service-specific stuff (processing JSON etc) is finished. (Block inception!)
Have the completion block of the request assign a property of the data manager, then call a delegate method or notification to let others know the results are available.
I'd prefer option 1.
I'm making several request from different sources, and because of this I want to add a property like: '"newsSource" = twitter' (JSON format) to the created NSArray resultsTwitter below. The reason is I want be able to handle each "newsitem" uniquely.
I'm new to blocks, but I think it might be an really easy way to do this "on the fly"?
If not possible within the block operation, any suggestion on how to do it after operation is done?
// Fetch data from Twitter (json complient)
NSURLRequest *request = [NSURLRequest requestWithURL:urlTwitter];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *responce, id jsonObject) {
NSLog(#"Responce: %#",jsonObject);
self.resultsTwitter = [jsonObject objectForKey:#"results"];
[self.tableView reloadData];
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *responce, NSError *error, id jsonObject) {
NSLog(#"Recieved an HTTP %d", responce.statusCode);
NSLog(#"The error was: %#",error);
}];
[operation start];
I may not have understood your question correctly, but as long as resultsTwitter is a NSMutableArray, you can add an object (in your case an NSDictionary with a single KVP) after it is initially populated.
Something like:
[resultsTwitter addObject:[NSDictionary dictionaryWithObjectsAndKeys:
#"twitter", #"newsSource",
nil]];
Example of instantiating a variable that can be accessed inside a block:
__block NSString *newssource = #"";
NSURLRequest *request = [NSURLRequest requestWithURL:urlTwitter];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *responce, id jsonObject) {
NSLog(#"Responce: %#",jsonObject);
self.resultsTwitter = [jsonObject objectForKey:#"results"];
[self.tableView reloadData];
newssource = #"twitter";
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *responce, NSError *error, id jsonObject) {
NSLog(#"Recieved an HTTP %d", responce.statusCode);
NSLog(#"The error was: %#",error);
}];
[operation start];
Create a Model class to encapsulate the behavior of all News Items.
This pattern is used in the AFNetworking example app, with each App.net post corresponding to a model object, which is initialized from JSON. I would strongly recommend against using a mutable dictionary rather than a model object as a means of representing items.