@Observable Macro στο SwiftUI: Πλήρης Οδηγός για το Observation Framework (2026)
Το @Observable macro του Swift αντικαθιστά το ObservableObject με ανά-ιδιότητα tracking, λιγότερο boilerplate και αισθητά καλύτερη απόδοση στο SwiftUI.
Το @Observable macro είναι μια μακροεντολή του Swift 5.9+ που μετατρέπει αυτόματα μια κλάση σε observable τύπο, αντικαθιστώντας το πρωτόκολλο ObservableObject και τα property wrappers @Published, @StateObject και @ObservedObject. Το Observation framework, διαθέσιμο από το iOS 17, παρακολουθεί προσβάσεις σε ιδιότητες στο επίπεδο του κάθε property (όχι σε επίπεδο ολόκληρου του αντικειμένου), οπότε οι views στο SwiftUI ανανεώνονται μόνο όταν αλλάζει το συγκεκριμένο πεδίο που διαβάζουν. Αυτό μειώνει αισθητά τα περιττά redraws και απλοποιεί τον κώδικα.
Το @Observable εφαρμόζεται σε reference types (classes) και είναι διαθέσιμο από iOS 17 / macOS 14 / Swift 5.9.
Αντικαθιστά τα ObservableObject, @Published, @StateObject, @ObservedObject και @EnvironmentObject.
Το tracking γίνεται ανά ιδιότητα, οπότε ένα view ανανεώνεται μόνο όταν αλλάζει η ιδιότητα που πραγματικά διαβάζει.
Για bindings σε child views χρησιμοποιείται το νέο property wrapper @Bindable αντί για @ObservedObject.
Η συνάρτηση withObservationTracking(_:onChange:) επιτρέπει χρήση του Observation εκτός SwiftUI, για παράδειγμα σε UIKit ή command-line εργαλεία.
Η μετάβαση από ObservableObject είναι σταδιακή. Τα δύο μοντέλα μπορούν να συνυπάρχουν στο ίδιο project χωρίς συγκρούσεις.
Τι είναι το @Observable macro;
Το @Observable είναι μια attached macro που γράφει για εσένα τον boilerplate κώδικα που χρειάζεται μια κλάση για να παρακολουθείται από το Observation framework. Όταν το επισημαίνεις πάνω από μια class, ο μεταγλωττιστής εισάγει αυτόματα μια συμμόρφωση με το πρωτόκολλο Observable, μετατρέπει κάθε stored property σε computed property με getter/setter που καλεί τα access(_:) και withMutation(of:_:) ενός internal ObservationRegistrar, και διατηρεί ένα ιδιωτικό registrar instance ανά αντικείμενο.
Όσοι δουλεύαμε με Objective-C και KVO στα παλιά χρόνια, η ιδέα δεν είναι εντελώς ξένη. Το framework παρακολουθεί αναγνώσεις και εγγραφές σε ιδιότητες. Η διαφορά είναι ότι το Observation είναι type-safe, δεν χρειάζεται keypaths ως strings, και ολόκληρη η συνδρομή γίνεται αυτόματα όταν ένα SwiftUI view διαβάζει μια ιδιότητα μέσα στο body του.
Δες τα επίσημα Apple docs για το Observation framework για τις πλήρεις υπογραφές των API. Το Observation κυκλοφόρησε με το Swift 5.9 και αξιοποιεί τη νέα δυνατότητα macros της γλώσσας, οπότε ο παραγόμενος κώδικας είναι ορατός και debuggable στο Xcode μέσω του "Expand Macro" menu (κάτι που προτιμώ να ανοίγω αρκετές φορές μέχρι να εσωτερικεύσει κανείς πώς δουλεύει).
@Observable vs ObservableObject: η σύγκριση
Πολλοί iOS developers ρωτούν αν πρέπει να μεταβούν άμεσα ή να περιμένουν. Η σύντομη απάντηση: αν στοχεύεις iOS 17+, χρησιμοποίησε @Observable για νέο κώδικα. Η σύγκριση των δύο μηχανισμών:
Χαρακτηριστικό
@Observable
ObservableObject
Ελάχιστη έκδοση
iOS 17 / macOS 14
iOS 13 / macOS 10.15
Property wrappers
Κανένα μέσα στην κλάση
Απαιτεί @Published
SwiftUI χρήση
@State, @Bindable, @Environment
@StateObject, @ObservedObject, @EnvironmentObject
Granularity ανανέωσης
Ανά ιδιότητα
Ολόκληρο το object
Combine integration
Όχι by default
Built-in objectWillChange
Computed properties
Παρακολουθούνται αυτόματα
Όχι
Boilerplate
Ελάχιστο
Σημαντικό
Η πιο πρακτική διαφορά είναι αυτή: στο ObservableObject, κάθε φορά που αλλάζει μια @Published ιδιότητα, όλα τα views που κρατούν αναφορά στο object κάνουν re-evaluate το body τους. Με @Observable, μόνο τα views που διαβάζουν τη συγκεκριμένη ιδιότητα ανανεώνονται. Σε μεγάλες οθόνες με πολλά independent fields, αυτό μπορεί να μειώσει τα body invalidations κατά 80% ή περισσότερο. Δεν είναι θεωρητικό. Φαίνεται εύκολα στο Instruments.
Βασική χρήση με SwiftUI
Ένα ελάχιστο παράδειγμα μοντέλου και view. Παρατήρησε ότι δεν χρησιμοποιούμε @Published και ότι το ίδιο το view κρατά το instance σε @State (όχι σε @StateObject):
import SwiftUI
import Observation
@Observable
final class CounterModel {
var count = 0
var label = "Πάτησε για αύξηση"
func increment() {
count += 1
}
}
struct CounterView: View {
@State private var model = CounterModel()
var body: some View {
VStack(spacing: 16) {
Text(model.label)
Text("Τιμή: \(model.count)")
.font(.largeTitle)
Button("+1", action: model.increment)
}
.padding()
}
}
Δύο κανόνες που αξίζει να εσωτερικεύσεις: (1) χρησιμοποίησε @State όταν το view κατέχει το model. Το SwiftUI θα κρατήσει το reference για όλο τον κύκλο ζωής του view, ακριβώς όπως έκανε το @StateObject. (2) Πέρνα το model απλά ως property σε child views όταν χρειάζονται μόνο read access. Δεν χρειάζεσαι κανένα property wrapper για read-only πρόσβαση, το tracking γίνεται μόνο του μόλις το body διαβάσει την ιδιότητα.
Για μοτίβα διαχείρισης κατάστασης σε συνδυασμό με persistence, δες τον οδηγό για το SwiftData persistence framework. Το SwiftData χρησιμοποιεί τον ίδιο Observation μηχανισμό για τα @Model objects του, οπότε όλα τα μοτίβα που δείχνω εδώ μεταφέρονται αυτούσια.
Πότε χρειάζομαι το @Bindable;
Το @Bindable είναι property wrapper για όταν θέλεις να δημιουργήσεις Binding σε ιδιότητες ενός @Observable τύπου, για παράδειγμα όταν περνάς το model σε TextField, Toggle ή Slider. Δεν είναι αντίστοιχο του @ObservedObject· είναι κάτι πιο εξειδικευμένο. Χρειάζεται μόνο για το $ prefix.
struct EditProfileView: View {
@Bindable var user: UserModel
var body: some View {
Form {
TextField("Όνομα", text: $user.name)
Toggle("Ειδοποιήσεις", isOn: $user.notificationsEnabled)
Stepper("Ηλικία: \(user.age)", value: $user.age, in: 0...120)
}
}
}
Πώς μεταφέρω υπάρχοντα ObservableObject;
Η μετάβαση είναι μηχανική και σταδιακή. Δεν χρειάζεται να μεταφέρεις όλο το app μαζί, η συνύπαρξη των δύο συστημάτων υποστηρίζεται πλήρως. Στο τελευταίο μου project, μετέφερα 14 models μέσα σε ένα απόγευμα ακολουθώντας ακριβώς τα παρακάτω βήματα ανά κλάση:
Αντικατέστησε το : ObservableObject με το attribute @Observable πάνω από τη δήλωση της κλάσης.
Αφαίρεσε όλα τα @Published από τις ιδιότητες, δεν χρειάζονται πλέον.
Στα views, αντικατέστησε @StateObject var με @State private var, @ObservedObject var με απλό var (ή @Bindable var αν χρειάζεσαι $ bindings), και @EnvironmentObject var με @Environment(MyModel.self) var.
Το .environmentObject(model) γίνεται .environment(model) στον parent view.
Αν χρησιμοποιούσες objectWillChange.send() manually, αφαίρεσέ το (δεν υπάρχει στο νέο μοντέλο). Αν χρειαζόσουν το objectWillChange για Combine pipelines, χρησιμοποίησε withObservationTracking (παρακάτω).
Πριν:
final class SettingsStore: ObservableObject {
@Published var darkMode = false
@Published var fontSize: Double = 14
}
struct SettingsView: View {
@StateObject private var store = SettingsStore()
var body: some View {
Toggle("Dark mode", isOn: $store.darkMode)
}
}
Μετά:
@Observable
final class SettingsStore {
var darkMode = false
var fontSize: Double = 14
}
struct SettingsView: View {
@State private var store = SettingsStore()
var body: some View {
@Bindable var store = store
Toggle("Dark mode", isOn: $store.darkMode)
}
}
Πώς βελτιώνει την απόδοση του SwiftUI;
Η performance διαφορά γίνεται εμφανής σε views με πολλά independent fields. Φαντάσου μια οθόνη με 20 textfields που κρατούν την κατάσταση τους σε ένα κοινό model. Με ObservableObject, κάθε πληκτρολόγηση σε οποιοδήποτε field προκαλεί ανανέωση όλων των 20, γιατί η μετάδοση γίνεται σε επίπεδο αντικειμένου. Με @Observable, ανανεώνεται μόνο το συγκεκριμένο TextField του οποίου η ιδιότητα άλλαξε.
Στο profiling με το Instruments (template "SwiftUI"), θα δεις δραστική μείωση στα body invocations. Σε πραγματικά apps, αυτό μεταφράζεται σε λιγότερα frame drops κατά το scroll σε λίστες, καλύτερη απόκριση σε forms, και χαμηλότερη κατανάλωση μπαταρίας. Είναι ένα από τα πιο σημαντικά SwiftUI optimizations που μπορείς να εφαρμόσεις σε μεγάλα codebases χωρίς να αλλάξεις την αρχιτεκτονική.
Επιπλέον, το @Observable παρακολουθεί computed properties αυτόματα. Αν έχεις var fullName: String { firstName + " " + lastName }, τα views που διαβάζουν το fullName θα ανανεωθούν όταν αλλάξει είτε το firstName είτε το lastName. Με ObservableObject αυτό απαιτούσε χειροκίνητο code με Combine.assign ή willSet. Αν εργάζεσαι σε δομές με ασύγχρονη ροή δεδομένων, ο οδηγός για το Swift Concurrency με async/await δείχνει πώς να συνδυάσεις @Observable models με actors και async/await.
withObservationTracking εκτός SwiftUI
Το Observation δεν είναι αποκλειστικότητα του SwiftUI. Με τη συνάρτηση withObservationTracking(_:onChange:) μπορείς να συνδέσεις παρακολούθηση σε οποιοδήποτε layer: UIKit view controllers, command-line tools, server-side Swift κώδικα. Η συνάρτηση εκτελεί ένα closure, καταγράφει ποιες ιδιότητες διαβάστηκαν, και καλεί το onChange την πρώτη φορά που αλλάζει κάποια από αυτές:
import Observation
func observe(_ apply: @escaping () -> Void) {
withObservationTracking {
apply()
} onChange: {
// Καλείται μόνο μία φορά. Επανα-εγγραφή για συνεχή παρακολούθηση.
Task { @MainActor in observe(apply) }
}
}
let model = CounterModel()
observe {
print("Count: \(model.count)")
}
model.count = 5
// Εκτυπώνει "Count: 0" στην αρχή, μετά "Count: 5"
Σημαντικό: το onChange ενεργοποιείται μόνο μία φορά. Για συνεχή tracking, πρέπει να κάνεις re-subscribe όπως φαίνεται παραπάνω. Αυτό σου δίνει πλήρη έλεγχο της συχνότητας. Μπορείς να κάνεις throttle, να ακυρώσεις πριν το επόμενο tick, ή να συγκεντρώσεις αλλαγές σε batches. Σε UIKit, αυτό είναι το αντίστοιχο που γεφυρώνει μια @Observable κλάση με το viewWillAppear ή με ένα NSNotification.
Συνηθισμένα σφάλματα και παγίδες
Έχω δει αρκετά apps να μπερδεύονται σε αυτά τα σημεία κατά τη μετάβαση, και ομολογώ ότι σε κάποια τα πάτησα κι εγώ:
Χρήση @StateObject με @Observable. Δεν compile-άρει. Οι παλιοί property wrappers δουλεύουν μόνο με ObservableObject. Πέρνα σε @State.
Value types. Το @Observable δουλεύει μόνο σε classes. Για structs, χρησιμοποίησε @State με mutating functions όπως πάντα.
Παρακολούθηση arrays/dictionaries. Το tracking γίνεται σε επίπεδο property assignment. Αν κάνεις model.items.append(...), το tracking το πιάνει γιατί καλείται το setter του items. Αν όμως κρατάς reference type μέσα σε array και αλλάζεις ιδιότητα του inner object, χρειάζεται και αυτό να είναι @Observable.
Lazy properties. Δεν παρακολουθούνται. Αν χρειάζεσαι tracking, μετέτρεψέ τες σε computed properties με κάποιον cache mechanism.
Combine pipelines. Δεν υπάρχει objectWillChange publisher. Για Combine integration, χρησιμοποίησε withObservationTracking για να γεφυρώσεις σε PassthroughSubject manually.
Συχνές ερωτήσεις
Είναι το @Observable αντικαταστάτης του Combine framework;
Όχι άμεσα. Το @Observable αντικαθιστά μόνο το ObservableObject για SwiftUI state. Το Combine παραμένει χρήσιμο για networking, debounce, merge και άλλα reactive operators. Αν χρειάζεσαι Combine pipeline από @Observable ιδιότητα, γεφύρωσε με withObservationTracking και ένα PassthroughSubject.
Μπορώ να έχω @Observable και ObservableObject στο ίδιο project;
Ναι, πλήρως. Είναι ανεξάρτητα συστήματα. Σταδιακή μετάβαση είναι το προτεινόμενο μοτίβο: μετέφερε ένα model τη φορά, ξεκινώντας από αυτά με τα περισσότερα @Published properties, εκεί θα δεις τη μεγαλύτερη performance βελτίωση και την πιο εμφανή απλοποίηση κώδικα.
Γιατί δεν χρειάζεται @Published στα @Observable models;
Το macro επεξεργάζεται κάθε stored property και αυτόματα παράγει κώδικα ισοδύναμο με @Published, δηλαδή getter/setter που ειδοποιούν τους subscribers μέσω του ObservationRegistrar. Είναι ένας από τους κύριους λόγους που γράφεις τόσο λιγότερο boilerplate.
Πώς περνώ ένα @Observable model σε όλα τα views;
Χρησιμοποίησε .environment(model) στον parent view και @Environment(MyModel.self) var model στα child views που το χρειάζονται. Είναι το αντίστοιχο του παλιού .environmentObject() και λειτουργεί με τον ίδιο τρόπο, αλλά με type-safe lookup χωρίς runtime crash αν το model λείπει.
Δουλεύει το @Observable με το SwiftData;
Ναι. Τα @Model classes του SwiftData είναι κάτω από την επιφάνεια @Observable, οπότε όλα τα παραπάνω μοτίβα ισχύουν και για αυτά. Μπορείς να τα χρησιμοποιείς απευθείας σε SwiftUI views χωρίς extra property wrappers, εκτός από @Bindable όταν χρειάζεσαι bindings σε form fields.
Το SensoryFeedback είναι ο declarative τρόπος να παίξεις haptics στο SwiftUI. Οδηγός με όλους τους τύπους, trigger patterns, σύγκριση με Core Haptics και best practices από UX engineer.
Πλήρης οδηγός για το App Intents framework στο iOS 26: AppIntent, AppEntity, AppShortcut, ενσωμάτωση με Apple Intelligence, Interactive Widgets και migration από SiriKit με κώδικα Swift.
Πρακτικός οδηγός για type-safe navigation στο SwiftUI με NavigationStack και NavigationPath: deep linking, pop-to-root, state restoration και accessibility patterns για iOS 26 με έτοιμα παραδείγματα κώδικα.