I am coming from a PowerShell background, and at the moment trying to get my head around Swift Concurrency. My first project as it relates to concurrency is a utility to en mass transcribe audio files, and I am banging my head on some basics. What I am trying to do here is simply get a list of the audio files, then loop through them displaying the one being worked on in the UI, and use a simple timer to simulate the transcription work for now. I found [this][1] that I thought would allow me to use a typical asyncAfter
timer but it is not working. My expectation is that the UI shows the current file name, then after a time the console shows the same while the UI shows the next file name. But the UI is showing only the last file name, and all the writes to console suggest that basically every timer is running in parallel. In a perfect world I would LIKE to transcribe everything in parallel, but that’s another problem, it seems like SFSpeechURLRecognitionRequest
doesn’t support this. Indeed, even just in sequence has given me problems, but I am not even 100% sure I AM doing it in sequence, so I am trying to get any head around forcing that, thus the failed code here.
struct ContentView: View {
@State private var fileName: String?
@State private var files: [URL] = []
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("\(fileName ?? "-")")
}
.padding()
.onAppear {
files = getFiles()
processFiles()
}
}
private func getFiles() -> [URL] {
do {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let path = documentsURL.appendingPathComponent("Voice Memos").absoluteURL
let contents = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil, options: [])
let files = (contents.filter {$0.pathExtension == "m4a"}).sorted { url1, url2 in
url1.path > url2.path
}
return files
}
catch {
print(error.localizedDescription)
return []
}
}
private func processFiles() {
for file in files {
Task {
await processFile(file)
}
}
}
private func processFile(_ url: URL) async {
fileName = url.lastPathComponent
let seconds = Double.random(in: 10.0...20.0)
Task.synchronous {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
print("\(url.lastPathComponent) \(seconds)")
}
}
}
}
extension Task where Failure == Error {
/// Performs an async task in a sync context.
///
/// - Note: This function blocks the thread until the given operation is finished. The caller is responsible for managing multithreading.
static func synchronous(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success) {
let semaphore = DispatchSemaphore(value: 0)
Task(priority: priority) {
defer { semaphore.signal() }
return try await operation()
}
semaphore.wait()
}
}