My app displays images in tableView cells What I want to achieve is to load the image in sequence even if the cell has disappeared off the screen. For example, I have cell 1 to 100, the cell is displayed and the images starts to download in background from 1 to 100 even if the user have already scrolled to cell 78. I'd also want to only download one to two image at a time so I don't bog up the network. With these criteria in mind, I was wondering what sort of setup would be most appropriate?
At the moment, I am using AlamoFire to download image. Would a combination of Alamofire with some sort of NSOperationQueue be suitable. I am very un familiar with NSOperationQueue at this stage. So I thought I'd try to find a recommended industry practice before I start going deep into setting NSOperationQueue
I'd also like to combine the functionality to continue the download even when the app is in background
Note. Any alternative library or framework solution is also welcome
The best solution out there is SDWebImage. The link to this repository is here.
If you wanna do it your own way without having any dependency then you have to follow the same things that SDWebImage does.
In short,
Asynchronously start download in thread, which downloads the images from the URL concurrently.
Then after downloading, use NSCache, to store the image and use the
imageURL as the key.
After storing the image in NSCache, also write the image in NSCacheDirectory, with the imagename same as that of its URL.
Now while fetching you have to check, whether the image exists in NSCache, using the imageURL as the key you can easily search that. If found, return image or then search NSCacheDirectory, whether the image exists there or not. If not then you must download the image and follow the steps once again.
Now NSCache is like the RAM. The data stored there will remain there as long as the Application is active. Once terminated, the data in NSCache will get cleaned.
Hence we also write the file in NSCacheDirectory, as data over here is persistent to as long as the app is not deleted from the device.
I hope I could make you understand as to how this thing works.
Cheers.
Related
I have an app which communicates with a server. In this app I have a tableview in which I display several people from my company (their first and last name and their profile image).
Every time the tableview opens or needs to refresh, I fetch the user list from my server. These users will all have an image_name, which I try to look up in an array on the app itself. If I can't find it there, I load it from the documents dir, if I can't find it there either I download it from my server and save in locally on the device to prevent future downloads.
This works very well and it's a very easy way to manage the users and their images, it also makes sure that I download an image only once if several users have the same image (e.g. the company logo when they haven't uploaded an image yet).
The problem is that I don't keep a reference to these users so the app has no clue which user uses which image OR even if an image is still in use.
So when person A has image X it will be downloaded to the iPhone. If user A then changes his image to Y, the app will download and display image Y correctly. However, image X will never get deleted from the persistent data.
I ask you, the stackoverflow community, what's the best way to handle this?
Should I start keeping a reference to my users so I can also keep a reference to the old image?
Is there any way to find the timestamp of the last time and image was read from the documents dir?
Should I store the image names in coreData and all the references to them? (some kind of custom ARC logic)
...
At some point in time you have the list of used images, at this point in time you also have a list of images saved to disk. Once per day you can take this information and, on a background thread, do a comparison of the used and saved and delete them. This shouldn't require any additional data storage.
If you wanted to allow images to hang around for a while after they stop being used you can 'touch' the file (update the fileModificationDate) each time you use the file and then later you can check the modification dates of all images and delete on that basis.
You could add a prefix to the image that you download and when you fetch images, check all images in persistent storage for this prefix and then remove if there are any. You should only need to delete (maximum) one image every time your client fetches, which wouldn't be too heavy on the client.
Sorry my english is bad.
I have a question about Grand Central Dispatch implementation.
I want to implement a slide show of photographs.
Imagine you have a list of urls to download photos from the cloud and show it.
My idea is to write the following concurrent code:
1) Download a fist photo
2) Show the picture and parallel start downloading the second
3) Wait 4 seconds or wait to download the photo (which last longer)
4) Go back to step 2
How I implement this in GCD?
Should I use Groups?
I think you should use SDWebImage.
It will remove your overhead of managing multithreading.
It is providing Asynchronous image downloader with cache support with an UIImageView category.
It provides track image download progress as well.
See Here.
One solution can be if using AFNetworking
Use UIImageView+AFNetworking ,once image downloaded with a url gets cached using AFImageCache (NSCache).
If you by chance use same url to download image but it will not opt for download but provide you cached image.
Take a look at this category of imageView UIImageView+AFNetworking's setImageWithURLRequest:placeholderImage:success:failure:
I have created a simple testing app to learn how to use NSURLSession. This App has to download images from a webservice and present them into a UITableView.
I've already written the first part of the App that reads a list of images urls from the web service, now, I want to display this list.
My doubt is:
given that the list of images could be a really long list, is it ok to create a NSURLSessionDownloadTask for each image?
I thought to create the session in the cellForRowAtIndexPath function and store the NSURLSessions in a NSDictionary using as key the IndexPath of the cell (and probably relying on NSURLCache to avoid to download the same images more than once).
Other solutions:
I can see three more solutions:
Using GCD with dispatch_async
Subclassing NSOperation and essentially store an NSOperation for any image I need to download.
Using a third party library like AFNetwork... but since it is a learning purpose app I prefer to go completely with my code
.
If the multiple NSURLSession isn't a good solution, I'd choose one of those options.
What do you think about this approach?
NSURLSessionTask is fine for a large number of downloads. One advantage of it over some of the other methods you mentioned is that downloads can be cancelled or paused. It also correctly implements concurrency for network operations, which is more difficult than many cats on the internet will lead you to believe (if you don't believe me, view the eskimo's 2010 WWDC session and sample code. NSOperation for network connections is not trivial).
NSURLSessionTask and friends are designed for exactly the kinds of problems you are trying to solve, and it's very well tested.
For a tableview, start the task in tableView:willDisplayCell:forRowAtIndexPath: and cancel (or pause) a task in tableView:didEndDisplayingCell:forRowAtIndexPath:. That will limit the active downloads to the currently visible cells.
Suggestion:
I also came across a similar situation were I need to download about 2000 Image files and 100 Video files. For that purpose I implemented a custom download manager using NSOperationQueue and blocks.
I have added this library to GitHub, please feel free to check the implementation.
IMO whilst it is ok to create an NSURLSessionTask for each image a standard first in first out implementation will cause problems when scrolling through your cells. The reason for this is that downloads will be queued on your NSURLSession and tasks will be executed in the order they've been added to the queue, in other words in a FIFO manner. Imagine a scenario where you've scrolled through a vast number of cells and you have to wait for all downloads to complete in order. You would not only have to wait a long time, you would be making unnecessary network requests for image assets that may no longer be relevant to your user.
Nick Lockwood created a great NSOperationQueue subclass called NSOperationStack that reverses the order of operations so that the the last operation is executed first (LIFO). IMO for a large number of downloads a LIFO implementation is a must.
NSOsperationStack is available here
If you combine this with an implementation that uses cellForRowAtIndexPath to initiate and NSURLCache to store downloads, you should end up with a very streamlined and efficient solution.
I would use (or at least take a look at) SDWebImage's SDWebImageManager.
Besides downloading you can set priority and continue in the background options which I think you'll want to have.
I have created an app in which users will be able to upload the images and they can also see all the images uploaded by other users. I have integrated the code for the pagination and downloading the 10 images at a time and showing them on the UITableView. I have used AFNetworking for this task which is saving the image in the cache memory. The problem is that when user keeps downloading the images and count goes to around 300 images, app crashes because the device runs out of memory. I am looking for the best solution of this issue. What I have thought is to keep 50 images in the cache at a time and when user downloads the newer images, older ones will be deleted from the cache. Please also let me know if I can do this with AFNetworking.
You should use SDWebImage instead of downloading images it will cache and you can customise that caching option also .
Iam using this SDWebImage for caching 150 +images in UICollectionview and same in UITableview and its work perfect.
Initially we need to add a placeholder then it will appear one by one.
implementation
1.Take out the SDWebImage code
SDWebImage
2.Import the header file in to your viewcontroller
#import <SDWebImage/UIImageView+WebCache.h>
3.then add a single line of code inside cellforrowindex() method. i will gve you example code snaps
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Try APSmartStorage. You can manage number of images stored in memory. And if 'memory warning' occurred images will be removed from memory. But anyway it's ok because all images stored in files, so you don't need to reload them from network.
As #Aklesh Rathaur mentioned in comments, you could try SDWebImage, which is also used by Facebook on their mobile app.
It does a pretty good job at caching and freeing memory when more memory is required. Quoting How is SDWebImage better than X?
On the other side, SDWebImage caches the UIImage representation in memory and store the original compressed (but decoded) image file on disk. UIImage are stored as-is in memory using NSCache, so no copy is involved, and memory is freed as soon as your app or the system needs it.
Let us know your results :-)
I am working on an app where I need to download lots of images locally (so that they are available offline). The number of images can be 100 - 10,000. Each image may vary from 100K- 250K
I can do this via a NSOperationQueue and I have the code to make this work already but this question is more of a conceptual nature. I am not sure what is the best approach to take here.
1) Do I download all images as soon as the user logs in for the first time ? Based on the number of images, this could take a long time and what if the user closes the app meanwhile. I understand there is a limit on the time that can be spent by a background process in this case? Honestly, I dont want to do anything in the background (ie when app is closed)
2) Do I download images when a particular category is selected by the user? If a category has 800 images, then what happens if the user selects another category before all of those 800 images are finished downloading? I can always start threaded downloaded but will the thread keep on running if the user selects another category ?
3) Put something in "Settings" to let the user decide this themselves. Something like "Total Images: 8000" Images Available : 2000 and a button to say "Download All" which would display a UIProgressView of what's going on....so the user will probably wait till it's all done.
Or some other approach?
Thoughts?
As far as I understand from your description, including comments, I think that the best approach would be downloading all of your images in a thread and make the download process resumable. In this way, you are going to mirror a remote database of images for offline use.
What this entails is:
you keep track of which images you have downloaded;
each time the app starts/resumes, you start the downloading thread exactly from where you left;
you should also provide a mechanism so that the user is suitably informed when he is trying to access an image which has not been downloaded yet, but I think this should be no problem (you might also provide a progress indicator somewhere).
I would only download the image when the user actually needs to do something with the image. Your users will hate you if you download all the images upfront. :)