Okay I have another question, tonight, using AFNetworking, i parse my JSON Stream, an add object an MutableArray, when i insert try to print the array outside of the success block, it says null, but inside of this block it works, so how can i pass the _listOfNewsArray into the mainthread ?
This is my code :
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"bgWhitelight" ofType:#"png"];
self.tableView.backgroundColor = [[UIColor alloc] initWithPatternImage:[[UIImage alloc] initWithContentsOfFile:path]];
NSURLRequest *newsRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://aXXXXXXXXXXXipt/beta.php"]];
AFJSONRequestOperation *newsJSONRequest = [AFJSONRequestOperation JSONRequestOperationWithRequest:newsRequest success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
NSArray *newsArray = [JSON objectForKey:#"news"];
_listOfNews = [[NSMutableArray alloc]init];
for (NSDictionary *oneNews in newsArray) {
CCENews *currentNews = [[CCENews alloc]init];
currentNews.title = [oneNews objectForKey:#"title"];
currentNews.content = [oneNews objectForKey:#"content"];
currentNews.category = [currentNews getHiResCategoryPicture:[oneNews objectForKey:#"category"]];
currentNews.date = [oneNews objectForKey:#"date"];
currentNews.imageURL = [oneNews objectForKey:#"pictureurl"];
[_listOfNews addObject:currentNews];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"%#", error);
}];
[newsJSONRequest start];
In fact, i found the solution, just using self.listOfNews, just had to think about it !-
Move the creation of listOfNews out of the block and into viewDidLoad, or make the ivar a block variable (_block NSM....). I prefer the first solution.
Related
This question already has answers here:
Can AFNetworking return data synchronously (inside a block)?
(6 answers)
Closed 9 years ago.
I was wondering if I could wait for request to be processed with afnetworking.
Lets say I got this method
- (MWPhoto *)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index {
//Request goes here, so the method doesn't return anything before it's processed
}
Is that doable?
This would be referred to a synchronous request.
If the method is called on the main thread it will make your app appear to have frozen and is not a suggested way to do networking.
See the dupe question I commented for details on how to do it if you still want to.
You can, but you never want the main queue waiting for some asynchronous operation to complete. If you want something to happen after your asynchronous operation is done, you should use the AFNetworking success block to specify what you want to happen when the operation is done.
So, if you want to provide the caller a pointer to the MWPhoto, rather than having a return type of MWPhoto *, have a return type of void, but supply a completion block so that the caller can handle it when it's done:
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index completion:(void (^)(MWPhoto *))completion
{
if (index < self.images.count) {
GalleryPicture *thumbnail = [images objectAtIndex:index];
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", API_URL, #"galleryPicture"]];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:gallery.objectId, #"galleryId", thumbnail.objectId, #"id", [NSNumber numberWithBool:NO], #"thumbnail", nil];
ViveHttpClient *httpClient = [[ViveHttpClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
GalleryPicture *picture = [[GalleryPicture alloc] initWithJSON:JSON];
completion([picture mwPhoto]);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// handle the error here
}];
// start your operation here
}
}
So, rather than:
MWPhoto *photo = [object photoBrowser:photoBrowser photoAtIndex:index];
// do whatever you want with `photo` here
You might instead do:
[object photoBrowser:photoBrowser photoAtIndex:index completion:^(MWPhoto *photo){
// do whatever you want with `photo` here
}];
Since AFURLConnectionOperation inherits from NSOperation, you can use NSOperation waitUntilFinished method to wait for the operation to end.
However, the success and failure blocks of AFURLConnectionOperation will be executed before waitUntilFinished completes. Nevertheless, you can access the response and error properties of the AFURLConnectionOperation after waitUntilFinished completes.
This is exactly what I did, which reffers to starting synchronyous request
- (MWPhoto *)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index {
if (index < self.images.count) {
GalleryPicture *thumbnail = [images objectAtIndex:index];
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", API_URL, #"galleryPicture"]];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:gallery.objectId, #"galleryId", thumbnail.objectId, #"id", [NSNumber numberWithBool:NO], #"thumbnail", nil];
ViveHttpClient *httpClient = [[ViveHttpClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(!error) {
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error];
GalleryPicture *picture = [[GalleryPicture alloc] initWithJSON:json];
return [picture mwPhoto];
}
}
return nil;
}
I am making two separate requests to obtain JSON from external sources, I have so far implemented the display of the data from the first request into my table view. My problem is, I need to combine both sets of data into a single table view and sort the data by a common key, which in this case is created_time. I understand I can use some form of array, but how would I go about doing this?
The first:
NSURL *url = [NSURL URLWithString:myURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id json) {
self.results = [json valueForKeyPath:#"data"];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[operation start];
The second:
NSURL *url = [NSURL URLWithString:#"https://api.twitter.com/1.1/search/tweets.json"];
NSDictionary *parameters = #{#"count" : RESULTS_PERPAGE,
#"q" : encodedQuery};
SLRequest *slRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url
parameters:parameters];
NSArray *accounts = [self.accountStore accountsWithAccountType:accountType];
slRequest.account = [accounts lastObject];
NSURLRequest *request = [slRequest preparedURLRequest];
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
});
To combine the data from your external sources, you'll want to do the following for each response you get back.
Also, for the sake of the example, I'm assuming the objects you'll be dealing with are all dictionaries. If they aren't, you'll want to add some logic in the comparison block to get at the created_time value depending upon the type of object each one is.
NSArray *data = [json valueForKeyPath: #"data"]; // This is the data from your first example. You'll have to do the same for your second example.
NSMutableArray *allResults = [NSMutableArray arrayWithArray: self.results];
[allResults addObjectsFromArray: data];
[allResults sortUsingComparator: ^NSComparisonResult(id obj1, id obj2) {
NSDictionary *dict1 = obj1;
NSDictionary *dict2 = obj2;
return [[dict1 objectForKey: #"created_time"] compare: [dict2 objectForKey: #"created_time"]];
}];
[self setResults: allResults];
[self.tableView reloadData];
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 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);
}];
How do you download images in order with AFNetworking? An by "in order", I also mean executing the success blocks in order.
Initially I thought it would be enough to use a NSOperationQueue and set each AFImageRequestOperation as a dependency of the next one. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_images addObject:image];
NSLog(#"%d", i);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
This prints i in order when the images are downloaded. However, when the requests are already cached, the success blocks are processed out of order. I suspect this is a NSOperation limitation, not AFNetworking.
Am I missing something?
As a workaround, I store the images in a dictionary and process them in order each time a request succeeds. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
_imageDictionary = [NSMutableDictionary dictionary];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_imageDictionary setObject:image forKey:#(i)];
[self processImages];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
- (void) processImages
{
for (NSInteger i = _images.count; i < _imageURLs.count; i++) {
UIImage *image = [_imageDictionary objectForKey:#(i)];
if (!image) break;
[_images addObject:image];
NSLog(#"%d", i);
}
}
This always prints i in order.
Your solution will work fine, here is another way to do it:
For the "perfect" UX you should issue all operations in parallel and process images by order as they come (don't wait if you don't have to).
(error handling is done differently here)
You could try this (untested, and you can better design the model [don't just use arrays like this]):
- (void) processImage:(UIImage*)image
{
//do something with the image or just [_images addObject:image]
}
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
}
__block NSMutableArray* queue = [[NSMutableArray alloc] initWithCapacity:[_imageURLs count]];
for (NSURL* url in _imageURLs) {
__block NSLock* lock = [[NSLock alloc] init];
__block NSMutableArray* container = [NSMutableArray new];
[lock lock];
[queue addObject:#[lock,container,url]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
void(^compBlock)(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image) = ^(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image)
{
[container addObject:image];
[lock unlock];
};
NSOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:compBlock
failure:compBlock];
[_downloadQueue addOperation:operation];
}
__block __weak id weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSArray* arr in queue) {
NSLock* lock = arr[0];
[lock lock];
NSMutableArray* container = arr[1];
if ([container count]) {
[weakSelf processImage:container[0]]; //might want to call this on main thread
} else {
//error on url = arr[2]
}
[lock unlock];
}
});
}