make resolver configurable

This commit is contained in:
mertalev
2025-09-11 15:15:20 -04:00
parent 9ccd98d871
commit a07f3c2ba4
2 changed files with 80 additions and 71 deletions
@@ -12,55 +12,59 @@ class AssetRequest: Request {
} }
class AssetResolver { class AssetResolver {
private static let requestQueue = DispatchQueue(label: "assets.requests", qos: .userInitiated) private let requestQueue: DispatchQueue
private static let processingQueue = DispatchQueue(label: "assets.processing", qos: .userInitiated) private let processingQueue: DispatchQueue
private static var batchTimer: DispatchWorkItem? private var batchTimer: DispatchWorkItem?
private static let batchLock = NSLock() private let batchLock = NSLock()
private static let batchTimeout: TimeInterval = 0.00025 // 250μs private let batchTimeout: TimeInterval
private static let fetchOptions = { private let fetchOptions: PHFetchOptions
let fetchOptions = PHFetchOptions() private var assetRequests = [AssetRequest]()
fetchOptions.wantsIncrementalChangeDetails = false private let assetCache: NSCache<NSString, PHAsset>
return fetchOptions
}()
private static var assetRequests = [AssetRequest]()
private static let assetCache = {
let assetCache = NSCache<NSString, PHAsset>()
assetCache.countLimit = 10000
return assetCache
}()
static func requestAsset(request: AssetRequest) { init(fetchOptions: PHFetchOptions, batchTimeout: TimeInterval = 0.00025, cacheSize: Int = 10000, qos: DispatchQoS = .unspecified) {
self.fetchOptions = fetchOptions
self.batchTimeout = batchTimeout
self.assetCache = {
let assetCache = NSCache<NSString, PHAsset>()
assetCache.countLimit = cacheSize
return assetCache
}()
self.requestQueue = DispatchQueue(label: "assets.requests", qos: qos)
self.processingQueue = DispatchQueue(label: "assets.processing", qos: qos)
}
func requestAsset(request: AssetRequest) {
requestQueue.async { requestQueue.async {
if (request.isCancelled) { if (request.isCancelled) {
request.completion(nil) request.completion(nil)
return return
} }
if let cachedAsset = assetCache.object(forKey: request.assetId as NSString) { if let cachedAsset = self.assetCache.object(forKey: request.assetId as NSString) {
request.completion(cachedAsset) request.completion(cachedAsset)
return return
} }
batchLock.lock() self.batchLock.lock()
if (request.isCancelled) { if (request.isCancelled) {
batchLock.unlock() self.batchLock.unlock()
request.completion(nil) request.completion(nil)
return return
} }
assetRequests.append(request) self.assetRequests.append(request)
batchTimer?.cancel() self.batchTimer?.cancel()
let timer = DispatchWorkItem(block: processBatch) let timer = DispatchWorkItem(block: self.processBatch)
batchTimer = timer self.batchTimer = timer
batchLock.unlock() self.batchLock.unlock()
processingQueue.asyncAfter(deadline: .now() + batchTimeout, execute: timer) self.processingQueue.asyncAfter(deadline: .now() + self.batchTimeout, execute: timer)
} }
} }
private static func processBatch() { private func processBatch() {
batchLock.lock() batchLock.lock()
if assetRequests.isEmpty { if assetRequests.isEmpty {
batchLock.unlock() batchLock.unlock()
@@ -90,13 +94,13 @@ class AssetResolver {
guard !activeAssetIds.isEmpty else { return } guard !activeAssetIds.isEmpty else { return }
let assets = PHAsset.fetchAssets(withLocalIdentifiers: activeAssetIds, options: Self.fetchOptions) let assets = PHAsset.fetchAssets(withLocalIdentifiers: activeAssetIds, options: self.fetchOptions)
assets.enumerateObjects { asset, _, _ in assets.enumerateObjects { asset, _, _ in
let assetId = asset.localIdentifier let assetId = asset.localIdentifier
for completion in completionMap.removeValue(forKey: assetId)! { for completion in completionMap.removeValue(forKey: assetId)! {
completion(asset) completion(asset)
} }
requestQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) } self.requestQueue.async { self.assetCache.setObject(asset, forKey: assetId as NSString) }
} }
for completions in completionMap.values { for completions in completionMap.values {
@@ -15,6 +15,11 @@ class ThumbnailRequest: Request {
class ThumbnailResolver: ThumbnailApi { class ThumbnailResolver: ThumbnailApi {
private static let imageManager = PHImageManager.default() private static let imageManager = PHImageManager.default()
private static let assetResolver = AssetResolver(fetchOptions: {
let fetchOptions = PHFetchOptions()
fetchOptions.wantsIncrementalChangeDetails = false
return fetchOptions
}(), qos: .userInitiated)
private static let requestOptions = { private static let requestOptions = {
let requestOptions = PHImageRequestOptions() let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true requestOptions.isNetworkAccessAllowed = true
@@ -67,7 +72,7 @@ class ThumbnailResolver: ThumbnailApi {
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], any Error>) -> Void) { func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64], any Error>) -> Void) {
let cancellationToken = CancellationToken() let cancellationToken = CancellationToken()
let thumbnailRequest = ThumbnailRequest(cancellationToken: cancellationToken, completion: completion) let thumbnailRequest = ThumbnailRequest(cancellationToken: cancellationToken, completion: completion)
AssetResolver.requestAsset(request: AssetRequest(cancellationToken: cancellationToken, assetId: assetId) { asset in Self.assetResolver.requestAsset(request: AssetRequest(cancellationToken: cancellationToken, assetId: assetId) { asset in
if cancellationToken.isCancelled { if cancellationToken.isCancelled {
return completion(Self.cancelledResult) return completion(Self.cancelledResult)
} }