summaryrefslogtreecommitdiff
path: root/Sora/Data/ImageCacheManager.swift
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-18 12:33:44 -0800
committerFuwn <[email protected]>2026-02-18 12:33:50 -0800
commit0587c64598267ecace6259241198425cdc284f3a (patch)
treeba90872e872e90ae98096c1da81ceb5a66d5c476 /Sora/Data/ImageCacheManager.swift
parentrefactor: share folder menu hierarchy across views (diff)
downloadsora-testing-0587c64598267ecace6259241198425cdc284f3a.tar.xz
sora-testing-0587c64598267ecace6259241198425cdc284f3a.zip
perf: reduce suggestion and image handling hot-path overhead
Diffstat (limited to 'Sora/Data/ImageCacheManager.swift')
-rw-r--r--Sora/Data/ImageCacheManager.swift77
1 files changed, 57 insertions, 20 deletions
diff --git a/Sora/Data/ImageCacheManager.swift b/Sora/Data/ImageCacheManager.swift
index 8a14198..5561f24 100644
--- a/Sora/Data/ImageCacheManager.swift
+++ b/Sora/Data/ImageCacheManager.swift
@@ -1,4 +1,3 @@
-import Combine
@preconcurrency import SwiftUI
@MainActor
@@ -13,14 +12,13 @@ final class ImageCacheManager {
directory: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?
.appendingPathComponent("SoraImageCache", isDirectory: true)
)
- private var cancellables = Set<AnyCancellable>()
private let downloadQueue = OperationQueue()
private var preloadingURLs = Set<URL>()
private var memoryWarningObserver: NSObjectProtocol?
// MARK: - Initialisation
private init() {
- downloadQueue.maxConcurrentOperationCount = 5
+ downloadQueue.maxConcurrentOperationCount = 3
downloadQueue.qualityOfService = .utility
#if os(iOS)
@@ -38,46 +36,85 @@ final class ImageCacheManager {
// MARK: - Public Methods
func preloadImages(_ urls: [URL]) {
- let newURLs = urls.filter { !preloadingURLs.contains($0) }
+ let newURLs = urls.filter { url in
+ !preloadingURLs.contains(url) && cache.cachedResponse(for: URLRequest(url: url)) == nil
+ }
for url in newURLs {
preloadingURLs.insert(url)
- downloadQueue.addOperation {
- let cancellable = URLSession.shared.dataTaskPublisher(for: url)
- .map { CachedURLResponse(response: $0.response, data: $0.data) }
- .sink(
- receiveCompletion: { _ in
- DispatchQueue.main.async { [weak self] in
- self?.preloadingURLs.remove(url)
- }
- },
- receiveValue: { [weak self] cachedResponse in
- self?.cache.storeCachedResponse(cachedResponse, for: URLRequest(url: url))
+ downloadQueue.addOperation { [weak self] in
+ guard let self else { return }
+
+ let completionSignal = DispatchSemaphore(value: 0)
+
+ URLSession.shared.dataTask(with: url) { data, response, _ in
+ Task { @MainActor [weak self] in
+ guard let self else {
+ completionSignal.signal()
+ return
+ }
+
+ defer {
+ preloadingURLs.remove(url)
+ completionSignal.signal()
}
- )
- DispatchQueue.main.async { [weak self] in
- self?.cancellables.insert(cancellable)
+ guard let data, let response else { return }
+
+ storeCachedResponse(
+ data: data,
+ response: response,
+ for: url
+ )
+ }
}
+ .resume()
+
+ completionSignal.wait()
}
}
}
func clearCache() {
cache.removeAllCachedResponses()
- cancellables.removeAll()
preloadingURLs.removeAll()
}
+ func storeCachedResponse(data: Data, response: URLResponse, for url: URL) {
+ cache.storeCachedResponse(
+ CachedURLResponse(response: response, data: data),
+ for: URLRequest(url: url)
+ )
+ }
+
func getCachedResponse(for url: URL) -> CachedURLResponse? {
cache.cachedResponse(for: URLRequest(url: url))
}
+ func loadImageData(for url: URL) async -> Data? {
+ if let cachedResponse = getCachedResponse(for: url) {
+ return cachedResponse.data
+ }
+
+ do {
+ let (data, response) = try await URLSession.shared.data(from: url)
+
+ storeCachedResponse(
+ data: data,
+ response: response,
+ for: url
+ )
+
+ return data
+ } catch {
+ return nil
+ }
+ }
+
private func handleMemoryPressure() {
cache.removeAllCachedResponses()
downloadQueue.cancelAllOperations()
- cancellables.removeAll()
preloadingURLs.removeAll()
}