Integrar Swift en proyectos antiguos y sobrevivir en el intento

1
1625

Índice de contenidos

  1. Introducción
  2. Entorno
  3. ¿Deberíamos?
  4. Añadir soporte para Swift al proyecto
  5. Consejos, problemas, y cosas a tener en cuenta
  6. Conclusiones


1. Introducción

Swift es el lenguaje de programación de código abierto desarrollado por Apple. Ofrece otra posibilidad para desarrollar las aplicaciones de las plataformas de Apple frente a Objective-C. Aunque ambos lenguajes tienen sus ventajas y desventajas, cada vez son más los que apostamos por este último.

No se pretende discutir en este tutorial sobre si es mejor o peor que Objective-C, al final dependerá de las características del proyecto y de las preferencias del equipo de desarrollo.

Aunque lo ideal sería comenzar un proyecto desde cero en Swift, en el mundo de la consultoría lo normal es encontrarnos con Apps que ya llevan unos años a sus espaldas. Y quizás llegados a cierto punto, y sobre todo ahora que los cambios entre versiones de Swift no son tan abruptos como lo fueron las tres primeras versiones del lenguaje nos planteemos añadir Swift a nuestro proyecto para hacernos la vida más fácil.

El objetivo de este tutorial no es tanto técnico como más bien contar nuestra experiencia al introducirlo en una aplicación y qué cosas hemos ido aprendiendo a base de palos por el camino y que no habría estado mal saber antes (y siempre hay margen para descubrir cosas nuevas).


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15’ (2.5 GHz Intel Core i7, 16 GB 1600 MHz DDR3)
  • Sistema Operativo: Mac OS X Sierra 10.13.6
  • Xcode 9.4.1
  • iOS 11.2.6


3. ¿Deberíamos?

"Settings"

Ante un proyecto de un tamaño considerable debemos plantearnos esta pregunta. En nuestro caso la respuesta fue: "¿Por qué no?", pero obviamente eso puede cambiar de un proyecto a otro. En nuestro caso aunque el proyecto tiene un tamaño considerable tampoco es enorme ni tenía unos tiempos de compilación brutales (esto último puede ser un punto en contra importante porque al menos en Xcode 9 introducir Swift lo aumentará sensiblemente y conozco proyectos en los que esto ya es un problema de entrada).

Al final la respuesta vendrá en función de las características del proyecto en particular. En nuestro caso tras probar a añadir el primer fichero en Swift al proyecto vimos que no nos incrementaba demasiado el tiempo de compilación y que no generaba ningún otro efecto colateral que impidiese al proyecto compilar. A medida que hemos ido incorporando ficheros de Swift se ha penalizado un poco mas el tiempo de build pero nada insalvable ni mucho menos.

Y por último, al final todo depende de las preferencias del equipo, nosotros estábamos a gusto con solo Objective-C, pero creíamos que la sintaxis de Swift y las nuevas características del lenguaje nos podían ser bastante útiles en el día a día. Si el equipo no se siente a gusto con el lenguaje, cree que le entorpecerá el desarrollo, o cualquier otro motivo entoces la respuesta esta clara.


4. Añadir soporte para Swift al proyecto

Xcode se encargará de configurar el soporte para Swift en el proyecto en cuanto se añada el primer fichero Swift al mismo. También te preguntará si deseas generar un "Bridging header". En él se podrá importar los ficheros Objective-C del proyecto que quieras exponer al código en Swift.

De forma análoga, también se puede exponer código Swift a Objective-C (se hablará más a fondo sobre esto un poco más adelante). Para ello Xcode generará una cabecera Objective-C con el nombre "<targetName>-Swift.h". Hay que tener en cuenta que este header es autogenerado al compilar el código Swift, por lo que muchas veces el completado de código expuesto en Swift puede no funcionar en Objective-C hasta que no se compile.

Ambos ficheros pueden cambiarse desde la configuración del proyecto.

"Settings"


5. Consejos, problemas, y cosas a tener en cuenta

Hay que tener en cuenta que Swift está pensado para ser interoperable con Objective-C, pero desde nuestra experiencia en sentido contrario la cosa se puede complicar.

Para proyectos nuevos hechos en Swift directamente lo normal es que en algún momento se necesite utilizar código Objective-C (por ejemplo, de alguna librería desarrollada en Swift), pero en nuestro caso es justo al contrario, partimos de la totalidad del código en Objective-C para ir progresivamente introduciendo nuevas funcionalidades en Swift e ir refactorizando las mas antiguas.

  • Introducir Swift progresivamente, para las nuevas funcionalidades de forma que pueda convivir con el código en Objective-C.

  • Se pueden heredar o extender clases de Objective-C desde Swift, pero no al contrario. Hay que tenerlo en cuenta si se pretende refactorizar una clase base en Swift.

  • De cara a extender la funcionalidad de una clase en Objective-C, siempre se puede crear una extensión de Swift para hacerlo (y de paso cumplir la O, Open-Closed principle, de SOLID ?)

  • "Si hay que forzarlo no puede ser bueno…" En general no es buena idea hacer force unwrap o forced cast en Swift (al contrario que en Objective-C, normalmente acaba en un crash de la App). Procura evitarlos salvo que estés mínimo al 99% seguro de que no será nil . Nuestro consejo es utilizar las secuencias de control del lenguaje if let, guard let, etc para asegurarse.

    "Esta vez, sin resurrecciones"
    Thanos

  • Si se pretende utilizar código Objective-C en Swift, debes importarlo en el Bridging Header. Si se importa una clase con dependencias, también se deben importar aquellos ficheros con la definición de aquellas dependencias que se deseen utilizar desde código Swift. Muchas veces puede ocurrir que importemos Foo.h que contiene un atributo de tipo bar de Bar y el compilador nos diga que Foo no tiene un atributo bar porque no hemos importado el fichero Bar.h.

  • Mucho cuidado con los casting de tipos de Objective-C a Swift, especialmente con NSArray y NSDictionary (hemos llegado a ver crashes en runtime con errores indescifrables en Swift por un casting por error de por ejemplo un NSDictionary<NSNumber*,NSString*> a [String: String])

  • Hay restricciones en las declaraciones de Swift que pueden ser representadas en Objective-C. No se pueden utilizar structs, clases anidadas, enums (genéricos), o tipos genéricos desde Objective-C. Tampoco podrás exponer opcionales de ciertos tipos (por ejemplo, Int, Double o Bool, que en Objective-C se expondrán como NSInteger, double y BOOL respectivamente). Para poder es necesario que una sea una subclase de NSObject.

  • Se pueden exponer a Objective-C, se puede utilizar el atributo @objc(name). El nombre es opcional, si no se indica se generará uno (ver el siguiente punto). También se puede utilizar el atributo @objcMembers que expondrá todas las declaraciones representables en Objective-C (personalmente lo desaconsejo, porque también expondrá subclases que puede que no queramos exponer). Si se intenta marcar algo que no sea representable en Objective-C, el compilador te lo dejará bien claro indicándote que no puede.

  • Cuando se expone código Objective-C a Swift y viceversa, hay que tener en cuenta que el compilador realiza una adaptación a los lenguajes de forma que sigan el estilo de cada uno, un método en Swift "@objc func circle(color: UIColor), size: CGSize) : Shape" se traducirá a Objective-C como "-(Shape*) circleWithColor:(UIColor*)color size:(CGSize)size;" y viceversa. Se pueden controlar con qué nombre se expondrán, en el caso de Swift "@objc(coolCircleWithCoolColor:size:" y en el caso de Objective-C con la macro NS_SWIFT_NAME (aunque de momento no permite renombrar métodos de instancia, solo de clase).

  • Se puede ver la interfaz generada por una clase Objective-C desde el asistente de Xcode. Puede llevar a errores porque no tiene en cuenta si está importada en el Bridging header "GeneratedInterface"

  • Se pueden exponer enums de Swift a Objective-C se le debe asignar el tipo Int, por ejemplo, este código expondrá los símbolos MyCoolEnumA y MyCoolEnumB a Objective-C.

    @objc enum MyCoolEnum: Int {
        case a
        case b
    }
    
  • Se puede jugar con las variables calculadas para exponer tipos a Objective-C que no pueden exponerse directamente, por ejemplo, para una variable readonly desde Objective-C. Puede que no sea muy elegante, pero a veces no queda otra si se tiene que convivir con Objective-C. Viendo el código también se puede pensar, "¿por qué no utilizar directamente un NSNumber y no complicarse?". Bueno, por poner un ejemplo, puede que se tenga una dependencia con una clase Objective-C pero 4 con código en Swift, y que ademas se tenga la intención de eliminar esa dependencia eventualmente, en ese caso puede que tenga mas sentido trabajar con un Int que con un NSNumber en el código Swift.

    class MyCoolClass : NSObject {
        var optInt : Int?
        @objc(optInt) var objcOptInt : NSNumber? {
            if let int = optInt {
                return NSNumber(value: int)
            }
            return nil
        }
    }
    


6. Conclusiones

En nuestra experiencia, tras varios meses de haber añadido nuestro primera funcionalidad en Swift al proyecto, no nos arrepentimos de la decisión tomada. De forma muy progresiva hemos ido desarrollando las nuevas funcionalidades en Swift, y por el camino hemos ido refactorizando algunos módulos para hacer limpieza.

A día de hoy no nos arrepentimos de la decisión tomada, poco a poco ha ido ganando terreno y actualmente aproximadamente el 60% del proyecto ha sido convertido a Swift.

¿Qué ventajas nos ha proporcionado? Pues bueno, tampoco es que nuestra productividad se haya multiplicado por diez, pero en líneas generales nos ha facilitado un poco la mantenibilidad y legibilidad del código. Por poner un ejemplo, antes hacíamos un uso intensivo de BlocksKit para intentar hacer la programación algo más "funcional" (nótense las comillas), y ahora las tenemos de forma nativa en el lenguaje.

Con la llegada de los Codables también nos ha permitido quitarnos la dependencia con Mantle para el mapeo de objetos JSON.

También la necesidad de declarar explícitamente la nullabilidad en Swift aunque por un lado es un dolor de cabeza por otra parte ha sido de ayuda para descubrir problemas en la aplicación derivados de situaciones teóricamente imposibles con las respuestas de los servicios que ni siquiera teníamos contempladas y que el patrón Null Object de Objective-C camuflaba.

Y de rebote, y aunque esto no tiene nada que ver con el desarrollo en sí, a los programadores de la versión Android de la App, que a su vez están haciendo lo mismo con Kotlin, también les resulta mucho más fácil poder echar un ojo al código Swift para comparar la lógica de una y otra dada las similitudes que guardan ambos lenguajes.

1 COMENTARIO

  1. Hola Daniel, tengo un problema relaciona con el tema y no consigo resolverlo. Me puedes ayudar por favor?

    el caso es que tengo mi projecto en objective c y un fichero en swift. al ejectutar
    _swipFichero = [SwiftFile new];

    me devuelve correctamente los print, el problema es quiero hacer los prints desde mi ViewController.h asi lo puedo poner en un label los datos, me pueden ayudar pr favor? estoy desesperado y no se a quien acudir! gracias y disculpad las molestias!

    let location = CLLocation(latitude: 55.751244, longitude: 37.618423) // Moscow

    public var moonPhaseManager: EKAstrologyCalc!

    override init() {

    moonPhaseManager = EKAstrologyCalc(location: location)

    let info = moonPhaseManager.getInfo(date: Date())

    print(«Current localtion: -«, info.location.coordinate)

    print(«Moon days at», «current date: -«, info.date)
    info.moonModels.forEach {
    print(«===========»)
    print(«Moon Age: -«, $0.age)
    print(«Moon rise: -«, $0.begin ?? Date.self)
    print(«Moon set: -«, $0.finish ?? Date.self)
    print(«Zodiac: -«, $0.sign)
    }
    print(«===========»)
    print(«Moon phase: -«, info.phase)
    print(«Moon trajectory: -«, info.trajectory)
    //print(«Ilumination: -«, info.illumination)

    }

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad