refactor
handle missing asset
This commit is contained in:
@@ -29,9 +29,11 @@
|
|||||||
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
||||||
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
||||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */; };
|
FEC340D12E7326630050078A /* AssetResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC340C92E7326630050078A /* AssetResolver.swift */; };
|
||||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */; };
|
FEC340D22E7326630050078A /* Thumbhash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC340CB2E7326630050078A /* Thumbhash.swift */; };
|
||||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */; };
|
FEC340D32E7326630050078A /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC340CF2E7326630050078A /* Request.swift */; };
|
||||||
|
FEC340D42E7326630050078A /* ThumbnailResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC340CC2E7326630050078A /* ThumbnailResolver.swift */; };
|
||||||
|
FEC340D52E7326630050078A /* Thumbnails.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC340CD2E7326630050078A /* Thumbnails.g.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -115,9 +117,11 @@
|
|||||||
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||||
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
FEC340C92E7326630050078A /* AssetResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetResolver.swift; sourceTree = "<group>"; };
|
||||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnails.g.swift; sourceTree = "<group>"; };
|
FEC340CB2E7326630050078A /* Thumbhash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbhash.swift; sourceTree = "<group>"; };
|
||||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailsImpl.swift; sourceTree = "<group>"; };
|
FEC340CC2E7326630050078A /* ThumbnailResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailResolver.swift; sourceTree = "<group>"; };
|
||||||
|
FEC340CD2E7326630050078A /* Thumbnails.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnails.g.swift; sourceTree = "<group>"; };
|
||||||
|
FEC340CF2E7326630050078A /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@@ -247,6 +251,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FEC340D02E7326630050078A /* Resolvers */,
|
||||||
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
B25D37792E72CA15008B6CA7 /* Connectivity */,
|
||||||
B21E34A62E5AF9760031FDB9 /* Background */,
|
B21E34A62E5AF9760031FDB9 /* Background */,
|
||||||
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
B2CF7F8C2DDE4EBB00744BF6 /* Sync */,
|
||||||
@@ -261,7 +266,6 @@
|
|||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
FED3B1952E253E9B0030FD97 /* Images */,
|
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -296,16 +300,34 @@
|
|||||||
path = ShareExtension;
|
path = ShareExtension;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
FED3B1952E253E9B0030FD97 /* Images */ = {
|
FEC340CA2E7326630050078A /* Assets */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FEAFA8722E4D42F4001E47FE /* Thumbhash.swift */,
|
FEC340C92E7326630050078A /* AssetResolver.swift */,
|
||||||
FED3B1932E253E9B0030FD97 /* Thumbnails.g.swift */,
|
);
|
||||||
FED3B1942E253E9B0030FD97 /* ThumbnailsImpl.swift */,
|
path = Assets;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FEC340CE2E7326630050078A /* Images */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FEC340CB2E7326630050078A /* Thumbhash.swift */,
|
||||||
|
FEC340CC2E7326630050078A /* ThumbnailResolver.swift */,
|
||||||
|
FEC340CD2E7326630050078A /* Thumbnails.g.swift */,
|
||||||
);
|
);
|
||||||
path = Images;
|
path = Images;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FEC340D02E7326630050078A /* Resolvers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FEC340CA2E7326630050078A /* Assets */,
|
||||||
|
FEC340CE2E7326630050078A /* Images */,
|
||||||
|
FEC340CF2E7326630050078A /* Request.swift */,
|
||||||
|
);
|
||||||
|
path = Resolvers;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -573,14 +595,16 @@
|
|||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */,
|
||||||
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */,
|
||||||
FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */,
|
|
||||||
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */,
|
||||||
FED3B1962E253E9B0030FD97 /* ThumbnailsImpl.swift in Sources */,
|
|
||||||
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */,
|
B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */,
|
||||||
FED3B1972E253E9B0030FD97 /* Thumbnails.g.swift in Sources */,
|
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */,
|
B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */,
|
||||||
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */,
|
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */,
|
||||||
|
FEC340D12E7326630050078A /* AssetResolver.swift in Sources */,
|
||||||
|
FEC340D22E7326630050078A /* Thumbhash.swift in Sources */,
|
||||||
|
FEC340D32E7326630050078A /* Request.swift in Sources */,
|
||||||
|
FEC340D42E7326630050078A /* ThumbnailResolver.swift in Sources */,
|
||||||
|
FEC340D52E7326630050078A /* Thumbnails.g.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import UIKit
|
|||||||
) -> Bool {
|
) -> Bool {
|
||||||
// Required for flutter_local_notification
|
// Required for flutter_local_notification
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
|
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
@@ -53,7 +53,7 @@ import UIKit
|
|||||||
|
|
||||||
public static func registerPlugins(binaryMessenger: FlutterBinaryMessenger) {
|
public static func registerPlugins(binaryMessenger: FlutterBinaryMessenger) {
|
||||||
NativeSyncApiSetup.setUp(binaryMessenger: binaryMessenger, api: NativeSyncApiImpl())
|
NativeSyncApiSetup.setUp(binaryMessenger: binaryMessenger, api: NativeSyncApiImpl())
|
||||||
ThumbnailApiSetup.setUp(binaryMessenger: binaryMessenger, api: ThumbnailApiImpl())
|
ThumbnailApiSetup.setUp(binaryMessenger: binaryMessenger, api: ThumbnailResolver())
|
||||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: BackgroundWorkerApiImpl())
|
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: binaryMessenger, api: BackgroundWorkerApiImpl())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import Photos
|
||||||
|
|
||||||
|
class AssetRequest: Request {
|
||||||
|
let assetId: String
|
||||||
|
var completion: (PHAsset?) -> Void
|
||||||
|
|
||||||
|
init(cancellationToken: CancellationToken, assetId: String, completion: @escaping (PHAsset?) -> Void) {
|
||||||
|
self.assetId = assetId
|
||||||
|
self.completion = completion
|
||||||
|
super.init(cancellationToken: cancellationToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetResolver {
|
||||||
|
private static let requestQueue = DispatchQueue(label: "assets.requests", qos: .userInitiated)
|
||||||
|
private static let processingQueue = DispatchQueue(label: "assets.processing", qos: .userInitiated)
|
||||||
|
|
||||||
|
private static var batchTimer: DispatchWorkItem?
|
||||||
|
private static let batchLock = NSLock()
|
||||||
|
private static let batchTimeout: TimeInterval = 0.001 // 1ms
|
||||||
|
|
||||||
|
private static let fetchOptions = {
|
||||||
|
let fetchOptions = PHFetchOptions()
|
||||||
|
fetchOptions.wantsIncrementalChangeDetails = false
|
||||||
|
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) {
|
||||||
|
requestQueue.async {
|
||||||
|
if (request.isCancelled) {
|
||||||
|
request.completion(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let cachedAsset = assetCache.object(forKey: request.assetId as NSString) {
|
||||||
|
request.completion(cachedAsset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
batchLock.lock()
|
||||||
|
if (request.isCancelled) {
|
||||||
|
batchLock.unlock()
|
||||||
|
request.completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assetRequests.append(request)
|
||||||
|
|
||||||
|
batchTimer?.cancel()
|
||||||
|
let timer = DispatchWorkItem(block: processBatch)
|
||||||
|
batchTimer = timer
|
||||||
|
batchLock.unlock()
|
||||||
|
|
||||||
|
processingQueue.asyncAfter(deadline: .now() + batchTimeout, execute: timer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func processBatch() {
|
||||||
|
batchLock.lock()
|
||||||
|
var completionMap = [String: [(PHAsset?) -> Void]]()
|
||||||
|
var activeAssetIds = [String]()
|
||||||
|
completionMap.reserveCapacity(assetRequests.count)
|
||||||
|
activeAssetIds.reserveCapacity(assetRequests.count)
|
||||||
|
for request in assetRequests {
|
||||||
|
if (request.isCancelled) {
|
||||||
|
request.completion(nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if var completions = completionMap[request.assetId] {
|
||||||
|
completions.append(request.completion)
|
||||||
|
} else {
|
||||||
|
activeAssetIds.append(request.assetId)
|
||||||
|
completionMap[request.assetId] = [request.completion]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assetRequests.removeAll(keepingCapacity: true)
|
||||||
|
batchTimer = nil
|
||||||
|
batchLock.unlock()
|
||||||
|
|
||||||
|
guard !activeAssetIds.isEmpty else { return }
|
||||||
|
|
||||||
|
let assets = PHAsset.fetchAssets(withLocalIdentifiers: activeAssetIds, options: Self.fetchOptions)
|
||||||
|
assets.enumerateObjects { asset, _, _ in
|
||||||
|
let assetId = asset.localIdentifier
|
||||||
|
for completion in completionMap.removeValue(forKey: assetId)! {
|
||||||
|
completion(asset)
|
||||||
|
}
|
||||||
|
requestQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
||||||
|
}
|
||||||
|
|
||||||
|
for completions in completionMap.values {
|
||||||
|
for completion in completions {
|
||||||
|
completion(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-115
@@ -3,38 +3,6 @@ import Flutter
|
|||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import Photos
|
import Photos
|
||||||
|
|
||||||
class CancellationToken {
|
|
||||||
var isCancelled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
class Request {
|
|
||||||
let cancellationToken: CancellationToken
|
|
||||||
|
|
||||||
init(cancellationToken: CancellationToken) {
|
|
||||||
self.cancellationToken = cancellationToken
|
|
||||||
}
|
|
||||||
|
|
||||||
var isCancelled: Bool {
|
|
||||||
get {
|
|
||||||
return cancellationToken.isCancelled
|
|
||||||
}
|
|
||||||
set(newValue) {
|
|
||||||
cancellationToken.isCancelled = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetRequest: Request {
|
|
||||||
let assetId: String
|
|
||||||
var completion: (PHAsset?) -> Void
|
|
||||||
|
|
||||||
init(cancellationToken: CancellationToken, assetId: String, completion: @escaping (PHAsset?) -> Void) {
|
|
||||||
self.assetId = assetId
|
|
||||||
self.completion = completion
|
|
||||||
super.init(cancellationToken: cancellationToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ThumbnailRequest: Request {
|
class ThumbnailRequest: Request {
|
||||||
weak var workItem: DispatchWorkItem?
|
weak var workItem: DispatchWorkItem?
|
||||||
let completion: (Result<[String: Int64], any Error>) -> Void
|
let completion: (Result<[String: Int64], any Error>) -> Void
|
||||||
@@ -45,13 +13,8 @@ class ThumbnailRequest: Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailApiImpl: ThumbnailApi {
|
class ThumbnailResolver: ThumbnailApi {
|
||||||
private static let imageManager = PHImageManager.default()
|
private static let imageManager = PHImageManager.default()
|
||||||
private static let fetchOptions = {
|
|
||||||
let fetchOptions = PHFetchOptions()
|
|
||||||
fetchOptions.wantsIncrementalChangeDetails = false
|
|
||||||
return fetchOptions
|
|
||||||
}()
|
|
||||||
private static let requestOptions = {
|
private static let requestOptions = {
|
||||||
let requestOptions = PHImageRequestOptions()
|
let requestOptions = PHImageRequestOptions()
|
||||||
requestOptions.isNetworkAccessAllowed = true
|
requestOptions.isNetworkAccessAllowed = true
|
||||||
@@ -62,29 +25,17 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
return requestOptions
|
return requestOptions
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let assetQueue = DispatchQueue(label: "thumbnail.assets", qos: .userInitiated)
|
|
||||||
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
|
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
|
||||||
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
||||||
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
|
private static let processingQueue = DispatchQueue(label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
|
||||||
private static let batchQueue = DispatchQueue(label: "thumbnail.batching", qos: .userInitiated)
|
|
||||||
|
|
||||||
private static let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
private static let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
private static let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
|
private static let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
|
||||||
private static var requests = [Int64: ThumbnailRequest]()
|
private static var requests = [Int64: ThumbnailRequest]()
|
||||||
private static let cancelledResult = Result<[String: Int64], any Error>.success([:])
|
private static let cancelledResult = Result<[String: Int64], any Error>.success([:])
|
||||||
private static let thumbnailConcurrencySemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount / 2 + 1)
|
private static let thumbnailConcurrencySemaphore = DispatchSemaphore(value: ProcessInfo.processInfo.activeProcessorCount / 2 + 1)
|
||||||
private static let assetCache = {
|
|
||||||
let assetCache = NSCache<NSString, PHAsset>()
|
|
||||||
assetCache.countLimit = 10000
|
|
||||||
return assetCache
|
|
||||||
}()
|
|
||||||
private static let activitySemaphore = DispatchSemaphore(value: 1)
|
private static let activitySemaphore = DispatchSemaphore(value: 1)
|
||||||
|
|
||||||
private static var assetRequests = [AssetRequest]()
|
|
||||||
private static var batchTimer: DispatchWorkItem?
|
|
||||||
private static let batchLock = NSLock()
|
|
||||||
private static let batchTimeout: TimeInterval = 0.001 // 1ms
|
|
||||||
|
|
||||||
private static let willResignActiveObserver = NotificationCenter.default.addObserver(
|
private static let willResignActiveObserver = NotificationCenter.default.addObserver(
|
||||||
forName: UIApplication.willResignActiveNotification,
|
forName: UIApplication.willResignActiveNotification,
|
||||||
object: nil,
|
object: nil,
|
||||||
@@ -116,7 +67,7 @@ class ThumbnailApiImpl: 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)
|
||||||
Self.requestAsset(request: AssetRequest(cancellationToken: cancellationToken, assetId: assetId) { asset in
|
AssetResolver.requestAsset(request: AssetRequest(cancellationToken: cancellationToken, assetId: assetId) { asset in
|
||||||
let item = DispatchWorkItem {
|
let item = DispatchWorkItem {
|
||||||
if cancellationToken.isCancelled {
|
if cancellationToken.isCancelled {
|
||||||
return completion(Self.cancelledResult)
|
return completion(Self.cancelledResult)
|
||||||
@@ -231,70 +182,6 @@ class ThumbnailApiImpl: ThumbnailApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func requestAsset(request: AssetRequest) {
|
|
||||||
assetQueue.async {
|
|
||||||
if (request.isCancelled) {
|
|
||||||
request.completion(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let cachedAsset = assetCache.object(forKey: request.assetId as NSString) {
|
|
||||||
request.completion(cachedAsset)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
batchLock.lock()
|
|
||||||
if (request.isCancelled) {
|
|
||||||
batchLock.unlock()
|
|
||||||
request.completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assetRequests.append(request)
|
|
||||||
|
|
||||||
batchTimer?.cancel()
|
|
||||||
let timer = DispatchWorkItem(block: processBatch)
|
|
||||||
batchTimer = timer
|
|
||||||
batchLock.unlock()
|
|
||||||
|
|
||||||
batchQueue.asyncAfter(deadline: .now() + batchTimeout, execute: timer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func processBatch() {
|
|
||||||
batchLock.lock()
|
|
||||||
var completionMap = [String: [(PHAsset?) -> Void]]()
|
|
||||||
var activeAssetIds = [String]()
|
|
||||||
completionMap.reserveCapacity(assetRequests.count)
|
|
||||||
activeAssetIds.reserveCapacity(assetRequests.count)
|
|
||||||
for request in assetRequests {
|
|
||||||
if (request.isCancelled) {
|
|
||||||
request.completion(nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if var completions = completionMap[request.assetId] {
|
|
||||||
completions.append(request.completion)
|
|
||||||
} else {
|
|
||||||
activeAssetIds.append(request.assetId)
|
|
||||||
completionMap[request.assetId] = [request.completion]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assetRequests.removeAll(keepingCapacity: true)
|
|
||||||
batchTimer = nil
|
|
||||||
batchLock.unlock()
|
|
||||||
|
|
||||||
guard !requests.isEmpty else { return }
|
|
||||||
|
|
||||||
let assets = PHAsset.fetchAssets(withLocalIdentifiers: activeAssetIds, options: Self.fetchOptions)
|
|
||||||
assets.enumerateObjects { asset, _, _ in
|
|
||||||
let assetId = asset.localIdentifier
|
|
||||||
for completion in completionMap[assetId]! {
|
|
||||||
completion(asset)
|
|
||||||
}
|
|
||||||
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForActiveState() {
|
func waitForActiveState() {
|
||||||
Self.activitySemaphore.wait()
|
Self.activitySemaphore.wait()
|
||||||
Self.activitySemaphore.signal()
|
Self.activitySemaphore.signal()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
class CancellationToken {
|
||||||
|
var isCancelled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
let cancellationToken: CancellationToken
|
||||||
|
|
||||||
|
init(cancellationToken: CancellationToken) {
|
||||||
|
self.cancellationToken = cancellationToken
|
||||||
|
}
|
||||||
|
|
||||||
|
var isCancelled: Bool {
|
||||||
|
get {
|
||||||
|
return cancellationToken.isCancelled
|
||||||
|
}
|
||||||
|
set(newValue) {
|
||||||
|
cancellationToken.isCancelled = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user