Как пометить результат распознавания текста Vision как отправляемый в Swift 6

У меня есть следующий код. Он просто запускает 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. Я не знаю, что здесь делать.

stackoverflow.com/questions/76415964/…
lorem ipsum 03.07.2024 20:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
66
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Несколько наблюдений:

  1. Я бы объявил ваш struct как Sendable:

    public struct TextRecognitionResult: Sendable {
        let observation: VNRecognizedTextObservation
        let text: VNRecognizedText
        let rect: CGRect
    }
    
  2. Я бы импортировал Vision как @preconcurrency:

    @preconcurrency import Vision
    
  3. Я бы сделал 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, не в таком хорошем состоянии?

OMGPOP 03.07.2024 21:47

Бывший. Все API (и некоторые новые функции языка Swift 6) постоянно меняются и должны стабилизироваться в ближайшие несколько месяцев, но до тех пор в собственных платформах Apple есть масса ложных срабатываний Swift 6 (а @preconcurrency — это временный символ). функция отключения некоторых слишком осторожных предупреждений). Вы не сделали ничего плохого. Apple все еще дорабатывает этот материал, и Swift 6 немного упрощает для нас это. См. hackingwithswift.com/articles/269/whats-new-in-swift-6, чтобы узнать о функциях языка, которые облегчают нам передачу вещей, которые не являются строго Sendable.

Rob 03.07.2024 22:07

Другие вопросы по теме