I am using AlamofireImage in my project quite a lot and I use
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
imageView.af_setImageWithURL(URL)
to fetch an image from my CDN. I have done some tests but correct me if I am wrong, this seems to store the downloaded image in to a cache. My tests included downloading a 5mb image. The first time it took about 20 seconds, the second time was instant.
The thing I would like to know is how can I clear the cache for a specific URL/image and re-download the image?
Say for example I update a users profile pic. The image name/URL will be exactly the same but I know the image has changes as the user selected a new image from their library or camera. I know the image has been uploaded successfully to the CDN as I can see the new image in the folder directly on the CDN.
You need to remove the image from the in-memory cache as well as the on-disk cache. You can do this as follows:
func clearImageFromCache() {
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
let URLRequest = NSURLRequest(URL: URL)
let imageDownloader = UIImageView.af_sharedImageDownloader
// Clear the URLRequest from the in-memory cache
imageDownloader.imageCache?.removeImageForRequest(URLRequest, withAdditionalIdentifier: nil)
// Clear the URLRequest from the on-disk cache
imageDownloader.sessionManager.session.configuration.URLCache?.removeCachedResponseForRequest(URLRequest)
}
Currently, the URLCache can only be cleared in this manner on the master branch. I just pushed f35e4748 which allows access to the underlying sessionManager in the ImageDownloader. This is not yet available in an actual release yet, but should be here sometime this week.
Swift3 and AlamofireImage 3.x
It appears removeImage does all that is needed.
// Clear what is in the cache, this will force a refresh to ensure fresh image is loaded next time
let urlRequest = Foundation.URLRequest(url: validUrl)
let imageDownloader = UIImageView.af_sharedImageDownloader
if let imageCache2 = imageDownloader.imageCache {
_ = imageCache2.removeImage(for: urlRequest, withIdentifier: nil)
}
Hope this might help you:
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
let imageDownloader = UIImageView.af_sharedImageDownloader
let imageCache = imageDownloader.imageCache
// Setting CachePolicy as reloadIgnoringLocalCacheData so that it won't use URL Cache next time when it is hitting same URL
let urlRequest = URLRequest(url: URL, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData)
// Clear the Image from the in-memory cache
let _ = imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
imageView.af_setImage(withURLRequest: urlRequest, placeholderImage: UIImage(named: "placeholder"), completion: { (response) in
self.imageView.image = response.result.value
})
Swift 5:
func clearImageCache(forUrl urlString: String) {
guard let url = URL(string: urlString) else {
return
}
let imageDownloader = UIImageView.af_sharedImageDownloader
let urlRequest = URLRequest(url: url)
// Clear the URLRequest from the in-memory cache
_ = imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
// Clear the URLRequest from the on-disk cache
imageDownloader.session.sessionConfiguration.urlCache?.removeCachedResponse(for: urlRequest)
}
My solution for the caching issue:
func af_setImageIgnoreCache(string: String?) {
guard let url = string, let nsurl = URL(string: url) else { return }
let urlRequest = URLRequest(url: nsurl, cachePolicy: .reloadIgnoringCacheData)
let imageDownloader = ImageDownloader.default
if let imageCache = imageDownloader.imageCache as? AutoPurgingImageCache, let urlCache = imageDownloader.sessionManager.session.configuration.urlCache {
_ = imageCache.removeImages(matching: urlRequest)
urlCache.removeCachedResponse(for: urlRequest)
}
af_setImage(withURLRequest: urlRequest)
}
The response left by #cnoon was correct. The reason it isn't working for so many of you was because you were probably using some sort of filter and passing nil as the withIdentifier parameter.
I was using a circular filter in this scenario:
// Clearing the cache didn't work like this
imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
// Worked when adding a CircleFilter().identifier() as `withIdentifier` as such:
imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: CircleFilter().identifier)
Related
I am developing Restaurant app.
There are 340 foods data.
I am getting this data from backend that developed with Laravel.
App is working well in online.
But, although network is turned off, All foods data should be displayed in app.
So, I tried to save foods data to local.
It is good to save text data to local(exactly, UserDefaults and FileSystem).
But, when I try to save images to local from urls, It occur error and don't save to local exactly.
Have you ever seen such problems?
If yes, I appreciate your help.
Thanks
I don't know exactly what error you faced, but I think the answer to the link can help you.
How do I make JSON data persistent for offline use (Swift 4)
Additionally, image data would be good to be cached.
If you communicate based on URLSession, you can process caching as below.
Saving an Image to a Cache
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
Bring up cached images
let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString)
Code
class Cache {
static let imageCache = NSCache<NSString, UIImage>()
}
extension UIImageView {
func imageDownload(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
if let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async() { [weak self] in
self?.contentMode = mode
self?.image = cacheImage
}
}
else {
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
print("Download image fail : \(url)")
return
}
DispatchQueue.main.async() { [weak self] in
print("Download image success \(url)")
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.contentMode = mode
self?.image = image
}
}.resume()
}
}
}
To make caching more convenient, use a library called Kingfisher.
Kingfisher
You need to manage lazy loadings, you can use SDWebImages instead of manage it manually
SDWebImages will automatically manage your images caches and will also load them without internet here is a simple usage of it
add pod in podfile
pod 'SDWebImage'
usagae:
import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
tl;dr I have an OperationQueue and I want to have two operations running at the time. Those operations download something asynchronously hence they all get triggered at once instead of running one after another.
I fill a table of very large images by doing doing the following for each of the images:
public func imageFromUrl(_ urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(url: url as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let imageData = data as Data? {
DispatchQueue.main.async {
self.setImageData(imageData)
}
}
});
task.resume()
}
calling it like imageView.imageFromUrl(...).
On slower internet connections, the calls stack and it starts loading every image at once. The user then has to wait for the downloads to "fight" each other and is staring at a blank screen for a while before the images all appear at once (more or less). It would be a much better experience for the user if one image appeared after another.
I thought about queuing up the items, downloading the first of the list, drop it from the list and call the function recursively like this:
func doImageQueue(){
let queueItem = imageQueue[0]
if let url = NSURL(string: (queueItem.url)) {
print("if let url")
let request = NSURLRequest(url: url as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
print("in")
if let imageData = data as Data? {
print("if let data")
DispatchQueue.main.async {
queueItem.imageView.setImageData(imageData)
self.imageQueue.remove(at: 0)
if(self.imageQueue.count>0) {
self.doImageQueue()
}
}
}
});
task.resume()
}
}
This does load the images one after another, by I think it's a waste of time not to have at least 2 requests running at a time. Making my current implementation handle 2 images at the same time would result in big spaghetti code so I've looked into Swift's OperationQueue.
I would do
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2
for (all images) {
queue.addOperation {
imageView.imageFromUrl(imageURL
}
}
But this also triggers all the calls at once, probably due to the fact that the requests run asynchronously and the method call ends before waiting for the image to be downloaded. How can I deal with that? The app will also run on watchOS, maybe there is a library for this already but I don't think this should be too hard to achieve without a library. Caching isn't a concern.
Your original code with the operation queue and your original iamgeFromUrl method are all you need if you make one small change to imageFromUrl. You need to add a couple lines of code to ensure that imageFromUrl doesn't return until the download is complete.
This can be done using a semaphore.
public func imageFromUrl(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let semaphore = DispatchSemaphore(value: 0)
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
semaphore.signal()
if let imageData = data as Data? {
DispatchQueue.main.async {
self.setImageData(imageData)
}
}
});
task.resume()
semaphore.wait()
}
}
As written now, the imageFromUrl will only return once the download completes. This now allows the operation queue to properly run the 2 desired concurrent operations.
Also note the code is modified to avoid using NSURL and NSURLRequest.
I'm using the most recent version of Xcode (8.1 at time of writing), which uses Swift 3.0.
All I'm trying to do is take a string, convert it to a URL and test that URL to see if it gives me a 404 error. I've been able to make a URL and URLRequest by using:
let url = URL(string: fullURL)
let request = URLRequest(url: url!)
but I've found myself unable to get anything working beyond that. I've searched around for help, but most, if not all of it, is written in Swift 2.0, which I've tried to convert to no avail. It seems that even if you change the naming convention to remove the NS prefix, that isn't enough. I tried using:
let response: AutoreleasingUnsafeMutablePointer<URLRequest> = nil
but that gives me an error that "fix-it" makes worse by sticking question marks and semi-colons everywhere.
Apple's documentation isn't helping me much, either. I'm seriously at a loss.
Does anybody know how to correctly set up and test a URL for 404 status in Swift 3.0?
try this out to give you the status codes of the responses - 200, 404 etc:
let url = URL(string: fullURL)
let task = URLSession.shared.dataTask(with: url!) { _, response, _ in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
}
}
task.resume()
You could also do the same, simply replacing the with: url! to use the request var as you defined in your example e.g. let task = URLSession.shared.dataTask(with: request) {...} But in this example I don't think you need to really.
Simple example:
let url = // whatever
let session = URLSession.shared
let task = session.downloadTask(with:url) { loc, resp, err in
let status = (resp as! HTTPURLResponse).statusCode
print("response status: \(status)")
}
task.resume()
Here is one more example from delegate method
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask){
let responseStatusCode = (dataTask.response as! HTTPURLResponse).statusCode
}
Here is the example https://github.com/ankitthakur/SwiftNetwork/blob/master/Sources/Shared/SwiftNetwork.swift
I have a function which gets the data from an website api.
func getData() {
print("getData is called")
//create authentication ... omitted
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
task.resume()
}
I am able to get data from API, and I have an observer so that each time when the app goes to foreground the getData() function can be called again to update the data.
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "willEnterForeground", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func willEnterForeground() {
print("will enter foreground")
getData()
}
I am pretty sure that when my app goes to foreground, the getData() is called again, however, my data doesn't get updated even when data on the server API has changed. I tried to close the app and open it again, but it still doesn't update the data. So I was wondering if someone can give me some suggestions. Any help will be appreciated!
You need a to make it a closure with a completion. This will enable it to make the request and then call reload "Table View" once you have actually gotten the data back from the request. This is what you need to change on your getData() function
func getData(completion: (json) -> Void)) {
print("getData is called")
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
completion(json)
}
Then you will need to adjust where you call this function because you have changed the parameters it should look more like this.
func willEnterForeground() {
print("will enter foreground")
getData(completion:{
self.data = json
self.tableView.reloadData()
})
}
that will wait till the data is back before it reloads the view updating all your content to reflect what is on the server.
Let me know if you have more questions.
I finally found out it is because I didn't remove the cache. Change the cache policy works then.
I'm trying to pre-load some images with the following code:
let thumbnailUrl = NSURL(string: urlString)
let fetcher = NetworkFetcher<UIImage>(URL: thumbnailUrl)
Shared.imageCache.fetch(fetcher) {
println("Finished")
}
But afterwards when I try to set it to the imageview, it downloads it again from the network instead of reading it from the cache. This is the code:
self.imageView.hnk_setImageFromURL(
NSURL(string: urlString)
success: { thumbnail in
println("Finished setting image")
}
)
Is this a bug or maybe I missunderstood the usage of imageCache.fetch()?
PD: I put breakpoints in the whole code, and I can guarantee the key for the cache (in this case, the url) is exactly the same, so I have no clue why the cache isn't resolving when used with .hnk_setImageFromURL()
I was stuck on this for 3 days. I hope this answer might help someone else in the future.
The issue was that I didn't specify a Format Name to the fetcher, while the UIImageView.hnk_setImageFromURL() does. The way to fix it is:
let thumbnailUrl = NSURL(string: urlString)
let fetcher = NetworkFetcher<UIImage>(URL: thumbnailUrl)
let cache = Shared.imageCache
let format = self.imageView.hnk_format
cache.fetch(fetcher, formatName: format.name) {
println("Finished")
}