I have a concurrent function running, that is connected to a UIBarbuttonItem
.init(barButtonSystemItem: .camera, target: self, action: #selector(runClassification(_:))
Although I have run into a weird issue. Running any task (as simple or complex) will not run on the very first initialisation of the view controller. Moving the code outside the Task
runs as expected. Running the function, leaving the view, and tapping the button again will successfully disable the buttons.
Print statements work but any code falls into the void. View Controller isn't constructed any differently than usual.
@objc func runClassification(_ sender: UIBarButtonItem) {
Task {
// This runs
navigationItem.rightBarButtonItems?.forEach { $0.isEnabled = false }
guard let image = imageView.image else { return }
do {
let response = try await MLClassifer.sharedManager.updateClassifications(capturedImage: image)
// None of this runs on first call.
print("MLClassifer.sharedManager.updateClassifications begining")
} catch {
debugPrint(error)
}
}
}
-- Update
MLClassifier.swift
private typealias ClassifierCheckedContinuation = CheckedContinuation<[VNRecognizedObjectObservation], Error> // 1
private var classifierContinuation: ClassifierCheckedContinuation?
@MainActor
func updateClassifications(capturedImage: UIImage) async throws -> [VNRecognizedObjectObservation] {
self.capturedImage = capturedImage
let orientation = CGImagePropertyOrientation(capturedImage.imageOrientation)
guard let ciImage = CIImage(image: capturedImage) else {
throw MLClassiferError.invalidConversion
}
guard let model = self.model else {
throw MLClassiferError.invalidModel
}
let mlModel = try VNCoreMLModel(for: model)
let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
let request = VNCoreMLRequest(model: mlModel) { request, error in
self.processClassifications(for: request, error: error)
}
try handler.perform([request])
return try await withCheckedThrowingContinuation { (continuation: ClassifierCheckedContinuation) in
self.classifierContinuation = continuation
}
}
func processClassifications(for request: VNRequest, error: Error?) {
if let error = error {
classifierContinuation?.resume(throwing: error)
return
}
guard let results = request.results, !results.isEmpty else {
classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
return
}
guard let classifications = results as? [VNRecognizedObjectObservation], !classifications.isEmpty else {
classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
return
}
guard let image = self.capturedImage,
let capturedImage = CIImage(image: image)
else {
classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
return
}
classifierContinuation?.resume(returning: classifications)
print("MLClassifer.sharedManager.updateClassifications returning")
}
CodePudding user response:
Seems to me like problem with converting old escaping closure callback API to async callback with withCheckedThrowingContinuation
. I see some that you are not resuming continuation, but rather just assigning some class variables self.classifierContinuation = continuation
which is propably like workaround for the function to be returning async throws ->
. Are you actually using [VNRecognizedObjectObservation]
object that you return from updateClassifications
function? If not, then change your updateClassifications
likewise to return Void
:
@MainActor
func updateClassifications(capturedImage: UIImage) async throws {
self.capturedImage = capturedImage
let orientation = CGImagePropertyOrientation(capturedImage.imageOrientation)
guard let ciImage = CIImage(image: capturedImage) else {
throw MLClassiferError.invalidConversion
}
guard let model = self.model else {
throw MLClassiferError.invalidModel
}
let mlModel = try VNCoreMLModel(for: model)
let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
let request = VNCoreMLRequest(model: mlModel) { request, error in
self.processClassifications(for: request, error: error)
}
try handler.perform([request])
return try await withCheckedThrowingContinuation { continuation in
continuation.resume(returning: () )
}
}
If you are actually using [VNRecognizedObjectObservatio]
object you returned from updateClassifications
function, then please provide the explanation , where exactly are you returning the object, as it was not clear to me. Also for more information about standard use of withCheckedThrowingContinuation
you can read following article . If there is anything unclear or need clarification, do not hesitate to ask.
CodePudding user response:
After some more semi-sleepless nights, I found the solution.
Rather than having it like this;
let request = VNCoreMLRequest(model: mlModel) { request, error in
self.processClassifications(for: request, error: error)
}
try handler.perform([request])
return try await withCheckedThrowingContinuation { (continuation: ClassifierCheckedContinuation) in
self.classifierContinuation = continuation
}
You need to have it like this:
Throwing the handler.perform
inside the closure seems to have been the missing puzzle.
return try await withCheckedThrowingContinuation { continuation in
self.classifierContinuation = continuation
let request = VNCoreMLRequest(model: mlModel) { request, error in
self.processClassifications(for: request, error: error)
}
try? handler.perform([request])
}