Home » Guías » Guía básica de depuración de código en Xcode 6
Banner Debug Xcode 6

Guía básica de depuración de código en Xcode 6

Una de las partes imprescindibles de todo desarrollo es la depuración del código, proceso que podríamos dividir en dos partes bien diferenciadas: interpretar los fallos que nos da el sistema y ser capaz de monitorizar nuestro código para localizar comportamientos no deseados en el mismo.

La presente guía, aunque se ilustra en Swift, funciona igual si trabajamos en Objective-C. Hemos de tener en cuenta que estamos hablando de funciones del propio Xcode y del depurador LLDB que forma parte del compilador LLVM, por lo que en este caso el lenguaje que usemos es indiferente. De hecho, si nuestro proyecto mezcla ambos lenguajes o usa otros como C++, servirá igualmente todo lo que aquí explicado.

Entendiendo los errores

EXC_BAD_INSTRUCTION, SIGABRT, EXC_BAD_ACCESS… ¿le suenan, verdad? Son algunos de los errores genéricos que nos encontraremos en nuestro trabajo, muchos de ellos difíciles de entender cuando el código mostrado ni siquiera es nuestro y no sabemos a qué se está refiriendo. Pues bien, veamos por qué sucede cada uno de estos:

  • EXC_BAD_INSTRUCTION: En el 99% de las veces este error es generado de manera intencionada. Es decir, un assertion o instrucción que provoca un error fatal para prevenir que la aplicación entre en un estado que no debería. Imaginen que tenemos un app que nada más arrancar carga una base de datos y ponemos su nombre para cargarla. Si lo hemos puesto mal y el archivo no existe, la librería de base de datos es probable que provoque uno de estos errores para evitar que la app arranque porque si no hay base de datos la app directamente no funcionará.
  • SIGABRT: Un error de este tipo se provoca por un excepción de código porque se quiere hacer o acceder a algo que no se puede. Por ejemplo, cuando una clase recibe la llamada a un método o un mensaje que no existe, como un Selector que no existe.
  • EXC_BAD_ACCESS: Cuando nos indica que intentamos acceder a la dirección 0x0, es porque estamos intentando acceder a una zona de memoria errónea, por ejemplo, a la dirección nil. Este puede provocarse por intentar acceder a un objeto que en ese momento ya ha sido liberado de memoria (este podría ser un posible caso). Estos pueden ser difíciles de trazar y solventar.

Hay determinados errores que son muy descriptivos a la hora de ser mostrados y que nos dicen exactamente cuál es el problema: un método que no existe en una clase, un valor incorrecto en una variable de un tipo determinado, un opcional desempaquetado que devuelve un valor nil, etc. Pero hay otros que directamente nos llevan al main del programa y tienes que ponerte a buscar tú qué ha fallado. Vamos a intentar dar algunos pasos básicos para intentar solventar y monitorizar los fallos.

Swift, un nuevo paradigma

Los errores en Swift, durante la ejecución, son mínimos. Uno de los motivos principales de la existencia de este, es que muchos de los errores de codificación que se tienen en Objective-C u otros lenguajes que se usan con Xcode suceden durante el flujo de ejecución lo que hace que (curiosamente) las apps de iOS sean menos estables estadísticamente hablando que las de Android. Y no tiene nada que ver con el sistema operativo, si no con la calidad del código y las herramientas que este tiene en su estructura para trasladar los fallos del ciclo de ejecución al ciclo de codificación o al de compilación.

En Swift, por ejemplo, se evalúa la tipificación de los datos y su contenido. De esta forma, si algo se pone donde no se debe o algún objeto tiene la posibilidad de estar vacío cuando no debería en el transcurso de la ejecución del programa, en el nuevo lenguaje tenemos herramientas que nos alertan de estos problemas y nos obligan a utilizar estructuras (como las opcionales) que trasladan los errores al momento de la codificación o de la compilación, pero no al de la ejecución que es donde se provocan los problemas de estabilidad de las apps.

Pero, por otro lado, aunque Swift sea un lenguaje con un estructura completamente segura que evita en un 99% los errores en tiempo de ejecución (trasladando estos a compilación o codificación, como hemos dicho), también es cierto que muchas de las APIs que usamos en un programa en Swift no están en dicho lenguaje, tanto a nivel de terceros como las propias librerías del sistema. Y aquí, sí pueden provocarse problemas. De hecho, por eso Apple está migrando sus librerías principales del sistema, como la propia Cocoa, a Swift: con el fin de aumentar la eficiencia y la seguridad de los desarrollos.

Trazando un error

Cuando tenemos un fallo y nuestro programa se cuelga, lo que tenemos a la izquierda es una lista del contenido de cada hilo de ejecución, a la derecha el código y el lugar donde se ha parado (que muchas veces no es el sitio real donde se provocado el error). Debajo se divide en dos partes: a la izquierda el contenido de la memoria en el hilo donde estemos y la derecha la salida en modo terminal del compilador y la consola del depurador.

Xcode 6 Debug 01

Lo primero que hemos de hacer es analizar qué ha pasado, y eso podemos hacerlo revisando el tipo de error que tenemos. Si es un SIGABRT o cualquiera de los otros errores que hemos comentado tendremos una idea más clara de qué ha pasado. Igualmente, puede que la descripción del error nos de más información como unrecognized selector sent to instance, por ejemplo, que nos indica que hemos llamado a un método de un clase que no existe.

El siguiente paso es analizar la información de los hilos relacionados con el error. Si nos vamos a la ventana de LLDB (el depurador del compilador LLVM) situada abajo a la derecha, veremos el error y luego un escueto (lldb) en color más claro. Si pulsamos ahí, veremos que podemos escribir. Vamos a inspeccionar el contenido del hilo donde se ha parado y que tenemos en la lista de la izquierda, el 1.

La instrucción f o frame (ambas instrucciones sirven) nos mostrará la información del hilo y nos dará mayor información sobre dónde se ha parado este. Mirando uno a uno podemos trazar paso a paso dónde ha ocurrido el error y buscar su origen. Vemos que está señalando con un -> la línea 57 que es donde hemos llamado a inicio! siendo inicio un opcional no inicializado con un valor.

Ahora obtenemos más información aun que nos dice claramente que el error procede de la librería principal del lenguaje Swift, libSwiftCore.dylib.

Podemos pulsar en los hilos directamente en la parte de la izquierda. Si vemos que hay algunos a los que no podemos acceder (por ejemplo, que del 4 salta al 23) podemos pulsar en el pequeño botón situado abajo a la izquierda (muy pequeño) que es un cuadrado con dos líneas rectas una debajo de otra. Al hacerlo, podremos ver todos los hilos.

Puntos de ruptura o breakpoints

Los breakpoints son un elemento esencial de cualquier depuración de código. Vamos a ir a una línea cualquiera donde queramos que la ejecución de nuestra aplicación se pare: vemos que hay un pequeño hueco entre la ventana del código y la línea que separa el navegador. En esa línea, podemos pulsar y veremos que aparece un pequeño rectángulo con punta a la derecha de color azul: ya hemos creado un punto de ruptura o breakpoint.

Cuando ejecutemos la aplicación, al llegar a este lugar la aplicación se interrumpirá como si fuera un error sin serlo. En ese momento, podremos examinar el estado en que está, consultar los valores que estén en memoria en ese momento y mucho más.

debug03

A la izquierda del depurador LLDB tenemos la ventana con los elementos que hay en memoria. En self están todas las propiedades y valores que pertenecen a la clase donde nos hemos parado y si desplegamos veremos un listado con lo que contiene y sus valores. Si por ejemplo, uno de los valores es un array podemos desplegar y ver los diferentes valores que tiene. Lo mejor es que nos movamos y curioseemos para ver qué información podemos mirar. También, por ejemplo, podemos pedir el valor de una variable con el comando p en el depurador.

Como vemos en el ejemplo más arriba, ponemos p y el nombre de la variable y se nos muestra su contenido, como si estuviéramos con el REPL del lenguaje.

Xcode 6 Debug 05

Mientras estemos depurando el código tenemos que prestar atención a los botones que vemos más arriba. El de la izquierda del todo despliega u oculta la ventana, el siguiente (el símbolo de los puntos de ruptura) nos permite habilitar o deshabilitar todos los puntos que tengamos en la app y el tercero nos permite parar la ejecución de un programa cuando queramos y revisar su contenido.

Los tres siguientes nos permiten navegar: Step Over, Step Into y Step Out. El primero avanzará línea a línea la ejecución, pero en las llamadas a métodos o funciones simplemente recuperará su valor sin entrar en ellos. Con Step Into sí entraremos en los métodos y funciones, alterando el flujo de ejecución, y Step Out nos permite salir del contexto donde estemos en ese momento (un método o función) o de un bucle.

Editar los puntos de ruptura

Si hacemos CMD+click o click secundario en el punto de ruptura (en el cuadrito azul con punta a la izquierda del código) veremos una serie de opciones que nos permitirán habilitar o deshabilitar el punto o editarlo.

Si lo editamos podemos hacer algunas cosas muy prácticas como condicionar que el punto de ruptura detenga el programa si la condición que ponemos se cumple, que se ignore un número de veces que queramos el salto de este punto, añadir una acción como un comando del depurador e incluso hacer que una vez salte el punto de ruptura, la ejecución del programa continue tras evaluar las condiciones sin detenerse y mostrar algún valor, por ejemplo.

debug04

Imaginad que tenemos un bucle de 200 vueltas y queremos que el punto salte cuando haya superado los 100 primeros. Ponemos la condición y luego podemos decirle mediante el comando p que nos muestre el valor de una variable. Si marcamos que continúe tras evaluar la condición conseguiremos que nos muestre el valor de esta sin necesidad de incluir nosotros ninguna instrucción extra en el código como un NSLog.

Manejar estos puntos de ruptura nos permite hacer muchas cosas y controlar aun mejor qué hace nuestro programa y por qué se comporta de una manera u otra que tal vez, se escape a lo que queremos.

Resumiendo

Hemos visto cuáles son los errores básicos que podemos encontrarnos en un programa, cómo revisar la información de depuración, controlar el flujo de ejecución y crear puntos de ruptura que nos permitan analizar los datos en cualquier momento para buscar con mayor eficiencia dónde pueden estar los errores.

La depuración puede hacerse tanto desde el Simulador como directamente con un dispositivo (aunque para esto último tenemos que ser desarrolladores registrados de un programa de pago). La mejor forma de aprender es practicar, probar y usar mucho esta herramienta para ir cogiendo soltura. Sin duda os será de gran ayuda. Practicad mucho y si tenéis cualquier duda, no dudéis en escribirnos o comentarlo en el post. Good Apple Coding.

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

ARKit

Analizando ARKit

Realizamos un análisis de ARKit, sus posibilidades, cómo funciona y lo comparamos con las Microsoft Hololens para ver el alcance de sus posibilidades. Un análisis en detalle incluyendo algunas pruebas en vídeo que hemos realizado mientras preparamos el curso de ARKit en Apple Coding Academy que verá la luz en septiembre, una vez tengamos versión final de esta interesante plataforma.

  • ¡Enhorabuena!

    Un post excelente que resume de forma clara algunos de los errores que con más frecuencia se producen en el desarrollo iOS.

    Como dices, es cierto que con Swift vamos a tener menos errores en tiempo de ejecución y más codificando, muy al contrario de lo que nos pasaba con Objective-C, esto es bueno bajo mi humilde punto de vista.

    Un saludo.

    • Julio César Fernández

      Me alegro que te sea de ayuda el post. Muchas gracias por tu opinión. Sin duda, como dices, Swift nos ayuda más a que no haya errores aunque nos haga trabajar más a los que desarrollamos.
      Un saludo.