.task { } — اصلاحکنندهای در SwiftUI است که در iOS 15 معرفی شد، هنگام ظاهر شدن View یک عملیات ناهمگام را شروع کرده و هنگام ناپدید شدن آن را به طور خودکار لغو میکند. برخلاف .onAppear که کد همگام را بدون امکان لغو اجرا میکند، .task با زمینه async/await کار میکند و چرخه حیات View را در نظر میگیرد: هنگام ناپدید شدن View، SwiftUI روی Task ایجاد شده cancel() فراخوانی میکند. این کار از نشت حافظه و اجرای عملیات پس از عدم نیاز به بهروزرسانی View جلوگیری میکند. به گفته Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024)، .task روش ترجیحی برای بارگذاری دادهها در SwiftUI است، زیرا با Structured Concurrency به طور امن کار کرده و زمان حیات عملیات ناهمگام را به طور خودکار مدیریت میکند.
نکات اصلی
.task { } — اصلاحکننده View است که هنگام ظاهر شدن View روی صفحه، یک Task در زمینه async ایجاد میکند. SwiftUI closure داده شده را در پسزمینه اجرا میکند، در حالی که نخ اصلی برای عملیات UI آزاد میماند. هنگامی که View ناپدید میشود، SwiftUI به طور خودکار Task را از طریق مکانیزم Structured Concurrency لغو میکند — این تضمین میکند که عملیات ناهمگام پس از عدم نیاز کسی به نتیجه آن ادامه نخواهد یافت.
به گفته Apple — Swift Programming Language (2025)، .task از مفهوم Structured Concurrency معرفی شده در Swift 5.5 استفاده میکند. هر .task یک وظیفه فرزند در چارچوب وظیفه والد View ایجاد میکند. اگر وظیفه والد لغو شود (View ناپدید شود)، تمام وظایف فرزند نیز به طور خودکار لغو میشوند. این کار مدیریت چرخه حیات عملیاتهای ناهمگام را در مقایسه با نگهداری دستی ارجاعهای DispatchWorkItem یا AnyCancellable به طور چشمگیری ساده میکند.
برخلاف رویکرد سنتی با @State + فراخوانی دستی در .onAppear، .task نیازی به نگهداری ارجاع به Task برای لغو بعدی ندارد. SwiftUI این کار را به طور خودکار انجام میدهد که مقدار کد تکراری را کاهش داده و خطر فراموش کردن لغو وظیفه را از بین میبرد.
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
}
}
}
}
بسیاری از توسعهدهندگان عادت دارند دادهها را در .onAppear بارگذاری کنند، اما با ظهور async/await و .task این رویکرد منسوخ شده است. .onAppear کد را به صورت همگام اجرا میکند — برای عملیاتهای ناهمگام درون .onAppear باید فراخوانی را در Task { } بپیچید و به صورت دستی یک ارجاع به آن برای لغو احتمالی نگه دارید. .task این کار را به طور خودکار انجام میدهد.
| ویژگی | .task { } | .onAppear |
|---|---|---|
| زمینه async | async/await داخلی | نیاز به پیچیدن در Task { } |
| لغو خودکار | بله، هنگام ناپدید شدن View | خیر، باید دستی پیادهسازی شود |
| Structured Concurrency | پشتیبانی میکند | پشتیبانی نمیکند |
| اجرای مجدد | فقط هنگام تغییر id | هر بار هنگام ظاهر شدن |
| توصیه Apple | روش ترجیحی | برای عملیات همگام |
.onAppear هنوز برای عملیاتهای همگام مفید است — مثلاً لاگگیری یا پیکربندی اولیه UI. اما برای بارگذاری ناهمگام دادهها، درخواستهای شبکه، کار با پایگاه داده یا سیستم فایل از .task استفاده کنید. این از نظر معماری امنتر و تمیزتر است.
// ❌ رویکرد قدیمی: Task در .onAppear بدون لغو
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ رویکرد مدرن: .task لغو را مدیریت میکند
func body() { var body: some View { Text("") }
.task { await loadData() }
اصلاحکننده .task(id:) یک پارامتر اضافی — شناسه — دریافت میکند. هنگامی که مقدار شناسه تغییر میکند، SwiftUI وظیفه فعلی را لغو کرده و یک وظیفه جدید با شناسه جدید راهاندازی میکند. این برای صفحههایی که دادهها به پارامتر انتخاب شده وابسته هستند ایدهآل است — مثلاً لیست مقالات بر اساس دستهبندی یا جزئیات محصول بر اساس 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 {
// مدیریت خطا
}
}
}
هنگامی که categoryId تغییر میکند، SwiftUI درخواست قبلی را لغو کرده و درخواست جدیدی راهاندازی میکند. این به ویژه هنگام جابجایی سریع بین دستهبندیها مهم است — درخواستهای قدیمی برای بهروزرسانی state با درخواستهای جدید رقابت نخواهند کرد. بدون .task(id:) باید تغییرات را به صورت دستی از طریق .onChange ردیابی کرده و Task را به صورت دستی مدیریت کنید.
اگرچه .task به طور خودکار وظیفه را هنگام ناپدید شدن View لغو میکند، خود عملیات ناهمگام باید به صورت مشارکتی لغو را بررسی کند. Swift از مدل لغو مشارکتی استفاده میکند — Task.cancel() اجرا را به زور متوقف نمیکند، بلکه فقط پرچم isCancelled را تنظیم میکند. کد درون وظیفه باید به صورت دورهای این پرچم را بررسی کند.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// بررسی لغو
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() اگر وظیفه لغو شده باشد CancellationError را پرتاب میکند. این سادهترین روش بررسی است — در هر زمینه async کار میکند. جایگزین — بررسی دستی Task.isCancelled قبل از عملیاتهای پرهزینه. برای URLSession درخواستهای شبکه هنگام لغو وظیفه به طور خودکار لغو میشوند، زیرا URLSession از ابتدا از Structured Concurrency پشتیبانی میکند.
.task برای سناریوهای متعددی مناسب است: از بارگذاری ساده JSON تا عملیاتهای موازی پیچیده با TaskGroup. بیایید سه نمونه استفاده معمول را بررسی کنیم.
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("بارگذاری ناموفق")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// پروفایل nil میماند
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("داشبورد")
.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
}
}
}
رایجترین اشتباه — تغییر ویژگیهای UI درون .task بدون جابجایی به نخ اصلی. اگرچه SwiftUI به طور خودکار بهروزرسانیها را هنگام تغییر @State در زمینه async به نخ اصلی بازمیگرداند، دستکاری مستقیم عناصر UIKit درون .task میتواند باعث crash شود.
// ❌ خطا: بدون مدیریت خطا
.task {
let data = await fetchData() // در خطا crash میکند!
items = data
}
// ✅ صحیح: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
سوالات متداول
.task به طور خودکار وظیفه را هنگام ناپدید شدن View لغو میکند و از Structured Concurrency پشتیبانی میکند. Task { } در .onAppear نیاز به نگهداری دستی ارجاع به وظیفه و فراخوانی cancel() در .onDisappear دارد. .task همچنین خواناتر است — به صراحت نشان میدهد که بارگذاری داده بخشی از چرخه حیات View است.
بله، برای اشتراک در AsyncSequence یا AsyncStream از for await value in publisher.values درون .task استفاده کنید. این هم با async/await و هم با Combine از طریق افزونه Publisher.values کار میکند. هنگام ناپدید شدن View، تکرار به طور خودکار پایان یافته و اشتراک لغو میشود.
بله، اگر View درون TabView در هر جابجایی دوباره ساخته شود. از iOS 18 به بعد TabView میتواند View را در حافظه نگه دارد — در این صورت .task دوباره راهاندازی نمیشود. اگر نیاز به بارگذاری مجدد دادهها در هر جابجایی دارید، از .task(id:) با شناسه برگه استفاده کنید.
SwiftUI هنگام ناپدید شدن View وظیفه را لغو میکند. اگر درخواست URLSession درون وظیفه بود، آن نیز لغو میشود. اگر وظیفه از لغو مشارکتی پشتیبانی نکند (مثلاً isCancelled را بررسی نکند)، اجرا ادامه مییابد اما نتیجه آن به state اعمال نمیشود، زیرا View دیگر وجود ندارد.
بله، میتوان چند اصلاحکننده .task به یک View اضافه کرد. هر کدام یک وظیفه مستقل ایجاد میکند. این برای جداسازی منابع داده مختلف مفید است: یکی برای بارگذاری پروفایل، دومی برای اشتراک در WebSocket، سومی برای نظارت بر موقعیت جغرافیایی.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید