.task { } — είναι ένας τροποποιητής στο SwiftUI, που παρουσιάστηκε στο iOS 15, ο οποίος ξεκινά μια ασύγχρονη λειτουργία όταν εμφανίζεται το View και την ακυρώνει αυτόματα όταν εξαφανίζεται. Σε αντίθεση με το .onAppear, το οποίο εκτελεί σύγχρονο κώδικα χωρίς δυνατότητα ακύρωσης, το .task λειτουργεί με περιβάλλον async/await και λαμβάνει υπόψη τον κύκλο ζωής του View: όταν το View εξαφανίζεται, το SwiftUI καλεί cancel() στο δημιουργημένο Task. Αυτό αποτρέπει διαρροές μνήμης και εκτέλεση λειτουργιών αφού το View δεν χρειάζεται πλέον ενημέρωση. Σύμφωνα με το Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), το .task είναι η προτιμώμενη μέθοδος φόρτωσης δεδομένων στο SwiftUI, καθώς λειτουργεί με ασφάλεια με το Structured Concurrency και διαχειρίζεται αυτόματα τη διάρκεια ζωής της ασύγχρονης λειτουργίας.
Κύρια σημεία
.task { } — είναι ένας τροποποιητής View που δημιουργεί ένα Task σε περιβάλλον async όταν το View εμφανίζεται στην οθόνη. Το 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 το κάνει αυτόματα, μειώνοντας την ποσότητα boilerplate κώδικα και εξαλείφοντας τον κίνδυνο να ξεχάσετε να ακυρώσετε την εργασία.
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 ακυρώνει το προηγούμενο αίτημα και ξεκινά ένα νέο. Αυτό είναι ιδιαίτερα σημαντικό κατά τη γρήγορη εναλλαγή κατηγοριών — τα παλιά αιτήματα δεν θα ανταγωνίζονται τα νέα για την ενημέρωση της κατάστασης. Χωρίς .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() // καταρρέει στο σφάλμα!
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), θα συνεχίσει να εκτελείται, αλλά το αποτέλεσμά της δεν θα εφαρμοστεί στην κατάσταση, καθώς το View δεν υπάρχει πλέον.
Ναι, μπορείτε να προσθέσετε πολλαπλούς τροποποιητές .task σε ένα View. Κάθε ένας δημιουργεί μια ανεξάρτητη εργασία. Αυτό είναι βολικό για διαχωρισμό διαφορετικών πηγών δεδομένων: ένα .task για φόρτωση προφίλ, δεύτερο για εγγραφή σε WebSocket, τρίτο για παρακολούθηση γεωτοποθεσίας.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης