06/19 Update inspections/issues sent via email, and duplicated issue photos issues

This commit is contained in:
Nguyen Ngo
2026-06-19 17:28:09 -04:00
parent 95c8238c00
commit a57d6f02ba
7 changed files with 1366 additions and 12 deletions
@@ -606,20 +606,36 @@ struct MultiLibraryPickerView: UIViewControllerRepresentable {
picker.dismiss(animated: true)
guard !results.isEmpty else { return }
var images: [UIImage] = []
// PHItemProvider.loadObject's completion handler can be invoked on
// arbitrary background queues, potentially concurrently for
// different results. Appending to a shared `[UIImage]` from
// multiple threads without synchronization is a data race
// Swift's Array is not thread-safe, and concurrent mutation can
// corrupt its storage, manifesting as duplicated, dropped, or
// reordered photos in the final result. This was the root cause
// of duplicated Photo Library attachments on issues.
//
// Fix: pre-size the array to one slot per result and write each
// loaded image into its own fixed index, guarded by a single
// serial queue so writes never overlap. Each slot is written at
// most once, so duplication is structurally impossible.
var images = [UIImage?](repeating: nil, count: results.count)
let writeQueue = DispatchQueue(label: "jqc.photopicker.write")
let group = DispatchGroup()
for result in results {
for (index, result) in results.enumerated() {
guard result.itemProvider.canLoadObject(ofClass: UIImage.self) else { continue }
group.enter()
result.itemProvider.loadObject(ofClass: UIImage.self) { object, _ in
if let img = object as? UIImage { images.append(img) }
if let img = object as? UIImage {
writeQueue.sync { images[index] = img }
}
group.leave()
}
}
group.notify(queue: .main) {
self.parent.onSelected(images)
self.parent.onSelected(images.compactMap { $0 })
}
}
}