Closed dlindenkreuz closed 4 years ago
Hmm, I'm not sure this is the correct way to create URLs with data://
scheme. I though I had some tests for this scenario. There is nothing in Nuke that would've prevented data://
scheme to work.
Yeah, I think this is how you create a URL with an inline image https://stackoverflow.com/questions/23644193/can-i-create-an-nsurl-that-refers-to-in-memory-nsdata. URL(dataRepresentation: someImage.pngData()!, relativeTo: nil)
– this probably won't work. "No overview available" – the documentation is empty 🤦♂️
For the record: I was able to solve my problem by implementing a custom URLProtocol
that skipped the system's URLCache
because it does not store cached values synchronously.
A more elegant working solution is to wrap Nuke.DataLoader
with a custom DataLoading
protocol implementation:
private class NotCancellable: Cancellable {
func cancel() { /* noop */ }
static let shared = NotCancellable()
}
class CachingDataLoader: DataLoading {
private let dataLoader: DataLoader
static let sharedCache: NSCache<NSURL, CachedURLResponse> = {
let result = NSCache<NSURL, CachedURLResponse>()
result.totalCostLimit = 80 * 1024 * 1024 // Megabytes
return result
}()
init(dataLoader: DataLoader = DataLoader()) {
self.dataLoader = dataLoader
}
class func storeResponse(url: URL, data: Data, mimeType: String?) {
let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
let cachedResponse = CachedURLResponse(response: response, data: data, storagePolicy: .allowed)
sharedCache.setObject(cachedResponse, forKey: url as NSURL, cost: data.count)
}
class func removeResponse(url: URL) {
sharedCache.removeObject(forKey: url as NSURL)
}
func loadData(with request: URLRequest, didReceiveData: @escaping (Data, URLResponse) -> Void, completion: @escaping (Error?) -> Void) -> Cancellable {
if let url = request.url, let cached = Self.sharedCache.object(forKey: url as NSURL) {
// use cached values
didReceiveData(cached.data, cached.response)
completion(nil)
return NotCancellable.shared
}
// call actual data loader implementation
return dataLoader.loadData(with: request, didReceiveData: didReceiveData, completion: completion)
}
}
This way, I can generate URLs to files that have not been saved yet (as part of a UIDocument package), pass the raw image data to CachingDataLoader
, load the images with Nuke and purge the cache as soon as my document has been saved successfully and the files have been created.
NSCache
gives me a thread-safe configurable cache that works synchronously.
Yeah, I think this is how you create a URL with an inline image https://stackoverflow.com/questions/23644193/can-i-create-an-nsurl-that-refers-to-in-memory-nsdata
This would have worked too, but serializing large images as base64 strings is way too expensive.
For anyone using custom NSURLProtocol
/ URLProtocol
implementations: registering the protocol via URLProtocol.registerClass
will not work as Nuke uses URLSessionConfiguration
explicitly which will override system default behaviour.
Register your custom URLProtocols directly via the ImagePipeline
configuration instead:
ImagePipeline.shared = ImagePipeline {
let config = DataLoader.defaultConfiguration
config.protocolClasses = [SpecialURLProtocol.self]
$0.dataLoader = DataLoader(configuration: config)
}
When loading an image from a URL initialized with
URL(dataRepresentation: someImage.pngData()!, relativeTo: nil)
, the request fails withSome context: I would like to cache and process images that already exist in memory, but do not have a local file system or remote URL representation.
This is the case when dragging & dropping an image from Safari, where
NSItemProvider.loadObject(ofClass: UIImage.self, ...)
gives me aUIImage
object that is ready to go and does not need to be downloaded via HTTP anymore.