El C en Objective C

Autor: | Última modificación: 4 de noviembre de 2022 | Tiempo de Lectura: 8 minutos
Temas en este post:

Para aprender Objective C, ¿hay que saber C?

Este es un artículo que tenía atascado en mi lista desde hace mucho tiempo. A menudo me preguntan si para aprender Objective C hay que aprender C primero. Mi respuesta es siempre la misma: NO.

De la misma forma que para aprender Español, no hace falta haber estudiado previamente Latín, tampoco es necesario conocer los dos lenguajes de los cuales desciende Objective C: C y Smalltalk.

Eso no quiere decir, que tener un cierto conocimiento de ambos no sea útil, pero eso algo que vendrá bien más adelante, cuando ya empezamos a crear aplicaciones más complejas en Objective C.

C es un DSL en Objective C

Objective C es un superconjunto estricto de C, es decir, cualquier programa correcto en C es un programa correcto en Objective C. Por lo tanto, podemos considerar C como un DSL dentro de Objective C, específico para tareas de bajo nivel, o cuando necesitamos extraer el máximo rendimiento posible.

Este caso no es muy frecuente, ya que Cocoa nos abstrae la mayoría de esos casos. Si necesitamos el máximo rendimiento, a menudo bajar a C no es suficiente y tenemos que re-escribir el código en OpenCL (para el OSX) o usando el lenguaje de Shaders de OpenGL (tal y como hace GPUImage), repasándole así el marrón a la GPU.

YouTube video

Dicho esto, también es cierto que a menudo veo alumnos con dificultades que provienen de un conocimiento superficial de ciertas características de C que terminan siendo usadas en frameworks de Objective C. Así que siguiendo la máxima de Agbo

«Ideas las justas, pero muy claras»

vamos echar un vistazo a algunos aspectos clave de C:

Estructuras de C (struct)

Las estructuras de C consisten en varios tipos primitivos agrupados bajo un mismo paraguas. Son similares a las clases en el sentido que también tienen «variables de instancia», pero no entienden mensajes de ningún tipo. Son solo datos, sin ningún comportamiento añadido.

Al contrario de los objetos, suelen almacenarse en la pila, y no las manejamos mediante punteros. Por eso, solemos declarar un CGRect como:

CGRect rect;

// en vez de

CGRect *rect; // casi seguro que no es lo que quieres.

Las estructuras se definen siguiendo la plantilla:

struct <nombre>{
// Lista de componentes
};

Por ejemplo,

struct CGPoint{
 float x;
 float y;
}

Se accede a los miembros (y miembras, que diría la filósofa, literata y pensadora Bibiana Aído) de una struct mediante un punto:

CGPoint origin = CGPointMake(0,0);
int horizontal = origin.x; // ¿Te suena esa sintaxis?

La sintaxis del punto para acceder a propiedades fue creada precisamente para parecerse al acceso a los miembros de una estructura. No es casualidad. Es una decisión…cuestionable.

En Objective C, las classes se implementan en el runtime como estructuras, pero en tu codigo es poco probable que las necesites, a no ser aquellas definidas en Core Graphics. Si crees que necesitas una estructura de C, lo más probable es que estés equivocado: usa una clase.

¿Por qué no puedo asignar un nuevo valor a la frame de una UIView?

self.someView.frame.origin.x += 10.0f;

Si lo intentas, este código te abofeteará con el siguiente error:

Lvalue required as left operand of assignment

Objective C
Lvalue required as left operand of assignment, biatch!

¿Por qué?

La razón es que estás usando la sintaxis del punto para dos cosas totalmente diferentes:

  • acceder a los miembros (y miembras) de una struct
  • acceder a las propiedades de un objeto

El código anterior se debería de re-escribir de esta forma para quedar más claro:

[[self view] frame].origin.x += 10.0f;

El método accesor -(CGRect) frame devuelve una copia del CGRect que UIView tiene almacenado. Si haces cambios a dicha copia, la original que está dentro de UIView no se enteraría. Para hacer cambios al frame, tienes que usar el método setFrame:, que requiere un CGRect completo.

 Tipos enumerados

La palabra clave enum permite crear un conjunto de constantes relacionadas entre sí. Un buen ejemplo son las constantes que indican las opciones en un método de clase de UIView animateWithDuration:delay:options:animations:completion:

La enumeraciones se crean con la siguiente plantilla:

enum AGTOptions {
   AGTOption1 = value1,
   AGTOption2 = value2,
};

Renombrando tipos con typedef

typedef le da un nuevo nombre a un tipo. Los typedefs son muy usados en Cocoa para dar nombres específicos dentro de una framework. Un buen ejemplo sería CGFloat, que no es más que un typedef a float.

Otro uso muy habitual es para darle nombres más civilizados a bloques.

La sintaxis sería:

typedef <nombre antiguo> <nombre nuevo>;

Por ejemplo,

typedef unsigned long handle;

¿Por qué a menudo se usa un typedef en combinación con struct o enum?

A menudo habrás visto estructuras declaradas con un typedef:

typedef struct {
   long x;
   long y;
} point;

// en vez de

struct point {
   long x;
   long y;
};

¿Por qué se hace esto?

En C, hay dos tipos de nombres:

  • tipos
  • tags

El compilador los trata de forma completamente distinta y los almacena en tablas distintas.
Cuando creas una estructura de esta forma:

struct point {
   long x;
   long y;
};

estás creando un tag llamado point. Para usarlo, tendrás que escribir:

struct point origin = {0,0};

Es decir, tienes que repetir la palabra struct cada vez que declaras una estructura de tipo point.

Esto se debe a que point es un tag y no un tipo.

El C en Objective C 1
Prometeo trae el fuego a la Humanidad. Y de paso, C también.

Esto NO es un fallo del lenguaje, es una decisión de diseño. Los Arcángeles San Ritchie y San Kernighan, cuando descendieron de los Cielos y nos trajeron C, querían que siempre estuviese claro si estabas usando una estructura, un enum o una union (otro tipo aglomerado parecido a una estructura). Por eso se exige que lo especifiques.

Esto tiene todo el sentido del mundo en un lenguaje de bajo nivel sin polimorfismo. Por lo tanto, cuando programas en C, deberías seguir las Santas Escrituras de San Kernighan & San Ritchie.

Algunos herejes, molestos por tener que repetir la palabra struct y sin entender que al Cielo solo se accede mediante el esfuerzo y la sabiduría, pasaron a usar ¡typedef para transformar el tag point en un tipo point!

El C en Objective C 2
¡Herejes!

Esto es un abuso del lenguaje, que causa problemas serios en programas de gran tamaño, y que debería de ser castigado con el garrote vil. Las razones se salen del alcance de una breve introducción a C, como es el caso de este artículo.

No obstante, en Objetive C, esta aberración no tiene demasiadas consecuencias, y como además es bastante común en Cocoa, la dejamos pasar. Pero que conste que NO es kosher.

Constantes

Este es otro punto problemático, ya que Objective C no tiene ninguna sintaxis propia para definir constantes. Por lo tanto, tenemos que tirar de C, o de Smalltalk.

La mayoría, aquejada del Síndrome de Edipo (odio al padre Smalltalk y encoñamiento con la madre, C), opta por el inferior estilo de definir constantes de C.

Para definir constantes en C hay dos mecanismos distintos:

  • un #define
  • las palabras mágicas extern const
#define

Con #define creamos una macro del preprocesador de C. En resumidas cuentas, es algo que se va a sustituir en el código fuente antes de ser compilado. Para cosas muy sencillas, como constantes, puede valer y es bastante rápido:

#define THE_ANSWER 42

Da un toque retro y vintage a tu código, si te van esas cosas. Sin embargo, no deberías usar macros para cosas más complejas. Es cutre y peligroso (sigue leyendo).

extern const

Aquí no estamos usando el preprocesador, sino el compilador, lo cual es más seguro. Sin embargo, la declaración de la constante es más laboriosa.

Cuando el compilador se encuentra con un int i; localiza memoria para un int. Si lo único que querías era decirle «sólo es una declaración, luego te lo defino», usamos extern:

extern int i; // la definición está en alguna otra parte, pero que sepas que existe

Para definir una constante, haríamos:

En el .h

const extern int kIndex;

y en el .m

const extern int kIndex = 1;

Es decir, tienes que repetir casi todo en ambos sitios. Si te fijas, es algo similar al uso de @class para avisar al compilador que una clase existe, y que ya la definirás más adelante.

Estas son las dos formas más comunes de definir constantes en Objective C. Ahora bien, ¿cual es la correcta? Ninguna de las dos.

Los creyentes, los que sabemos que sólo Smalltalk es orientado a objetos y Alan Kay su único profeta, usamos métodos de clase para las constantes. Este mecanismo tiene varias ventajas:

  • Es más flexible
  • Siempre se sabe a qué clase está asociada una constante
  • Hasta tus amigos más frikis te mirarán raro.

Macros

Las macros del procesador de C son simples sustituciones de un texto por otro, y el preprocesador no tiene ni idea si lo que está sustituyendo es código válido o no. Es decir, si quieres tener bugs muy raros, usa macros.

Las macros de C se han usado históricamente para:

  • eliminar llamadas a funciones muy comunes
  • «añadir» nueva sintaxis al lenguaje

Para lo primero, es preferible usar funciones inline, e incluso estas ya no son necesarias desde hace años (el compilador ya no necesita esas ayuditas).

Para lo segundo, en el caso de Objective C no es necesario usar las macros. El lenguaje cuenta con herramientas mucho más potentes para esto, como son las categorías y los bloques.

Moraleja: NO uses macros en Objective C. No son necesarias y pueden acarrear problemas muy molestos.

Con esto no quiero decir que la idea de las macros sea mala, es tan solo su implementación en C que deja mucho que desear.

Funciones Inline

Las funciones inline surgieron en su momento para eliminar la necesidad de usar macros para simular funciones que son llamadas muy a menudo.

Cuando el compilador ve una llamada a una función inline, NO llama a la función, sino que inserta su código directamente. De esta forma te ahorras la llamada a la función.

Esto tiene sentido con funciones muy sencillas y que se usan muy a menudo, es decir cuando tu cuello de botella es la llamada a la función y no el código de la misma.

Para sugerirle al compilador que transforme en inline una función, se añade la palabra clave inline a su declaración:

inline int f(void){
   // Blá, blá
}

Hoy por hoy, no tiene sentido hacer esto, ya que el compilador ignorará en la mayoría de los casos tu recomendación y hará lo que le dé la gana. En resumidas cuentas, deja que el compilador se encargue de esas micro-optimizaciones. Si quieres optimizar tu código, piensa más bien en un mejor algoritmo.

Punteros

Esta es la Gran Cáscara de Plátano. Afortunadamente, en Objective C no hacemos grandes virguerías con punteros, al contrario que en C. Por lo tanto, basta con tener claros algunos conceptos para no meterse en líos.

Un puntero es una variable que contiene la dirección de otra.

Una analogía que suele funcionar bastante bien es la de una hoja de cálculo. Cuando una celda hace referencia a otra, la primera es un puntero a la segunda. Si la celda B1 apunta a la celda D20, B1 es un puntero a D20 (B1 -> D20).

Para declarar un puntero usamos una sintaxis muy familiar, el *:

NSDate *today = [NSDate date];

Estas cosas es mejor leerlas desde la variable (today) hacia fuera: today es un puntero a un NSDate, en concreto al objeto devuelto por [NSDate date].

Con un puntero se pueden hacer fundamentalmente 2 cosas:

  • Averiguar a quién apunta
  • Acceder a aquello a lo que apunta

Averiguar a quien apunta

Para obtener la dirección de un puntero, se utiliza el operador &.

// el compilador te avisará de que probablemente
// te vas a meter en un lio (y tiene razón)
long address = &today;

En Objective C prácticamente solo lo usamos en un caso, cuando tenemos que pasar un objeto NSError por referencia.

Acceder a la variable a la que apunta

Para ello, se utiliza una sintaxis muy similar a la de declaración. Esto no es casualidad, es para recordarte que se trata de un puntero:

(*today) // aquí accedo al objeto que devolvió [NSDate date]

Esto no se usa jamás en Objective C, así que si quieres, olvídalo.

Los más observadores se estarán preguntando: si para acceder al objeto apuntado hace falta el asterisco, ¿por qué escribimos [today timeIntervalSince1970] en vez de [*today timeIntervalSince1970]?

El por qué es muy sencillo: porque así lo decidieron los creadores del lenguaje y su sintaxis. La verdad es que se agradece, porque con la abundancia proliferante de corchetes ya tenemos bastante cruz, como para añadir una plaga de asteriscos.

El cómo se hizo, es algo que se sale del objetivo de este artículo, pero tal vez lo veamos en algún futuro artículo sobre el runtime de Objective C.

YouTube video

La mejor explicación de punteros jamás creada por el hombre…en plastilina o no.

Para saber más de C Moderno

¿Te ha gustado este artículo? ¿Quieres saber más sobre C? Pues léete el Nuevo Testamento: 21st Century C: C Tips from the New School.

El C en Objective C 3

[email protected]

¿Trabajo? Aprende a programar y consíguelo.

¡No te pierdas la próxima edición del Aprende a Programar desde Cero Full Stack Jr. Bootcamp!

 

Prepárate en 4 meses, aprende las últimas tecnologías y consigue trabajo desde ya. 

 

Solo en España hay más de 120.400 puestos tech sin cubrir, y con un sueldo 11.000€ por encima de la media nacional. ¡Es tu momento!

 

🗓️ Próxima edición: 13 de febrero

 

Reserva tu plaza descubre las becas disponibles.