Caching downloaded images: FileManager / CachePolicy / URLCache / NSCache? - ios

I'm need to implement the common scenario of having a table view with N cells where each of those cells needs to download an image to be displayed within it.
The service protocol to call to download the images could be either HTTP or HTTPS.
I am using an URLSessionDownloadTask this way:
func downloadImage(urlStr: String, completion: #escaping (UIImage?, Error?) -> Void) {
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
let task = session.downloadTask(with: request, completionHandler: {
(fileUrl, response, error) in
// Call 'completion' depending on result
})
task.resume()
}
Where session is an URLSession with default configuration and associated operation queue:
self.session = URLSession(configuration: configuration, delegate: nil, delegateQueue: self.operationQueue)
So, what I want is to avoid to download an image that was already downloaded. And I would like them to have some expiration time.
I've read some articles and posts and I'm not completely clear about the differences between the options I found:
A. Using FileManager to actually store the image as a file, and removing it after checking the expiration time.
B. Setting the cachePolicy property of URLRequest.
C. Using URLCache
D. Using NSCache
About A:
What is actually the difference between storing the image as file and using a cache? Could have the file storage offer any benefit that a cache does not? Those images are not user-related, I can download them from a server when needed.
About B:
I read Apple's documentation about that, but I don't fully understand if for my scenario I should use NSURLRequestUseProtocolCachePolicy.
How does this option actually work? It is enough to set the policy and then you don't have to care about anything else? How does the URLRequest now that the image is asked for download has been already downloaded and cached?
About C:
How does it should be correctly implemented? Could anybody provide me an example/tutorial in case this is the best approach? What about expiration date?
About D:
I found an example I understood, but would it be a good approach having the previous options? What about expiration date also here?
In summary: which of the options would be the most efficient and appropriate for my scenario, and why?

From what I inferred about your question "what I want is to avoid to download an image that was already downloaded. And I would like them to have some expiration time."
To avoid images from downloading again, you can implement the following use case where, you store the images in the NSCache using the urls of the image itself.
This would be something like as discussed in the link.
For the expiration time case, if you want to remove all images at a particular expiration time, then just make a check for that scenario and empty the cache.
For the case where you want to remove the individual images, based on their expiration time, you can check the response from the server for the expiration key, and again remove cache in case the limit has been breached.

Related

Setting DownloadRequest's URL

I'm developing a resume function for file downloads. It looks good for the most of it (getting the resume data of the cancelled request, etc.), but when I'm creating the request for resuming the download, its URL is nil.
The code for creating the request is pretty straightforward:
let downloadRequest = sessionManager.download(resumingWith: resumableData)
The downloadRequest object has a URLRequest variable but it is get-only.
I've read the doc but found no answer there.
Please note that the first request (the one that was cancelled) was created using this code:
let dataRequest = sessionManager.request(urlRequest)
How is the DownloadRequest object getting its url? Am I missing something obvious?
The answer lies in the first request made. When first starting to download the file, even if you don't plan to resume it later, you need to create an Alamofire.DownloadRequest (URLSessionDownloadTask) and not an Alamofire.DataRequest (URLSessionDataTask). The rest is the same: you capture the resume data when it fails (or if you cancel it by producing resume data), then you create the next request with that data.
Hope this helps!

Does Firebase Storage's getMetaData() call the backend (and thus introduce a small delay)?

I currently use the following (Swift 4) code to download an image stored on the Firebase Storage :
func getImage(completion: #escaping (UIImage?)->()) {
let ref = Storage.storage().reference().child("myImage.jpg")
ref.getMetadata() {
(metadata, error) in
guard let url = metadata?.downloadURLs?.first, error == nil else {
print(String(describing: error))
completion(nil)
return
}
//got url download image here and return it
//this function is not important but it does it asynchroniously
func downloadImageOrReturnACachedVersionOfItBy(url, completion)
}
I notice a considerable delay in downloading the image, even if downloadImageOrReturnACachedVersionOfItBy(...) actually takes care of image cashing
Questions:
Does the function getMetadata() contact the backend each time its called?
And thus can this introduce a round-trip web service calling delay?
If so...to avoid this, would it be a good idea store the returned url, when the image was uploaded, returned in the metadata from Firebase putData() locally in the app? Are these download url's fixed for life? Are the automatically being invalidated somehow?
1) Does the function getMetadata() contact the backend each time its
called? And thus can this introduce a round-trip web service calling
delay?
Yes, this is because the download URL may have changed (e.g. developer revoked it), so we need to fetch it again.
Note that we have a downloadURL() method that does the same thing but without you having to parse metadata manually.
2) If so...to avoid this, would it be a good idea store the returned
url, when the image was uploaded, returned in the metadata from
Firebase putData() locally in the app? Are these download url's fixed
for life? Are the automatically being invalidated somehow?
I'd recommend using the FirebaseUI integration with SDWebImage, which automatically caches and displays these images.
// Reference to an image file in Firebase Storage
let reference = storageRef.child("images/stars.jpg")
// UIImageView in your ViewController
let imageView: UIImageView = self.imageView
// Placeholder image
let placeholderImage = UIImage(named: "placeholder.jpg")
// Load the image using SDWebImage
imageView.sd_setImage(with: reference, placeholderImage: placeholderImage)

Refresh cached image in AlamofireImage?

Is it possible to automatically remove cached image and download newer if image was updated on server? I have tried AlamofireImage and STXImageCache but they both download an image only once and do not update it.
I try like that:
private let downloader = ImageDownloader()
func downloadImage(path: String?, completion: #escaping (UIImage?) -> ()) {
guard let path = path else { return }
let urlRequest = URLRequest(url: URL(string: path)!)
downloader.download(urlRequest) { response in
completion(response.result.value)
}
}
Manual update is not so good also, because if I don't know if an image was updated on server I have to forget about image caching at all.
Looks like none of available pods are able to refresh cached images, or any other cached content, when the content is changed on server. It is technically possible due to Content-Control technology, but apple does not seem to provide this possibility https://developer.apple.com/documentation/foundation/nsurlrequest.cachepolicy/1414422-reloadrevalidatingcachedata
reloadRevalidatingCacheData
Specifies that the existing cache data may
be used provided the origin source confirms its validity, otherwise
the URL is loaded from the origin source.
This constant is unimplemented and shouldn’t be used.
Your best move would be to ask server developer to generate unique URL addresses to any image that will change(avatar, backgrounds, icons etc), or if server developer is not available you should just get rid of the caching in places where there is such a problem and use casual download.
In any case you can use my gist for downloading images cached and not cached here https://gist.github.com/sam-moshenko/562ec61431c4a0ebeb68899b4d1b4d26 (Just don't forget to install pod 'AlamofireImage')

Cache Image from Firebase

I have multiple cells that currently hold different photos from Firebase. Every time a user loads these images then scrolls, they are re-downloaded which eats up data fast. I find this concerning to any user who has a metered data plan. What could I do to solve this? Does Firebase offer any options to cache downloaded images?
This is how I am currently calling an image into a cell:
if let imageName = post["image"] as? String {
let imageRef = FIRStorage.storage().reference().child("images/\(imageName)")
imageRef.data(withMaxSize: 25 * 1024 * 1024, completion: { (data, error) -> Void in if error == nil {
let image = UIImage(data: data!)
cell.postImageView.image = image
Use Kingfisher to cache images. It's light and very easy to use. Just pass your url from firebase and it will automatically cache it.
let url = URL(string: "url_of_your_image")
imageView.kf.setImage(with: url)
You might use Alamofire too. It does not handle caching automatically though, but against Kingfisher, it has the ability to handle almost all kinds of networking needs.
PS: Yes I know that -generally- I do not need any networking capabilities if I'm using Firebase.
But, for example; since Firebase Database and Firestore cannot handle full-text search, you need to use third-party solutions, so, you might be in need of full-featured networking utility sometime.
Firebase already does cache the database locally, before it fetch real-time data from the server, so the problem is not as severe. But if you want to do better caching, use Glide, Glide caches images and you can specify time signatures so it re-fetches images only if they are updated.
This is super easy, but you do need to host your images in google cloud services, or aws, or anywhere even
ImageView imageView = (ImageView) findViewById(R.id.my_image_view);
Glide.with(this).load("url").into(imageView);

Is there a way to change the url path of a PDF manually and have it update real time?

I'm building an app for my school which includes a lunch menu, however since the lunch menu changes every month I don't want to keep updating my app just for that. I know you can open PDFs with Xcode either locally or online, but is there a way to change the url path of the pdf manually and have it update real time within the app. Any help would be much appreciated and I'm new to developing IOS apps, so any links that may help would be great.
If you control the website or have a line of communication with the person who does then you could agree that the latest menu always gets returned from example.com/current/lunch.pdf but really this should be set up as a GET request example.com/index.php?lunchmenu=current so that it is down to the website to respond with the file or the file location rather than the app's responsibility to guess the filename. So for example:
if let url = NSURL(string: "http://www.example.com/index.php?lunchmenu=current") {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let dataTask = session.dataTaskWithURL(url, completionHandler: {(d,_,_) in
if let data = d {
String(data:data, encoding:NSUTF8StringEncoding) // this is could be your file name
}
})
dataTask.resume()
}
It would then be possible to perform other requests like: example.com/index.php?lunchmenu=nextmonth. Working in this way means that if for some reason the naming pattern or location of the files changed your app would still keep working.
If you don't have access to the school website but do know where the file is stored, you could query your own website for the file information and either update this automatically (or if there was no other way - manually). Again, this would prevent an app update if the structure of the school website changed.
Only rely on static file locations and naming systems if you really have to.

Resources