У меня есть следующий код. Он просто запускает Vision API, чтобы получить текст из изображения. Я использую очень простой GCD, чтобы отправить тяжелую операцию Vision в фоновую очередь, а затем отправить ее обратно в основную очередь для completion
:
public struct TextRecognitionResult {
let observation: VNRecognizedTextObservation
let text: VNRecognizedText
let rect: CGRect
}
public enum TextRecognitionUtil {
private static let queue = DispatchQueue(label: "text_recognition", qos: .userInitiated)
public static func process(
image: UIImage,
recognitionLevel: VNRequestTextRecognitionLevel,
completion: @Sendable @escaping ([TextRecognitionResult]) -> Void)
{
guard let cgImage = image.cgImage else {
completion([])
return
}
let request = VNRecognizeTextRequest { (request, error) in
guard
error == nil,
let observations = request.results as? [VNRecognizedTextObservation]
else {
DispatchQueue.main.async {
completion([])
}
return
}
var results = [TextRecognitionResult]()
// Vision's origin is on bottom left
let transform = CGAffineTransform.identity
.scaledBy(x: 1, y: -1)
.translatedBy(x: 0, y: -image.size.height)
.scaledBy(x: image.size.width, y: image.size.height)
for observation in observations {
guard let text = observation.topCandidates(1).first else { continue }
let rect = observation.boundingBox.applying(transform)
results += [TextRecognitionResult(observation: observation, text: text, rect: rect)]
}
DispatchQueue.main.async {
completion(results)
}
}
request.recognitionLevel = recognitionLevel
self.queue.async {
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
}
}
}
Этот код нарушает параллелизм Swift 6, потому что TextRecognitionResult
не является Sendable
:
Sending 'results' risks causing data races; this is an error in the Swift 6 language mode
Однако мой TextRecognitionResult
не может быть напрямую помечен как отправляемый, потому что VNRecognizedTextObservation
и VNRecognizedText
не являются отправляемыми, и оба они являются типами, определенными в Vision, и я не могу изменить их. Это довольно распространенная практика в GCD. Я не знаю, что здесь делать.
Несколько наблюдений:
Я бы объявил ваш struct
как Sendable
:
public struct TextRecognitionResult: Sendable {
let observation: VNRecognizedTextObservation
let text: VNRecognizedText
let rect: CGRect
}
Я бы импортировал Vision
как @preconcurrency
:
@preconcurrency import Vision
Я бы сделал results
неизменяемым (используя compactMap
вместо цикла for
):
public enum TextRecognitionUtil {
enum TextRecognitionUtilError: Error {
case noCgImage
case notVNRecognizedTextObservation
}
private static let queue = DispatchQueue(label: "text_recognition", qos: .userInitiated)
public static func process(
image: UIImage,
recognitionLevel: VNRequestTextRecognitionLevel = .accurate,
completion: @Sendable @escaping (Result<[TextRecognitionResult], Error>) -> Void)
{
guard let cgImage = image.cgImage else {
completion(.failure(TextRecognitionUtilError.noCgImage))
return
}
let request = VNRecognizeTextRequest { request, error in
guard
error == nil,
let observations = request.results as? [VNRecognizedTextObservation]
else {
DispatchQueue.main.async {
completion(.failure(error ?? TextRecognitionUtilError.notVNRecognizedTextObservation))
}
return
}
// Vision's origin is on bottom left
let transform = CGAffineTransform.identity
.scaledBy(x: 1, y: -1)
.translatedBy(x: 0, y: -image.size.height)
.scaledBy(x: image.size.width, y: image.size.height)
let results = observations.compactMap { (observation) -> TextRecognitionResult? in
guard let text = observation.topCandidates(1).first else { return nil }
let rect = observation.boundingBox.applying(transform)
return TextRecognitionResult(observation: observation, text: text, rect: rect)
}
DispatchQueue.main.async {
completion(.success(results))
}
}
request.recognitionLevel = recognitionLevel
self.queue.async {
let handler = VNImageRequestHandler(cgImage: cgImage)
}
}
}
Я также изменил тип возвращаемого значения замыкания на Result
, чтобы вызывающая сторона могла отличить ошибку от отсутствия результатов распознавания.
Swift 6, вероятно, немного упростит это, но вышеописанное работает в Xcode 15.4.
> «Я бы импортировал Vision как @preconcurrency:» Вы имеете в виду, что Apple Vision не находится в состоянии «готовности к параллелизму»? или вы имеете в виду, что мой код, использующий Vision, не в таком хорошем состоянии?
Бывший. Все API (и некоторые новые функции языка Swift 6) постоянно меняются и должны стабилизироваться в ближайшие несколько месяцев, но до тех пор в собственных платформах Apple есть масса ложных срабатываний Swift 6 (а @preconcurrency
— это временный символ). функция отключения некоторых слишком осторожных предупреждений). Вы не сделали ничего плохого. Apple все еще дорабатывает этот материал, и Swift 6 немного упрощает для нас это. См. hackingwithswift.com/articles/269/whats-new-in-swift-6, чтобы узнать о функциях языка, которые облегчают нам передачу вещей, которые не являются строго Sendable
.