Combos en ASP.NET MVC

Buenas! Una de las preguntas más referentes en ASP.NET MVC consiste en como crear combos, enlazarlas, etc, etc… La verdad es que la documentación sobre este punto es un poco difusa y dispersa así que intentaré en este post mostrar un poco las distintas formas que tenemos en ASP.NET MVC de crear combos.

Para ilustrar las distintas opciones partimos de una clase “Database” que simula un contexto de ORM. Es una clase que simplemente tiene dos listas (estáticas), una de ciudades (Cities) y otra de provincias (States). La definición de las clases City y State son:

    public class City

    {

        public int Id { get; set; }

        public string Name { get; set; }

        public string CP { get; set; }

        public int StateId { get; set; }

    }

 

    public class State

    {

        public int Id { get; set; }

        public string Name { get; set; }

    }

1. SelectListItem

En HTML una combo (<select>) contiene una lista de opciones que siempre son clave y texto que se muestra (ambos alfanuméricos). Para representar esta información en ASP.NET MVC disponemos de la clase SelectListItem. SelectListItem nos permite almacenar la clave (Value) y el texto (Text), así como un valor booleano (Selected) que indica si este es el elemento seleccionado por defecto a la combo (se corresponde al atributo selected del tag <option>).

Una posible forma de usarlo sería así:

var items = new List<SelectListItem>();

items = Database.Cities.Select(c => new SelectListItem()

    {

        Text = c.Name,

        Value = c.Id.ToString()

    }).ToList();

 

ViewBag.Cities = items;

return View();

Obtenemos las ciudades y luego convertimos cada objeto “City” en un objeto SelectListItem. Finalmente guardamos esta lista de SelectListItem en el ViewBag.

Para mostrar esta combo basta con usar en la vista:

@Html.DropDownList("Cities")

El nombre usado “Cities” es el nombre del campo usado en el ViewBag y donde se encuentra la lista de SelectListItem.

La combo generada tendrá un atributo name llamado Cities y esto es importante a la hora de recibir el valor seleccionado de la combo. Hay una gran confusión en esto. Una combo envia UN SOLO ELEMENTO al controlador: El ID del elemento seleccionado.

Veamos como podemos recibir la ciudad seleccionada:

[HttpPost]

public ActionResult Index(string Cities)

{

    var id = Int32.Parse(Cities);

    // Recuperamos la ciudad ==> Consulta a BBDD

    var city = Database.Cities.FirstOrDefault(c => c.Id == id);

    // Operamos con la ciudad

}

Dos cosas a destacar:

  1. Lo que recibimos en Cities NO es la lista de ciudades. Es el valor de la propiedad Value del SelectListItem seleccionado (en mi caso el ID de la ciudad seleccionada).
  2. El nombre del parámetro (Cities) es el mismo que el nombre del campo del ViewBag (y el mismo que pusimos en la llamada a Html.DropDownList).

2. IEnumerable

Tener que convertir siempre nuestros datos (en este caso una lista de ciudades) a una lista de SelectListItem es muy pesado. Por suerte hay una clase SelectList que hace esto por nosotros. Basta con pasarle el IEnumerable que queremos, el nombre de la propiedad que es la clave y el nombre de la propiedad que contiene el texto a mostrar.

public ActionResult Index()

{

    var items = Database.Cities;

    ViewBag.Cities = new SelectList(items, "Id", "Name");

    return View();

}

En este punto ViewBag.Cities contiene una SelectList (que implemente IEnumerable<SelectListItem>) y el resto del código ya es exactamente el mismo que antes.

3. Otros orígenes de datos

Hasta ahora en la vista hemos usado Html.DropDownList pasándole tan solo una cadena (Cities). Esta cadena determina:

  • El nombre del atributo name del <select> generado. Que a su vez es el nombre del parámetro cuando recibimos los datos
  • El nombre del campo del ViewBag que tiene los elementos.

Si no queremos que estos dos valores sean iguales, podemos espcificarle a Html.DropDown donde está el IEnumerable<SelectListItem> que contiene los datos de la combo. Así en la vista podríamos utilizar:

@Html.DropDownList("selectedCity", ViewBag.Cities as IEnumerable<SelectListItem>)

Con esto le estamos diciendo que nos genere un <select> cuyo atributo name valga “selectedCity” y que los datos están en ViewBag.Cities.

Ahora cuando recibimos los datos debemos tener presente que el parámetro ya NO se llama Cities, si no selectedCity:

[HttpPost]

public ActionResult Index(string selectedCity)

{

    var id = Int32.Parse(selectedCity);

    // Recuperamos la ciudad ==> Consulta a BBDD

    var city = Database.Cities.FirstOrDefault(c => c.Id == id);

    // Operamos con la ciudad

}

4. HtmlDropDownListFor

Este helper lía un poco porque tendemos a compararlo con el resto de helpers similares. Así, si yo hago Html.TextboxFor(m=>m.Name) me va a generar un Textbox vinculado a la propiedad Name del ViewModel de la vista. HtmlDropDownListFor también espera una propiedad del modelo pero NO es la propiedad que tiene los elementos, si no donde dejará el valor del elemento seleccionado.

Mucha gente se confunde y se cree que la propiedad que pasamos a Html.DropDownListFor es la propiedad que contiene los valores a mostrar. Imaginemos que tenemos el siguiente ViewModel:

public class ComboCitiesViewModel

{

    public IEnumerable<City> Cities { get; set; }

    public int SelectedCity { get; set; }

}

La acción Index nos queda ahora de la siguiente forma:

public ActionResult Index()

{

    var items = Database.Cities;

    var vm = new ComboCitiesViewModel();

    vm.Cities = items;

    return View(vm);

}

Para usar Html.DropDownListFor podemos hacerlo tal y como sigue:

@Html.DropDownListFor(m=>m.SelectedCity, new SelectList(Model.Cities, "Id", "Name"))

Le paso DOS parámetros a Html.DropDownListFor:

  1. La propiedad del ViewModel que contendrá el valor seleccionado
  2. El IEnumerable<SelectListItem> con los datos. Fijaos que en este caso dado que mi ViewModel contiene un IEnumerable<City> uso la clase SelectList que hemos visto antes para hacer la conversión.

Para recibir los datos puedo declarar la siguiente acción:

[HttpPost]

public ActionResult Index(ComboCitiesViewModel info)

{

    var id = info.SelectedCity;

    // Recuperamos la ciudad ==> Consulta a BBDD

    var city = Database.Cities.FirstOrDefault(c => c.Id == id);

    // Operamos con la ciudad

}

Y aquí es donde hay otro punto de confusión: en info NO VAS A RECIBIR la lista de ciudades. Es decir la propiedad Cities va a ser null:

image

¿Y eso? Pues bueno, simple y llanamente nadie manda estos valores de vuelta para el controlador. La lista de ciudades NO está en la petición POST que hace el navegador y por lo tanto el controlador no la recibe.

De hecho, podríamos modificar el parámetro ComboCitiesViewModel para que fuese un string llamado SelectedCity y funcionaría igual.

5. Combos encadenadas

Lo que vamos a ver es una implementación de combos encadenadas pero SIN ajax. Es decir, seleccionas provincia, envías la petición y te aparecen las ciudades. En una web “actual” seguramente se haría via Ajax, pero hacerlo de la “manera antigua” nos permitirá terminar de ver como funcionan las combos.

Antes que nada modificamos el viewmodel:

public class ComboCitiesViewModel

{

    public IEnumerable<City> Cities { get; set; }

    public int SelectedCity { get; set; }

    public IEnumerable<State> States { get; set; }

    public int SelectedState { get; set; }

}

La acción Index que envía la página inicial la modificamos para que rellene States:

public ActionResult Index()

{

    var items = Database.States;

    var vm = new ComboCitiesViewModel();

    vm.States = items;

    return View(vm);

}

La vista nos quedará de la siguiente forma:

@model MvcCombos.Models.ComboCitiesViewModel

 

@using (Html.BeginForm()) {

    <label for="Cities">Ciudad:</label>

 

        @Html.DropDownListFor(m=>m.SelectedState, new SelectList(Model.States, "Id", "Name"))

 

    if (Model.SelectedState != 0)

    {

        @Html.DropDownListFor(m=>m.SelectedCity, new SelectList(Model.Cities, "Id", "Name"))

    }

 

    <input type="submit" value="enviar" />

}

Es importante entender lo que hacemos en la vista:

  1. Si NO hay provincia seleccionada (Model.SelectedState vale 0) entonces mostramos la primera combo para seleccionar estado.
  2. Si hay estado seleccionado entonces generamos la combo para seleccionar la ciudad.

Nota: Este código tiene algunos problemas, como p.ej. que ocurre si el usuario selecciona una provincia, envía el formulario, y cuando aparece de nuevo la vista con las dos combos modifica la provincia seleccionada? En una aplicación real deberías, al menos, deshabilitar la combo de estados cuando ya haya estado seleccionado.

Y finalmente ahora la acción que recibe los resultados debe gestionar que será llamada dos veces (una para seleccionar el estado, la segunda con estado y ciudad):

[HttpPost]

public ActionResult Index(ComboCitiesViewModel info)

{

    if (info.SelectedState != 0 && info.SelectedCity == 0)

    {

        info.States = Database.States;

        info.Cities = Database.Cities.Where(c => c.StateId == info.SelectedState);

        return View(info);

    }

 

    var id = info.SelectedCity;

    // Recuperamos la ciudad ==> Consulta a BBDD

    var city = Database.Cities.FirstOrDefault(c => c.Id == id);

    // Operamos con la ciudad

}

Fíjate en un par de cosas:

  1. Debemos volver a cargar todos las provincias dentro del viewmodel. Si no cuando la vista intente renderizar la combo de provincias dará error.
  2. En las ciudades seleccionamos tan solo aquellas que son de la provincia que el usuario ha seleccionado.

Insisto, en una vista “real” la segunda vez no mostraríamos la combo de provincias, quizá mostraríamos el nombre de la ciudad seleccionada. Veamos como podríamos hacerlo.

Por un lado podemos modificar el viewmodel:

public class ComboCitiesViewModel

{

    public IEnumerable<City> Cities { get; set; }

    public int SelectedCity { get; set; }

    public IEnumerable<State> States { get; set; }

    public int SelectedState { get; set; }

    public string SelectedStateName { get; set; }

}

Añadimos la propiedad para guardar el nombre de la provincia seleccionada. Y en la vista usamos esta propiedad o Html.DropDownList en función de si hay o no provincia seleccionada:

@model MvcCombos.Models.ComboCitiesViewModel

 

@using (Html.BeginForm()) {

    <label for="Cities">Ciudad:</label>

 

    if (Model.SelectedState == 0)

    {

        @Html.DropDownListFor(m => m.SelectedState, new SelectList(Model.States, "Id", "Name"))

    }

    else

    {

        <text>Provincia @Model.SelectedStateName </text>

        @Html.DropDownListFor(m=>m.SelectedCity, new SelectList(Model.Cities, "Id", "Name"))

    }

 

    <input type="submit" value="enviar" />

}

Finalmente en la acción que recibe los datos nos ahorramos de rellenar de nuevo las provincias (ya que la vista ya no las usará la segunda vez) y ponemos el nombre de la provincia seleccionada (fíjate que hemos de ir a buscarlo a la BBDD ya que solo tenemos el ID):

[HttpPost]

public ActionResult Index(ComboCitiesViewModel info)

{

    if (info.SelectedState != 0 && info.SelectedCity == 0)

    {

        info.SelectedStateName = Database.States.Single(s => s.Id == info.SelectedState).Name;

        info.Cities = Database.Cities.Where(c => c.StateId == info.SelectedState);

        return View(info);

    }

 

    var id = info.SelectedCity;

    // Recuperamos la ciudad ==> Consulta a BBDD

    var city = Database.Cities.FirstOrDefault(c => c.Id == id);

    // Operamos con la ciudad

}

Con esta aproximación:

  1. La primera vez la vista mostrará la combo de provincias y el controlador recibirá en SelectedState el id de la provincia seleccionada.
  2. La segunda vez la vista mostrará un texto con el nombre de la provincia y la combo de ciudades y el controlador recibirá en SelectedCity el ID de la ciudad seleccionada. Esta segunda vez el controlador NO recibirá SelectedState ya que no se envía. En nuestro caso no es necesario ya que lo podemos sacar de la ciudad. Si fuese necesario deberíamos usar un campo Hidden.

Bueno… creo que esto más o menos es todo. ¡Espero que este post os ayude a resolver las dudas que podáis tener con las combos en ASP.NET MVC!

¡Un saludo!

HTML5 – Que tus usuarios suban su foto a su perfil (WebRTC)

Venga, seguro que como la mitad de mortales tienes una idea de negocio que consiste en hacer una web y que te la compre Google (la otra mitad esperan que la compre Microsoft :p).

Si este es el caso, ya sabes que se trata de tener cuantos más usuarios mejor (ahí tienes el caso de Mammoth que han lanzado una campaña viral para que todos nos apuntemos allí aunque no tengamos ni idea de que va). Hoy en día cualquier web que se precie tiene un perfil donde el usuario puede subir una foto suya para que sea su avatar. Imagina la situación de que un usuario se registra a tu web y no tiene a mano ninguna foto suya para subir. ¿No estaría bien que se pudiese hacer una foto con la webcam del portátil y subirla directamente? Todo ello des de tu web, por supuesto.

Pues bien, eso es ni más ni menos lo que permite WebRTC. 😉

WebRTC significa Web Real Time Communications y es una de las futuras APIs de HTML5 que más darán que hablar. Hablo en futuro porque actualmente no son un estándard terminado: la especificación actual es todavía un Working Draft. Eso significa que su soporte en navegadores es todavía muy escaso y en la mayoría de casos experimental. El código de este post ha sido probado en Chrome 26 y funciona. No he probado en otros navegadores, pero por lo que sé IE10 no soporta todavía WebRTC y por lo que he leído FF lo soporta a partir de su versión 20. Opera también parece que lo soporta pero no sé a partir de que versión. Al final no tengais ninguna duda de que IE terminará soportando WebRTC, pero como digo: no es un estándard terminado y su definición puede cambiar.

Manos a la obra

La implementación de WebRTC se basa básicamente en una función javascript llamada getUserMedia que está en el objeto navigator. De todos modos como ya digo el soporte puede ser experimental y así p.ej. en Chrome está función está prefijada y debe usarse webkitGetUserMedia. Cosas del desarrollo para web.

Nota: Personalmente lo de los vendor prefixes me parece una aberración. No sé, si una característica se soporta, se soporta y punto. No veo porque tenemos que andar prefijando cosas porque “están a medias” y tal. Al final el problema para el desarrollador es el mismo: debes acordarte de meter todos los prefijos que toquen. En CSS aún puede tener un pase pero… ¿en javascript? ¿De veras tenemos que prefijar una función javascript? A mi me parece que algo se nos está yendo de las manos, pero bueno los prefijos están ampliamente aceptados por el W3C así que supongo que será mejor tenerlos que no tenerlos.

La función getUserMedia permite obtener un stream de datos local. Resumiendo, con getUserMedia podemos obtener la imagen de la webcam o el sonido del microfono. Básicamente le pasamos dos o tres parámetros:

  1. Los streams locales que queremos capturar (p.ej audio y/o video).
  2. La función de callback a invocar cuando la captura haya empezado
  3. La función de callback en caso de error (opcional).

Así para capturar la webcam bastaría con:

navigator.getUserMedia({video: true}, onSucessCallback, onFailCallback);

Para no andar jugando con los prefijos es preferible hacer algo como:

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

Así a medida que los navegadores vayan incorporando la función (con o sin prefijo) pues ya la tendremos disponible.

Vale… capturamos un stream de video, pero lo suyo es poderlo mostrar ¿no? En HTML5 tenemos una etiqueta que nos permite mostrar vídeos (<video />). ¿No sería genial que la pudiesemos utilizar? Pues, por suerte, podemos. Para ello nos tenemos que apoyar en otra API de HTML5: window.URL

Con los métodos de window.URL podemos crear URLs “ficticias” que apunten a “objetos” (técnicamente Blobs) que están vivos dentro del documento. La idea viene a ser la siguiente: con getUserMedia capturamos un stream de video. Para mostrar videos tenemos la etiqueta <video />. Pero la etiqueta <video /> espera la URL del vídeo. Pues con window.URL vamos a poder crear esta URL.

El código es muy simple:

<!DOCTYPE html>

<html>

<head></head>

<body>

    <video autoplay></video>

    <script>

        var onErrorCallback = function (e) {

            console.log(‘Error!’, e);

        };

        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

        navigator.getUserMedia({ video: true}, function (localMediaStream) {

            var video = document.querySelector(‘video’);

            video.src = window.URL.createObjectURL(localMediaStream);

        }, onErrorCallback);

    </script>

</body>

</html>

Bueno… Si ejecutais este código veréis algo como:

image

Por supuesto no vereis a este tipo en pantalla, seguramente el que aparezca sea más feo, pero bueno eso son cosas que pasan 😛 😛 😛 😛

La función getUserMedia pide permisos. Es decir el usuario debe confirmar que da acceso a la webcam en este caso:

image

Vale… ya estamos mostrando el vídeo de la webcam. Pero el objetivo era que el usuario pudiese subir una foto de su perfil a nuestra web, ¿recordáis?

Bueno, para ello acude en nuestra ayuda otro de los nuevos elementos de HTML5: el <canvas />. Como ya sabréis la mayoría el canvas de HTML5 es un espacio dentro del documento para dibujar gráficos en 2D o en 3D.

Pues bien, la idea es volcar el frame actual del video al canvas. Y por suerte nos basta con llamar al método drawImage del contexto 2D del canvas. Sí, tan simple como esto.

Veamos el código:

<!DOCTYPE html>

<html>

<head>

    <style>

        video {width: 300px; height: 300px;}

    </style>

 

</head>

<body>

    <video autoplay></video>

    <input type="button" value="snaphsot!" id="snap" />

    <canvas></canvas>

 

    <script>

        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

        // Definición de variables globales

        var localUserMedia = null;

        var canvas = document.querySelector(‘canvas’);

        var video = document.querySelector(‘video’);

        var ctx = canvas.getContext(‘2d’);

        // Cuando cargamos el vídeo guardamos la relación

        // de aspecto y ajustamos el tamaño del canvas

        // para que se mantenga

        video.addEventListener(‘loadedmetadata’, function (e) {

            var relation =  e.target.videoWidth/e.target.videoHeight;

            canvas.width = 300;

            canvas.height = 300/relation;

        }, false);

        // Capturamos el frame actual del video

        document.getElementById(‘snap’).addEventListener(‘click’, function (e) {

            if (localUserMedia) {

                ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

            }

        }, false);

        // Callback de error de getUserMedia

        var onErrorCallback = function (e) {

            console.log(‘Error!’, e);

        };

        // Capturamos el video con getUserMedia y lo

        // mandamos a un vídeo

        navigator.getUserMedia({ video: true }, function (localMediaStream) {

            var video = document.querySelector(‘video’);

            video.src = window.URL.createObjectURL(localMediaStream);

            localUserMedia = localMediaStream;

        }, onErrorCallback);

    </script>

</body>

</html>

He puesto comentarios en el código para que sea más fácil de seguir. Pero la clave está en que ahora al pulsar el botón de “snapshot” utilizamos drawImage para volcar el contenido del frame actual del vídeo al canvas.

Una vez tenemos la imagen en un canvas ya tenemos vía libre! Por un lado podríamos utilizar el propio canvas para permitir que el usuario manipule la imagen y luego subirla via Ajax (ver mi post Crea tu propio Instagram) o bien podemos directamente volcar el contenido del canvas en una imagen (utilizando toDataURL del propio canvas).

No voy a poner en este post como subir los datos del canvas al servidor porque sería repetir lo que puse en el post de Crea tu propio Instagram (Paso 5 del post).

En fin… hemos visto como gracias a WebRTC podemos (podremos) hacer algo que hasta hace poco parecía fuera totalmente de las posibilidades de la web: el acceso a dispositivos locales tales como micro y webcam. Y WebRTC no se queda ahí! Según la especificación será posible montar meetings on-line utilizando plataforma 100% web! Pero esta parte está todavía en una fase muy experimental e inicial de implementación!

Un saludo!

Objective-C para desarrolladores de C# (ii)–Punteros

Aaahhh… los punteros son una de las bestias negras del desarrollo. Desterrados de los dominios de los lenguajes orientados a objetos “modernos” como Java por ser demasiado “próclives a errores” los punteros se han convertido en una especie de ser mitológico, temido por muchos desarrolladores que tiemblan cuando ven un asterisco dando vueltas por ahí… Incluso C# los tiene medio apartados por ahí, rodeados de unsafes por todas partes.

¿Qué es un puntero?

Un puntero no es nada más que una variable normal y corriente pero que su valor en lugar de ser un entero, o un carácter, o un número decimal es una dirección de memoria:

int *a;

int i=10;

a=&i;

La primera línea declara un puntero a int. No hay un tipo de datos específico para puntero, en su lugar se pone un asterisco que sigue al tipo de datos.

Nota: El tipo de datos de la variable a (el puntero) es int*. Que en mi código haya un espacio entre el int y el * y ninguno entre el * y la a es porque el parser es así de majo y me deja hacerlo. Verás otra gente que teclea int* a; y es equivalente (y estrictamente hablando más cierto).

La segunda línea es obvia (declara un int) y la tercera línea utiliza el operador de referencia (&) para obtener la dirección de la variable i y asignarla al puntero a.

En este punto el valor de a es la dirección de memoria de la variable i. Si tengo un puntero puedo modificar el contenido de dicho puntero utilizando el operador de dereferencia (*):

*a=11;

En este punto el contenido del puntero a vale 11. O dicho de otra manera: el valor de posición de memoria a la que apuntaba el puntero ha pasado a valer 11. ¿Y qué había en esta posición de memoria? Pues la variable i. Así si ahora imprimo la variable i veré que vale 11.

O de forma más clara, *a es equivalente a i. Modificar el valor de *a implica modificar el valor de i (y a la inversa, modificar el valor de i implica modificar el valor de *a).

¿Por qué son peligrosos los punteros?

Per se, los punteros no son peligrosos. Son simplemente un alias para acceder a una posición de memoria. Pues entonces, ¿qué es lo que los hace peligrosos?

Pues su peligro reside en lo que se llama aritmética de punteros y que permite modificar no el contenido de un puntero si no el propio puntero en sí. Eso significa que un puntero tiene la capacidad de apuntar a direcciones arbitrarias de memoria:

int *a;

int i=10;

a=&i;

a = a + 0x300;

*a=11;

Fíjate en la penúltima línea: Le estamos sumando 0x300 (768) a la dirección de a. De forma que ahora a apunta 768 elementos más allá de la dirección de memoria de i. Cuando modifico el contenido de a, estoy modificando una dirección de memoria arbitraria y lo más normal es que:

image

El otro problema típico de los punteros, no es tanto de los punteros en sí, si no del runtime del lenguaje (me da igual si es C/C++ o Objective-C). En runtimes dotados de garbage collector (como el CLR), el propio sistema se encarga de destruir aquellos objetos que no se usan. ¿Y como sabe el runtime que nadie usa un objeto? Pues, porque no hay referencias que apunten a él. El hecho de tener un GC nos garantiza de que las referencias siempre apuntan a un objeto válido (ya que el GC no destruirá un objeto mientras tenga referencias que lo apunten). Pero, que ocurre si no tenemos GC? Pues que puedo tener dos referencias que apunten a un objeto y alguien puede destruir este objeto. Cuando luego más tarde uso una de esas referencias para acceder al objeto, me puede dar un error ya que en la dirección de memoria apuntada por esta referencia ya no está el objeto, hay otros datos. Este error viene a ser el contrario del NullReferenceException: En lugar de tener un error porque la referencia NO apunta a nada (vale null), tengo un error porque la dirección de memoria a la que apunta la referencia ya no contiene el objeto que contenía porque este ha sido destruído.

He estado usando en todo este párrafo la palabra referencia y no puntero, para poner de manifiesto que podríamos tener este error también si usáramos un lenguaje basado en referencias (y no en punteros) como C# si tuviesemos una gestión manual de memoria. La principal causa de errores de C/C++ es la gestión manual de memoria, no los punteros en sí. En los lenguajes que tienen gestión manual de memoria se denomina con el término dangling pointer a un puntero que apunta a una dirección de memoria donde ya NO hay el objeto que había.

No voy a entrar en las diferencias entre punteros y referencias (porque son muy tenues y depende un poco de cada lenguaje). Quédate con que tanto un puntero como una referencia apuntan a una dirección de memoria. Si tienes GC puedes estar seguro de que en esta dirección habrá un objeto válido. Si no tienes GC entonces NO puedes estar seguro de esto y ahí empiezan realmente tus problemas. La única característica que tienen los punteros y no tienen las referencias es la aritmética de punteros.

¿Cual es la posición de Objective-C?

Primero, Objective-C es un superconjunto de C (eso significa que todo el código C es código Objective-C válido). C tiene punteros y aritmética de punteros así que Objective-C también.

Lo que diferencia Objective-C de C es la gestión de memoria. En C (y en C++) la gestión es totalmente manual: Cuando quieres crear un objeto debes reservar la memoria una sola vez y luego liberarla una sola vez cuando ya nadie más vaya a usar el objeto.

Òbjective-C usa un contador de referencias para ello: El runtime mantiene un contador que indica cuantos punteros apuntan a un objeto en un momento dado. Cuando dicho contador llega a 0 el runtime destruye el objeto. Hay dos modelos de gestión de memoria dentro del runtime:

  1. Manual: Nosotros somos los encargados de indicarle al runtime cuando queremos que incremente y decremente dicho contador. Este modelo de memoria adolece de los mismos problemas que tiene C/C++ para gestionar la memoria (memory leaks si no decrementamos suficientes veces el contador o dangling pointers si lo decrementamos demasiadas veces).
  2. Automática (ARC): El encargado de decrementar o incrementar el contador es el compilador. La verdad es que ARC es muy sencillo de usar y nos permite desarrollar casi, casi, casi como si tuviesemos un GC.

Nota: El runtime de Objective-C soporta también el uso de Garbage Collector. Pero como en iOS no se puede usar, no hablaré del GC de Objective-C. Además su uso está marcado como obsoleto en favor de ARC.

En esta serie de posts veremos tanto la gestión manual (MRC) como la automática (ARC) de memoria.

Desde el punto de vista de un desarrollador de C# debes quedarte con lo siguiente:

  1. Si usas ARC la forma de desarrollar será muy parecida a C#: vamos a crear objetos y nos despreocuparemos de liberar la memoria. ARC lo hará por nosotros.
  2. A diferencia de C++ que admite el paso por valor de objetos, en Objective-C se admite tan solo paso por referencia a través de punteros. Eso lo hace más parecido a C# (cuando se pasa un objeto no se pasa por valor, si no que se pasa una referencia a dicho objeto).

Bueno… lo dejamos aquí por el momento. En el siguiente post de la serie, más 😉

Saludos a todos!