← Alle Beitrage

Das @State-Makro: Was Xcode 27 nicht mehr kompiliert

Apples Release Notes zu iOS 27 eröffnen den Eintrag zu @State mit der Beschreibung eines Fehlers, den SwiftUI seit iOS 13 mit sich herumträgt:3 „Ein @State mit einem Ausdruck als Anfangswert hat diesen Ausdruck bisher bei jeder Neuinstanziierung des View-Structs erneut ausgewertet. Im Fall von @State private var model = Model() bedeutet das, dass Model.init() über die Lebensdauer des Views hinweg viele Male aufgerufen wird.”1

Die Behebung ist eine Neuimplementierung. „Xcode 27 führt eine neue @State-Implementierung ein, die diese wiederholte Auswertung vermeidet. Dieses neue Verhalten wird auf iOS 17 und die zugehörigen Betriebssysteme zurückportiert. Das neue @State ist als Swift-Makro implementiert. Es ist weitgehend quellkompatibel mit der Property-Wrapper-Variante, mit einigen Ausnahmen.”1

Entscheidend ist, woran dieser Satz den Auslöser knüpft. Apple nennt Xcode, nicht ein Deployment Target.

Kurzfassung

  • Xcode 27 implementiert @State als Swift-Makro neu, und Apples Symbolseiten benennen den Wechsel aus beiden Richtungen: Auf der Seite zur Struktur State heißt es „Wenn Sie mit Xcode 27 oder neuer bauen, verwendet das System stattdessen das Makro State()”, auf der Seite zum Makro State() steht „Wenn Sie mit Xcode 26 oder älter bauen, verwendet das System stattdessen den Property Wrapper State.”23
  • Ihr Deployment Target kann das Makro nicht abwählen. Es trägt dieselbe Verfügbarkeit ab iOS 13.0 wie der Property Wrapper, den es ersetzt, und der Wechsel richtet sich nach der Xcode-Version statt nach dem Target. Die Laufzeithälfte wird nur auf „iOS 17 und die zugehörigen Betriebssysteme” zurückportiert — Projekte mit Unterstützung für iOS 15 oder 16 bekommen die Änderung zur Kompilierzeit also ohne sie.
  • Das stille Verwerfen ist altes, unverändertes Verhalten. Apple schreibt, es habe „sich durch das Makro nicht geändert, aber einige dieser Fälle kompilieren nicht mehr.”1 Das Makro verwandelt einen Fehler, der den Wert Ihres Initializers verschluckt hat, in einen Build-Fehler.
  • An zwei Mustern bricht die Kompilierung: an einem Initializer, der einer @State-Property zuweist, die zusätzlich am Deklarationsort einen Anfangswert trägt, und an einer Extension, die den memberwise Initializer aufruft, den der Compiler für ein durchgängig privates Struct synthetisiert.1 Apple schreibt „einige dieser Fälle”, nicht alle, und benennt nie, welche.
  • Vor dem Schreiben habe ich vier ausgelieferte Apps geprüft: 267 @State-Deklarationen, davon 201 mit Anfangswert am Deklarationsort, null Vorkommen der beiden brechenden Muster, ein Beinahe-Treffer — und ein grep-Idiom, das unter macOS falsche Entwarnung produziert.6

Der Eintrag steht in den Release Notes zu iOS und iPadOS 27 unter SwiftUI, nicht in den Release Notes zu Xcode 27, die keinen entsprechenden Eintrag führen.7 Ein merkwürdiger Ort für eine Änderung, die Apple Xcode zuschreibt — und ein guter Grund dafür, dass die Nachricht viele erst spät erreichen wird.

Der Performance-Fehler, den Apple behoben hat

Apples Beschreibung des alten Verhaltens ist für eine Release Note ungewöhnlich deutlich. Model.init() werde „über die Lebensdauer des Views hinweg viele Male aufgerufen.”1 SwiftUI instanziiert View-Structs permanent neu, und jede Neuinstanziierung hat den Ausdruck rechts vom Gleichheitszeichen erneut ausgewertet. Das Ergebnis hat SwiftUI verworfen, weil bereits existierender State gewinnt — geleistet wurde die Arbeit trotzdem.

Die Makro-Seite formuliert den neuen Vertrag in einem Satz: „Eine State()-Property instanziiert ihren Standardwert, wenn SwiftUI den View zum ersten Mal instanziiert.”2

Apples Seite zu den SwiftUI-Neuerungen ergänzt eine Einschränkung, die die Release Note auslässt — und genau diese Einschränkung ist der planungsrelevante Teil: „Bauen Sie Ihr Projekt mit Xcode 27 oder neuer, damit das Attribut @State das Makro State() verwendet, um einen State-Wert in einer App, Scene oder View zu erzeugen. Diese Änderung initialisiert und speichert Ihre Property nur dann genau einmal, wenn es sich um eine Klasse handelt.”4

„Wenn es sich um eine Klasse handelt” schränkt den Gewinn erheblich ein und zielt direkt auf das Muster, von dem moderne SwiftUI-Codebasen voll sind. Ein @Observable-Objekt in @State zu halten, ist Apples dokumentierter Ansatz, und das Beispiel auf der Makro-Seite hält genau so eine @Observable class Library.2 Der Initializer eines Structs ist meist billig. Ein Klassen-Initializer, der einen Store öffnet, eine Query startet oder einen Observer registriert, ist es nicht — und Apple beschreibt ihn als „viele Male” laufend.1

Was sich tatsächlich geändert hat und was nicht

Apple formuliert Semantik und Kompilierverhalten in einem einzigen Satz, dessen beide Hälften in entgegengesetzte Richtungen zeigen: „Wenn Sie an der @State-Deklaration einen Anfangswert angeben und zusätzlich versuchen, ihr in einem Initializer einen Wert zuzuweisen, wird der Wert aus dem Initializer verworfen. Dieses Verhalten hat sich durch das Makro nicht geändert, aber einige dieser Fälle kompilieren nicht mehr.”1

An der Bedeutung des Codes hat sich nichts geändert. Unter dem Property Wrapper tat ein Initializer, der einer @State-Property mit Anfangswert am Deklarationsort zuwies, schlicht nichts — stillschweigend, und der Build lief sauber durch. Das Makro lässt die Regel unangetastet und nimmt ihr das Schweigen.

Der abgeschaffte Fehlermodus ist der teure. Jemand schreibt einen Initializer, reicht einen Titel hindurch, sieht den falschen Titel gerendert und geht im View-Body auf die Suche. Der Compiler wusste es die ganze Zeit und hatte keine Möglichkeit, es zu sagen.

Apples eigenes Beispiel trägt die beiden Kommentare, auf die es ankommt:

struct StickerPageView: View {
    @State private var page = StickerPage()
    let title: String

    init(title: String) {
        // `title` won't have any effect
        // this also won't compile with @State macro
        self.page = StickerPage(title: title)
        self.title = title
    }
}

„Won’t have any effect” und „won’t compile” stehen in benachbarten Zeilen. Der erste Kommentar beschreibt Xcode 26, der zweite Xcode 27. Gleicher Code, gleiche Bedeutung, anderes Urteil.

Behoben wird das, indem der Anfangswert an der Deklaration entfällt:

struct StickerPageView: View {
    @State private var page: StickerPage // no initial value expression
    let title: String

    init(title: String) {
        self.page = StickerPage(title: title) // works!
        self.title = title
    }
}

Apple reduziert die Regel auf eine Anweisung: „Wenn Sie den Anfangswert über einen Initializer zuweisen, geben Sie an der @State-Deklaration keinen Anfangswert an.”1

Die zutreffende Zusammenfassung lautet also nicht, dass das Makro die Zuweisung im Initializer kaputt gemacht hätte. Diese Zuweisung war bereits kaputt, und das Makro ist das Erste, was es laut ausspricht. Wer die Änderung als Regression verkauft, hat die Richtung verdreht — für einen Release-Branch ist die praktische Folge allerdings dieselbe: Builds, die gestern durchliefen, scheitern heute.

Eine Einschränkung sollte man mitnehmen. Apple hat „einige dieser Fälle” geschrieben, nicht alle, und benennt nie, welche. Ein sauberer Build unter Xcode 27 ist ein Beleg über Ihren Code, kein Beweis über die Regel.

Der synthetisierte Initializer verschwindet

Die zweite Ausnahme hat mit Anfangswerten nichts zu tun und trifft Code, der an der Aufrufstelle @State überhaupt nicht erwähnt.

„Wenn alle gespeicherten Member eines Structs privat sind, synthetisiert der Compiler einen privaten init, der sich in einer Extension desselben Typs verwenden lässt:”1

struct StickerPageView: View {
    @State private var page: StickerPage
    private let title: String
    ...
}

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.init(page: page, title: title) // using the synthesized init
    }
}

„Das State-Makro deaktiviert diesen synthetisierten Initializer. Der obige Code kompiliert deshalb nicht mehr. Zur Abhilfe weisen Sie den Membern die Werte explizit zu:”1

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.title = title
        self.page = page
    }
}

Dieses Muster aufzuspüren ist heimtückischer als beim ersten, denn die brechende Aufrufstelle sitzt in einer anderen Deklaration als das @State, das sie verursacht. Ein grep nach @State fördert sie nicht zutage.

Apple benennt die Wirkung und belässt es dabei. Die veröffentlichte Deklaration des Makros passt zum Symptom:

@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`_`), prefixed(`__`), prefixed(`$`))
macro State()

Ein Accessor-Makro, das get und set beisteuert, verändert aus Sicht des Compilers, was die Property überhaupt ist — und die Synthese des memberwise Initializers speist sich aus gespeicherten Properties.2 Die Deklaration als Ursache zu lesen, ist meine Schlussfolgerung und nicht Apples Aussage; die Abhilfe hängt vom Mechanismus ohnehin nicht ab: Schreiben Sie die Zuweisungen von Hand aus.

Generische Inferenz und Property-Wrapper-Komposition

Den verbleibenden Ausnahmen widmet Apple je einen Satz, und beide verdienen eine Notiz in der Migrationscheckliste, auch wenn keine von beiden viele Projekte treffen wird.

Die generische Inferenz kommt vager weg als alles andere in dem Eintrag: „In seltenen Situationen ist die automatische Inferenz generischer Argumente von @State mit der Makro-Implementierung weniger flexibel. Geben Sie den Typ spezifischer an.”1 Apple nennt keinen Fall und keine Diagnose, nach der man Ausschau halten könnte. Die Abhilfe ist eine explizite Typannotation an der Deklaration, überall dort angewandt, wo der Compiler sich beschwert.

Die Komposition formuliert Apple als Grenze, nicht als Bruch: „Die Komposition von @State mit anderen Property Wrappern oder Makros wird nicht unterstützt.”1 Prüfenswert, falls Sie @State je in einen eigenen Property Wrapper eingebettet haben — und daran zu denken lohnt sich, dass Apple das als nicht unterstütztes Terrain rahmt und nicht als etwas, das früher funktioniert hat.

Kein Deployment Target kommt daran vorbei

Drei Sätze von drei Apple-Seiten schließen jeden Fluchtweg.

Das Symbol des Makros State() trägt Verfügbarkeit ab iOS 13.0, iPadOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0 und visionOS 1.0 und deckt sich damit mit dem Property Wrapper, den es ersetzt — ein niedrigeres Minimum weicht dem Makro also nicht aus.23 Und der Wechsel richtet sich nach dem Compiler, nicht nach dem Target: „Wenn Sie mit Xcode 27 oder neuer bauen, verwendet das System stattdessen das Makro State().”3

Die Laufzeithälfte der Änderung hat eine Untergrenze, die die Kompilierzeithälfte nicht hat. Apple schreibt, das neue Verhalten werde „auf iOS 17 und die zugehörigen Betriebssysteme zurückportiert” — darunter klafft eine Lücke: Ein Projekt, das auf iOS 15 oder 16 deployt, bekommt das Makro zur Kompilierzeit und damit die Quellkompatibilitäts-Ausnahmen, aber nicht das zurückportierte Laufzeitverhalten.1 Was diese Targets stattdessen bekommen, sagt Apple nicht. Wenn Sie ein OS älter als iOS 17 unterstützen, behandeln Sie den Build-Bruch als sicher und die Behebung der wiederholten Initialisierung auf Ihren ältesten Geräten als unbestätigt.

Projekt in Xcode 27 öffnen, bauen — und Sie haben das neue @State. Kein Info.plist-Schlüssel, keine Build-Einstellung und keine Verfügbarkeitsprüfung geht in diese Entscheidung ein.

Der 27er-Zyklus bringt drei brechende Änderungen mit, die Entwickler gern unter einer Überschrift ablegen, obwohl sie zu drei verschiedenen Zeitpunkten zuschlagen. Die Anforderung an den Launch Screen bindet an „Apps, die mit dem 27.0 SDK oder neuer gebaut wurden”, und kostet Sie eine Ablehnung im App Store. Die Pflicht zum Scene Life Cycle bindet an Apps, die „mit dem neuesten SDK” gebaut wurden, und kostet Sie eine App, die nicht mehr startet. Das @State-Makro bindet an die Xcode-Version und kostet Sie einen Build. Apple bezieht die ersten beiden auf das SDK und die dritte auf die Toolchain — womit @State als Erstes an der Reihe ist: Sie begegnen ihm beim ersten Build, bevor Sie einen plist-Schlüssel oder ein Deployment Target angefasst haben.

Der Druck, diese Toolchain zu öffnen, kommt nach veröffentlichtem Zeitplan — für iOS 27 allerdings noch nicht. Apples Anforderungsseite lautet derzeit: „Seit dem 28. April 2026 müssen Apps, die zu App Store Connect hochgeladen werden, mit Xcode 26 oder neuer und einem SDK für iOS 26, iPadOS 26, tvOS 26, visionOS 26 oder watchOS 26 gebaut sein.”5 Für das SDK zu iOS 27 hat Apple kein entsprechendes Datum veröffentlicht. Apple hat das SDK-Minimum in den vergangenen Jahren jeweils im Frühjahr angehoben, eine 27er-Frist ist also eine begründete Erwartung und keine Tatsache — jedes konkrete Datum, das Sie anderswo lesen, ist eine Vermutung.

Was vier ausgelieferte Apps tatsächlich enthalten

Bevor ich über fremden Code schreibe, habe ich die Prüfung an meinem eigenen laufen lassen: vier Apps im App Store, alle SwiftUI, alle derzeit mit Xcode 26.6 gebaut.6

App Swift-Dateien @State-Deklarationen Mit Wert am Deklarationsort Typen mit @State
Reps 77 120 83 31
Return 57 57 42 14
Ace Citizenship 26 63 54 11
Banana List 55 27 22 8
Gesamt 215 267 201 64

Zwei Befehle grenzen die Arbeit ein. Der erste findet @State-Deklarationen mit Anfangswert, und die Zeichenklasse hinter @State verdient ihren Platz: [^A-Za-z0-9_] hält @StateObject aus den Ergebnissen heraus, was eine schlichte Suche nach @State nicht tut.

grep -rn --include="*.swift" \
  --exclude-dir=.build --exclude-dir=DerivedData --exclude-dir=build \
  -E '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' .

Der Befehl setzt voraus, dass Attribut und Deklaration in derselben Zeile stehen. Eine Property mit @State in einer eigenen Zeile über private var page = StickerPage() erzeugt keinen Treffer — dasselbe Versagen durch falsche Entwarnung wie unten, nur in anderem Gewand. Lesen Sie die Lücken als „noch nicht geprüft”, nicht als „sauber”.

Der zweite grenzt auf Dateien ein, die zusätzlich einen Initializer deklarieren — die einzige Stelle, an der die erste Ausnahme zubeißen kann:

find . -name "*.swift" -not -path "*/build/*" -not -path "*/DerivedData/*" -print0 |
while IFS= read -r -d '' f; do
  grep -qE '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' "$f" || continue
  grep -qE '^[[:space:]]*(private |public |internal |fileprivate )?init[[:space:]]*[(<]' "$f" || continue
  echo "$f"
done

Die Schleife wirkt schwergewichtiger als eine Pipe nach xargs, und unter macOS verdient sie dieses Gewicht. BSD-grep gibt für -Z keine NUL-Trenner aus, wie GNU-grep es tut, weshalb grep -rlZ ... | xargs -0 grep -l dem zweiten grep einen einzigen, per Zeilenumbruch zusammengeklebten Klumpen übergibt. Bei einem Projekt, dessen Pfad ein Leerzeichen enthält — Banana List etwa — bekommen Sie eine Bildschirmseite „No such file or directory” auf der Standardfehlerausgabe und überhaupt nichts auf der Standardausgabe, was sich genau wie eine saubere Prüfung liest.6 Leere Ausgabe als „keine Treffer” zu lesen, dreht die Antwort ausgerechnet bei den Projekten um, die sie am ehesten brauchen.

Über 215 Swift-Dateien hinweg kam die Auswahlliste auf 20 Dateien: neun in Reps, sechs in Return, drei in Ace Citizenship und zwei in Banana List. Alle 20 von Hand zu lesen ergab in allen vier Projekten dasselbe Ergebnis.

  • Apples exakte erste Form, ein Anfangswert am Deklarationsort plus eine Zuweisung an dieselbe Property innerhalb eines Initializers desselben Typs: null.
  • Die zweite Ausnahme, eine Extension, die den synthetisierten memberwise Initializer eines Structs mit @State-Deklaration aufruft: null.
  • Die Kompositions-Ausnahme, @State neben einem weiteren Property Wrapper oder Makro an einer Deklaration: null.

Ein Beinahe-Treffer verdient eine Beschreibung, denn er liegt eine Zeile neben der Form, die Apple zu entfernen empfiehlt. Ein View zur Profileinrichtung in Reps deklariert @State private var healthWriteStatus: HealthAuthorizationState = .notRequested und weist dann in seinem Initializer self._healthWriteStatus = State(initialValue: ...) zu. Beide Stellen tragen einen Wert, Apples Abhilfesatz greift also wörtlich: an der @State-Deklaration keinen Anfangswert angeben.1 Die Zuweisung nutzt die Unterstrich-Form statt der Wrapped-Value-Form aus Apples Beispiel, und Apples Release Note erwähnt die Unterstrich-Form an keiner Stelle.

Bleibt die Frage, die meine Prüfung nicht beantworten konnte. Das Idiom _x = State(initialValue:) taucht 15-mal in dreien der vier Apps auf und bleibt der übliche Weg, State aus einem Initializer-Parameter zu befüllen. Apples Eintrag behandelt es nicht. Die Makro-Deklaration erzeugt zwar einen Peer mit Unterstrich-Präfix,2 was vielsagend ist, aber keine Garantie. Ich baue mit Xcode 26.6 (Build 17F113), konnte also weder das Idiom noch den Wortlaut der resultierenden Kompilierfehler verifizieren.6 Wer eine Xcode-27-Beta hat, klärt beides an einem Nachmittag. Bis das jemand tut: Suchen Sie in Ihrem Code nach der Deklarationsform, nicht nach einem Fehlertext.

Die ehrliche Schlagzeile aus 267 Deklarationen lautet, dass der meiste SwiftUI-Code unangetastet durchgeht — was zu Apples Formulierung „weitgehend quellkompatibel” passt.1 Gefährdet sind die Views mit handgeschriebenen Initializern, und die ballen sich stark: Weniger als eine von zehn Swift-Dateien enthielt über vier Codebasen hinweg sowohl einen handgeschriebenen Initializer als auch ein @State mit eigenem Anfangswert.

FAQ

Muss ich Aufrufe von _page = State(initialValue:) ändern?

Apples Eintrag sagt es nicht. Die Unterstrich-Form weist direkt den projizierten Speicher zu, nicht den Wrapped Value, und sie kommt in der Release Note nirgends vor: kein initialValue, kein Unterstrich-Beispiel, keine Erwähnung in irgendeine Richtung.1 Die veröffentlichte Deklaration des Makros erzeugt zwar einen Peer mit _-Präfix, was vielsagend ist, aber keine Garantie.2 Drei der vier geprüften Apps nutzen dieses Idiom, insgesamt 15-mal, die Antwort betrifft also mehr Codebasen als die beiden dokumentierten Ausnahmen.6 Bis Apple sich dazu äußert: Bauen Sie das Projekt unter Xcode 27 und lassen Sie den Compiler antworten, statt auf Verdacht zu refaktorieren.

Hat das Makro geändert, was mit einem im Initializer zugewiesenen Wert passiert?

Nein. Apple ist explizit: Das Verwerfen habe „sich durch das Makro nicht geändert, aber einige dieser Fälle kompilieren nicht mehr.”1 Unter Xcode 26 tat ein Initializer, der einer @State-Property mit bereits vorhandenem Anfangswert am Deklarationsort zuwies, nichts und kompilierte. Unter Xcode 27 scheitern einige dieser Fälle im Build. Die Semantik blieb, wo sie war, und die Diagnose kam hinzu — ein Build, der hier bricht, ist also der Compiler, der einen Fehler meldet, den Sie ohnehin schon hatten.

Wie finde ich den gefährdeten Code in meinem Projekt?

Suchen Sie nach @State-Deklarationen mit Anfangswert, schränken Sie die Treffer auf Dateien ein, die zusätzlich einen Initializer deklarieren, und lesen Sie diese Auswahlliste von Hand. Schließen Sie @StateObject über eine Zeichenklasse aus (@State[^A-Za-z0-9_]) und ziehen Sie unter macOS eine find -print0-Schleife der Pipe grep -rlZ | xargs -0 vor: BSD-grep gibt dort keine NUL-Trenner aus, und jeder Pfad mit Leerzeichen erzeugt leere Ausgabe, die wie ein sauberes Ergebnis aussieht.6 Über vier Apps und 215 Swift-Dateien hinweg kam die Auswahlliste auf 20 Dateien. Suchen Sie getrennt nach Extensions, die self.init(...) auf einem View-Struct aufrufen, denn die zweite Ausnahme hinterlässt in der Nähe des verursachenden @State keine Spur.

Wird meine App tatsächlich schneller?

Nur unter bestimmten Umständen, und Apple grenzt sie enger ein, als die Release Note vermuten lässt. Die Release Note beschreibt allgemein, dass wiederholte Auswertung vermieden wird,1 während der Eintrag zu den SwiftUI-Neuerungen sagt, die Änderung initialisiere und speichere „Ihre Property nur dann genau einmal, wenn es sich um eine Klasse handelt.”4 Der Gewinn landet bei @State private var model = SomeObservableClass(), wo die alte Implementierung den Klassen-Initializer bei jeder Neuinstanziierung des Views ausführte und das Ergebnis wegwarf. Ein @State private var isPresented = false war nie das Problem.

Die wichtigsten Erkenntnisse

Für iOS-Entwickler: - Suchen Sie nach zwei Formen, nicht nach einer. Die erste sitzt an einer @State-Deklaration neben einem Initializer; die zweite sitzt in einer Extension, die self.init(...) auf einem View-Struct aufruft, dessen gespeicherte Member sämtlich privat sind.1 - Beheben Sie die erste, indem Sie den Wert am Deklarationsort löschen, nicht die Zuweisung im Initializer. Apples Anweisung lautet, den Initializer als einzige Quelle zu behalten und die Deklaration ohne Anfangswert zu lassen.1

Für Teams, die ältere SwiftUI-Codebasen pflegen: - Bemessen Sie die Zeit für die Durchsicht an der Auswahlliste, nicht an der Codebasis. Dateien mit sowohl einem @State mit Anfangswert als auch einem handgeschriebenen Initializer waren in meinen vier Apps 20 von 215, und jedes Vorkommen des ersten Musters muss in einer davon liegen. Nach dem zweiten Muster suchen Sie getrennt, denn es versteckt sich in Extensions.6 - Behandeln Sie einen gebrochenen Build als gefundenen Fehler. SwiftUI hat den Initializer-Wert, den Ihr Compiler jetzt zurückweist, ohnehin schon verworfen — wo die Änderung zubeißt, war also bereits ein Verhalten falsch.1

Für Release-Verantwortliche: - Planen Sie die @State-Prüfung vor der Arbeit ein, die im selben Zyklus vom SDK ausgelöst wird. Der Launch-Screen-Schlüssel und der Scene Life Cycle binden beide an das SDK, gegen das Sie bauen; @State bindet an die Xcode-Version, mit der Sie bauen, und kommt deshalb zuerst.13 - Planen Sie nicht mit einer SDK-Frist für iOS 27. Apples veröffentlichtes Minimum ist weiterhin das SDK zu iOS 26, verpflichtend seit dem 28. April 2026, und für 27 hat Apple nichts angekündigt.5


Drei Durchsetzungspunkte kommen in einem Zyklus und scheitern an drei verschiedenen Stellen: Der Launch-Screen-Schlüssel stoppt eine Einreichung, die Scene-Pflicht stoppt einen Start, und das @State-Makro stoppt einen Build. Zum Rest dessen, was im selben SDK landet, siehe Was in SwiftUI für iOS 27 neu ist. Der Sammelpunkt der gesamten Reihe ist die Apple-Ökosystem-Serie.

Quellenangaben


  1. Apple, iOS & iPadOS 27 Release Notes, Abschnitt SwiftUI, New Features (Radar 105893279). Quelle für die Beschreibung des alten Verhaltens („A @State declared with an expression as its initial value used to evaluate the expression each time the view struct re-instantiates. In the case of @State private var model = Model(), this means Model.init() gets called many times throughout the view’s lifetime”), die neue Implementierung („Xcode 27 introduces a new @State implementation that avoids this repeated evaluation. This new behavior back-deploys to iOS 17 aligned OSes. The new @State is implemented with a Swift macro. It is largely source compatible with the property wrapper version, with a few exceptions”), die erste Ausnahme und ihre Anweisung („If you provide an initial value at @State declaration, and also try to assign a value to it in an initializer, the initializer value is discarded. This behavior has not changed because of the macro, but some such cases no longer compile” und „When assigning initial value via an initializer, do not provide an initial value at the @State declaration”), beide StickerPageView-Code-Listings zur ersten Ausnahme, die zweite Ausnahme („When all stored members of a struct are private, the compiler synthesizes a private init that can be used in an extension of the same type” und „The state macro disables this synthesized initializer. So the code above no longer compiles. To mitigate, assign value to members explicitly”) mit ihren beiden Code-Listings, den Hinweis zur generischen Inferenz („In rare situations, the automatic inference of generic arguments of @State is less flexible with the macro implementation. Write the type with more specificity”) und den Hinweis zur Komposition („Composing @State with other property wrappers or macros is not supported”). Alle Code-Listings wortgetreu übernommen. Am 25. Juli 2026 gegen Apples Dokumentations-JSON verifiziert, da die HTML-Seite ihren Inhalt über JavaScript rendert. 

  2. Apple, State() macro, SwiftUI-Makro-Referenz. Quelle der veröffentlichten Deklaration (@attached(accessor, names: named(init), named(get), named(set)), @attached(peer, names: prefixed(_), prefixed(__), prefixed($)), macro State()), der Verfügbarkeitsliste (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, macOS 10.15, tvOS 13.0, visionOS 1.0, watchOS 6.0), des Toolchain-Hinweises („When you build with Xcode 26 or earlier, the system uses the State property wrapper instead”), des neuen Initialisierungsvertrags („A State() property instantiates its default value the first time SwiftUI instantiates the view”) und des Beispiels „Store observable objects”, das eine @Observable class Library in @State hält. 

  3. Apple, State, SwiftUI-Struktur-Referenz. Weiterhin deklariert als @frozen @propertyWrapper struct State<Value> mit Verfügbarkeit ab iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, macOS 10.15, tvOS 13.0, visionOS 1.0 und watchOS 6.0. Quelle des Toolchain-Hinweises in die andere Richtung: „When you build with Xcode 27 or later, the system uses the State() macro instead.” 

  4. Apple, SwiftUI Updates, Juni 2026, General. Quelle der Klassen-Einschränkung: „Build your project in Xcode 27 or later so that the @State attribute uses the State() macro to create a state value in an App, Scene, or View. This change only initializes and stores your property once when it’s a class.” 

  5. Apple, Upcoming requirements, Apple Developer News. Quelle des aktuellen SDK-Minimums: „Since April 28, 2026 Apps uploaded to App Store Connect must be built with Xcode 26 or later using an SDK for iOS 26, iPadOS 26, tvOS 26, visionOS 26, or watchOS 26.” Geprüft am 25. Juli 2026; die Seite führt keine Anforderung auf, die sich auf das SDK zu iOS 27 bezieht. 

  6. Prüfung des Autors an vier ausgelieferten SwiftUI-Apps (Reps, Return, Ace Citizenship und Banana List) unter macOS 26.5.2 mit Xcode 26.6 (Build 17F113), 25. Juli 2026. Die Zahlen stammen aus einem Skript, das jede Typdeklaration über die Klammertiefe parst und Initializer-Rümpfe darin verortet, gegengeprüft mit dem oben veröffentlichten grep-Befehl, der die Zahlen zu Anfangswerten am Deklarationsort in allen vier Projekten exakt reproduzierte (83, 42, 54 und 22). Das Verhalten von BSD-grep wurde direkt bestätigt: Unter macOS gibt grep -rlZ zeilengetrennte statt NUL-getrennte Ausgabe aus, weshalb xargs -0 ein einziges zusammengefügtes Argument erhält und die Pipeline bei jedem Pfad mit Leerzeichen mit „No such file or directory” fehlschlägt. Die Zählung des Idioms _x = State(initialValue:) (15 Vorkommen in Reps, Return und Banana List) stammt aus demselben Durchlauf. Das Verhalten unter Xcode 27 wurde nicht getestet, und es werden hier keine Texte von Kompilierfehlern angegeben, weil Xcode 27 auf der verwendeten Maschine nicht installiert war. 

  7. Apple, Xcode 27 Release Notes. Am 25. Juli 2026 nach Radar 105893279 und nach jedem Eintrag durchsucht, der das @State-Makro beschreibt; weder das eine noch das andere kommt vor. Die einzige Erwähnung von @State ist ein unabhängiger MusicKit-Fix (Radar 176947544). 

Verwandte Beiträge

Xcode 27 entfernt ld64 und verlangt eindeutige Modulnamen

Xcode 27 entfernt den Linker ld64 und verlangt eindeutige Clang-Modulnamen. Ein Audit von sieben Projekten und getestete…

20 Min. Lesezeit

Die Launch-Screen-Regel von iOS 27: Vier Schlüssel oder Ablehnung

Apps, die mit dem iOS 27 SDK gebaut werden, müssen einen Launch Screen deklarieren – sonst lehnt der App Store ab. Die v…

13 Min. Lesezeit