Disclaimer: I'm a recovering developer, new to Swift and macOS development, came from C/C++ in Linux & Windows. Have working knowledge on how thread works.
I'm writing a sample macOS app in Swift6 and SwiftUI to familiarize myself with the language and macOS. My goal for the app is to do one thing only: generate video thumbnails from a given folder and display them in a view.
After some research, it appears that the async version of generateBestRepresentationForRequest
(api link) is the way to generate thumbnails. However it's confusing to me how Swift6 is guarding the data race and how its concurrency model works.
I have included the pseudocode below that outlines the code I have (Gemini AI assist and other tools helped
Can you please review the code, specifically I need help on writing the grammatically correct and "Swift6" concurrency code in func generateThumbnails()
to accomplish the following:
- async calls to
generateBestRepresentationForRequest
. My understanding is that the async call returns to its caller thread immediately. If that is correct, I presume it's safe to call this API from the main UI thread. - I expect that whenever a thumbnail becomes available, the worker thread (apart from Main UI thread) will call the "callback function" that's somehow "registered" with the async function. Need some help here for code example.
- In such "callback function", I expect to create a new PhotoAsset object based on the thumbnail data, and then safely insert that new object to the main UI's photoAssets[ ] array. This insertion per my understanding must happen in MainUI thread. If so can you please give code example?
- In my mental model, this app at runtime will have 2 threads: a Main UI thread that updates the UI when the @state variable changes, and a worker thread that gets "unblocked" whenever a thumbnail becomes available.
Sorry for the long post with very loaded questions. Much appreciate your feedback and help.
Thanks! See below the pseudo code.
// represents a single thumbnail
struct PhotoAsset: {
let id: String
let url: URL
let fileName: String
var thumbnail: Data? //png format raw data
}
struct ContentView: View {
@State private var isLoading = false
@State private var photoAssets: [PhotoAsset] = []
@State private var loadedFolderURL: URL?
var body: some View {
VStack { /*Later: add code to display the photoAssets */ }
.padding()
.navigationTitle("Photo2")
.toolbar {
ToolbarItem {
Button("Select Video Folder") {
let openPanel = NSOpenPanel()
if openPanel.runModal() == .OK {
// Task { //I doubt we need Task here but I can be wrong
generateThumbnails(from: openPanel.url)
// }
}
}
.disabled(isLoading) // Disable the button while loading
}
}
} //end var body
func generateThumbnails(from folderURL: URL) {
loadedFolderURL = folderURL // Store for reloading
photoAssets.removeAll() // Clear previous results from the view
isLoading = true
do {
let fileManager = FileManager.default
let directoryURL = folderURL
// Check if the directory exists and is accessible
// ....
guard let enumerator = fileManager.enumerator(at: directoryURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants, .skipsPackageDescendants],
errorHandler: { (url, error) -> Bool in
print("Error enumerating '\(url.path)': \(error)")
return true // Continue enumeration even if there are errors with some files.
}) else {
//show error and alert
print("Error: enumerator is nil")
return
}
for case let fileURL as URL in enumerator {
//check if the file exists and that it is not a directory
//...
// Trying to use the async version of generateBestRepresentation
// Need help here to complete the sample code with a "callback function"
let request = QLThumbnailGenerator.Request(fileAt: fileURL,
size: CGSize(width: 256, height: 256),
scale: NSScreen.main?.backingScaleFactor ?? 1,
representationTypes: .thumbnail)
try QLThumbnailGenerator.shared.generateBestRepresentation(for: request) async
// .....
}
} catch {
print("Error listing files: \(error)")
isLoading = false
// Show an error to the user.
}
}
}//end struct ContentView