En los artículos anteriores, hemos decidido usar el ejemplo de procesar JSON y crear objetos Swift como un banco de pruebas para avanzar en nuestro conocimiento de Swift y sacar el máximo provecho del lenguaje.
También hemos visto por qué procesar JSON es un buen ejemplo, y cuales son las dificultades con las que podríamos encontrarnos.
En este artículo, vamos a hacer un primer intento y repasaremos los siguientes puntos:
- gestión de errores en Swift y en Cocoa.
- Lattner, perra traidora, ¡me habías dicho que el código sería más corto!
- Nuestra carta a los Reyes Magos (en serio).
Antes de empezar, vamos a hacer recuento de los objetos que vamos a necesitar y cómo se comportan.
NSURL
Cuando le pasamos una cadena para inicializarse, se comporta como la mayoría de las clases de Objective C: si casca, devuelve nil. Primer problema: Swift y Objective C notifican los errores de forma muy diferente. Mientras que Swift lanza un ErrorType, Objective C devuelve nil.
Habrá que torear ese morlaco.
NSData
Una vez que tenemos la url correcta, toca pasársela a NSData para que la descargue y lea su contenido. De momento no nos preocupamos en que esto bloquea (le meteremos mano más tarde), y nos concentramos en que si casca, también devuelve nil, “a la” Objective C.
NSJSONSerialization
Es la clase que va a leer ese NSData y si casca pues también devuelve nil… ah, pues no, éste en concreto sí que lanza un ErrorType. La hemos cagado, ahora tendremos que manejar, posiblemente en un mismo método, dos mecanismos de gestión de error distintos.
¿Pero y si la llamada a NSJSONSerialization tiene éxito? Pues entonces nos devuelve un AnyObject. Esto también es un problema, porque AnyObject es como Swift le llama al id de ObjectiveC. En Objective C, devolver id está muy bien, pero a Swift esas vaguedades no le gustan nada.
Además, es mentira. Lo que devuelve NSJSONSerialization en realidad es:
- Un Diccionario de [String:AnyObject].
- Un Array de dichos diccionarios.
En realidad, el diccionario tampoco es de [String:AnyObject]. Según la definición de JSON, los valores no pueden ser cualquier cosa, sino cadenas, números, otros diccionarios, array de diccionarios, boleanos y null. De momento, no nos vamos a poner demasiado pesados, y vamos a dejar ese AnyObject, pero que conste que sabemos que no está bien.
Primer Intento
Una primera aproximación del código para procesar JSON en Swift sería ésta. Vamos a llamar todo dentro de un if let, para ir extrayendo todos los Opcionales con los que nos vamos encontrando. En el momento en que nos encontremos con un Opcional vacío (con nil), el proceso para.
A NSJSONSerialiazation (el de la discordia, que no devuelve nil en caso de error, sino que lanza un ErrorType), le llamaremos con try?. Esto cambia el mecanismo de gestión de errores y hace que en vez de lanzar un ErrorType, devuelva nil. Así todo es homogéneo y lo podemos tratar por igual.
if let url = NSURL(string: "http://bit.ly/1SptCj7"), let jsonData = NSData(contentsOfURL: url), let jsonObjects = try? NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as? Array<[String:AnyObject]>, let library = jsonObjects{ for each in library{ // extraer a todo quisqui if let title = each["title"], let authors = each["authors"], let tags = each["tags"], let pdfURL = each["pdf_url"], let imageURL = each["image_url"]{ // Todavía falta parsear los autores y los tags para tener un [String] // Además hay que comprobar que todos los elementos existen y tienen valores // razonables. // Cagüenlaputa, esto no se acaba nunca. // Yo me vuelvo a Objective C echando ostias print((title, authors, tags, pdfURL, imageURL)) } } }
Procesando el Array de Diccionarios
Cuando ya tenemos el Array de diccionarios JSON, aun nos queda mucho trabajo por hacer:
- Comprobar que el diccionario es correcto: tiene los elementos necesarios y éstos a su vez tienen valores adecuados.
- Crear los Books.
- Crear la Library.
Recordemos que Book es un tipo que vamos a usar para representar un libro y Library es un tipo que representará un conjunto de libros.
Nuestro código es una mierda
https://www.youtube.com/watch?v=63UlBsdElsY
Llegados a este punto, no nos queda más remedio que aceptar la triste realidad: nuestro código es una ? Así, tal cual.
Adolece de los siguientes defectos, algunos gravísimos:
- Es largo y repetitivo. Cada vez que tengas que procesar un JSON distinto tendrás que escribir esa retahíla.
- Proclive a errores. Como escribas mal una de las claves del diccionario de JSON, estás jodido.
- Bloquea en la llamada a NSData y no hacemos nada al respecto.
- Se entremezclan dos conceptos totalmente distintos: valor de retorno y éxito o fracaso de la llamada. Todo sale por el mismo agujero: el retorno. Los animales, como los pájaros, que no tienen ano y uretra (dos salidas para cosas muy distintas) se dice que tienen una cloaca. Es una descripción muy adecuada para nuestro código. Esto es muy grave.
- Es aburrido de cojones de escribir. Esto es lo más grave e inaceptable. Seamos sinceros, estamos aquí para divertirnos, no para entregar cosas útiles y muchísimo menos a tiempo. 😉
- No sé donde va a ir: dentro de unos inits o fuera.
La Carta a los Reyes Magos
Una de las técnicas que más me ha ayudado es la de la “Lista de los Reyes Magos”.
Cuando estás perdido y tienes que diseñar algo complejo o que se te hace cuesta arriba, para. Para y piensa qué seria la situación ideal. Aquello que de verdad te gustaría tener, sin preocuparte por lo utópico que pueda parecer.
Escribe tu carta a los Reyes Magos y poco a poco vamos a ir intentando acercarnos a ella. A lo mejor llegamos muy cerca, ¡quien sabe!
La sabiduría de los ancianos
Hace algunos años, de chiripa me encontré con uno de los lenguajes más interesantes jamás creados por el ser humano: Snobol4. Es muy antiguo, anterior a la “programación estructurada”, tiene una sintaxis rarísima y posee algunas características tan avanzadas que pasadas décadas aun no se encuentran en la mayoría de los lenguajes “modernos”. Aprender Snobol4 es ¡como encontrarte los restos de una avanzadísima civilización alienígena en tu patio trasero!
Una de las cosas que me llamaron la atención es que toda función retorna dos valores, que son capturados y procesados de distinta manera:
- El valor de retorno normal y corriente.
- Un estatus de éxito o fracaso.
Toda función tiene éxito o fracasa, y eso se puede utilizar para controlar el flujo de forma muy interesante.
Pues bien, yo quiero eso: que la gestión de error y los valores de retorno sean cosas totalmente distintas para evitar confusiones y no mezclar el caso común donde todo sale bien, con la excepción de cuando todo se va al carajo.
Snobol4 fue creado por Ralph E. Griswold, de la Universidad de Arizona. Es uno de los genios olvidados de la informática. Su contribución, por estar demasiado adelantada a su tiempo pasó desapercibida.
Después de crear Snobol4, creó otro lenguaje también visionario, llamado Icon. Una de las características más avanzadas del mismo, es que basándose en el mecanismo de éxito/fracaso de toda función, añade el concepto de corutina.
De momento no nos metemos en detalles de lo que es, pero sí vamos a ver lo que hace: hace que el código asíncrono parezca síncrono. Por la cara.
Esto simplifica enormemente nuestro código ya que se elimina el infierno de callbacks, tan común en Node.js o GCD.
Pues bien, lo que nuestro amigo Ralph inventó allá por los 70, mientras los demás escuchaban a Los Chichos y soñaban con Farrah Fawcett en bañador, es la base del éxito de lenguajes como Go de Google.
Gracias a las corutinas de Ralph, Go es considerado uno de los mejores lenguajes para programación concurrente del momento.
También mis amigos los creadores de Zewo (una de las más prometedoras frameworks para programación Swift en el servidor, ¡ya hablaremos de ellos por aquí en breve!) se han fijado en eso y ¡lo añadieron a Swift!
Está claro que la idea, aunque no sepamos muy bien como funciona todavía, es buena. Así que la quiero.
Mi lista de los Reyes Magos queda así:
- Código corto y reutilizable. Nada de estar re-escribiendo cosas.
- El caso común en que todo sale bien debe de ser muy claro.
- El caso excepcional en que las cosas salen mal debe de estar claramente diferenciado para que no haya confusiones.
- Debe de ser asíncrono cuando haga falta, pero no debe de parecerlo y no quiero líos de callbacks ni pollas en vinagre.
- Todas las transformaciones de los datos, desde el JSON hasta el objeto Swift deberían de estar ocultas. No quiero perder tiempo con detalles y minucias.
- Debe de ser elegante y swiftero.
¿Mucho pedir? ¡Ya lo veremos!
En el próximo capítulo, empezaremos a acercarnos a nuestro ideal aprendiendo un par de patrones: el del validador y el del init señorito.
Si tienes algo que deseas compartir o quieres formar parte de KeepCoding, escríbenos a [email protected].