.task { } — چیست، اصلاح‌کننده async و بارگذاری داده‌ها در View

نویسنده: IT Sectr منتشر شده: 2026-06-26 زمان مطالعه: 9 دقیقه

.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 { } — اصلاح‌کننده SwiftUI برای بارگذاری ناهمگام داده‌ها هنگام ظاهر شدن View، قابل دسترس از iOS 15.
  • لغو خودکار — هنگام ناپدید شدن View، SwiftUI Task را لغو کرده و از نشت حافظه جلوگیری می‌کند.
  • زمینه async/await — درون .task فراخوانی‌های async بدون نیاز به DispatchQueue یا Combine در دسترس هستند.
  • .task(id:) — نسخه با شناسه، وظیفه را هنگام تغییر مقدار مشخص شده دوباره راه‌اندازی می‌کند.
  • Structured Concurrency — .task از Structured Concurrency و TaskGroup برای عملیات موازی پشتیبانی می‌کند.

.task { } در SwiftUI چیست

.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 این کار را به طور خودکار انجام می‌دهد که مقدار کد تکراری را کاهش داده و خطر فراموش کردن لغو وظیفه را از بین می‌برد.

swift
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
            }
        }
    }
}

.task در مقابل .onAppear: تفاوت‌های کلیدی

بسیاری از توسعه‌دهندگان عادت دارند داده‌ها را در .onAppear بارگذاری کنند، اما با ظهور async/await و .task این رویکرد منسوخ شده است. .onAppear کد را به صورت همگام اجرا می‌کند — برای عملیات‌های ناهمگام درون .onAppear باید فراخوانی را در Task { } بپیچید و به صورت دستی یک ارجاع به آن برای لغو احتمالی نگه دارید. .task این کار را به طور خودکار انجام می‌دهد.

ویژگی.task { }.onAppear
زمینه asyncasync/await داخلینیاز به پیچیدن در Task { }
لغو خودکاربله، هنگام ناپدید شدن Viewخیر، باید دستی پیاده‌سازی شود
Structured Concurrencyپشتیبانی می‌کندپشتیبانی نمی‌کند
اجرای مجددفقط هنگام تغییر idهر بار هنگام ظاهر شدن
توصیه Appleروش ترجیحیبرای عملیات همگام

.onAppear هنوز برای عملیات‌های همگام مفید است — مثلاً لاگ‌گیری یا پیکربندی اولیه UI. اما برای بارگذاری ناهمگام داده‌ها، درخواست‌های شبکه، کار با پایگاه داده یا سیستم فایل از .task استفاده کنید. این از نظر معماری امن‌تر و تمیزتر است.

swift
// ❌ رویکرد قدیمی: 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:) — راه‌اندازی مجدد هنگام تغییر داده

اصلاح‌کننده .task(id:) یک پارامتر اضافی — شناسه — دریافت می‌کند. هنگامی که مقدار شناسه تغییر می‌کند، SwiftUI وظیفه فعلی را لغو کرده و یک وظیفه جدید با شناسه جدید راه‌اندازی می‌کند. این برای صفحه‌هایی که داده‌ها به پارامتر انتخاب شده وابسته هستند ایده‌آل است — مثلاً لیست مقالات بر اساس دسته‌بندی یا جزئیات محصول بر اساس ID.

swift
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 را به صورت دستی مدیریت کنید.

لغو وظایف و بررسی isCancelled

اگرچه .task به طور خودکار وظیفه را هنگام ناپدید شدن View لغو می‌کند، خود عملیات ناهمگام باید به صورت مشارکتی لغو را بررسی کند. Swift از مدل لغو مشارکتی استفاده می‌کند — Task.cancel() اجرا را به زور متوقف نمی‌کند، بلکه فقط پرچم isCancelled را تنظیم می‌کند. کد درون وظیفه باید به صورت دوره‌ای این پرچم را بررسی کند.

swift
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

.task برای سناریوهای متعددی مناسب است: از بارگذاری ساده JSON تا عملیات‌های موازی پیچیده با TaskGroup. بیایید سه نمونه استفاده معمول را بررسی کنیم.

بارگذاری با مدیریت خطا

swift
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 می‌ماند
            }
        }
    }
}

بارگذاری موازی با TaskGroup

swift
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
            }
    }
}

اشتباهات رایج با .task

رایج‌ترین اشتباه — تغییر ویژگی‌های UI درون .task بدون جابجایی به نخ اصلی. اگرچه SwiftUI به طور خودکار به‌روزرسانی‌ها را هنگام تغییر @State در زمینه async به نخ اصلی بازمی‌گرداند، دستکاری مستقیم عناصر UIKit درون .task می‌تواند باعث crash شود.

  • فراموشی try/catch — .task خطاها را به طور خودکار مدیریت نمی‌کند. تمام توابع پرتاب‌کننده درون آن باید در do/catch پیچیده شوند، در غیر این صورت برنامه crash می‌کند.
  • شرایط رقابتی (race condition) — اگر چند .task(id:) با idهای مختلف راه‌اندازی شده و همان state را به‌روزرسانی کنند، رقابت ممکن است. از ویژگی‌های جداگانه برای منابع داده مختلف استفاده کنید.
  • عملیات‌های همگام طولانی — .task کد همگام را ناهمگام نمی‌کند. اگر درون .task کار همگام سنگینی وجود دارد، آن را در Task.detached بپیچید یا به یک متد async جداگانه منتقل کنید.
  • نادیده گرفتن CancellationError — هنگام بررسی Task.checkCancellation() خطای CancellationError باید به بالا منتقل شود، نه سرکوب. سرکوب لغو می‌تواند منجر به نشت حافظه شود.
swift
// ❌ خطا: بدون مدیریت خطا
.task {
    let data = await fetchData() // در خطا crash می‌کند!
    items = data
}

// ✅ صحیح: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

سوالات متداول

تفاوت بین .task و استفاده از Task { } در .onAppear چیست؟

.task به طور خودکار وظیفه را هنگام ناپدید شدن View لغو می‌کند و از Structured Concurrency پشتیبانی می‌کند. Task { } در .onAppear نیاز به نگهداری دستی ارجاع به وظیفه و فراخوانی cancel() در .onDisappear دارد. .task همچنین خواناتر است — به صراحت نشان می‌دهد که بارگذاری داده بخشی از چرخه حیات View است.

آیا می‌توان از .task برای اشتراک در publisher استفاده کرد؟

بله، برای اشتراک در AsyncSequence یا AsyncStream از for await value in publisher.values درون .task استفاده کنید. این هم با async/await و هم با Combine از طریق افزونه Publisher.values کار می‌کند. هنگام ناپدید شدن View، تکرار به طور خودکار پایان یافته و اشتراک لغو می‌شود.

.task با TabView چگونه کار می‌کند — آیا وظیفه هنگام جابجایی برگه‌ها دوباره راه‌اندازی می‌شود؟

بله، اگر View درون TabView در هر جابجایی دوباره ساخته شود. از iOS 18 به بعد TabView می‌تواند View را در حافظه نگه دارد — در این صورت .task دوباره راه‌اندازی نمی‌شود. اگر نیاز به بارگذاری مجدد داده‌ها در هر جابجایی دارید، از .task(id:) با شناسه برگه استفاده کنید.

اگر View حاوی .task قبل از اتمام درخواست ناپدید شود چه اتفاقی می‌افتد؟

SwiftUI هنگام ناپدید شدن View وظیفه را لغو می‌کند. اگر درخواست URLSession درون وظیفه بود، آن نیز لغو می‌شود. اگر وظیفه از لغو مشارکتی پشتیبانی نکند (مثلاً isCancelled را بررسی نکند)، اجرا ادامه می‌یابد اما نتیجه آن به state اعمال نمی‌شود، زیرا View دیگر وجود ندارد.

آیا می‌توان از چند .task روی یک View استفاده کرد؟

بله، می‌توان چند اصلاح‌کننده .task به یک View اضافه کرد. هر کدام یک وظیفه مستقل ایجاد می‌کند. این برای جداسازی منابع داده مختلف مفید است: یکی برای بارگذاری پروفایل، دومی برای اشتراک در WebSocket، سومی برای نظارت بر موقعیت جغرافیایی.

خلاصه

  • .task { } — اصلاح‌کننده SwiftUI برای عملیات‌های ناهمگام با لغو خودکار هنگام ناپدید شدن View.
  • پشتیبانی async/await — درون .task زمینه کامل async بدون نیاز به پیچیدن در Task در دسترس است.
  • .task(id:) — وظیفه را هنگام تغییر شناسه دوباره راه‌اندازی می‌کند، جایگزین .onChange دستی می‌شود.
  • لغو مشارکتی — برای بررسی لغو درون وظیفه از Task.checkCancellation() استفاده کنید.
  • Structured Concurrency — .task از TaskGroup و عملیات موازی با لغو وظایف فرزند پشتیبانی می‌کند.
  • جایگزینی .onAppear — برای بارگذاری ناهمگام داده‌ها به جای ترکیب .onAppear + Task + .onDisappear از .task استفاده کنید.
  • مدیریت خطا — تمام فراخوانی‌های async درون .task باید برای جلوگیری از crash در do/catch پیچیده شوند.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید