
Haben Sie versucht, VStack mehr als 10 Aufrufe hinzuzufügen?
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2")
Ich habe es versucht - es wird nicht kompiliert. Ja, ich war auch zuerst überrascht und stürzte mich in das Studium des Swift-Forums und des Githubs. Das Ergebnis meiner Studie war - "es kompiliert immer noch nicht ¯\_(ツ)_/¯
". Aber warte, mal sehen warum.
Funktionsersteller
Zunächst sollten Sie verstehen, wie eine solche Syntax allgemein verfügbar wurde. Die Grundlage für eine solche deklarative Erstellung von Elementen, die für uns so ungewöhnlich sind, ist der Function Builder- Mechanismus.
Auf dem Github in der schnellen Evolution gibt es einen Vorschlag von John McCall und Doug Gregor - Funktionsersteller (Vorschlag: SE-XXXX) , in dem sie detailliert beschreiben, mit welchem Problem sie konfrontiert waren, warum beschlossen wurde, den Funktionsersteller zu verwenden und was es war solche.
Was ist das?
Es ist schwierig, dies auf den Punkt zu bringen, aber kurz gesagt - dies ist ein Mechanismus, mit dem Sie Argumente und Inhalte im Körper eines Schatzes auflisten und daraus ein allgemeines Ergebnis liefern können.
Zitat aus Vorschlag: SE-XXXX :
Die Hauptidee ist, dass wir das Ergebnis des Ausdrucks, einschließlich verschachtelter Ausdrücke wie if und switch, zu einem Ergebnis formen, das zum Rückgabewert der aktuellen Funktion wird. Dieser "Build" wird vom Funktionsgenerator gesteuert, bei dem es sich um ein benutzerdefiniertes Attribut handelt.
Das OriginalDie Grundidee ist, dass wir die "ignorierten" Ausdrucksergebnisse eines Anweisungsblocks - auch in verschachtelten Positionen wie den Körpern von ifsandschaltanweisungen - in einen einzelnen Ergebniswert einbauen, der zum Rückgabewert der aktuellen Funktion wird. Die Art und Weise, wie diese Sammlung ausgeführt wird, wird von einem Funktions-Builder gesteuert, bei dem es sich lediglich um eine neue Art von benutzerdefinierten Attributtypen handelt.
Mit einem solchen Mechanismus können Sie deklarativen baumartigen Code ohne unnötige Satzzeichen schreiben:
let myBody = body { let chapter = spellOutChapter ? "Chapter" : "" div { if useChapterTitles { h1(chapter + "1. Loomings.") } p { "Call me Ishmael. Some years ago" } p { "There is now your insular city" } } }
Dies ist dank des neuen Attributs @_functionBuilder
verfügbar. Dieses Attribut markiert einen Builder, es kann eine Struktur sein. Dieser Builder implementiert eine Reihe spezifischer Methoden. Darüber hinaus wird dieser Builder für sich genommen als Benutzerattribut in verschiedenen Situationen verwendet.
Im Folgenden werde ich zeigen, wie es funktioniert und wie man solchen Code organisiert.
Warum ist das so?
Apple möchte daher die integrierte domänenspezifische Sprache DSL unterstützen .
John McCall und Doug Gregor weisen darauf hin, dass ein solcher Code viel einfacher zu lesen und zu schreiben ist - dies vereinfacht die Syntax, macht ihn prägnanter und infolgedessen wird der Code besser unterstützt. Gleichzeitig stellen sie fest, dass ihre Lösung kein universelles DSL ist.
Diese Lösung konzentriert sich auf eine Reihe spezifischer Probleme, einschließlich der Beschreibung linearer und Baumstrukturen wie XML, JSON, Ansichtshierarchien usw.
Wie arbeite ich damit?
Sie können Ihren eigenen Funktionsersteller erstellen. Für mich war es einfacher zu verstehen, wie es funktioniert. Betrachten Sie ein primitives Beispiel eines Builders, der Zeichenfolgen verkettet.
Unter der Haube funktioniert das folgendermaßen:
let result = stringsReduce { return MyBuilder.build("1", "2") }
Es ist wichtig, dass bei der Implementierung des Builders die Methoden genau statisch sind, mit bestimmten Namen und einem bestimmten Parametertyp aus dieser Liste . Sie können nur den Typ und den Namen des Eingabeparameters ändern.
static func buildBlock(_ <*atrs*>: <*String*>...) -> <*String*>
Bestimmte Methodennamen werden im Builder gesucht und in der Kompilierungsphase ersetzt. Und wenn die Methode nicht gefunden wird, tritt ein Kompilierungsfehler auf.
Und das ist Magie. Wenn Sie den Builder implementieren, sagt Ihnen der Compiler überhaupt nichts. Es wird nicht über die verfügbaren Methoden gesprochen, es wird nicht bei der automatischen Vervollständigung helfen. Nur wenn Sie Client-Code schreiben, der von diesem Builder nicht verarbeitet werden kann, wird ein undeutlicher Fehler angezeigt.
Bisher habe ich mich nur von einer Liste von Methoden leiten lassen.
Warum brauchen wir also andere Methoden? Nun, zum Beispiel, um solchen Code mit Prüfungen zu unterstützen
stringsReduce { if .random() {
Um diese Syntax zu unterstützen, muss der Builder die buildEither(first:/second:)
implementieren
static func buildEither(first: String) -> String { return first } static func buildEither(second: String) -> String { return second }
Antwort der Gemeinschaft
Das Lustige ist, dass dies noch nicht in Swift 5.1 ist, das heißt, Pull-Anfrage mit dieser Funktion wurde noch nicht gegossen, aber Apple hat sie dennoch bereits zu Xcode 11 Beta hinzugefügt. Unter Funktionsersteller → Stellplätze → Schnelle Foren können Sie die Reaktion der Community auf diesen Vorschlag sehen.
ViewBuilder
VStack
nun zu VStack
und VStack
Sie die Dokumentation des Initialisierers init (Ausrichtung: Abstand: Inhalt :) .
Es sieht so aus:
init(alignment: HorizontalAlignment = .center, spacing: ? = nil, @ViewBuilder content: () -> Content)
Vor der Inhaltstabelle befindet sich ein benutzerdefiniertes Attribut @ViewBuilder
Es wird wie folgt deklariert:
@_functionBuilder public struct ViewBuilder {
Das Benutzerattribut ist @ _functionBuilder
, das zu Beginn seiner Deklaration registriert wird.
Wenn Sie die Dokumentation noch weiter unten buildBlock
, sehen Sie viele statische buildBlock
Methoden, die sich in der Anzahl der Argumente unterscheiden.
Dies bedeutet, dass der Ansichtscode
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2") Text("Placeholder3") } }
unter der Haube wird zu solchen umgewandelt
var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Placeholder1"), Text("Placeholder2"), Text("Placeholder3")) } }
Das heißt, Erfüllt die Builder-Methode buildBlock (:: _ :) .
Aus der gesamten Liste ist die Methode mit der maximalen Anzahl von Argumenten dieser Typ buildBlock (:::::::: :) :) (10 Argumente):
extension ViewBuilder { public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View }
Wenn Sie also zum ursprünglichen Beispiel VStack
und versuchen, VStack
und elf Ansichten im Inneren zu erhöhen, versucht der Compiler, die ViewBuilder'a buildBlock
Methode zu finden, die 11 Eingabeargumente enthält. Aber es gibt keine solche Methode: daher der Kompilierungsfehler.
Dies gilt für alle Sammlungen, die die Initialisierung mit dem Attribut @ViewBuilder im Initialisierer verwenden: V | H | Z-Stapel, Liste, Gruppe und andere, in denen Sie mehr als eine Ansicht als Aufzählung deklarieren können.
Und das ist traurig.
MEM (Entschuldigung, ich habe kein anständiges Mem gefunden)
Wie man ist
Wir können diese Einschränkung mit ForEach
umgehen ForEach
struct TestView : View { var body: some View { VStack { ForEach(texts) { i in Text(«\(i)») } } } var texts: [Int] { var result: [Int] = [] for i in 0...150 { result.append(i) } return result } }
Oder Sammlungen verschachteln:
var body: some View { VStack { VStack { Text("Placeholder_1") Text("Placeholder_2")
Aber solche Entscheidungen sehen aus wie Krücken und es gibt nur Hoffnung auf eine bessere Zukunft. Aber wie ist diese Zukunft?
Swift hat bereits Variadic-Parameter . Dies ist die Fähigkeit einer Methode, Argumente durch Aufzählung zu akzeptieren. Mit der allen bekannten Druckmethode können Sie beispielsweise sowohl print(1, 2)
als auch print(1, 2, 3, 4)
schreiben print(1, 2, 3, 4)
ohne dass die Methode unnötig überladen wird.
print(items: Any...)
Diese Funktion der Sprache reicht jedoch nicht aus, da die buildBlock
Methode verschiedene generische Argumente als Eingabe akzeptiert.
Das Hinzufügen von Variadic-Generika würde dieses Problem lösen. Mit variadischen Generika können Sie von vielen generischen Typen abstrahieren, z. B.:
static func buildBlock<…Component>(Component...) -> TupleView<(Component...)> where Component: View
Und Apple ist einfach verpflichtet, dies hinzuzufügen. Alles ruht jetzt gegen diesen Mechanismus. Und es scheint mir, dass sie es einfach nicht geschafft haben, es bis zur WWDC 2019 zu beenden (aber dies ist nur Spekulation).