Итак, у меня есть этот код:
fileprivate class DocumentScanDelegate: NSObject, VNDocumentCameraViewControllerDelegate {
static let shared = DocumentScanDelegate()
var compressionQuality: CGFloat = 1
var onScanSuccess: (UIImage) -> Void = { _ in }
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
controller.dismiss(animated: true)
guard scan.pageCount >= 1 else { return }
let lastPage = scan.imageOfPage(at: scan.pageCount - 1)
let compressed = lastPage.compressed(quality: compressionQuality)
onScanSuccess(compressed)
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
controller.dismiss(animated: true)
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
controller.dismiss(animated: true)
}
}
У меня 2 ошибки:
shared
:Статическое свойство «shared» не является безопасным для параллелизма, поскольку тип «DocumentScanDelegate», отличный от «Sendable», может иметь общее изменяемое состояние; это ошибка в языковом режиме Swift 6
controller.dismiss
звонят:Вызов основного метода экземпляра, изолированного от актера, 'dismiss(animated:completion:)' в синхронном неизолированном контексте; это ошибка в языковом режиме Swift 6
И то, и другое имеет смысл. Итак, я добавил @MainActor
к DocumentScanDelegate
. Затем первое предупреждение исчезло. Но второе предупреждение становится:
Главный метод экземпляра, изолированный от актера, 'documentCameraViewController(_:didFinishWith:)' не может использоваться для удовлетворения требований неизолированного протокола; это ошибка в языковом режиме Swift 6
Затем я использую @preconcurrency
для аннотации VNDocumentCameraViewControllerDelegate
соответствия:
@MainActor
fileprivate class DocumentScanDelegate: NSObject, @preconcurrency VNDocumentCameraViewControllerDelegate {
...
}
Этот @preconcurrency
трюк очень похож на видео WWDC 2024 (https://developer.apple.com/videos/play/wwdc2024/10169/?time=1520)
Ошибка становится:
Главный метод экземпляра, изолированный от актера, 'documentCameraViewController(_:didFinishWith:)' не может использоваться для удовлетворения требований неизолированного протокола; это ошибка в языковом режиме Swift 6
Со следующей дополнительной ошибкой:
Класс «VNDocumentCameraScan» не соответствует протоколу «Sendable».
Затем я использую @preconcurrency
импорт:
@preconcurrency import VisionKit
То же предупреждение сохраняется.
Также есть новое предупреждение (почему?):
Атрибут @preconcurrency в модуле VisionKit не имеет никакого эффекта.
Потому что я хочу запустить сканирование документов из статической вспомогательной функции, и мне нужен способ сохранить этот делегат
Предупреждение «@preconcurrency
не имеет эффекта» напоминает мне об этой проблеме. Импортируете ли вы что-нибудь еще, что также может импортировать VisionKit
? Если VisionKit
«покрыт» другим импортом без предварительного параллелизма, @preconcurrency import VisionKit
не будет иметь никакого эффекта.
У меня есть @preconcurrency import Vision
в другом файле этого модуля. Хотя я считаю, что VisionKit
зависит от Vision
, а не наоборот.
Я только что попробовал удалить @preconcurrency
вместо Vision
и добавить @preconcurrency
вместо VisionKit
. Затем я получил 2 предупреждения: (1) для Vision: Add '@preconcurrency' to suppress 'Sendable'-related warnings from module 'Vision'
и (2) для VisionKit: '@preconcurrency' attribute on module 'VisionKit' has no effect
.
Удалите весь импорт VisionKit
из файла. Файл все еще компилируется? Если это так, это означает, что импортируется что-то еще VisionKit
, и выясните, что это такое.
Нет, не компилируется. Я также просмотрел всю кодовую базу и обнаружил, что это единственные 2 файла, которые импортируют фреймворки, связанные со зрением (один импортирует Vision
, а другой VisionKit
). На всякий случай я также удалил файл, который импортирует Vision
, и все равно предупреждение то же самое.
> «Затем я использую @preconcurrency для аннотации соответствия VNDocumentCameraViewControllerDelegate:» Почему бы просто не обернуть вызовы controller
в задачу вместо добавления @MainActor
ко всему классу? Task { @MainActor in controller.dismiss(animated: true) }
Функции протокола VNDocumentCameraViewControllerDelegate
не гарантированно будут вызваны @MainActor
, если аннотация отсутствует в определении протокола (также я не могу найти ничего о том, что она вызывается в основном потоке документации).
@fabianm, вы не можете передать UIViewController через границу изоляции, поскольку он не подлежит отправке (если я что-то не упускаю? Не могли бы вы написать ответ? Спасибо)
Продолжение нашего разговора в комментариях здесь.
Учитывая ваш последний вопрос, я не совсем уверен, почему это работает, если честно, но я попробовал это со строгой проверкой параллелизма, установленной на «завершение», и он не жалуется. Я предполагаю, что захватить controller
в закрытии задачи не проблема, потому что доступ к нему гарантированно будет возможен только там, где @MainActor
.
import VisionKit
private class DocumentScanDelegate: NSObject, VNDocumentCameraViewControllerDelegate {
@MainActor
static let shared = DocumentScanDelegate()
var compressionQuality: CGFloat = 1
var onScanSuccess: (UIImage) -> Void = { _ in }
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
Task { @MainActor in
controller.dismiss(animated: true)
}
guard scan.pageCount >= 1 else { return }
let lastPage = scan.imageOfPage(at: scan.pageCount - 1)
let compressed = lastPage.compressed(quality: compressionQuality)
onScanSuccess(compressed)
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
Task { @MainActor in
controller.dismiss(animated: true)
}
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
Task { @MainActor in
controller.dismiss(animated: true)
}
}
}
вы включили полную проверку в настройках сборки?
Да, установлено значение complete
. Я также пытался использовать Swift 6, и он тоже не жалуется.
Странно, я не вижу здесь предупреждения. Я вижу аннотацию @MainActor
в определении Apple VNDocumentCameraViewController
- означает ли это, что она также может быть отправлена?
Ах, «Классы, отмеченные @MainActor, неявно доступны для отправки, потому что главный актер координирует весь доступ к своему состоянию. Эти классы могут иметь хранимые свойства, которые являются изменяемыми и неотправляемыми». Из документа Apple Developer.apple.com/documentation/swift/sendable#
Я принимаю этот ответ, но очень подозреваю, что в будущем сам протокол VNDocumentCameraViewControllerDelegate
будет помечен как MainActor (поскольку это операции, связанные с пользовательским интерфейсом), к тому времени эта Task
операция нам уже не понадобится. Но мы посмотрим.
Спасибо. Вероятно, в этом вы правы. Если вы абсолютно уверены, что он всегда будет вызываться в основном потоке, вы также можете использовать MainActor.assumeIsolated
, чтобы избежать планирования асинхронного закрытия контроллера представления.
Я уверен на 99%. Однако это нигде не задокументировано, поэтому я бы сейчас перестраховался и использовал Task { @MainActor in }
Зачем нужен
shared
? Почему бы просто не создаватьDocumentScanDelegate
только тогда, когда вам это нужно?