Un paso que muchos de nosotros tendremos que dar: migrar nuestros proyectos a Swift 3. Un proceso que el asistente de migración nos facilitará en gran medida, aunque otra parte tendremos que hacerla aceptando sugerencias de cambio en el código tras la migración y una última parte supondrán cambios manuales en el código para adaptar a las nuevas especificaciones.
Como ya comentamos en pasados artículos, Swift 3 ha sufrido importantes cambios en la especificación de casi todos los componentes de Cocoa, incluyendo SpriteKit y el resto de librerías. Cambios sutiles, pero que nos obligan a re-escribir parte del código y a entender una forma más «swiftificada» de plantear nuestro código.
Aprovechando la necesario migración de nuestro juego en SpriteKit, Virtual Judo (que es mitad juego, mitad app, ya que mezcla componentes tanto de SpriteKit como de UIKit) vamos a escribir qué pasos, tanto automáticos, como semiautomáticos o manuales hemos tenido que dar hasta conseguir la compilación con 0 errores y 0 warnings (el soñado objetivo de todo programador).
Asistente de migración
El primer y necesario paso, el asistente de migración. Nada más abrir nuestro proyecto Xcode 8 detecta que nuestro proyecto está hecho en una versión anterior y nos pedirá si queremos migrarlo. En mi caso, Virtual Judo está perfectamente codificado en Swift 2.2 con 0 errores y 0 warnings, testado en la versión 7.3.1 de Xcode. Al ver el cuadro de diálogo aceptamos, le decimos que queremos migrar a la versión 3.0 y elegimos el único target del proyecto (en mi caso, Virtual Judo). Ahora le damos a siguiente y empieza el proceso automático. Al terminar, nos encontramos una versión que nos resume el cambio en gran parte de los ficheros, la mayoría propios pero algunos librerías de terceros, como la que se encarga de gestionar la conexión a la base de datos SQLite3.
Hagamos un repaso general de los cambios que realiza en el código:
- La función
didMoveToView(view:SKView)
que se usa para iniciar cualquier objeto de tipoSKScene
de SpriteKit, ahora ha cambiado y nos hará el cambio pertinente. Ahora se llama comodidMove(to view: SKView)
. Este es uno de los cambios más importantes de especificación que hemos de tener en cuenta: las funciones demasiado gramaticales del Swift (heredadas de Objective-C) ahora se hacen más cortas y los parámetros más claros. - Cuando hacemos referencia a un elemento de una escena a través del método
childNodeWithName
, nos ha hecho algo parecido. Ha dividido la especificación y ahora queda comochildNode(withName:" ")
. - En algunos casos (aunque no era buena práctica) hacíamos uso del método de construcción de puntos
CGPoint
denominadoCGPointMake
. Swift 3 ha cambiado dichos constructores por el propio inicializador por defecto delstruct
. De forma que ahora funciona comoCGPoint(x: y:)
. - Las acciones también han cambiado y la muy usada acción
SKAction.waitForDuration
ahora se ha convertido enSKAction.wait(forDuration:)
. Como vemos, es una constante el cambio de especificación a métodos de nombres más cortos y específicos y que los parámetros dejen claro por sí solos el contenido. Es decir, por un lado tenemos la acción (wait
) y por otro cuánto hemos de esperar (el parámetroforDuration
), no como antes que usábamos ambos conceptos gramaticales en el nombre del método dejando el valor del parámetro solo. IgualmenteSKAction.moveTo
pasa a serSKAction.move(to:)
. Y ojo, el cambio más importante aunque sutil:self.runAction
ahora esself.run
. Otros como los fundidos pasan deSKAction.fadeOutWithDuration
aSKAction.fadeOut(withDuration:)
. - En el famoso
AppDelegate.swift
también tenemos cambios importantes. La cabecera pasa de:func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
afunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
donde ahora el parámetrolaunchOptions
recoge sus valores de un diccionario de tiposUIApplicationLaunchOptionsKey
en vez de usar un genéricoNSObject
como en la versión anterior. - Aquí, en este fichero, hacemos uso de
NSFileManager.defaultManager()
para comprobar la ubicación de la base de datos y si está o no copiada de inicio. En Swift 3, esto cambia. Ahora este singleton se llama simplementeFileManager.default
. Nada más. De igual formaNSSearchPathDirectory.DocumentDirectory
se convierte enFileManager.SearchPathDirectory.documentDirectory
yNSSearchPathDomainMask.UserDomainMask
enFileManager.SearchPathDomainMask.userDomainMask
. Como vemos, son propiedades dentro deFileManager
y luego propiedades de estas. Y de hecho, siguiendo con los ficheros, el famosoNSBundle.mainBundle().URLForResource
ahora se convierte enBundle.main.url(forResource:)
. De nuevo vemos cómo un singleton cambia radicalmente su especificación deNSBundle.mainBundle()
aBundle.main
donde pasamos de un método a una propiedad calculada, más práctico y rápido. - También hacemos uso aquí del método de los
String
que permite añadir una componente de ruta de archivos:stringByAppendingPathComponent
que se convierte enappendingPathComponent
. De igual forma, el método de copia se convierte siguiente las directrices de construcción de APIs de Swift 3 que ya hemos comentado. Pasamos decopyItemAtPath(from, toPath:pathCopy)
acopyItem(atPath: from, toPath:pathCopy)
. Vemos nuevamente cómo separa la acción de los componentes de dicha acción: unos al nombre del método y otros a los nombres de los parámetros. - Entramos también con el cambio de colores: ahora
UIColor.blackColor()
pasa a serUIColor.black
. Pasamos de un método a una propiedad calculada, lo cual es mucho más eficiente. - Otro detalle importante es como las propiedades pasan a empezar todas con minúsculas. De esta forma
CGRectZero
pasa a serCGRect.zero
, pasando de una método genérico a un componente del propio tipoCGRect
y, muy importante, con el nombre de dicha propiedadzero
en minúscula. Todas las enumeraciones que usemos también empezarán por minúscula, como usar el.Justified
en la propiedadtextAlignment
de unUITextView
, que ahora se llama.justified
cambiando la J mayúscula por una minúscula. De igual forma, las enumeraciones.Left
o.Top
que también nos ayudan a justificar, eliminan mayúscula:.left
o.top
. - Las propiedades que pueden ser
true
ofalse
, como la que nos dice si unUIScrollView
debe tener scroll o no, pasando a añadir un is que antecede a la misma, con el propósito de dejar más claro que guarda un sí o un no. Por lo que.scrollEnabled
ahora esisScrollEnabled
. De igual forma, la famosa propiedadhidden
de igual forma ha pasado a serisHidden
. - La función que nos permite controlar el control de toques
touchesBegan
cambia en su especificación igualmente: deoverride func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
aoverride func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
. El parámetrowithEvent event
pasa a serwith event
. De hecho, cuando recuperamos el valorfirst
del conjuntotouches
de tipoSet<UITouch>
que nos permite acceder al primer dedo que ha tocado la pantalla (al toque del mismo), la propiedad a la que ahora accedemos para preguntar por la posición pasa de sertouch.locationInNode(self)
atouch.location(in: self)
. - Leves cambios, como el método
reverse()
de una secuencia que ahora esreversed()
. Y de hecho, la otra propiedad más usada de las secuenciasenumerate()
también añadido el participio conenumerated()
. Y más cambios en losString
, métodos comostringByReplacingOccurrencesOfString
ahora esreplacingOccurrences(of: "", with: "")
. Y otro muy usado:componentsSeparatedByString
ahora escomponents(separatedBy: ",")
. - Más cambios sutiles de mayúsculas y minúsculas que nos pueden volver un poco locos: el método
scaleMode
de lasSKScene
pasa de ser.AspectFill
(por ejemplo) a.aspectFill
, cambiando esa primera a en minúscula. Hemos de recordar que la primera letra de cualquier valor en una enumeración (según las nuevas directrices de construcción de APIs en Swift 3) han de ser siempre minúsculas y no mayúsculas como hasta ahora. - Otro cambio importante: el centro de notificaciones. El muy famoso
NSNotificationCenter.defaultCenter()
, pasa de singleton a propiedad calculada conNotificationCenter.default
, perdiendo como otros muchos elNS
por el camino. Y de singletons tenemos unos cuantos porque, por ejemplo, el acceso a las propiedades de la pantalla conUIScreen.mainScreen()
pasa a serUIScreen.main
. De igual formaUIDevice.currentDevice()
pasa a serUIDevice.current
. - Otro sospechoso habitual:
NSUserDefaults.standardUserDefaults()
pasa a serUserDefaults.standard
. Más simple. Y de hecho, una cosa buena que heredan los métodosset
es la inferencia. Antes, para poner en valor entero enNSUserDefaults
llamábamos al métodosetInteger
. Ahora no. Simplemente llamamos aset
y según el tipo el lenguaje ya sabe qué dato hay que poner. Por lo tantoNSUserDefaults.standardUserDefaults().setInteger(4, forKey: "test")
pasa ahora a serUserDefaults.standard.set(4, forKey: "test")
.
Y hasta ahí, no he tenido que hacer mucho más en cuanto al código. Eso sí, el wrapper que usaba de SQLite he tenido que descartarlo porque este NO se ha transformado correctamente y daba demasiados errores. Principalmente porque trabaja mucho con tipos NSString
distinguiéndolos de String
o incluso tipos cString
. Y, lo más peligroso, usaba muchos tipos de valor UnsafePointer<Int8>
para recuperar valores de la base de datos. Conclusión: que al ver que es poco eficiente, he decidido buscar otra solución para esta gestión.
Pero el resto de cosas las ha convertido y no he tenido que hacer nada más. Cuando sustituya el wrapper de base de datos os contaré cómo ha ido el tema, pero en principio, casi el 99% del trabajo lo ha realizado el asistente de migración de Swift 3. Los único problemas que podéis tener es si habéis metido mucho el dedo con tipos como los UnsafePointer
o tal vez vuestro código no está muy actualizado y usáis métodos o clases deprecadas. ¿Cómo ha sido vuestra experiencia de migración? Contádnoslo y si tenéis alguna duda no dudéis en acudir a nosotros. Un saludo, buena migración y Good Apple Coding.