Vamos a crear un protocolo que defina estos requisitos. Voy a llamarlo Linkable.
El protocolo no debe restringir el tipo de datos del nodo. Por lo tanto, usaremos un tipo de marcador de posición usando la palabra clave de tipo asociado.
Cada nodo puede enlazar con el nodo anterior y el siguiente. Por lo tanto, necesitamos dos propiedades de lectura-escritura. Llamémoslas simplemente siguiente y anterior. Estas deberían ser propiedades opcionales, ya que ambas pueden ser nulas:
Finalmente, añadimos un inicializador y estamos casi hechos con el protocolo Linkable. Esto es lo que tenemos hasta ahora:
12345678protocolLinkable{ valor asociado de tipo D var: D {get}var siguiente: Self?{getset}var anterior: Self?{getset}init(valor: D)}
swift
Todavía hay una cuestión que debemos abordar.Para sacar a la superficie el problema, veamos qué sucede si intentamos adoptar el protocolo en un tipo de valor .
Entonces, declaramos la estructura del StringNode y lo hacemos conforme al protocolo Linkable`. Xcode generará el siguiente código para satisfacer los requisitos del protocolo:
123456789structStringNode:Linkable{var value:Stringvar next:StringNode?var previous:StringNode?init(value:String){self.value = value }}
swift
Desafortunadamente, este código no se compila: «El tipo de valor $0027StringNode$0027 no puede tener una propiedad almacenada que lo contenga recursivamente». La razón de este extraño error de compilación no es obvia, así que intentemos aclararla un poco.
Estructuras y asignación de memoria
El error tiene que ver con la asignación de la memoria. Los tipos de valor guardan sus datos directamente en su almacén. Mientras que con los tipos de referencia, los datos existen en otro lugar y el almacén sólo guarda una referencia a ellos.
Y esto es lo que significa para nosotros: StringNode es un tipo de valor (una estructura). Como tal, debe almacenar sus datos en su lugar de almacenamiento. Por lo tanto, el compilador debe saber el tamaño exacto de la estructura antes de asignar la cantidad de memoria requerida. Para calcular el tamaño de la estructura, el compilador también debe conocer el tamaño de las propiedades de la estructura.
Una instancia de StringNode podría contener dos valores del mismo tipo. Y cada una de estas propiedades podría también contener dos valores de tipo StringNode, y así sucesivamente. Debido a esta recursividad, no hay forma de calcular los requerimientos de memoria del StringNode.
Por lo tanto, el compilador no nos permitirá crear una estructura que contenga recursivamente otra instancia del mismo tipo.
La solución
Necesitamos reforzar la semántica de los tipos de referencia para nuestros tipos de nodos. Cambiamos el protocolo Linkable a un protocolo de sólo clase heredando de AnyObject. (AnyObject es un protocolo que sólo puede ser adoptado por clases.)
12345678protocolLinkable:AnyObject{ associatedtype D var value: D {get}var siguiente: Self?{getset}var anterior: Self?{getset}init(valor: D)}
swift
Vamos a liberar el poder de los genéricos para crear una clase de Nodo que pueda contener tipos arbitrarios:
1234567891011121314final fileprivate classNode<T>:Linkable{privatevar storage: T var next:Nodo<T>?var previous:Nodo<T>?var valor: T {retorno almacenamiento }init(valor: T){ almacenamiento = valor }}
swift
El archivo de la clase Node es privado porque los clientes no deben interactuar con él directamente. Es definitivo para evitar la subclasificación. Esto asegura que la abstracción no sea violada.