How to download many images with AFNetworking? - ios

I am making an app..
I need to download many pictures from a server, but I don't know how to do :(
I try to use this code:
UIImageView *image=(UIImageView *)[cell viewWithTag:100];
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:#"FileName"]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
image.image=[UIImage imageWithData:data];
});
data=nil;
});
This code is slow... I need to do it more quickly... I think AFNetworking is the best option, isn't it ?

You have several options, one of the AFNetworking. However, if you go with AFNetworking, you should use the UIImageView+AFNetworking class, inside the UIKit+AFNetworking folder.
Here's the documentation for it: http://cocoadocs.org/docsets/AFNetworking/2.0.1/Categories/UIImageView+AFNetworking.html
Another great option is SDWebImage which gives you more advanced control over caching and image handling, such as processing images before they're displayed, handling your own caching, etc.

AFNetworking would be perfect for your needs. It has its own UIImageView category that handles asynchronous image loading very smoothly. Give it a try.

Like previous answers have suggested, AFNetworking is a great way to go.
I too, though, want to suggest using SDWebImage. There are a lot of nice features in there, such as cool cache handling and image decompression.
For starters, try doing something like
[self.image setImageWithURL:[NSURL URLWithString:imagePath]
placeholderImage:[UIImage imageNamed:#"placeholder"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[image setImage:image];
}];
When it comes to speeding up the code, I am not sure why your code is slow in the first place since you are already doing it asynchronously.

AFNetworking is a fantastic framework and will make easy for you to do async image request.
However using AFNetworking will not speed up your code.
Probably will make your code slow, but the difference should be irrelevant, only if your images are too larger and the time to decode the image are high (I don't think this is the problem).

Related

how to handle online images in Parse

so I'm using Parse in my app as backend cloud service. I looked into Parse documents and see that they have PFFile method which can upload local images to Parse, and PFImageView class to retrieve remote image from Parse. But in my app, we have a lot of online images with URLs, and how can I easily display these images in my app without worrying about caches and all? Or is there any way to download and upload online images to Parse easily so that I can just use Parse's service?
Since you aren't loading images from Parse, there's no reason to use PFFile or PFImageView. Downloading and uploading them would be redundant if you know they will remain available at their respective URL's. This will allow you to load images asynchronously (in the background) from a URL:
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: #"http://myurl/mypic.jpg"]];
if ( data == nil )
return;
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageWithData: data]];
});
});
As far as cacheing goes, if your images are large you may notice a delay in their loading and may still need to cache. This is one of the most popular solutions:
https://github.com/path/FastImageCache

UIImageView+AFNetworking Managing Caching & Allocations

I am using the UIImageView+AFNetworkingin order to download and display an image from a URL in a UITableView. Everything is working great and my question is around the caching elements & allocations that are implemented with this call.
In using instruments to track memory I see that as I scroll my largest memory allocation quickly becomes this VM: Foundation which appears as if it is caching the images I am downloading in some way.
Which is great for user when the view the same image but I never see it release the memory. (I have not been able to get it to a memory warning yet). I just want to make sure then when needed this allocation will get released when needed. Below is the stack track of those VM : Foundation
Is there any need for me to monitor this and release the memory for the cache when needed to ensure smooth scrolling? Or is there anything else I should be watching to ensure this is handled properly?
Here is how I am calling for the images in cellForRowAtIndexPath I am calling the setImageWithURLRequest. Any advice or improvements would be appreciated.
NSString *imageURL = [NSString stringWithFormat:#"https://IMAGE-URL/%#",imageURL];
__weak MainTableViewCell *weakCell = cell;
NSURLRequest *urlRequest = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:imageURL]];
[cell.postImage setImageWithURLRequest:urlRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
MainTableViewCell *strongCell = weakCell;
strongCell.postImage.image = image;
[UIView animateWithDuration:.15 animations:^{
strongCell.postImage.alpha = 1;
}];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
}];
You really shouldn't worry about clearing image cache as AFNetworking handle this for you automatically.
It stores images internally in NSCache that as the docs say :
incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. The system automatically carries out these policies if memory is needed by other applications. When invoked, these policies remove some items from the cache, minimizing its memory footprint.
However, during AFNetworking development there were some troubles with this automatic removal behaviour, so finally manual cache clearing was added when receiving UIApplicationDidReceiveMemoryWarningNotification. But this all happens internally and you shouldn't do it yourself.

Cannot download images from remote server using SDWebImageManager on iOS

Want to achieve image gallery through UICollectionView. I have a set of image url's in an array, say around 20-30 url's. Using SDWebImageManager to download images and cache it and display on the collection view.
See my code below:
for(int i=0;i<[imagePath count];i++) {
[manager downloadWithURL:[NSURL URLWithString:[imagePath objectAtIndex:i]] options:0 progress:^(NSUInteger receivedSize, long long expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
if(image){
NSLog(#"SUCCESS");
NSString *localKey = [NSString stringWithFormat:#"Item-%d", i];
NSLog(#"%#",localKey);
[[SDImageCache sharedImageCache] storeImage:image forKey:localKey];
}
}];
}
All it does is, display the first image that has the url link indexed 0 in my imagePath array and the rest of the cell's remain blank. When tried to print it just displays SUCCESS once and Item-0. I guess its not going any further. It downloads just one image(first url's image in the array). Please help me with this. I am breaking my head on this from a long time. Not sure if i am on right track. Or please do suggest me other alternatives of achieving image gallery through multiple url's stored in an array.
I have to wonder what your cellForItemAtIndexPath is doing. If it's trying to retrieve the images from the cache directly, you might see precisely the behavior you describe (where the above code is running asynchronously and thus the images are not yet downloaded by the time cellForItemAtIndexPath tries to retrieve them from the cache).
Personally, I'd suggest that you do not even bother trying to populate your cache in advance. Just have cellForItemAtIndexPath just use the UIImageView+WebCache category method setImageWithURL and remove the for loop in your question altogether. It will automatically fill the cache for you and the problem of missing images will probably go away.
If you're having troubles retrieving your images, you should use one of the SDWebImage methods that passes a NSError object back to the block. For example:
[cell.imageView setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
if (error)
NSLog(#"%s: setImageWithURL error: %#", __FUNCTION__, error);
}];
If the URL address is not valid, you may see a 404 error.
If you're determined to use your for loop, a couple of other thoughts:
BTW, you are logging only if image is non-nil. You might want an else statement for your if statement fails (e.g. perhaps the error object can tell you something interesting). If an API provides an error object, you should avail yourself of it.
It's a little curious that are taking a downloaded image and adding it to the cache with another key; now you have two copies of the image in your cache, which is a wasteful use of space. Once you've downloaded an image, it's already in the cache, and if you try to retrieve the image again using the same URL, it will pull it from the cache. There's no point in creating another entry in the cache with your own key.
If you're not going to use the progress block, you can just pass nil for that parameter.

UIImages NSURLs and Threads

I am trying to build a nice function to access the network for images, if they are found on the web, I store them in a cache system I made.
If the image was already stored on the cache, I return it.
The function is called getImageFromCache and returns an image if it is in the cache, else, it would go to the network and fetch.
The code might look like this:
UIImageView* backgroundTiles = [[UIImageView alloc] initWithImage[self getImageFromCache:#"http://www.example.com/1.jpg"]];
Now, I am moving on to using threads because of big latencies due to network traffic. So I want images to show a temp image before I get the result from the web.
What I want to know is how can I keep track of so many images being accessed sequentially, being added to UIImageViews by this function (getImageFromCache).
Something just won't work there:
-(UIImage*)getImageFromCache:(NSString*)forURL{
__block NSError* error = nil;
__block NSData *imageData;
__block UIImage* tmpImage;
if(forURL==nil) return nil;
if(![self.imagesCache objectForKey:forURL])
{
// Setting a temporary image until we start getting results
tmpImage = [UIImage imageNamed:#"noimage.png"];
NSURL *imageURL = [NSURL URLWithString:forURL];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
imageData = [NSData dataWithContentsOfURL:imageURL options:NSDataReadingUncached error:&error];
if(imageData)
{
NSLog(#"Thread fetching image URL:%#",imageURL);
dispatch_async(dispatch_get_main_queue(), ^{
tmpImage = [UIImage imageWithData:imageData];
if(tmpImage)
{
[imagesCache setObject:tmpImage forKey:forURL];
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
// Couldn't build an image of this data, probably bad URL
[imagesCache setObject:[UIImage imageNamed:#"imageNotFound.png"] forKey:forURL];
});
}
else
return [imagesCache objectForKey:forURL];
return tmpImage;
}
This is not a direct answer to your question, but are you aware that there is no need to use GCD to download things asynchronously (on a background thread)? Just use NSURLConnection and its delegate methods. All your code will be on the main thread but the actual connection and downloading will happen in the background.
(And in fact I have written a class, MyDownloader, that takes care of all this for you:
http://www.apeth.com/iOSBook/ch37.html#_http_requests
Scroll down to the part about MyDownloader and its subclass MyImageDownloader, which is doing exactly the sort of thing you need done here. Moreover, note the subsequent code in that chapter showing how to use a notification when a download completes, prompting the table view that need these images to reload the row that contains the image view whose image has just arrived.)
its good your building it from scratch but if you want to save the all the work, there's a drop in Replacement SDWebImage Library with support for remote images coming from the web, and has all the functionality Like Temp Image, Asychronous Loading, Caching etc, you said you need
In your background thread, once the download has completed and you've saved the image to the cache, I'd suggest you post a notification using the NSNotificationCenter to let other parts of your app know that the cache has been updated.
This assumes that whichever part of the app manages the image views has registered its interest in those notification with the addObserverForName method. When it receives such a notification, it can then attempt to retrieve the images from the cache again and update its image views if appropriate.
Depending on the number of image views, you may want to pass through the image url in the notification in some way (e.g. in the userInfo dictionary), and then based on that decide which image views should be refreshed rather than refreshing them all.
I should add that I would also recommend getting rid of the inner dispatch_async call. There's no need for that, although you may need to add synchronisation to your cache object so it can be safely accessed from the main thread as well as the download thread.

Doesn't load picture from valid URL

UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
I use the code above and then check
if (image)
return image;
else
{
NSLog(#"no image on URL");
return nil;
}
But sometimes (very very seldom) I don't get an image from a valid url. The url is valid 100%.
Usually it takes nearly one second to load a picture, but when it can't load a picture the process takes much more time (20-200 seconds).
And then i get "no image on URL".
Is there a better way to get a picture from URL?
I'd rather get "no image on URL" in one second then waiting so long.
P.S. srry for my poor english
It is very rare that you want to use dataWithContentsOfURL:. It's a blocking call so requires a background thread. It's also inflexible and doesn't provide good error returns (which is the problem you're encountering).
See the URL Loading System Programming Guide. Generally you'll want to configure an asynchronous NSURLConnection for this kind of work. If you're doing a lot of network operations, you may want to consider a framework like MKNetworkKit or AFNetworking which handle a lot of the complexities for you.

Resources