From ec5d74dcb322304f2b304d6380126ff0839b551d Mon Sep 17 00:00:00 2001 From: Darren Ford Date: Tue, 26 Apr 2022 17:14:23 +1000 Subject: [PATCH 1/2] Added a URLSession cache for remote data downloading When working with a tv series, I found that the loading of the poster information for each tv episode re-loads ALL of the posters for the series for each episode, even if the poster data was the same for the previously selected episode. Adding a URL cache to the URLSession to avoid re-downloading (ive fixed the cache size to 128 meg) makes working with an entire TV series a MUCH smoother experience. For example, for a 'popular' tv series, seeing the first poster for a selected search result might take 10-20 seconds (as it was downloading 20-30 posters in the background before displaying). For each episode in the series, it would re-download the same URL data for the posters. Adding the cache here means it is only downloaded once. --- Framework/src/RemoteData.swift | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Framework/src/RemoteData.swift b/Framework/src/RemoteData.swift index 39f8704c..1a0c7f62 100644 --- a/Framework/src/RemoteData.swift +++ b/Framework/src/RemoteData.swift @@ -7,6 +7,25 @@ import Foundation +fileprivate class RemoteCaching { + // Used a session that caches its results + static let remoteLoadSession: URLSession = { + // Create URL Session Configuration + let configuration = URLSessionConfiguration.default.copy() as! URLSessionConfiguration + + // Set the in-memory cache to 128 MB + let cache = URLCache() + cache.memoryCapacity = 128 * 1024 * 1024 + configuration.urlCache = cache + + // Define Request Cache Policy + configuration.requestCachePolicy = .useProtocolCachePolicy + configuration.urlCache = cache + + return URLSession(configuration: configuration) + }() +} + @objc public class RemoteData : NSObject { private static let queue = DispatchQueue(label: "io.metaz.RemoteDataQueue") @@ -97,8 +116,7 @@ import Foundation if self.data == nil { return; } - - URLSession.dataTask(url: url) { (d, resp, err) in + RemoteCaching.remoteLoadSession.dataTask(with: url) { (d, resp, err) in if let error = err { let info = [NSLocalizedDescriptionKey: error.localizedDescription] let statusCode = (resp as? HTTPURLResponse)?.statusCode ?? 0 From d6a42540c45fc96e0c89e476d8ba98603e036c89 Mon Sep 17 00:00:00 2001 From: Darren Ford Date: Tue, 26 Apr 2022 17:27:09 +1000 Subject: [PATCH 2/2] Support multithreaded downloads for RemoteData All posters for a search selection are downloaded before the first one is shown in the UI. For popular tv series, there can be 20 or more posters for the series which are all downloaded before the 'first' poster appears in the UI. Currently, the posters are downloaded serially which means that on a slow-ish connection to tvDB (for eg) this can take quite a while before the 'first' poster appears in the UI. This patch moves patch downloading onto the global 'utility' thread which allows for multiple simultaneous downloads, limiting the number of simultaneous downloads to 10. --- Framework/src/RemoteData.swift | 43 +++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/Framework/src/RemoteData.swift b/Framework/src/RemoteData.swift index 1a0c7f62..8fd6099c 100644 --- a/Framework/src/RemoteData.swift +++ b/Framework/src/RemoteData.swift @@ -7,28 +7,27 @@ import Foundation -fileprivate class RemoteCaching { - // Used a session that caches its results - static let remoteLoadSession: URLSession = { - // Create URL Session Configuration - let configuration = URLSessionConfiguration.default.copy() as! URLSessionConfiguration +// Limit the number of simultaneous downloads +private let RemoteDownloadLimiter = DispatchSemaphore(value: 10) - // Set the in-memory cache to 128 MB - let cache = URLCache() - cache.memoryCapacity = 128 * 1024 * 1024 - configuration.urlCache = cache +// A URLSession that caches its results +private let RemoteLoadSession: URLSession = { + // Create URL Session Configuration + let configuration = URLSessionConfiguration.default.copy() as! URLSessionConfiguration - // Define Request Cache Policy - configuration.requestCachePolicy = .useProtocolCachePolicy - configuration.urlCache = cache + // Set the in-memory cache to 128 MB + let cache = URLCache() + cache.memoryCapacity = 128 * 1024 * 1024 + configuration.urlCache = cache - return URLSession(configuration: configuration) - }() -} + // Define Request Cache Policy + configuration.requestCachePolicy = .useProtocolCachePolicy + configuration.urlCache = cache + + return URLSession(configuration: configuration) +}() @objc public class RemoteData : NSObject { - private static let queue = DispatchQueue(label: "io.metaz.RemoteDataQueue") - public let url : URL public let expectedMimeType : String @@ -110,13 +109,19 @@ fileprivate class RemoteCaching { let url = self.data!.url let expectedMimeType = self.data!.expectedMimeType - RemoteData.queue.async { + + DispatchQueue.global(qos: .utility).async { + RemoteDownloadLimiter.wait() + defer { + RemoteDownloadLimiter.signal() + } + var downloadData : Data?, responseError : NSError? let signal = DispatchSemaphore(value: 0) if self.data == nil { return; } - RemoteCaching.remoteLoadSession.dataTask(with: url) { (d, resp, err) in + RemoteLoadSession.dataTask(with: url) { (d, resp, err) in if let error = err { let info = [NSLocalizedDescriptionKey: error.localizedDescription] let statusCode = (resp as? HTTPURLResponse)?.statusCode ?? 0