EVNs:
[!NOTE] A task’s (inside SwiftUI View
.task {}
) lifetime is tied to the view’s lifetime. Refer: WWDC21: Discover concurrency in SwiftUI
Task { [weak self] in
~[weak self]~ here didn’t have any meaningEven when the view is dimissed the task still perform fetching and deinit method will not call until the task is finished(print the string print(“Result for fetch »> (result) seconds”)) the the deinit method will trigger
deinit {
print("deinit triggered!!!")
}
func performFetchV2() {
Task { [weak self] in
// guard let self else { return }
do {
print("Start fetching ...")
let result = try await self?.clock.measure {
try await self?.fetchThumbnailsV2(ids: self?.imageIds ?? [])
}
print("Result for fetch >>> \(result) seconds")
} catch {
print("Error: \(error)")
}
}
}
[!CAUTION] Because the view(?)(
AsyncAwaitBootCampViewModel
) will not release if the task not finish yet(thedeinit
function not trigger). So if we manual cancel task insidedeinit
will not work. We should manual call cancel insideonDisappear
:.onDisappear { vm.cancellTasks() }
deinit {
//tasks["task2"]?.cancel()//=> didn't work because deinit didn't call if task not finis yet
}
// Call this method inside onDisappear of the view
func cancellTasks() {
tasks["task2"]?.cancel()
}
func performFetch() {
Task {
do {
print("Start fetching ...")
let result = try await clock.measure {
try await fetchThumbnails(ids: imageIds)
}
print("Result for fetch >>> \(result) seconds")
/*
Result for fetch >>> 17.063496375 seconds seconds
Result for fetch >>> 17.066791167 seconds seconds
Result for fetch >>> 17.037929667 seconds seconds
*/
} catch {
print("Error: \(error)")
}
}
}
func fetchThumbnails(ids: [String]) async throws {//-> [String: (Image, String)] {
var result: [ThumbnailModel] = []
for id in ids {
let image = try await fetchFullImage(id: id)
let metaData = try await fecchMetaData(id: id)
result.append(ThumbnailModel(thumbnail: image, metadata: metaData))
}
imagesThumbs = result
}
func fetchFullImage(id: String) async throws -> String {
try await Task.sleep(nanoseconds: oneSecondSleep)
return id
}
func fecchMetaData(id: String) async throws -> String {
try await Task.sleep(nanoseconds: oneSecondSleep)
let randomString = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
return randomString.randomElement() ?? "Empty"
}
func performFetchV2() {
Task { //[weak self] in
do {
print("Start fetching ...")
let result = try await clock.measure {
try await fetchThumbnailsV2(ids: imageIds)
}
print("Result for fetch >>> \(result) seconds")
/*
Result for fetch >>> 8.529140541 seconds seconds
Result for fetch >>> 8.533008334 seconds seconds
Result for fetch >>> 8.527049416 seconds seconds
*/
} catch {
print("Error: \(error)")
}
}
}
func fetchThumbnailsV2(ids: [String]) async throws {
print("Start fetchThumbnailsV2 ...")
var result: [ThumbnailModel] = []
for id in ids {
async let image = fetchFullImage(id: id)
async let metaData = fecchMetaData(id: id)
let (im, data) = try await (image, metaData)
result.append(ThumbnailModel(thumbnail: im, metadata: data))
}
imagesThumbs = result
}
func performFetchV3() {
Task {
do {
let result = try await clock.measure {
try await fetchThumbnailsV3(ids: imageIds)
}
print("Result for fetch >>> \(result) seconds")
/*
Result for fetch >>> 1.136505542 seconds seconds
Result for fetch >>> 1.142909625 seconds seconds
Result for fetch >>> 1.13683075 seconds seconds
*/
} catch {
print("Error: \(error)")
}
}
}
func fetchThumbnailsV3(ids: [String]) async throws {
try await withThrowingTaskGroup { group in
for id in ids {
group.addTask {
async let image = self.fetchFullImage(id: id)
async let metaData = self.fecchMetaData(id: id)
return ThumbnailModel(
thumbnail: try await image,
metadata: try await metaData
)
}
}
for try await model in group {
imagesThumbs.append(model)
}
}
}
If we change code as below, result will be 2.166060333 seconds
group.addTask {
// async let image = self.fetchFullImage(id: id)
// async let metaData = self.fecchMetaData(id: id)
// return ThumbnailModel(
// thumbnail: try await image,
// metadata: try await metaData
// )
let image = try await self.fetchFullImage(id: id)
let metaData = try await self.fecchMetaData(id: id)
return ThumbnailModel(
thumbnail: image,
metadata: metaData
)
}
import SwiftUI
extension Thread {
nonisolated static var isOnMain: Bool {
return Thread.isMainThread
}
}
struct ContentView: View {
@State var didTapButton = false
var body: some View {
VStack {
Button("Run experiment") {
didTapButton = true
}
.disabled(didTapButton)
}
.padding()
.task(id: didTapButton) {
guard didTapButton else { return }
print("Task call first is on main here: \(Thread.isOnMain)") // true
let experiment = Experiment()
await experiment.testAsync()
didTapButton = false
}
}
}
class Experiment {
func testAsync() async {
print("is on main before: \(Thread.isOnMain)")
try? await Task.sleep(for: .seconds(5))
print("is on main after: \(Thread.isOnMain)")
}
/*
1:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Swift version 6.1.2 Xcode 16.4, macos 15.5
Result:
Task call first is on main here: true
is on main before: false
is on main after: false
2:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Swift version 6.2 Xcode 26.0 beta, macos 15.5
with Default Actor Isolation setting: nonisolated
Result:
Task call first is on main here: true
is on main before: false
is on main after: false
3:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Swift version 6.2 Xcode 26.0 beta, macos 15.5
with Default Actor Isolation setting: MainActor
Result:
Task call first is on main here: true
is on main before: true
is on main after: true
4:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
marked testAsync with nonisolated: nonisolated func testAsync() async
Swift version 6.2 Xcode 26.0 beta, macos 15.5
with Default Actor Isolation setting: MainActor
Result:
Task call first is on main here: true
is on main before: false
is on main after: false
*/
}