
Avez-vous essayé d'ajouter plus de 10 vues à VStack?
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2")
J'ai essayé - il ne compile pas. Oui, j'ai également été surpris au début et plongé dans l'étude du forum Swift et de github. Le résultat de mon étude était - "il ne compile toujours pas ¯\_(ツ)_/¯
". Mais attendez, voyons pourquoi.
Constructeur de fonctions
Pour commencer, il vaut la peine de comprendre comment une telle syntaxe est devenue généralement disponible. La base d'une telle création déclarative d'éléments si inhabituels pour nous est le mécanisme Function Builder .
Sur le github en évolution rapide, il y a une proposition de John McCall et Doug Gregor - Function builders (Proposition: SE-XXXX) , dans laquelle ils décrivent en détail le problème auquel ils étaient confrontés, pourquoi il a été décidé d'utiliser le générateur de fonctions et de quoi il s'agissait. tel.
Alors qu'est-ce que c'est?
Il est difficile de décrire cela en un mot, mais en bref - c'est un mécanisme qui vous permet de répertorier les arguments, du contenu dans le corps d'un trésor et de donner un résultat général de tout cela.
Citation de la proposition: SE-XXXX :
L'idée principale est que nous prenions le résultat de l'expression, y compris les expressions imbriquées comme if et switch, et les formions en un résultat, qui devient la valeur de retour de la fonction actuelle. Cette "construction" est contrôlée par le générateur de fonctions, qui est un attribut personnalisé.
L'originalL'idée de base est que nous prenons les résultats d'expression "ignorés" d'un bloc d'instructions - y compris dans des positions imbriquées comme les corps des instructions de commutation de commutateurs - et les construisons en une seule valeur de résultat qui devient la valeur de retour de la fonction actuelle. La façon dont cette collecte est effectuée est contrôlée par un générateur de fonctions, qui n'est qu'un nouveau type de type d'attribut personnalisé;
Un tel mécanisme vous permet d'écrire du code arborescent déclaratif, sans caractères de ponctuation inutiles:
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" } } }
Ceci est disponible grâce au nouvel attribut @_functionBuilder
. Cet attribut marque un constructeur, il peut s'agir d'une structure. Ce générateur implémente un certain nombre de méthodes spécifiques. De plus, ce générateur est utilisé seul, comme attribut utilisateur dans diverses situations.
Ci-dessous, je montrerai comment cela fonctionne et comment organiser un tel code.
Pourquoi ça?
Apple veut donc prendre en charge le langage DSL intégré spécifique au domaine .
John McCall et Doug Gregor soulignent qu'un tel code est beaucoup plus facile à lire et à écrire - cela simplifie la syntaxe, la rend plus concise et, par conséquent, le code devient plus pris en charge. Dans le même temps, ils notent que leur solution n'est pas une DSL universelle.
Cette solution se concentre sur un ensemble spécifique de problèmes, y compris la description de structures linéaires et arborescentes telles que XML, JSON, hiérarchies de vue, etc.
Comment travailler avec?
Vous pouvez créer votre propre générateur de fonctions, il m'a été plus facile de comprendre comment cela fonctionne. Prenons un exemple primitif d'un générateur qui concatène des chaînes.
Sous le capot, cela fonctionnera comme ceci:
let result = stringsReduce { return MyBuilder.build("1", "2") }
Il est important que dans l'implémentation du générateur, les méthodes doivent être exactement statiques, avec des noms spécifiques et avec un type spécifique de paramètres de cette liste . Vous ne pouvez modifier que le type et le nom du paramètre d'entrée.
static func buildBlock(_ <*atrs*>: <*String*>...) -> <*String*>
Les noms de méthodes spécifiques seront recherchés dans le générateur et substitués au stade de la compilation. Et si la méthode n'est pas trouvée, une erreur de compilation se produira.
Et c'est magique. Lorsque vous implémentez le générateur, le compilateur ne vous dira rien du tout. Il ne dira pas sur les méthodes disponibles, cela n'aidera pas avec une saisie semi-automatique. Ce n'est que lorsque vous écrivez du code client qui ne peut pas être traité par ce générateur que vous obtenez une erreur indistincte.
Jusqu'à présent, la seule solution que j'ai trouvée est d'être guidée par une liste de méthodes .
Alors pourquoi avons-nous besoin d'autres méthodes? Eh bien, par exemple, pour prendre en charge un tel code avec des contrôles
stringsReduce { if .random() {
Pour prendre en charge cette syntaxe, le générateur doit implémenter les buildEither(first:/second:)
static func buildEither(first: String) -> String { return first } static func buildEither(second: String) -> String { return second }
Réponse de la communauté
Ce qui est drôle, c'est que ce n'est pas encore dans Swift 5.1, c'est-à-dire qu'une demande de pull avec cette fonctionnalité n'a pas encore été versée, mais néanmoins, Apple l'a déjà ajoutée à Xcode 11 beta. Et dans Function builders → Pitches → Swift Forums, vous pouvez voir la réaction de la communauté à cette proposition.
ViewBuilder
Revenons maintenant à VStack
et consultez la documentation de son init initialiseur (alignement: espacement: contenu :) .
Cela ressemble à ceci:
init(alignment: HorizontalAlignment = .center, spacing: ? = nil, @ViewBuilder content: () -> Content)
Et avant la table de contenu, il y a un attribut personnalisé @ViewBuilder
Il est déclaré comme suit:
@_functionBuilder public struct ViewBuilder {
L'attribut utilisateur est @ _functionBuilder
, qui est enregistré au début de sa déclaration.
Et si vous regardez la documentation encore plus bas, vous pouvez voir de nombreuses méthodes statiques buildBlock
qui diffèrent par le nombre d'arguments.
Cela signifie que le code d'affichage
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2") Text("Placeholder3") } }
sous le capot est converti en tels
var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Placeholder1"), Text("Placeholder2"), Text("Placeholder3")) } }
C'est-à-dire remplit la méthode du générateur buildBlock (:: _ :) .
De la liste entière, la méthode avec le nombre maximum d'arguments est ce type buildBlock (::::::::: :) :) (10 arguments):
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 }
Et en conséquence, en revenant à l'exemple d'origine, lorsque vous essayez d'augmenter VStack
et onze vues à l'intérieur, le compilateur essaie de trouver la méthode ViewBuilder'a buildBlock
, qui a 11 arguments d'entrée. Mais il n'y a pas une telle méthode: d'où l'erreur de compilation.
Cela est vrai pour toutes les collections qui utilisent l'initialisation avec l'attribut @ViewBuilder dans l'initialiseur: V | H | Z-Stack, List, Group et autres, à l'intérieur desquelles vous pouvez déclarer plusieurs vues en tant qu'énumération.
Et c'est triste.
MEM (désolé, je n'ai pas trouvé de mème décent)
Comment être
Nous pouvons contourner cette limitation en utilisant 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 } }
Ou collections imbriquées:
var body: some View { VStack { VStack { Text("Placeholder_1") Text("Placeholder_2")
Mais de telles décisions ressemblent à des béquilles et il n'y a qu'un espoir pour un avenir meilleur. Mais à quoi ressemble cet avenir?
Swift possède déjà des paramètres Variadic . Il s'agit de la capacité d'une méthode à accepter des arguments par énumération. Par exemple, la méthode d' print
connue de tous vous permet d'écrire à la fois print(1, 2)
et print(1, 2, 3, 4)
et ceci sans surcharge inutile de la méthode.
print(items: Any...)
Mais cette fonctionnalité du langage n'est pas suffisante, car la méthode buildBlock
accepte différents arguments génériques en entrée.
L' ajout de génériques variadiques résoudrait ce problème. Les génériques variadiques vous permettent d'abstraire de nombreux types génériques, par exemple, quelque chose comme ceci:
static func buildBlock<…Component>(Component...) -> TupleView<(Component...)> where Component: View
Et Apple est simplement obligé d'ajouter cela. Tout repose maintenant contre ce mécanisme. Et il me semble qu'ils n'ont tout simplement pas réussi à le terminer d'ici la WWDC 2019 (mais ce ne sont que des spéculations).