Refresh cached image in AlamofireImage? - ios

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')

Related

Does creating an INImage from a URL work?

I would like to use INImage.init(url:) for displaying a user profile image in a notification.
This initializer is documented with:
A URL that specifies an image file on a remote server. The image file can be in any format supported by the system, but it is recommended that you use PNG images.
However, as far as I can tell, this simply does not work – the image in the URL is never displayed. I have seen many other posts claiming that it does not work for them either.
Am I missing something or is this simply a bug?
I am working around it right now with the following extension:
extension INImage {
convenience init?(contentsOf url: URL) {
if let data = try? Data(contentsOf: url) {
self.init(imageData: data)
} else {
return nil
}
}
}
This doesn't seem great, because the image isn't cached and has to be downloaded every time a notification comes in, which seems really bad.

How do I release cached images when using UIImage(data:)?

I'm noticing heavy memory usage from my image cache in my collection view and need to understand how to release it. I understand the difference between UIImage(named:) and UIImage(contentsOfFile:). However, I'm using UIImage(data:) and I can't seem to find any documentation on releasing image caches in this instance. Any help appreciated. Here's my code snippet:
if let setImage = cell?.viewWithTag(101) as? UIImageView {
if let url = URL(string: imageURLs[indexPath.item]) {
let task = URLSession.shared.dataTask(with: url, completionHandler: { data, _, error in
guard let data = data, error == nil else {
print("No data detected: \(Error.self)")
return
}
DispatchQueue.main.async {
let newImageData = UIImage(data: data)
self.imageData[indexPath.item] = newImageData!
setImage.image = self.imageData[indexPath.item] as? UIImage
}
})
task.resume()
URLSession.shared.finishTasksAndInvalidate()
}
}
UIImage(data:) doesn’t store in the system image cache. So, if you remove all of the references to the associated images from your imageData, make sure the image views are getting released, etc., you should be good.
If imageData a simple collection, consider making it a NSCache, where you can constrain the total count or cost. Also, we often employ two tier caching mechanisms (with smaller in-memory limits, larger persistent storage caches).
You might consider using one of the many third party libraries (AlamofireImage, KingFisher, SDWebImage, etc.) as they tend to employ decent caching strategies, getting you out of the weeds of this. They all offer nice “asynchronous image” extensions to UIImageView, too. (For example, the implementation you’ve shared with us is going to suffer from backlogging issues if you scroll quickly through a big collection view, something that is easily tackled with these UIImageView extensions.) Your UICollectionViewDataSource really should not be burdened with this sort of code.
I was under the impression that Firestore auto-caching applied to cloud Storage, but it only applies to cloud Database. Once I implemented local caching with NSCache, my problem was solved.

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)

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

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.

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);

Resources