I want to download images from server asynchronously but they aren't coming serially. When I make a synchronous request then it downloads serially but creates other problems.
Code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){
NSString *UrlStr=urlString;
NSURL *imageURL=[NSURL URLWithString:[UrlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *tempData=[NSData dataWithContentsOfURL:imageURL];
UIImage *imgData=[UIImage imageWithData:tempData];
dispatch_async(dispatch_get_main_queue(), ^{
if (tempData!=nil) {
((FXImageView *)view).image = imgData;
[_images addObject:imgData];
}
else{
((FXImageView *)view).image = [UIImage imageNamed:#"NoImage.png"];
[_images addObject:[UIImage imageNamed:#"NoImage.png"]];
}
});
})
i also tried NSOperationQueue but wasn't successful.
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^(void){
NSString *UrlStr=urlString;
NSURL *imageURL=[NSURL URLWithString:[UrlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSData *tempData=[NSData dataWithContentsOfURL:imageURL];
UIImage *imgData=[UIImage imageWithData:tempData];
dispatch_async(dispatch_get_main_queue(), ^{
if (tempData!=nil) {
((FXImageView *)view).image = imgData;
[_images addObject:imgData];
}
else{
((FXImageView *)view).image = [UIImage imageNamed:#"NoImage.png"];
[_images addObject:[UIImage imageNamed:#"NoImage.png"]];
}
});
})
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW6
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW5
Not the same... I don't have rep to comments. I created a serial queue, u used a concurrent queue.. Follow the link to understand the theory behind.
Try NSOperationQueue with maxConcurrentOperationCount set to 1, but note that in this case you should implement your image download as NSOperation subclass. Please check out Apple documentation on NSOperation to get a clue on how to implement asynchronous NSOperation.
Related
I have got a productImageArray that contains url as elements of the array.
I'm trying to load those urls in my image view.
Following is the way as of how I'm loading it.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
NSData *storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];
self.productImage.image = [UIImage imageWithData:storeImageData];
dispatch_sync(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
});
});
The problem is that,
Only the last cell of my tableview loads the image whereas the remaining cell does not load the image from the url
Is there any other better way of loading the image from url directly into my UIImage using native methods?
When I use the following code, each cell of my tableview loads the image data but still it freezes the User interface till the data is loaded completely
NSData *storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];
self.productImage.image = [UIImage imageWithData:storeImageData];
#Anbu.Karthik answer is right.
But, maybe the simplest solution is to use something like SDWebImage no? This library will handle this issue and much more (cache, error management, proper tableview cells handling, ...).
I think you should, at least, take a few minutes to look at it: https://github.com/rs/SDWebImage
Edit:
If you use SDWebImage, and UIActivityIndicator-for-SDWebImage, you can replace your entire code by this:
[self.productImage setImageWithURL:[NSURL URLWithString:productImageArray[indexPath.row]]
usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
More informations on UIActivityIndicator-for-SDWebImage: https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage
try this
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
NSData *storeImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:productImageArray[indexPath.row]]];
dispatch_sync(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
self.productImage.image = [UIImage imageWithData:storeImageData];
});
});
or try like
self.productImage.image = nil; //// [UIImage imageNamed:#"default.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:productImageArray[indexPath.row]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *currentImage = [UIImage imageWithData:data];
if (currentImage) {
dispatch_async(dispatch_get_main_queue(), ^{
UITableviewCell *getCurrentCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (getCurrentCell)
self.productImage.image = currentImage;
});
}
}
}];
[task resume];
NSString *url_Img1 = #"Your url";
Uiimageview *view_Image.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_Img1]]];
I am trying to load images by their URL and store them in NSMutableArray in order. My current code works properly if I were not to care about storing the images in order, however it stores them not in order. It currently stores the images in the articleImage array based on the speed at which the asynchronous requests are completed. I have tried playing around with insertObject:AtIndex but could not get anything to work. To clarify, the NSMutableArray that I am trying to store the images in (in orderly fashion) is articleImage.
Here is some code from my viewDidLoad:
dispatch_async(dispatch_get_main_queue(), ^{
if(articleInfoJSONArray.count > 0)
{
for(int i=0; i<articleInfoJSONArray.count; i++)
{
[issueID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"issueID"]];
[articleID addObject:[[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleID"]];
NSString *imageLink = [[articleInfoJSONArray objectAtIndex:i] objectForKey:#"articleImage"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageLink]];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[articleImage addObject:image];
if(articleImage.count == articleInfoJSONArray.count)
[self imagesLoaded];
});
});
}
}
});
Here is my imagesLoaded:
- (void)imagesLoaded
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
ViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"MainView"];
[self presentViewController:vc animated:NO completion:nil];
}
Try to use dispatch_group. A dispatch group monitors work that has been added to it, and it will know when that work is done. :) http://commandshift.co.uk/blog/2014/03/19/using-dispatch-groups-to-wait-for-multiple-web-services/
One way i did an image download is with NSOperationQueue and NSOperation. You could define a NSOperationQueue in your header file:
#property (strong, nonatomic) NSOperationQueue *sequentialOperationQueue;
in your implementation do:
self.sequentialOperationQueue = [[NSOperationQueue alloc] init];
self.sequentialOperationQueue.maxConcurrentOperationCount = 1;
then you can add:
for (NSDictionary *imageDict in imagesToFetch) {
ImageDownloadOperation *imgDownloadOperation = [[ImageDownloadOperation alloc] initWithImageLocationDict:imageDict];
[self.sequentialOperationQueue addOperation:imgDownloadOperation];
}
LogoDownloadOperation is a subclass of NSOperation. this way you always have only one active download and process them in the order you want. For details on NSOperation check the apple doc.
in extract i did in ImageDownloadOperation:
- (void)start {
NSURL *imageUrl = [NSURL URLWithString:self.imageDict[#"imageUrl"]];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDownloadTask *downloadPhotoTask = [session
downloadTaskWithURL:imageUrl
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
self.sessionTask = nil;
[self done];
return;
}
NSData *imageData = [NSData dataWithContentsOfURL:location];
NSBlockOperation *saveImageBlockOperation = [NSBlockOperation blockOperationWithBlock:^{
[SharedAppDelegate.entityManager saveImage:imageData
imageDict:self.imageDict
inManagedObjectContext:SharedAppDelegate.managedObjectContext];
}];
saveImageBlockOperation.qualityOfService = NSQualityOfServiceBackground;
[[NSOperationQueue mainQueue] addOperation:saveImageBlockOperation];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.sessionTask = nil;
[self done];
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
As you can see, i store the imageData via my AppDelegate in CoreData. Instead of my way, you could give the ImageDownloadOperation a pointer to your NSMutableArray, then you can store the data direct in your array.
You could make an array of [UIImage new] then once the task is complete
replace the empty image images[i] = newImage
EDIT
NSMutableArray *imageArray = [NSMutableArray new];
for (int i=0; i<articleInfoJSONArray.count; i++) {
[imageArray addObject:[UIImage new]];
}
for (int i=0; i<articleInfoJSONArray.count; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
//download image
imageArray[i] = downloadedImage;
});
}
I have an URL, which when copied into a browser, displays an image. My function is supposed to download the image asynchronously.
- (UIImage *)downloadImage:(NSString *)imageURL
{
NSError *error = nil;
NSURL *urlString = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
__block UIImage *image;
if (!error) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
image = [UIImage imageWithData:data];
});
return image;
} else {
NSLog(#"%#", [error localizedDescription]);
}
return nil;
}
When I try to display the image in an UIImageView I get no errors, no nothing. I have NSLogged out both data and the imageURL passed in, and none of those are empty.
Any suggestions?
By calling dispatch_async, you're scheduling that work to happen later. Your function exits with nil before that work is done. You'll want to add a callback block to your function or make it block until you receive and process the image data.
Here is an example of a function with a block callback and how to use it.
- (void)downloadImageAtURL:(NSString *)imageURL withHandler:(void(^)(UIImage *image))handler
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
if (!error) {
UIImage *downloadedImage = [UIImage imageWithData:data];
handler(downloadedImage); // pass back the image in a block
} else {
NSLog(#"%#", [error localizedDescription]);
handler(nil); // pass back nil in the block
}
});
}
- (void)keyboardDidShow:(NSNotification *)aNotification {
[self downloadImageAtURL:#"" withHandler:^(UIImage *image) {
if (image) {
// display
} else {
// handle probelm
}
}];
}
The call to dataWithContentsOfURL:options:error: needs to be within the dispatch_queue block for it to be asynchronous. Any changes to the UI need to be in the mainThread. It should look something like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage* image = [[UIImage alloc] initWithData:imageData];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
//load image into UIImageView
}
});
}
});
You are not downloading the image asynchronously. Moreover, a method that is supposed to return a value in an async way cannot return that value through the method return value, but it should return it using a block.
You can try to do something like this:
- (void)downloadImage:(NSString *)imageURL onComplete:(void (^)(UIImage *, NSError * error))onComplete
{
NSURL *urlString = [NSURL URLWithString:imageURL];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:urlString options:NSDataReadingUncached error:&error];
image = [UIImage imageWithData:data];
if (onComplete) {
// Keep in mind that onComplete block will be called on a background thread.
// If you need to use it on UIImageView, you must set it on main thread.
onComplete(image, error);
}
});
}
Then, when you need to set the UIImageView image:
__weak typeof(self)selfB = self; // Better to use a weak reference inside blocks to avoid retain cycles
[self downloadImage:myURLString onComplete:^(UIImage * image, NSError * error) {
dispatch_async(dispatch_get_main_queue(), ^{ // As you can see, we use main thread for UI updates
selfB.imageView.image = image;
});
}];
I would like to be able to retrieve an image from parse like this:
-(UIImage *) image {
__block NSData * imageData;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
imageData = data;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return [UIImage imageWithData:imageData];
}
But since the block is executed on the mail thread and the semaphore is waiting on the main thread, the block is never executed. How can I redo my code? I need to able to return an image without a completionBlock since this method is called by a library I use.
You can try using the GCD, to parse and load in the background, then use the image in the main thread.
The code maybe like followings:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = nil;
__block NSData *imageData;
[self.imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
imageData = data;
}];
image = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
return image; // use your image here.
}
});
});
The completion block runs on the main thread... which is blocked waiting for your semaphore. You have a deadlock here. Have you considered using the synchronous getData in a background thread to fetch the information instead?
So I am taking this UIImage data and and converting to a string in base64. the problem is that it hangs on the UI thread whilst converting and I am not sure why.
- (void)processImage:(UIImage*)image{
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
[self.spinnerOutlet setAlpha:0.0f];
[self.spinnerOutlet startAnimating];
dispatch_async(myQueue, ^{
// Convert image
NSData *myData = [UIImagePNGRepresentation(image) base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString *myString = [NSString stringWithUTF8String:[myData bytes]];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
[self showSuccessAlertView:#"Success!" message:#"Submitting Image..."];
snapShotInBase64 = myString;
[self sendImagePostRequest];
});
});
}
Try this code:
- (void)processImage:(UIImage*)image{
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
[self.spinnerOutlet setAlpha:0.0f];
[self.spinnerOutlet startAnimating];
dispatch_async(myQueue, ^{
// Convert image
NSData *myData = [UIImagePNGRepresentation(image) base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString *myString = [NSString stringWithUTF8String:[myData bytes]];
snapShotInBase64 = myString;
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
[self showSuccessAlertView:#"Success!" message:#"Submitting Image..."];
});
});
dispatch_barrier_async(myQueue, ^{
[self sendImagePostRequest];
});
}
or
- (void)processImage:(UIImage*)image{
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
[self.spinnerOutlet setAlpha:0.0f];
[self.spinnerOutlet startAnimating];
dispatch_async(myQueue, ^{
// Convert image
NSData *myData = [UIImagePNGRepresentation(image) base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString *myString = [NSString stringWithUTF8String:[myData bytes]];
snapShotInBase64 = myString;
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
[self showSuccessAlertView:#"Success!" message:#"Submitting Image..."];
dispatch_async(myQueue, ^{
[self sendImagePostRequest];
});
});
});
}
hope will help. If you upload image in server, why you don`t use AFNetworking library