I understand the new async syntax in Swift in the sense that if I call it, then it will handle a pool of asynchronous queues / threads (whatever) to do the work. What I don't understand is how we return to the main thread once it's all over.
// On main thread now
let manager = StorageManager()
let items = await manager.fetch // returns on main thread?
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async {
fetchAndWait()
}
func fetchAndWait() {
if isPreview { return }
let items = try? read()
fetchedItems = items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}
I want to make sure that I read and write from/to disk in the correct way i.e. is thread safe when necessary and concurrent where possible. Is it best to declare this struct as a @MainActor ?
CodePudding user response:
There is nothing in the code you've given that uses async
or await
meaningfully, and there is nothing in the code you've given that goes onto a "background thread", so the question as posed is more or less meaningless. If the question did have meaning, the answer would be: to guarantee that code doesn't run on the main thread, put that code into an actor. To guarantee that code does run on the main thread, put that code into a @MainActor
object (or call MainActor.run
).
CodePudding user response:
The async
methods do not return automatically to the main thread, they either:
- complete in the background whatever they are doing
or
- explicitly pass at a certain moment the execution to the main thread using, for example,
DispatchQueue
.
In the code above you can start by correcting the fact that fetch()
does not return any value (items
will receive nothing based on your code).
Example of your code for case 1 above:
let manager = StorageManager()
let items = await manager.fetch // not on the main thread, the value will be stored in the background
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async -> [Item] {
if isPreview { return }
let items = try? read()
return items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}
Example for case 2 above (I created an @Published
var, which should only be written on the main thread, to give you the example):
class ViewModel: ObservableObject {
let manager = StorageManager()
@Published var items = [Item]() // should change value only on main thread
func updateItems() {
Task { // Enter background thread
let fetchedItems = await self.manager.fetch()
DispatchQueue.main.async { // Back to main thread
self.items = fetchedItems
}
}
}
}
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async -> [Item] {
if isPreview { return }
let items = try? read()
return items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}