ios swift swift-extensions system-design

ios - ¿Cómo usar correctamente las extensiones de clase en Swift?



swift-extensions system-design (2)

En Swift, históricamente he usado extensiones para extender tipos cerrados y proporcionar funcionalidades prácticas y sin lógica, como animaciones, extensiones matemáticas, etc. Sin embargo, dado que las extensiones son dependencias difíciles esparcidas por todo el código base, siempre pienso tres veces antes de implementar algo como una extensión.

Últimamente, sin embargo, he visto que Apple sugiere el uso de extensiones en mayor medida, por ejemplo, la implementación de protocolos como extensiones separadas.

Es decir, si tiene una clase A que implementa el protocolo B, terminará con este diseño:

class A { // Initializers, stored properties etc. } extension A: B { // Protocol implementation }

Al entrar en ese agujero de conejo, comencé a ver más códigos basados ​​en extensiones, como:

fileprivate extension A { // Private, calculated properties } fileprivate extension A { // Private functions }

Una parte de mí, como los componentes básicos que obtienes cuando implementas protocolos en extensiones separadas. Hace que las partes separadas de la clase sean realmente distintas. Sin embargo, tan pronto como herede esta clase, tendrá que cambiar este diseño, ya que las funciones de extensión no pueden ser anuladas.

Creo que el segundo enfoque es ... interesante. Una buena cosa con esto es que no tiene que anotar cada propiedad privada y funcionar como privada, ya que puede especificar eso para la extensión.

Sin embargo, este diseño también divide las propiedades almacenadas y no almacenadas, las funciones públicas y las privadas, haciendo que la "lógica" de la clase sea más difícil de seguir (escriba clases más pequeñas, lo sé). Eso, junto con los problemas de subclasificación, me detiene un poco en el porche del país de las maravillas de extensión.

Me encantaría escuchar cómo la comunidad Swift del mundo mira las extensiones. ¿Qué piensas? ¿Hay un Silverbullet?


Esta es solo mi opinión, por supuesto, así que toma lo que escribiré fácil.

Actualmente estoy usando el extension-approach en mis proyectos por algunas razones:

  • El código está muy limpio : mis clases nunca superan las 150 líneas y la separación a través de extensiones hace que mi código sea más legible y separado por responsabilidades

Esto es usualmente como se ve una clase:

final class A { // Here the public and private stored properties } extension A { // Here the public methods and public non-stored properties } fileprivate extension A { // here my private methods }

Las extensiones pueden ser más de una, por supuesto, depende de lo que haga su clase. Esto es simplemente útil para organizar su código y leerlo desde la barra superior de Xcode

  • Me recuerda que Swift es un lenguaje de programación orientado a protocolos , no un lenguaje OOP. No hay nada que no puedas hacer con el protocolo y las extensiones de protocolo. Y prefiero usar protocolos para agregar una capa de seguridad a mis clases / estructura. Por ejemplo usualmente escribo mis modelos de esta manera:

    protocol User { var uid: String { get } var name: String { get } } final class UserModel: User { var uid: String var name: String init(uid: String, name: String) { self.uid = uid self.name = name } }

De esta manera, aún puede editar sus valores de uid y name dentro de la clase UserModel , pero no puede hacerlo ya que solo manejará el tipo de protocolo de User .


Utilizo un enfoque similar, que se puede describir en una oración:

Ordenar las responsabilidades de un tipo en extensiones

Estos son ejemplos de aspectos que estoy poniendo en extensiones individuales:

  • La interfaz principal de un tipo, como se ve desde un cliente.
  • Conformidades de protocolo (es decir, un protocolo de delegado, a menudo privado).
  • Serialización (por ejemplo todo NSCoding relacionado con NSCoding ).
  • Partes de un tipo que viven en un hilo de fondo, como devoluciones de llamada de red.

A veces, cuando aumenta la complejidad de un solo aspecto, incluso divido la implementación de un tipo en más de un archivo.

Aquí hay algunos detalles que describen cómo ordeno el código relacionado con la implementación:

  • El foco está en la membresía funcional.
  • Mantener las implementaciones públicas y privadas cerradas, pero separadas.
  • No dividir entre var y func .
  • Mantenga todos los aspectos de la implementación de una funcionalidad juntos: tipos anidados, inicializadores, conformidades de protocolo, etc.

Ventaja

La razón principal para separar los aspectos de un tipo es facilitar la lectura y la comprensión.

Cuando se lee un código extranjero (o mi propio código anterior), comprender el panorama general es a menudo la parte más difícil de sumergirse. Darle a un desarrollador una idea de un contexto de algún método ayuda mucho.

Hay otro beneficio: el control de acceso hace que sea más fácil no llamar algo inadvertidamente. Un método que solo debe llamarse desde un subproceso en segundo plano se puede declarar private en la extensión "fondo". Ahora simplemente no puede ser llamado desde otra parte.

Restricciones actuales

Swift 3 impone ciertas restricciones en este estilo. Hay un par de cosas que solo pueden vivir en la implementación del tipo principal:

  • propiedades almacenadas
  • anulando func / var
  • función / var overidable
  • inicializadores requeridos (designados)

Estas restricciones (al menos las tres primeras) se deben a la necesidad de conocer de antemano el diseño de datos del objeto (y la tabla de testigos para Swift puro). Las extensiones pueden potencialmente cargarse tarde durante el tiempo de ejecución (a través de marcos, complementos, dlopen, ...) y cambiar el diseño del tipo después de que se hayan creado las instancias podría frenar su ABI.

Una modesta propuesta para el equipo Swift :)

Se garantiza que todo el código de un módulo estará disponible al mismo tiempo. Las restricciones que impiden la separación completa de los aspectos funcionales podrían evitarse si el compilador Swift permitiera "componer" tipos dentro de un solo módulo . Con los tipos de composición, quiero decir que el compilador recopilaría todas las declaraciones que definen el diseño de un tipo de todos los archivos dentro de un módulo. Al igual que con otros aspectos del lenguaje, encontraría dependencias internas de los archivos automáticamente.

Esto permitiría realmente escribir extensiones "orientadas a aspectos". No tener que declarar propiedades almacenadas o anulaciones en la declaración principal permitiría un mejor control de acceso y separación de inquietudes.