URLCache (iOS). storeCachedResponse works asynchronously. How to catch the completion?
Asked Answered
I

1

5

Just discovered that the function storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) works asynchronously. That is, the result is not returned immediately after execution. I did not find a description of this in the official documentation. See example:

cache = URLCache(memoryCapacity: 0, diskCapacity: 100 * 1024 * 1024, diskPath: "myCache")
let config = URLSessionConfiguration.default
config.requestCachePolicy = .returnCacheDataElseLoad
config.urlCache = cache
let session = URLSession(configuration: config)
session.dataTask(with: request, completionHandler: {[unowned self]
    (data, response, error) in

    if let data = data, let response = response, ((response as HTTPURLResponse)?.statusCode ?? 500) < 300 {
        let cachedData = CachedURLResponse(response: response, data: data)                                            
        self.cache.storeCachedResponse(cachedData, for: request)
        let testCachedData = self.cache.cachedResponse(for: request)
    }
}

Theoretically testCachedData must contain cached response. But what it actually contains:

testCachedData?.response.url // Ok
testCachedData?.isEmpty // false
testCachedData?.data // 0 bytes!!!

Although testCachedData?.data says it contains 0 bytes, we can write this data to a file, and this file will contain real data, not 0. If we deep into local cache directory (~/Library/Caches/myApp/MyCache) when pausing at breakpoint right after cachedResponse call, we can see that folder with cached files (fsCachedData) doesn't exist yet. Now let's insert delay between storeCachedResponse and cachedResponse:

cache = URLCache(memoryCapacity: 0, diskCapacity: 100 * 1024 * 1024, diskPath: "myCache")
let config = URLSessionConfiguration.default
config.requestCachePolicy = .returnCacheDataElseLoad
config.urlCache = cache
let session = URLSession(configuration: config)
session.dataTask(with: request, completionHandler: {[unowned self]
    (data, response, error) in

    if let data = data, let response = response, ((response as HTTPURLResponse)?.statusCode ?? 500) < 300 {
        let cachedData = CachedURLResponse(response: response, data: data)                                            
        self.cache.storeCachedResponse(cachedData, for: request)
        delay(5) // JUST 5 SEC DELAY
        let testCachedData = self.cache.cachedResponse(for: request)
    }
}

Now:

testCachedData?.response.url // Ok
testCachedData?.isEmpty // false
testCachedData?.data // contains bytes

So, after 5 sec delay we see that cached files folder (fsCachedData) exists and contains cached file (e.g. D8A30D21-C8F1-4FCA-967E-F6B440998173).

The point is how to catch the completion of storeCachedResponse?

I'm going to use cached files right after they are created. Moreover, I'm going to handle cached files directly, and it's not the best solution to set delay.

Incident answered 5/12, 2018 at 2:52 Comment(4)
there is no point to get value after setting on previous line ? you already have response in that function just use response and outside this function whereever you want to use then read from cache.Defy
Can You be more precise? Which function do You mean? Function or closure?Volost
self.cache.storeCachedResponse(cachedData, for: request) delay(5) // JUST 5 SEC DELAY let testCachedData = self.cache.cachedResponse(for: request)Defy
The subject matter of this topic is that the instance of response appears before the data being actually cached to file. And there is no way to use response immediately after it's appearance.Volost
F
16

Actually I couldn't understand why you calling cached data immediately after caching!? In my opinion you should call cached data before requesting url with session if data is exist return cached data else request from the scratch.

For example :

private let allowedDiskSize = 100 * 1024 * 1024
private lazy var cache: URLCache = {
    return URLCache(memoryCapacity: 0, diskCapacity: allowedDiskSize, diskPath: "gifCache")
}()

typealias DownloadCompletionHandler = (Result<Data,Error>) -> ()

private func createAndRetrieveURLSession() -> URLSession {
    let sessionConfiguration = URLSessionConfiguration.default
    sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
    sessionConfiguration.urlCache = cache
    return URLSession(configuration: sessionConfiguration)
}

private func downloadContent(fromUrlString: String, completionHandler: @escaping DownloadCompletionHandler) {

    guard let downloadUrl = URL(string: fromUrlString) else { return }
    let urlRequest = URLRequest(url: downloadUrl)
    // First try to fetching cached data if exist
    if let cachedData = self.cache.cachedResponse(for: urlRequest) {
        print("Cached data in bytes:", cachedData.data)
        completionHandler(.success(cachedData.data))

    } else {
        // No cached data, download content than cache the data
        createAndRetrieveURLSession().dataTask(with: urlRequest) { (data, response, error) in

            if let error = error {
                completionHandler(.failure(error))
            } else {

                let cachedData = CachedURLResponse(response: response!, data: data!)
                self.cache.storeCachedResponse(cachedData, for: urlRequest)

                completionHandler(.success(data!))
            }
        }.resume()
    }
}

And usage:

self.downloadContent(fromUrlString: ANY_URL, completionHandler: { (result) in

            switch result {
            case .success(let yourData):
                // handle data

            case .failure(let error):
                debugPrint(error.localizedDescription)
            }
 })

First time it will fetch data from the web and in second request it will return cached data immediately.

Felder answered 29/4, 2019 at 19:36 Comment(6)
Great answer. Thank You, Coder ACJHPVolost
You're welcome, if it's correct answer don't forget to please accept it.Felder
A very good example of how CacheURLResponse and storeCachedResponse works. Thanks @CoderACJHP.Wimbush
What is the purpose of the usage section? @CoderACJHPWimbush
Just for exampleFelder
Yeah I figured later. I had to tweak this function a little for my own usage but thanks a ton for this answer it really helped me understand the API URLCache really fast.Wimbush

© 2022 - 2024 — McMap. All rights reserved.