.task { } là một bổ tố trong SwiftUI được giới thiệu trong iOS 15, khởi chạy một thao tác bất đồng bộ khi View xuất hiện và tự động hủy nó khi View biến mất. Không giống như .onAppear, thực thi mã đồng bộ mà không có khả năng hủy, .task làm việc với ngữ cảnh async/await và tôn trọng vòng đời của View: khi View biến mất, SwiftUI gọi cancel() trên Task đã tạo. Điều này ngăn chặn rò rỉ bộ nhớ và việc thực thi các thao tác sau khi View không còn cần cập nhật nữa. Theo Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task là cách ưu tiên để tải dữ liệu trong SwiftUI vì nó hoạt động an toàn với Structured Concurrency và tự động quản lý thời gian sống của thao tác bất đồng bộ.
Các điểm chính
.task { } là một bổ tố View tạo ra một Task trong ngữ cảnh async khi View xuất hiện trên màn hình. SwiftUI thực thi closure được cung cấp trong một luồng nền, giữ cho luồng chính rảnh cho các thao tác UI. Khi View biến mất, SwiftUI tự động hủy Task thông qua cơ chế Structured Concurrency — điều này đảm bảo rằng thao tác bất đồng bộ không tiếp tục thực thi sau khi kết quả của nó không còn cần thiết nữa.
Theo Apple — Swift Programming Language (2025), .task sử dụng khái niệm Structured Concurrency được giới thiệu trong Swift 5.5. Mỗi .task tạo ra một tác vụ con trong tác vụ của View cha. Nếu tác vụ cha bị hủy (View biến mất), tất cả các tác vụ con cũng tự động bị hủy. Điều này đơn giản hóa triệt để việc quản lý vòng đời của các thao tác bất đồng bộ so với việc lưu trữ thủ công các tham chiếu đến DispatchWorkItem hoặc AnyCancellable.
Không giống như cách tiếp cận truyền thống với @State + gọi thủ công trong .onAppear, .task không yêu cầu lưu trữ tham chiếu đến Task để hủy sau này. SwiftUI tự động thực hiện việc này, giảm mã boilerplate và loại bỏ rủi ro quên hủy một tác vụ.
struct ArticlesView: View {
@State var articles: [Article] = []
@State var error: Error?
var body: some View {
List(articles) { article in
Text(article.title)
}
.task {
do {
articles = await APIClient().fetchArticles()
} catch {
self.error = error
}
}
}
}
Nhiều nhà phát triển đã quen với việc tải dữ liệu trong .onAppear, nhưng với sự xuất hiện của async/await và .task, cách tiếp cận này đã trở nên lỗi thời. .onAppear thực thi mã một cách đồng bộ — đối với các thao tác bất đồng bộ bên trong .onAppear, cần phải bọc lệnh gọi trong Task { } và giữ thủ công tham chiếu để có thể hủy. .task thực hiện việc này tự động.
| Đặc điểm | .task { } | .onAppear |
|---|---|---|
| Ngữ cảnh async | Async/await tích hợp | Cần wrapper Task { } |
| Tự động hủy | Có, khi View biến mất | Không, phải thực hiện thủ công |
| Structured Concurrency | Hỗ trợ | Không hỗ trợ |
| Thực thi lại | Chỉ khi id thay đổi | Mỗi lần View xuất hiện |
| Khuyến nghị Apple | Cách ưu tiên | Cho thao tác đồng bộ |
.onAppear vẫn hữu ích cho các thao tác đồng bộ — ví dụ, ghi log hoặc thiết lập UI ban đầu. Nhưng để tải dữ liệu bất đồng bộ, yêu cầu mạng, thao tác cơ sở dữ liệu hoặc hệ thống tệp, hãy sử dụng .task. Nó an toàn hơn và sạch hơn từ góc độ kiến trúc.
// ❌ Cách cũ: Task trong .onAppear không hủy
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Cách mới: .task quản lý việc hủy
func body() { var body: some View { Text("") }
.task { await loadData() }
Bổ tố .task(id:) chấp nhận một tham số bổ sung — một định danh. Khi giá trị của định danh thay đổi, SwiftUI hủy tác vụ hiện tại và bắt đầu một tác vụ mới với định danh mới. Điều này lý tưởng cho các màn hình nơi dữ liệu phụ thuộc vào một tham số đã chọn — ví dụ, danh sách bài viết theo danh mục hoặc chi tiết sản phẩm theo ID.
struct CategoryView: View {
let categoryId: Int
@State var items: [Item] = []
var body: some View {
List(items) { item in
Text(item.name)
}
.task(id: categoryId) {
await loadItems(for: categoryId)
}
}
func loadItems(for id: Int) async {
do {
items = await APIClient().fetchItems(categoryId: id)
} catch {
// xử lý lỗi
}
}
}
Khi categoryId thay đổi, SwiftUI hủy yêu cầu trước đó và bắt đầu yêu cầu mới. Điều này đặc biệt quan trọng khi chuyển đổi danh mục nhanh — các yêu cầu cũ sẽ không cạnh tranh với yêu cầu mới để cập nhật trạng thái. Nếu không có .task(id:), bạn sẽ phải theo dõi thủ công các thay đổi qua .onChange và quản lý Task thủ công.
Mặc dù .task tự động hủy tác vụ khi View biến mất, bản thân thao tác bất đồng bộ phải hợp tác kiểm tra việc hủy. Swift sử dụng mô hình hủy hợp tác — Task.cancel() không dừng thực thi một cách cưỡng chế, mà chỉ đặt cờ isCancelled. Mã bên trong tác vụ nên định kỳ kiểm tra cờ này.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Kiểm tra hủy
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() ném CancellationError nếu tác vụ đã bị hủy. Đây là cách kiểm tra đơn giản nhất — nó hoạt động trong bất kỳ ngữ cảnh async nào. Một cách thay thế là kiểm tra Task.isCancelled thủ công trước các thao tác tốn kém. Đối với URLSession, các yêu cầu mạng tự động bị hủy khi tác vụ bị hủy, vì URLSession hỗ trợ Structured Concurrency một cách tự nhiên.
.task phù hợp với nhiều kịch bản: từ tải JSON đơn giản đến các thao tác song song phức tạp với TaskGroup. Hãy xem xét ba trường hợp sử dụng điển hình.
struct ProfileView: View {
@State var profile: Profile?
@State var isLoading = true
var body: some View {
Group {
if isLoading {
ProgressView()
} else if let profile {
Text(profile.name)
} else {
Text("Tải thất bại")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// hồ sơ vẫn là nil
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("Bảng điều khiển")
.task {
stats = await Task {
await withThrowingTaskGroup { group in
group.addTask { await API().fetchUsers() }
group.addTask { await API().fetchOrders() }
group.addTask { await API().fetchRevenue() }
return DashboardStats(
users: try await group.next(),
orders: try await group.next(),
revenue: try await group.next()
)
}
}.value
}
}
}
Lỗi phổ biến nhất là thay đổi các thuộc tính UI bên trong .task mà không chuyển sang luồng chính. Mặc dù SwiftUI tự động trả các cập nhật về luồng chính khi sửa đổi @State trong ngữ cảnh async, việc thao tác trực tiếp các phần tử UIKit bên trong .task có thể gây ra sự cố.
// ❌ Lỗi: không xử lý lỗi
.task {
let data = await fetchData() // sập khi có lỗi!
items = data
}
// ✅ Đúng: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Câu hỏi thường gặp
.task tự động hủy tác vụ khi View biến mất và hỗ trợ Structured Concurrency. Task { } trong .onAppear yêu cầu giữ thủ công tham chiếu đến tác vụ và gọi cancel() trong .onDisappear. .task cũng dễ đọc hơn — nó chỉ rõ rằng việc tải dữ liệu là một phần của vòng đời View.
Có, để đăng ký AsyncSequence hoặc AsyncStream, hãy sử dụng for await value in publisher.values bên trong .task. Điều này hoạt động với cả async/await và Combine thông qua phần mở rộng Publisher.values. Khi View biến mất, vòng lặp sẽ tự động kết thúc và đăng ký sẽ bị hủy.
Có, nếu View bên trong TabView được tạo lại mỗi khi chuyển. Từ iOS 18, TabView có thể giữ Views trong bộ nhớ — trong trường hợp này .task không khởi động lại. Sử dụng .task(id:) với định danh tab nếu bạn cần tải lại dữ liệu mỗi khi chuyển tab.
SwiftUI sẽ hủy tác vụ khi View biến mất. Nếu yêu cầu URLSession nằm trong tác vụ, nó cũng sẽ bị hủy. Nếu tác vụ không hỗ trợ hủy hợp tác (ví dụ, không kiểm tra isCancelled), nó sẽ tiếp tục chạy, nhưng kết quả của nó sẽ không được áp dụng cho trạng thái vì View không còn tồn tại.
Có, bạn có thể thêm nhiều bổ tố .task trên một View. Mỗi bổ tố tạo một tác vụ độc lập. Điều này hữu ích để tách biệt các nguồn dữ liệu khác nhau: một .task để tải hồ sơ, một .task khác để đăng ký WebSocket, một .task thứ ba để theo dõi vị trí địa lý.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm