Make backup status reconciliation memory-safe
Replace full [PhotoAsset] array enumeration with PHFetchResult batch enumeration (autoreleasepool + Task.yield per batch) to keep memory bounded during reconciliation. Introduce ManifestIndex with two O(1) Set<String> lookups (localIdentifier + lowercased filename) instead of O(n×m) linear scans. Scope manifest Data+struct inside buildManifestIndex so they are released by ARC before the comparison loop begins. Add UIApplication.didReceiveMemoryWarningNotification handler to cancel in-flight tasks under memory pressure. Separate debounceTask from refreshTask to avoid cancelling long-running reconciles on every photo library event. fetchResultForReconciliation returns PHFetchResult directly — no [PhotoAsset] array is ever allocated during reconciliation. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -68,6 +68,39 @@ final class PhotoLibraryService: PhotoLibraryProtocol {
|
||||
return assets
|
||||
}
|
||||
|
||||
/// Returns a raw PHFetchResult for memory-safe reconciliation.
|
||||
/// No PhotoAsset array is created — caller enumerates in batches.
|
||||
func fetchResultForReconciliation(filter: BackupFilter) -> PHFetchResult<PHAsset> {
|
||||
let options = PHFetchOptions()
|
||||
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
|
||||
|
||||
var subpredicates: [NSPredicate] = []
|
||||
var mediaTypes: [PHAssetMediaType] = []
|
||||
if filter.includePhotos { mediaTypes.append(.image) }
|
||||
if filter.includeVideos { mediaTypes.append(.video) }
|
||||
|
||||
if !mediaTypes.isEmpty {
|
||||
subpredicates.append(NSPredicate(
|
||||
format: "mediaType IN %@",
|
||||
mediaTypes.map(\.rawValue)
|
||||
))
|
||||
}
|
||||
if !filter.includeScreenshots {
|
||||
subpredicates.append(NSPredicate(
|
||||
format: "NOT ((mediaSubtype & %d) != 0)",
|
||||
PHAssetMediaSubtype.photoScreenshot.rawValue
|
||||
))
|
||||
}
|
||||
|
||||
if subpredicates.count > 1 {
|
||||
options.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: subpredicates)
|
||||
} else {
|
||||
options.predicate = subpredicates.first
|
||||
}
|
||||
|
||||
return PHAsset.fetchAssets(with: options)
|
||||
}
|
||||
|
||||
func exportAsset(_ asset: PhotoAsset) async throws -> URL {
|
||||
let fetchResult = PHAsset.fetchAssets(
|
||||
withLocalIdentifiers: [asset.localIdentifier],
|
||||
|
||||
Reference in New Issue
Block a user