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;
}
Related
I'm new to Objective-C, just wondering how to use NSArray object outside from JSON.
For example:
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
}
NSLog(#"%#",myFinalListArray); //(This one showing all results..)
}];
NSLog(#"%#",myFinalListArray); //(This one giving empty result)
I have defined myFinalListArray and added objects in for loop.
If you use NSLog inside the loop or outside the loop it will show you results. But if I use this after }]; (after the code is ending.),
it's giving me empty array.
If you are accessing myFinalListArray in tableview then you can reload tableview inside the block after fetching data.
Or if you are accessing this array in some other task then you have to make notification call (have to add observer) and then post notification that will call some other method and access your array there and do your further stuff.
The block of code associated with sendAsynchronousRequest isn't executed until the network fetch has completed; this takes some time. While the network fetch is happening your code continues to execute, starting with the line immediately after sendAsynchronousRequest which is NSLog(#"%#",myFinalListArray); - but because the network operation hasn't completed you get an empty array.
In the block you need to include the code that you need to process the array, update your user interface or whatever (If you update UI make sure you dispatch the operation on the main thread)
This will work. You can try with this.
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
//Pass here the reference the a array. It will return you the array of you county when downloaded complete.
[self getURLResponse:&myFinalListArray];
NSLog(#"countryArray:%#",myFinalListArray);
}
-(void)getURLResponse:(NSMutableArray**)countryArray{
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
NSURLResponse *response;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:
request returningResponse:&response error:&error];
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
*countryArray = [[NSMutableArray alloc]initWithArray:myFinalListArray copyItems:YES];
}
-(void)sendRequest
{
NSURL *url = [NSURL URLWithString:#"http://acumen-locdef.elasticbeanstalk.com/service/countries"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError) {
if (data.length > 0 && connectionError == nil)
{
NSMutableArray *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
if( !myFinalListArray )
{
myFinalListArray=[[NSMutableArray alloc]init];
}
for (NSDictionary *countryList in greeting) {
[myFinalListArray addObject:countryList[#"name"]];
}
}
[self printArray];
}];
}
//create method that will execute after response
-(void) printArray
{
NSLog(#"%#",myFinalListArray); //(This one showing all results..)
}
Use
__block NSMutableArray *myFinalListArray = [[NSMutableArray alloc] init];
This should work.
Happy Coding.
sendAsynchronousRequest runs asynchronously, meaning that the code below is already performed while the request is still running, so the NSLog is logging the empty array.
Only when the request finishes, the array is filled up but your outer NSLog was already performed.
I have an app which downloads a set of photos from a server. I am using an Asynchronous request because I don't want the UI to be blocked. However, I am finding that the request is very slow and takes ages to load.
I know you can set the queue type to [NSOperationQueue mainQueue] but that just puts the Asynchronous request back on the main thread which defeats the whole point of making the request Asynchronously in the first place.
Is there anyway to speed up the request or to tell iOS: "Run this request in the background, but do it ASAP, don't leave it till the end of the queue"???
Here is my code:
// Set up the photo request.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:PHOTO_URL, pass_venue_ID, PHOTO_CLIENT_ID, PHOTO_CLIENT_SECRET]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// Begin the asynchromous image loading.
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
// Convert the response data to JSON.
NSError *my_error = nil;
NSDictionary *feed = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&my_error];
// Check to see if any images exist
// for this particular place.
int images_check = [[NSString stringWithFormat:#"%#", [[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"count"]] intValue];
if (images_check > 0) {
// Download all the image link properties.
images_prefix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"prefix"];
images_suffix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"suffix"];
images_width = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"width"];
images_height = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"height"];
// Set the image number label.
number_label.text = [NSString stringWithFormat:#"1/%lu", (unsigned long)[images_prefix count]];
// Download up to 5 images.
images_downloaded = [[NSMutableArray alloc] init];
// Set the download limit.
loop_max = 0;
if ([images_prefix count] > 5) {
loop_max = 5;
}
else {
loop_max = [images_prefix count];
}
for (NSUInteger loop = 0; loop < loop_max; loop++) {
// Create the image URL.
NSString *image_URL = [NSString stringWithFormat:#"%#%#x%#%#", images_prefix[loop], images_width[loop], images_height[loop], images_suffix[loop]];
// Download the image file.
NSData *image_data = [NSData dataWithContentsOfURL:[NSURL URLWithString:image_URL]];
// Store the image data in the array.
[images_downloaded addObject:image_data];
}
// Load the first image.
[self load_image:image_num];
}
else if (images_check <= 0) {
// error...
}
}
else {
// error
}
}];
Thanks for your time, Dan.
i think your problem isnt the request running slow, its that you are updating UI elements not on the main thread, surround any UI updates (like setting the text on labels) with
dispatch_sync(dispatch_get_main_queue(), ^{
<#code#>
});
As Fonix said its not iOS that responding slow but dataWithContentsOfURL doesn't work in background thread. Apple's recommendation is that you should use NSURLConnection asynchronously with delegates
- didReceiveResponse
- didReceiveData
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:_mAuthenticationTimeoutInterval];
In these methods you can make use of chunks of data as well.
If you actually want these multiple downloads to be faster you should use parallel downloading using NSOperationQueue. You can refer enter link description here
I think a good solution could be using AFNetworking when combined with NSOperation, check this code I wrote to do more than one operation asynchronously
NSMutableArray *operations = [[NSMutableArray alloc] init];
for(NSObject *obj in caches) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
//...set up your mutable request options here
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSInteger statusCode = operation.response.statusCode;
if(statusCode==200) {
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"API Call error:%#", error.localizedDescription);
}];
[[requestManager operationQueue] addOperation:operation];
[operations addObject:operation];
if([operations count] >= MAX_API_CALL) break;
}
[AFHTTPRequestOperation batchOfRequestOperations:operations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
NSError *error;
for (AFHTTPRequestOperation *op in operations) {
if (op.isCancelled){
}
if (op.responseObject){
// process your responce here
}
if (op.error){
error = op.error;
}
}
}];
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 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);
}];
There are, for example, 100 JSON files and appropriate 100 images on my ftp server.
1.With loading JSON where are no bug (I hope)
NSString *recipesPath = #"ftp://.../recipes/";
NSString *recipeFileName = [NSString stringWithFormat:#"00001.json", recipeCode];
NSString *recipeFileFullPath = [recipesPath stringByAppendingString:recipeFileName];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:recipeFileFullPath]];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSError *error = nil;
NSDictionary *recipeDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
2.But images can not be loaded
NSString *recipeImageName = #"recipeImages/00001-1.jpg";
NSString *recipeImageFullPath = [recipesPath stringByAppendingString:recipeImageName];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:recipeImageFullPath]];
if (request) {
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if (responseData) {
UIImage *image = [UIImage imageWithData:responseData];
}
}
responseData nearly always is nil.
May be there is the other method?
All this code is in MyMethod which I execute in NSOperationQueue:
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(MyMethod) object:nil];
[operationQueue addOperation:operation];
EDIT: image sizes are not big - from 50 to 100 kbyte
EDIT: can image file extension affect to downloading process?
You can try this:
NSURL *url = [NSURL URLWithString:recipeImageFullPath];
NSData *data = [NSData alloc] initWithContentsOfURL:url];
Please try to test it:
NSError *requestError;
NSURLResponse *urlResponse = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&requestError];
/* Return Value
The downloaded data for the URL request. Returns nil if a connection could not be created or if the download fails.
*/
if (responseData == nil) {
// Check for problems
if (requestError != nil) {
...
}
}
else {
// Data was received.. continue processing
}