Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit f4168f7

Browse files
committed
Move OCR text extraction to background thread
- Wrap Vision framework OCR operations in Task.detached with userInitiated priority - Use withCheckedContinuation to bridge between sync and async contexts - Prevents UI thread blocking during computationally expensive OCR operations - Maintains thread safety with nonisolated function declarations This fixes the hang risk warnings by ensuring Vision's synchronous perform() method runs on a background thread instead of blocking the UI.
1 parent b905a9f commit f4168f7

1 file changed

Lines changed: 40 additions & 29 deletions

File tree

Features/Monitoring/Domain/Services/ClaudeStatusExtractor.swift

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -206,36 +206,47 @@ final class ClaudeStatusExtractor: ObservableObject, Loggable {
206206
}
207207

208208
private nonisolated func extractTextFromImage(_ image: CGImage) async -> String? {
209-
// Preprocess image for better OCR
210-
guard let processedImage = preprocessImage(image) else { return nil }
211-
212-
let request = VNRecognizeTextRequest()
213-
request.recognitionLevel = .accurate
214-
request.usesLanguageCorrection = false
215-
request.minimumTextHeight = 0.02
216-
217-
let handler = VNImageRequestHandler(cgImage: processedImage, options: [:])
218-
219-
do {
220-
try handler.perform([request])
221-
222-
guard let observations = request.results else { return nil }
223-
224-
let recognizedStrings = observations.compactMap { observation -> String? in
225-
guard observation.confidence > Configuration.ocrConfidenceThreshold else { return nil }
226-
return observation.topCandidates(1).first?.string
227-
}
228-
229-
let fullText = recognizedStrings.joined(separator: "\n")
230-
231-
if fullText.contains("esc to interrupt") || fullText.contains("interrupt") {
232-
return parseClaudeStatus(from: fullText)
209+
await withCheckedContinuation { continuation in
210+
Task.detached(priority: .userInitiated) {
211+
// Preprocess image for better OCR
212+
guard let processedImage = self.preprocessImage(image) else {
213+
continuation.resume(returning: nil)
214+
return
215+
}
216+
217+
let request = VNRecognizeTextRequest()
218+
request.recognitionLevel = .accurate
219+
request.usesLanguageCorrection = false
220+
request.minimumTextHeight = 0.02
221+
222+
let handler = VNImageRequestHandler(cgImage: processedImage, options: [:])
223+
224+
do {
225+
try handler.perform([request])
226+
227+
guard let observations = request.results else {
228+
continuation.resume(returning: nil)
229+
return
230+
}
231+
232+
let recognizedStrings = observations.compactMap { observation -> String? in
233+
guard observation.confidence > Configuration.ocrConfidenceThreshold else { return nil }
234+
return observation.topCandidates(1).first?.string
235+
}
236+
237+
let fullText = recognizedStrings.joined(separator: "\n")
238+
239+
if fullText.contains("esc to interrupt") || fullText.contains("interrupt") {
240+
let status = self.parseClaudeStatus(from: fullText)
241+
continuation.resume(returning: status)
242+
} else {
243+
continuation.resume(returning: nil)
244+
}
245+
} catch {
246+
self.logger.debug("OCR failed: \(error)")
247+
continuation.resume(returning: nil)
248+
}
233249
}
234-
235-
return nil
236-
} catch {
237-
logger.debug("OCR failed: \(error)")
238-
return nil
239250
}
240251
}
241252

0 commit comments

Comments
 (0)