Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 860
Дмитрий Акимов Avatar asked Dec 05 '18 02:12

Дмитрий Акимов


1 Answers

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.

like image 189
Coder ACJHP Avatar answered Oct 14 '22 06:10

Coder ACJHP