You said:
I am having trouble getting the result out of the task
Cannot find 'result' in scope.
Yes, as presented in your question, result
is a local variable inside the task, so you can't refer to it outside of the Task
(unless you save it, or the title string, in a property of the struct
).
If I move let gotTitle = result ? "Success" : "The Request Failed" to within the task I see the warning "Initialization of immutable value 'gotTitle' was never used".
Yes, you have defined gotTitle
to be a local variable that just happens to have the same name as the property of the same name.
So, before I go to the solution, let's make a few observations so we understand what is going on. The key issue is that a Task
with an await
inside it runs asynchronously. Consider:
var body: some View {
VStack {
Button(action: {
Task {
print(Date(), "started task")
let result = await restore()
let gotTitle = result ? "Success" : "The Request Failed" // a local variable; we'll fix this in the next example
print(Date(), "task finished", gotTitle)
}
print(Date(), "after task submitted")
}) {
Text("Restore Purchase")
}
}
.alert(gotTitle, isPresented: $presentAlert, actions: { })
}
Note that I have moved the let gotTitle
inside the Task
block. You can’t reference result
outside of this block of code.
Anyway, when you tap on the button, you will see the following in the console:
2022-04-18 00:38:03 +0000 after task submitted
2022-04-18 00:38:03 +0000 started task
2022-04-18 00:38:06 +0000 task finished Success
Note the sequence of events:
- “started task” showed up after “after task submitted”
- “task finished” showed up seconds after “started task”
Hopefully, this illustrates why it makes no sense to refer to result
where the printing of “after task submitted” is taking place. You haven’t even gotten to the declaration/assignment of result
by that point.
So, the moral of the story is that if you want something updated after the asynchronous task, it needs to be immediately after the await
line, inside the Task
(or whatever context it is in). If you put it outside of the Task
, it means that it won’t wait for the asynchronous Task
to finish.
So, how would you access the result out of the Task
block. You would save it to an ObservedProperty
(which, coincidentally, helps separate the business logic from the view):
struct TestAlert: View {
@ObservedObject var restoreRequest = AppStoreRestoreRequest()
@State var isPresented = false
var body: some View {
VStack {
Button {
Task {
await restoreRequest.restore()
isPresented = true
}
} label: {
Text("Restore Purchase")
}
}
.alert(restoreRequest.state.title, isPresented: $isPresented, actions: { })
}
}
class AppStoreRestoreRequest: ObservableObject {
@Published var state: State = .notDetermined
func restore() async {
let result = (try? await AppStore.sync()) != nil
state = result ? .success : .failure
}
}
extension AppStoreRestoreRequest {
enum State {
case notDetermined
case success
case failure
}
}
extension AppStoreRestoreRequest.State {
var title: String {
switch self {
case .notDetermined: return "Not determined."
case .success: return "Success."
case .failure: return "The request failed."
}
}
}
So, the performance of the request (and the current state) is pulled out of the view and the state of the request is captured in the AppStoreRestoreRequest
.