问题
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.
回答1:
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.
来源:https://stackoverflow.com/questions/53624466/urlcache-ios-storecachedresponse-works-asynchronously-how-to-catch-the-compl