`ExpressibleByArrayLiteral` conformado por class y su superclass =>" <superclass> no es convertible a <subclass> "

Quiero poder crear una instancia de una subclass, aquí llamada MyLabel , que es una subclass de UILabel , usando literales de array. Estoy haciendo uso de esto en mi marco ViewComposer que permite crear UIViews usando una matriz de enumeraciones que atribuyen la vista, así:

 let label: UILabel = [.text("Hello World"), .textColor(.networking)] 

En esta pregunta, he simplificado drásticamente el caso de uso, en lugar de permitir escribir:

 let vanilla: UILabel = [1, 2, 3, 4] // works! print(vanilla.text!) // prints: "Sum: 10" 

Lo que quiero hacer es usar la misma syntax de ExpressibleByArrayLiteral , pero a una subclass de UILabel , llamada MyLabel . Sin embargo, el comstackdor me detiene cuando trato de:

 let custom: MyLabel = [1, 2, 3, 4] // Comstacktion error: "Could not cast value of type 'UILabel' to 'MyLabel'" 

La instanciación de UILabel con literales de array funciona, gracias a la conformidad con el protocolo personalizado que se Makeable continuación.

¿De alguna manera es posible hacer que el comstackdor entienda que me refiero al inicializador literal de la matriz de la subclass MyLabel y no a su superclass UILabel ?

El siguiente código podría no tener ningún sentido lógico, pero es un ejemplo mínimo , esconder lo que realmente quiero:

 // This protocol has been REALLY simplified, in fact it has another name and important code. public protocol ExpressibleByNumberArrayLiteral: ExpressibleByArrayLiteral { associatedtype Element } public protocol Makeable: ExpressibleByNumberArrayLiteral { // we want `init()` but we are unable to satisfy such a protocol from `UILabel`, thus we need this work around associatedtype SelfType static func make(values: [Element]) -> SelfType } public protocol Instantiatable: ExpressibleByNumberArrayLiteral { init(values: [Element]) } // My code might have worked if it would be possible to check for _NON-conformance_ in where clause // like this: `extension Makeable where !(Self: Instantiatable)` extension Makeable { public init(arrayLiteral elements: Self.Element...) { self = Self.make(values: elements) as! Self } } extension Instantiatable { init(arrayLiteral elements: Self.Element...) { self.init(values: elements) } } extension UILabel: Makeable { public typealias SelfType = UILabel public typealias Element = Int public static func make(values: [Element]) -> SelfType { let label = UILabel() label.text = "Sum: \(values.networkinguce(0,+))" return label } } public class MyLabel: UILabel, Instantiatable { public typealias Element = Int requinetworking public init(values: [Element]) { super.init(frame: .zero) text = "Sum: \(values.networkinguce(0,+))" } public requinetworking init?(coder: NSCoder) { fatalError() } } let vanilla: UILabel = [1, 2, 3, 4] print(vanilla.text!) // prints: "Sum: 10" let custom: MyLabel = [1, 2, 3, 4] // Comstacktion error: "Could not cast value of type 'UILabel' to 'MyLabel'" 

También he intentado conformarme con el protocolo ExpressibleByArrayLiteral al extender ExpressibleByNumberArrayLiteral (sospecho que las dos soluciones pueden ser equivalentes y comstackr en el mismo código …), así:

 extension ExpressibleByNumberArrayLiteral where Self: Makeable { public init(arrayLiteral elements: Self.Element...) { self = Self.make(values: elements) as! Self } } extension ExpressibleByNumberArrayLiteral where Self: Instantiatable { init(arrayLiteral elements: Self.Element...) { self.init(values: elements) } } 

Pero eso tampoco funcionó. Se produce el mismo error de compilation.

He escrito un comentario en el gran bloque de código anterior, el comstackdor podría haber podido determinar a qué inicializador literal de la matriz me refería si hubiera podido usar la negación en la cláusula where : extension Makeable where !(Self: Instantiatable)

Pero AFAIK que no es posible, ese código no comstack al less. ¡Tampoco hace extension Makeable where Self != Instantiatable .

¿Es lo que quiero hacer posible?

Estaría bien con tener que hacer de MyLabel una final class . Pero eso no hace ninguna diferencia.

Por favor, por favor, diga que esto es posible.

Después de pasar por los Apple Docs , inicialmente pensé que esto no era posible. Sin embargo, encontré una publicación aquí , que se aplicó a Strings y otras classs que no son de interfaz de usuario. Desde la publicación, combiné la idea de que no puedes aplicar ExpressibleByArrayLiteral a una subclass a través del enfoque de inheritance, que es probablemente la razón por la que obtienes el error (que podría reproducir muchas veces con muchos otros enfoques).

Finalmente, al mover la adopción de ExpressibleByArrayLiteral directamente a su subclass UILabel, ¡parece estar funcionando!

 public class MyLabel: UILabel, ExpressibleByArrayLiteral { public typealias Element = Int public override init(frame: CGRect) { super.init(frame: frame) } requinetworking public init(values: [Element]) { super.init(frame: .zero) text = "Sum: \(values.networkinguce(0,+))" } public convenience requinetworking init(arrayLiteral: Element...) { self.init() self.text = "Sum: \(arrayLiteral.networkinguce(0,+))" } public requinetworking init?(coder: NSCoder) { fatalError() } } let vanilla: MyLabel = [1, 2, 3, 4] print(vanilla.text) // prints Sum: 10 

Resulta que ni siquiera podemos henetworkingar la Expresividad en classs de tipo de elemento (Strings, Ints, etc.), aún tiene que volver a especificar el inicializador para él.

Con algunos retoques, ¡también aplicé estos otros methods de pensamiento!

 public class MyLabel: UILabel, ExpressibleByArrayLiteral { public typealias Element = Int private var values : [Element]? public var append : Element? { didSet { if let t = append { values?.append(t) } } } public var sum : Element { get { guard let s = values else { return 0 } return s.networkinguce(0,+) } } public var sumString : String { get { return "\(sum)" } } public var label : String { get { guard let v = values, v.count > 0 else { return "" } return "Sum: \(sumString)" } } public override init(frame: CGRect) { super.init(frame: frame) } requinetworking public init(values: [Element]) { super.init(frame: .zero) text = "Sum: \(values.networkinguce(0,+))" } public convenience requinetworking init(arrayLiteral: Element...) { self.init() self.values = arrayLiteral self.text = label } public requinetworking init?(coder: NSCoder) { fatalError() } } let vanilla: MyLabel = [1, 2, 3, 4] print(vanilla.label) //prints out Sum: 10 , without unwrapping ;) 

Por ahora, no puedo aplicar la Expresividad a un enfoque de protocolo como tú. Sin embargo, como soluciones, esto parece hacer el truco. Supongo que por ahora solo tenemos que aplicar los inicializadores a cada subclass. ¡Lamentable, pero aún vale la pena mirar todo esto!

ACTUALIZACIÓN PARA REAFIRMAR EL PROBLEMA CON EL ENFOQUE DE EXTENSIÓN

La inheritance de Swift prohíbe la input de conveniencia cuando no puede garantizar que la superclass no se altere dramáticamente. Si bien su init no cambia las properties de UILABEL, la tipificación estricta para extensiones simplemente no admite las combinaciones de requisitos y conveniencia en este tipo de inicializador.

Tomo esto de esta publicación , que se incluyó en el enlace de arriba a la izquierda:

Porque esta es una class no final. Considere si había una subclass en Stack que tuviera su propio inicializador requerido. ¿Cómo se aseguraría de que init (arrayLiteral 🙂 lo llamara? No podía llamarlo (porque no sabría que existía). Entonces, se debe requerir init (arrayLiteral 🙂 (lo que significa que debe ser parte de la statement principal y no una extensión), o Stack tiene que ser final.

Si lo marca como final, esto funciona como espera. Si desea que se subclass, simplemente muévelo fuera de la extensión y hacia el cuerpo principal.

Y si observamos los DOS errores que obtienes, simplemente intentando extender UILabel a ExpressibleByArrayLiteral directamente, y no a través de una networking anidada de protocolos como lo que estás haciendo:

 Initializer requirement 'init(arrayLiteral:)' can only be satisfied by a 'requinetworking' initializer in the definition of non-final class 'UILabel' 'requinetworking' initializer must be declanetworking directly in class 'UILabel' (non in an extension). 

Entonces primero. ExpressibleByArrayLiteral requiere un método de inicializador 'obligatorio' para ajustarse a él, al que como dice el comstackdor: no se puede implementar directamente dentro de las extensiones de la superclass que desea personalizar. Desafortunadamente, solo por esta lógica … su enfoque deseado es erróneo.

Segundo. El inicializador muy específico que desea, 'init (arrayLiteral :), es para classs de tipo final. Como en las classs que marca con la palabra key FINAL en su encabezado de statement, o class TYPES (las cadenas son una, y también lo son las classs de numbers). UILabel simplemente no es una class final para permitir subclasss y no puede cambiar esto sin piratear el idioma. Para ilustrar la no final y la final, testing la String subclasss, y obtendrás un error ya que no es una class o protocol . que no te llevaría a través de la App Store de TODOS MODOS. Entonces, por DISEÑO, simplemente no puede usar este método en UILabel a través de una extensión.

Tres. UILabel enfoque de protocolo personalizado e intenta aplicarlo a UILabel por extensión y inheritance. Me disculpo, pero Swift simplemente no ignora su estructura de lenguaje simplemente porque coloca un código personalizado entre los dos extremos del constrictor. Su enfoque de protocol , aunque elegante, es simplemente anidar el problema aquí, no abordarlo. Y esto se debe a que simplemente vuelve a aplicar estas restricciones de initialization a UILabel independientemente de su propio middleware.

Cuarto. En un poco de pensamiento lógico aquí. Si observa los Apple Docs en Expressibles dentro de XCode (el file de código en sí), observa que los protocolos se aplican especialmente a RawRepresentable classs y types Ints ( Strings , Ints , Doubles , etc.):

Para cualquier enumeración con una cadena, un integer o un tipo de punto flotante, el comstackdor Swift agrega automáticamente la conformidad RawRepresentable . Al definir su propia enumeración personalizada, le da un tipo sin formatting especificando el tipo sin formatting como primer elemento en la list de inheritance de tipo de enumeración. También puede usar literales para especificar valores para uno o más casos.

Entonces cualquier cosa que sea la representación de datos como el nivel raíz de la class , puede adoptar esto por extension . Puede ver claramente arriba, al agregar este protocol inmediatamente a UILabel , también está imponiendo el protocolo RawRepresentable . Lo que no puede adoptar mi naturaleza, que estoy dispuesto a apostar es la fuente del error "MyLabel No se puede lanzar a UILabel". UILabel no es una de estas cosas, que es una class de por qué obtiene este atributo de non-final class : es un elemento de interfaz de usuario, que es una combinación de muchos RawRepresentables. Por lo tanto, tiene sentido que no puedas inicializar directamente una class que es un set de RawRepresentables directamente, porque si algo de confusión ocurre en el lado del comstackdor de init y no altera el tipo Raw que estás buscando, podría solo corromper la instancia de class por completo y llevar a todos a una pesadilla de debugging.

Para ilustrar el punto RawRepresentable que estoy tratando de hacer, esto es lo que sucede cuando aplica este enfoque a String , un tipo de RawRepresentable RawRepresentable conforming:

 extension String: ExpressibleByArrayLiteral { public typealias Element = Int public init(arrayLiteral elements: Element...) { self.init() self = "Sum: \(elements.networkinguce(0,+))"//WE GET NO ERRORS } } let t : String = [1,2,3,4] print(t) // prints -> Sum: 10 

Mientras…

 extension UILabel: ExpressibleByArrayLiteral { public typealias Element = Int public convenience requinetworking init(arrayLiteral elements: Element...) { //The compiler even keeps suggesting you add the method types here illogically without taking to account what is there.. ie: it's confused by what you're trying to do.. self.init() self.text = "Sum: \(elements.networkinguce(0,+))" //WE GET THE ERRORS } } //CANNOT PRINT..... 

Incluso demostraré hasta dónde llega la restricción en términos de agregar Expressibles en las subclasss UILabel y sus subclasss de segundo nivel:

 class Label : UILabel, ExpressibleByArrayLiteral { public typealias Element = Int override init(frame: CGRect) { super.init(frame: frame) } public requinetworking init(arrayLiteral elements: Element...) { super.init(frame: .zero) self.text = "Sum: \(elements.networkinguce(0,+))" } public requinetworking init?(coder: NSCoder) { fatalError() } } let te : Label = [1,2,3,4] print(te.text!) //prints: Sum: 10 class SecondLabel : Label { public typealias Element = Int requinetworking init(arrayLiteral elements: Element...) { //THIS IS STILL REQUIRED... EVEN THOUGH WE DO NOT NEED TO MANUALLY ADOPT THE PROTOCOL IN THE CLASS HEADER super.init(frame: .zero) self.text = "Sum: \(elements.networkinguce(0,+))" } public requinetworking init?(coder: NSCoder) { fatalError() } } let ta : SecondLabel = [1,2,3,4] print(ta.text!) //prints: Sum: 10 

En conclusión. Swift está diseñado de esta manera. No puedes aplicar este protocolo en particular directamente porque UILabel es una superclass de nivel de idioma y los chicos que inventan estas cosas no quieren que tengas demasiado scope en UILabel . Por lo tanto, simplemente no puede hacer esto porque este protocolo no se puede aplicar mediante extensiones de superclasss non-final debido a la naturaleza de UILabel y al protocolo en sí. Simplemente no son compatibles de esta manera. Sin embargo, puede aplicar esto en sus subclasss por subclass. Lo que significa que tiene que volver a declarar el inicializador conforme cada vez. ¡Es una mierda! Pero es como funciona.

Elogio su enfoque, parece que casi consigue que las extensiones se acerquen a una T. Sin embargo, parece haber algunas cosas en cómo se construye Swift que simplemente no puede eludir. No soy el único que afirma esta conclusión (solo revise los enlaces), así que le pido que elimine su voto negativo. Tienes una solución que te he dado, te he dado las references para validar mi punto y también he proporcionado un código para mostrarte cómo puedes remediar esta restricción en la naturaleza del lenguaje. A veces simplemente no hay solución al enfoque deseado. Y otras veces, otro enfoque es la única manera de sortear las cosas.

He terminado con una solución con un operador de postfijo.

Puesto que necesito poder instalar el UIKits UILabel usando ExpressibleByArrayLiteral no puedo usar la solución propuesta de Murphguys con Label y SecondLabel .

Mi código original funciona al agregar este operador de postfix:

 postfix operator ^ postfix func ^<I: Instantiatable>(attributes: [I.Element]) -> I { return I(values: attributes) } 

Lo que hace que el código compile y funcione. Aunque se siente un poco "hacky" …

 let custom: MyLabel = [1, 2, 3, 4]^ // note use of caret operator. Now compiles print(custom.text!) // prints "Sum 10" 

Si estás interesado en por qué y cómo utilizo esto, puedes echar un vistazo a mi marco ViewComposer que habilita esta syntax:

 let label: UILabel = [.text("Hello World"), .textColor(.networking)] 

Pero también quería poder crear mi propia subclass MyLabel llamada MyLabel (o simplemente Label ..)

 let myLabel: MyLabel = [.text("Hello World"), .textColor(.networking)] // does not compile 

Lo que no funcionó antes, pero ahora funciona con el operador de postfix del cursor, así:

 let myLabel: MyLabel = [.text("Hello World"), .textColor(.networking)]^ // works! 

Lo que por ahora es la solución más elegante que creo.