
¿Has intentado agregar más de 10 vistas a VStack?
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2")
Lo intenté, no se compila. Sí, al principio también me sorprendió y me sumergí en el estudio del foro Swift y Github. El resultado de mi estudio fue: "todavía no compila ¯\_(ツ)_/¯
". Pero espera, veamos por qué.
Generador de funciones
Para comenzar, vale la pena entender cómo esta sintaxis se volvió generalmente disponible. La base de tal creación declarativa de elementos tan inusuales para nosotros es el mecanismo del generador de funciones .
En el github en swift-evolution hay una propuesta de John McCall y Doug Gregor - Constructores de funciones (Propuesta: SE-XXXX) , en la que describen en detalle qué problema estaban enfrentando, por qué se decidió usar el Functions Builder y cuál era tal.
Entonces que es esto?
Es difícil describir esto en pocas palabras, pero en resumen: este es un mecanismo que le permite enumerar argumentos, algunos contenidos en el cuerpo de un tesoro y dar un resultado general de todo esto.
Cita de la propuesta: SE-XXXX :
La idea principal es que tomamos el resultado de la expresión, incluidas las expresiones anidadas como if y switch, y las formamos en un resultado, que se convierte en el valor de retorno de la función actual. Esta "compilación" es controlada por el generador de funciones, que es un atributo personalizado.
El originalLa idea básica es que tomamos los resultados de expresiones "ignoradas" de un bloque de declaraciones, incluso en posiciones anidadas como los cuerpos de las declaraciones de conmutadores, y las construimos en un único valor de resultado que se convierte en el valor de retorno de la función actual. La forma en que se realiza esta colección está controlada por un generador de funciones, que es solo un nuevo tipo de tipo de atributo personalizado;
Tal mecanismo le permite escribir código declarativo en forma de árbol, sin caracteres de puntuación innecesarios:
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" } } }
Esto está disponible gracias al nuevo atributo @_functionBuilder
. Este atributo marca un constructor, puede ser una estructura. Este constructor implementa una serie de métodos específicos. Además, este generador se usa solo, como un atributo de usuario en diversas situaciones.
A continuación, mostraré cómo funciona y cómo organizar dicho código.
¿Por qué es esto?
Entonces, Apple quiere ofrecer soporte para el lenguaje DSL de dominio específico incorporado.
John McCall y Doug Gregor señalan que dicho código es mucho más fácil de leer y escribir: esto simplifica la sintaxis, lo hace más conciso y, como resultado, el código se vuelve más compatible. Al mismo tiempo, notan que su solución no es un DSL universal.
Esta solución se centra en un conjunto específico de problemas, incluida la descripción de estructuras lineales y de árbol como XML, JSON, jerarquías de vista, etc.
¿Cómo trabajar con eso?
Puede crear su propio generador de funciones, me fue más fácil entender cómo funciona. Considere un ejemplo primitivo de un constructor que concatena cadenas.
Debajo del capó, esto funcionará así:
let result = stringsReduce { return MyBuilder.build("1", "2") }
Es importante que en la implementación del generador, los métodos deben ser exactamente estáticos, con nombres específicos y con un tipo específico de parámetros de esta lista . Solo puede cambiar el tipo y el nombre del parámetro de entrada.
static func buildBlock(_ <*atrs*>: <*String*>...) -> <*String*>
Se buscarán nombres de métodos específicos en el generador y se sustituirán en la etapa de compilación. Y si no se encuentra el método, se producirá un error de compilación.
Y esto es mágico. Cuando implemente el generador, el compilador no le dirá nada en absoluto. No dirá sobre los métodos disponibles, no ayudará con el autocompletado. Solo cuando escribe un código de cliente que este constructor no puede procesar, obtiene un error indistinto.
Hasta ahora, la única solución que he encontrado es guiarme por una lista de métodos .
Entonces, ¿por qué necesitamos otros métodos? Bueno, por ejemplo, para admitir dicho código con cheques
stringsReduce { if .random() {
Para admitir esta sintaxis, el constructor debe implementar los buildEither(first:/second:)
static func buildEither(first: String) -> String { return first } static func buildEither(second: String) -> String { return second }
Respuesta de la comunidad
Lo curioso es que esto aún no está en Swift 5.1, es decir, una solicitud de extracción con esta característica aún no se ha vertido, pero, sin embargo, Apple ya la ha agregado a Xcode 11 beta. Y en Constructores de funciones → Parcelas → Foros rápidos, puede ver la reacción de la comunidad a esta propuesta.
ViewBuilder
Ahora regrese a VStack
y vea la documentación de su inicializador init (alineación: espaciado: contenido :) .
Se ve así:
init(alignment: HorizontalAlignment = .center, spacing: ? = nil, @ViewBuilder content: () -> Content)
Y antes de la tabla de contenido hay un atributo personalizado @ViewBuilder
Se declara de la siguiente manera:
@_functionBuilder public struct ViewBuilder {
El atributo de usuario es @ _functionBuilder
, registrado al comienzo de su declaración.
Y si mira la documentación aún más abajo, puede ver muchos métodos estáticos de buildBlock
que difieren en la cantidad de argumentos.
Esto significa que el código de vista
var body: some View { VStack { Text("Placeholder1") Text("Placeholder2") Text("Placeholder3") } }
debajo del capó se convierte a tal
var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Placeholder1"), Text("Placeholder2"), Text("Placeholder3")) } }
Es decir cumple el método de construcción buildBlock (:: _ :) .
De la lista completa, el método con el número máximo de argumentos es este tipo buildBlock (:::::::: :) :) (10 argumentos):
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 }
Y en consecuencia, volviendo al ejemplo original, cuando intentas aumentar VStack
y once vistas dentro, el compilador intenta encontrar el método ViewBuilder'a buildBlock
, que tiene 11 argumentos de entrada. Pero no existe tal método: de ahí el error de compilación.
Esto es cierto para todas las colecciones que usan la inicialización con el atributo @ViewBuilder en el inicializador: V | H | Z-Stack, List, Group y otros, dentro de los cuales puede declarar más de una vista como enumeración.
Y esto es triste.
MEM (lo siento, no encontré un meme decente)
Como ser
Podemos eludir esta limitación usando 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 } }
O colecciones de anidamiento:
var body: some View { VStack { VStack { Text("Placeholder_1") Text("Placeholder_2")
Pero tales decisiones parecen muletas y solo hay esperanza para un futuro más brillante. ¿Pero cómo es este futuro?
Swift ya tiene parámetros Variadic . Esta es la capacidad de un método para aceptar argumentos por enumeración. Por ejemplo, el método de print
conocido por todos le permite escribir tanto print(1, 2)
como print(1, 2, 3, 4)
y esto es sin sobrecargas innecesarias del método.
print(items: Any...)
Pero esta característica del lenguaje no es suficiente, ya que el método buildBlock
acepta diferentes argumentos genéricos como entrada.
Agregar genéricos Variadic resolvería este problema. Los genéricos variables le permiten abstraer de muchos tipos genéricos, por ejemplo, algo como esto:
static func buildBlock<…Component>(Component...) -> TupleView<(Component...)> where Component: View
Y Apple simplemente está obligado a agregar esto. Todo descansa contra este mecanismo ahora. Y me parece que simplemente no lograron terminarlo en WWDC 2019 (pero esto es solo especulación).