Introducción
iOS proporciona muchas formas de almacenamiento local: sistema de ficheros (NSFileManager), base de datos relacionales (SQLite), sistema de almacenamiento orientado a objetos (Core Data) y zona de información sensible encriptada (Keychain). En este apartado analizaremos las principales funcionalidades de cada uno de ellos, compararemos SQLite con Core Data y finalmente comentaremos cómo podemos añadir la potente capacidad iCloud a un proyecto desarrollado con Core Data. Por ejemplo, añadir iCloud a un proyecto de libreta de notas nos permite tener sincronizadas nuestras notas en tiempo real entre nuestros diferentes dispositivos Apple.
- 5.1 Sistema de ficheros: Bundle y área de documentos
- 5.2 Bases de datos relacionales locales (SQLite)
- 5.3 Core Data, sistema de almacenamiento orientado a objetos
- 5.4 SQLite vs. Core Data
- 5.5 iCloud + Core Data
- 5.6 Información sensible encriptada, keychain
5.1 Sistema de ficheros: Bundle y área de documentos
Bundle: Es la zona que contiene los recursos añadidos en tiempo de compilación. Es una zona solo de lectura. Puede contener cualquier tipo de documento: imágenes, ficheros xml, vídeos, bases de datos SQLite, etc.
Es importante asegurase que dentro de las Build Phases, en Copy Bundle Resources se encuentran los recursos que queremos empaquetar con nuestra aplicación.

Swift:
Área de documentos: El área de documentos de nuestra aplicación. Es una zona de lectura/escritura. Para acceder al path base usamos:
Swift:
En esta zona podemos realizar todas las funciones relacionadas con un sistema de ficheros tradicional, tanto sobre ficheros como sobre directorios: copiar, mover, borrar, preguntar por la existencia, etc. El path base a la zona de documentos puede no estar creado. Es importante usar el siguiente código para asegurarnos de su existencia antes de usarlo para guardar documentos:
Swift:
Cuando usamos una base de datos local usualmente añadimos al bundle un fichero de base de datos que contiene la definición de las tablas y los datos iniciales que queremos que contenga la aplicación. Cuando se ejecuta la aplicación miramos si ese fichero existe en el área de documentos, si no existe lo copiamos desde el Bundle. Realizamos esta copia porque el Bundle es un espacio solo de lectura.
Swift:
Si queremos escribir un fichero de texto en el área de documentos es importante tener en cuenta el encoding del texto. Habitualmente se trabaja con UTF-8. Lo que hacemos es:
- Acceder al path base.
- Convertir el NSString en un NSData códificandolo en UTF-8.
- (NSData* content = [data dataUsingEncoding:NSUTF8StringEncoding] y
- Finalmente usar el método writeToFile de NSData para escribir el fichero.
writeToFile tiene un parámetro llamado atomically que indica si debe escribir antes la información en un fichero temporal y luego renombrar. Esta es una medida de seguridad extrema que se usa para asegurar que existe espacio, que no habrá problemas con la batería, etc.
5.2 Bases de datos relacionales locales (SQLite)
Apple proporciona el acceso a bases de datos SQLite ( https://www.sqlite.org/ SQLite es una librería que implementa un modelo transaccional de base de datos relacional basado en un único fichero. Su código es de dominio público y se puede usar para cualquier uso privado o comercial. Deberemos tener conocimientos de modelado de bases de datos relacionales: definir entidades, relaciones, claves primarias, claves foráneas, índices, etc.
Existen muchas herramientas gratuitas que nos permiten modelizar nuestra base de datos antes de añadirla a nuestro proyecto como recurso. Existe otra opción que consiste en introducir dentro del código de la aplicación los scripts SQL para la creación e inicialización de las tablas. En este apartado vamos a optar por la primera opción. Usaremos la aplicación gratuita de Mac SQLiteBrowser ( https://sqlitebrowser.org/ para modelizar nuestra base de datos.


Creamos las tablas User y City. Modelizamos la relación 1:N como un atributo dentro de la tabla User que apunta al atributo city_id (su clave primaria) de la tabla City. El fichero database.sqlite3 generado por SQLiteBrowser puede ser añadido directamente a nuestro proyecto.
Como ya hemos comentado en el apartado de sistema de ficheros, en el método viewDidLoad de nuestro ViewController comprobamos si existe nuestro fichero de base datos en nuestra área de documentos. Si no existe, será la primera vez que se ejecute la aplicación, copiamos desde el bundle nuestro recurso al área de documentos. Realizamos esta copia porque el bundle es una zona de lectura y necesitamos acceso a la base de datos en modo lectura/escritura.
Swift:
Ahora ya podemos abrir la base de datos desde el área de documentos.
Swift:
Hemos declarado la variable g_database de forma static. Se suele realizar de esta forma o usando un patrón Singleton para prevenir problemas de rendimiento, lecturas erróneas, etc. Esto no asegura su buen funcionamiento en aplicaciones concurrentes. Una buena solución es crear un único hilo que se encargue de todas las operaciones con la base de datos o incluso realizarlas en el hilo principal.
El uso de SQLite en nuestra aplicación implica la construcción de sentencias en lenguaje SQL dentro del código.
Para insertar filas construiremos sentencias SQL, insert.
Swift:
- Las partes parametrizables de la sentencia se marcan en el string SQL con el símbolo ? y se instancian usando:
Swift:
- Ejecutamos la sentencia con:
Swift:
En nuestro ejemplo primero insertaremos filas de la tabla City para después al insertar las filas de la tabla User poder relacionarlas con esa tabla usando la clave foránea user_city_id
Swift:
Las consultas, las realizamos construyendo sentencias de tipo select. En la construcción de los select deberemos usar joins para combinar dos o más tablas usando de forma correcta sus claves primarias e índices.
Iteramos sobre los resultados de las consultas usando sqlite3_step. Accedemos a los valores de cada atributo seleccionado mediante los métodos sqlite3_column_int y sqlite3_column_text dependiendo del tipo del atributo devuelto.
Swift:
Las operaciones de actualización (update) y borrado (delete) consisten en generar sus sentencias SQL.
Actualización:
Swift:
Borrado:
Swift:
5.3 Core Data, sistema de almacenamiento orientado a objetos
Core Data proporciona una capa de almacenamiento de objetos. Esta capa puede usar diferentes soportes de almacenamiento: SQLite, binario, etc. En nuestra explicación usaremos almacenamiento de objetos sobre SQLite.
Nuestra estructura de objetos será esta:

Cuando creamos nuestro proyecto indicamos que usaremos Core Data, marcando el check “Use Core Data".

Esto nos generará un fichero CoreData1.xcdatamodeld, al pulsar en él accederemos al editor de modelo de datos. Este editor nos permite modelar objetos y sus relaciones.
El editor tiene dos vistas: gráfica y en forma de tabla.


Distinguimos dos tipos de elementos de estructura:
Entity: Similares a las entidades del modelo relacional (pulsar en el botón Add Entity). Estas entidades tienen atributos (pulsar en el botón Add Attribute).
Relationship: Similar a las relaciones del modelo relacional (mantener pulsado el botón Add Attribute para que nos aparezca un menú que nos permita crear las relaciones, Add Relationship).
Aunque hemos comentado que los tipos de objetos anteriores tienen analogías con sus homólogos de un sistema de bases de datos relacionales, existen importantes diferencias. No tenemos que definir claves primarias ni índices. El sistema gestionará de forma automática los identificadores de objeto que luego usará para materializar las relaciones.
Una vez creado el modelo generaremos las clases.
- Pulsamos en New File sobre el árbol del ficheros y seleccionamos iOS/Core Data/ NSManagedObject subclasses

- Seleccionamos un modelo, podemos tener varios en el mismo proyecto.

- Seleccionamos las entidades del modelo que queremos generar. Podemos marcarlas todas.

- El resultado es la generación de clases que podemos usar directamente en nuestro proyecto.
Estas clases derivan de NSManagedObject. Esta clase es la que permite la grabación y lectura de objetos usando Core Data.

Importante: Algunas modificaciones del modelo usando el editor visual hacen necesario el borrado de las clases generadas y una nueva generación.
Comenzamos a usar el modelo creado en el código.
- Añadimos a nuestro ViewController una propiedad NSManagedObjectContext.
Esta clase representa un conjunto de objetos cargados y gestionados por Core Data.
- Para comenzar a usar nuestro modelo debemos realizar tres operaciones básicas: determinar el modo de concurrencia, indicar cuál es su definición y seleccionar un formato de grabación.
En el método viewDidLoad de nuestro ViewController realizamos estas operaciones.
Creamos nuestra propiedad NSManagedObjectContext indicando que gestionará el modelo desde el hilo principal NSMainQueueConcurrencyType ( Apartado programación concurrente ).
Esto solo se debe realizar si sabemos que nuestros objetos tendrán un tamaño pequeño. Podemos indicar que nuestro modelo se gestione desde otro hilo usando NSPrivateQueueConcurrencyType
// Other threads
// self.m_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
- Indicamos dónde se encuentra el fichero que contiene la información de la definición (entidades, relaciones, tipo de los atributos, etc.).
NSURL*url = [bundle URLForResource:@"CoreData1"withExtension: @"momd"];
NSManagedObjectModel*model = [[ NSManagedObjectModel alloc] initWithContentsOfURL:url];
- Indicamos el formato en que queremos que se grabe. Creamos un NSPersistentStoreCoordinator y le añadimos un soporte de tipo SQLiteaddPersistentStoreWithType:NSSQLiteStoreType. Al añadir un soporte de este tipo indicaremos el nombre del fichero. Este fichero no debe existir, lo creará y lo gestionará Core Data. Creamos un Persistent Store ligado a la base de datos database.sqlite con las siguientes opciones:
- NSMigratePersistentStoresAutomaticallyOption: Le indica al NSPersistentStoreCoordinator que debe usar la información del modelo de objetos para crear un formato físico. En este caso el formato físico serán tablas de SQLite.
- NSInferMappingModelAutomaticallyOption: Le indica al NSPersistentStoreCoordinator que si ya existe el formato físico, fichero sqlite por ejemplo, debe comprobar que el mapeo entre el modelo de objetos y el formato físico es el correcto. Si no lo es, usa unas reglas automáticas para añadir o eliminar entidades, relaciones y atributos.
NSDictionary*options = [ NSDictionarydictionaryWithObjectsAndKeys:
[ NSNumbernumberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[ NSNumbernumberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSArray*searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString*documentPath = [searchPaths objectAtIndex:0];
NSURL*storeUrl = [ NSURLfileURLWithPath: [documentPath stringByAppendingPathComponent: @"database.sqlite"]];
NSError*error;
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration: nilURL:storeUrl options:options error:&error];
- Finalmente indicamos el modelo que será gestionado por el NSPersistentStoreCoordinator que acabamos de crear.
Podemos añadir más de un NSPersistentStore al NSPersistentStoreCoordinator. Los NSPersistentStore pueden grabar en diferentes formatos: SQLite, Binary, etc.
El NSPersistentStoreCoordinator no es seguro en entornos concurrentes, se deben usar mecanismos de bloqueo ( Apartado programación concurrente ).
Una vez inicializado nuestro NSManagedObjectContext ya podemos comenzar a trabajar.
Creación:
- Creamos los objetos usando el constructor de NSEntityDescription indicándole el objeto NSManagedObjectContext que se ocupará de su gestión.
DBCity *City = [NSEntityDescription
insertNewObjectForEntityForName:@"City"
inManagedObjectContext:context];
DBCity *City = [NSEntityDescription
insertNewObjectForEntityForName:@"City"
inManagedObjectContext:context];
- Asignamos propiedades usando simplemente la sintaxis de Objective-C:
- Finalmente llamamos al método save del NSManagedObjectContext:
if (![context save: &error]) {
NSLog(@"Error save: %@", [error localizedDescription]);
}
La clase NSManagedObjectContext realiza la grabación de forma inteligente. Solo graba los cambios en el modelo: creación, modificación o borrado.
Podemos acumular cambios y realizar una única llamada a save. En el ejemplo siguiente creamos tres usuarios.
insertNewObjectForEntityForName:@"User"
inManagedObjectContext:context];
user1.user_name =@"John";
user1.user_phone =@"999 888 777";
user1.city_rel = City;
DBUser *user2 = [NSEntityDescription
insertNewObjectForEntityForName:@"User"
inManagedObjectContext:context];
user2.user_name =@"Steve";
user2.user_phone =@"999 888 777";
user2.city_rel = City2;
DBUser *user3 = [NSEntityDescription
insertNewObjectForEntityForName:@"User"
inManagedObjectContext:context];
user3.user_name =@"Peter";
user3.user_phone =@"999 888 777";
user3.city_rel = City;
if (![context save: &error]) {
NSLog(@"Error save: %@", [error localizedDescription]);
}
Podemos observar que las relaciones se implementan como una asignación de objetos estándar en Objective-C: user1.city_rel = City;
Selección:
La selección más sencilla es la carga de todos los objetos de una clase concreta. En este caso no usamos nuestra clase DBUser, usamos la clase padre NSManagedObject y accedemos a las propiedades usando el método
valueForKey.
NSManagedObjectContext*context = [ self m_managedObjectContext];
NSFetchRequest*fetchRequest = [[ NSFetchRequest alloc] init];
NSEntityDescription*entity = [ NSEntityDescription
entityForName:@"User"inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray*fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject*info in fetchedObjects) {
NSLog(@"Name: %@", [info valueForKey: @"user_name"]);
}
En muchas ocasiones solo queremos cargar los objetos de una clase que cumplan una determinada condición. Para realizar este tipo de consultas usamos la clase NSPredicate. En el ejemplo queremos que el modelo se cargue solo con los usuarios que están relacionados con Londres.
Observemos que no solo se cargan los objetos, también se cargan todos los relacionados con ellos. Esta vez no usaremos la clase padre genérica NSManagedObject, usaremos nuestras clases DBUser y DBCity. En nuestro ejemplo cargamos los objetos DBUser y esto provocará la carga de los objetos DBCity con los que est´ relacionado algún objeto DBUser
Para acceder al nombre de la ciudad usamos la forma habitual en Objective-C, user.city_rel.city_name.
NSManagedObjectContext*context = [ self m_managedObjectContext];
NSFetchRequest*fetchRequest = [[ NSFetchRequest alloc] init];
NSEntityDescription*entity = [ NSEntityDescription
entityForName:@"User"inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicatepredicateWithFormat:@"city_rel.city_name == 'London'"]];
NSArray*fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (DBUser *user in fetchedObjects) {
NSLog(@"User name: %@ City name: %@", user.user_name, user.city_rel.city_name);
}
Inicializamos el NSPredicate desde un string donde indicamos al condición que se debe cumplir:
Modificación:
Al cambiar el valor de cualquier propiedad de un objeto cargado en el modelo, este queda marcado para ser grabado cuando se realice la siguiente llamada al método save de NSManagedObjectContext.
Podemos modificar la selección anterior para modificar el número de teléfono de los usuarios conectados con Londres.
- Después de la carga modificamos la propiedad user_phone user.user_phone = @"902 888 888";
- Al finalizar el bucle que los recorre llamamos al método save del NSManagedObjectContext
NSManagedObjectContext*context = [ self m_managedObjectContext];
NSFetchRequest*fetchRequest = [[ NSFetchRequest alloc] init];
NSEntityDescription*entity = [ NSEntityDescription
entityForName:@"User"inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicatepredicateWithFormat:@"city_rel.city_name == 'London'"]];
NSArray*fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (DBUser *user in fetchedObjects) {
NSLog(@"User name: %@ City name: %@", user.user_name, user.city_rel.city_name);
user.user_phone =@"902 888 888";
}
if (![context save: &error]) {
NSLog(@"Error save: %@", [error localizedDescription]);
}
Borrado:
Para borrar un elemento o conjunto de elementos usamos el método deleteObject de NSManagedObjectContext.
En el ejemplo cargaremos en memoria todos los usuarios relacionados con la ciudad de Nueva York y luego procederemos a su borrado.
NSManagedObjectContext*context = [ self m_managedObjectContext];
NSFetchRequest*fetchRequest = [[ NSFetchRequest alloc] init];
NSEntityDescription*entity = [ NSEntityDescription
entityForName:@"User"inManagedObjectContext:context];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicatepredicateWithFormat:@"city_rel.city_name == 'New York'"]];
NSArray*fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (DBUser *user in fetchedObjects) {
NSLog(@"User name: %@ City name: %@", user.user_name, user.city_rel.city_name);
[context deleteObject:user];
}
if (![context save: &error]) {
NSLog(@"Error save: %@", [error localizedDescription]);
}
Importante: Debemos ejecutar el método save para aplicar el borrado al almacenamiento físico; si no lo hacemos estaremos borrando solo el objeto de la memoria.
Desde iOS9 existe una forma de borrar todos los objetos asociados de una clase sin cargarlos en memoria. Creamos un NSBatchDeleteRequest a partir de un NSFetchRequest que indica la clase a la que pertenecen los objetos que queremos borrar.
NSFetchRequest*request = [[ NSFetchRequest alloc] initWithEntityName:@"User"];
NSBatchDeleteRequest *delete = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
NSError*deleteError =nil;
[context executeRequest:delete error: &deleteError];
NSError*error;
if (![context save: &error]) {
NSLog(@"Error save: %@", [error localizedDescription]);
}
5.4 SQLite vs. Core Data
Mejor SQLite en:
- Uso de memoria. Core Data usa mucha más memoria. Cuando cargamos un objeto cargamos todos sus atributos y todos los objetos relacionados con él de forma automática. Core Data carga jerarquías de objetos. Con SQLite podemos acceder a atributos concretos de una entidad y leer las claves foráneas de las relaciones sin cargar todo el objeto relacionado.
- Espacio en disco. Core Data usa mucho más espacio en disco, hasta cuatro veces más. Añade metainformación (información interna sobre la estructura del propio modelo), identificadores de objetos e incluso duplica información para aumentar la velocidad de carga.
- Multiplataforma. SQLite es un entorno multiplataforma. Los comandos SQL y el fichero físico lo podemos usar de forma directa en una aplicación para otro sistema (Android o Windows Phone).
Mejor en Core Data:
- Mejor tiempo de acceso. Una vez se ha realizado la carga inicial, pensemos que gran parte del modelo ya se encuentra en memoria y por lo tanto el acceso es muy rápido.
- No hay gestión de SQL dentro del código. Los desarrolladores trabajamos directamente con objetos. Nunca vemos sentencias SQL, no tenemos que crear un modelo relacional (definición de claves primarias, foráneas, índices, etc.), no nos preocupamos de conversión de tipos para realizar la grabación.
- Integración con iCloud. Este es uno de los puntos fuertes de Core Data. Está totalmente integrado con iCloud. Resulta muy espectacular ver cómo nuestros datos se sincronizan en tiempo real entre nuestra aplicación ejecutándose en un iPhone y la misma ejecutándose en nuestro iPad. La integración con iCloud la vamos a tratar en el siguiente punto.
5.5 iCloud + Core Data
Apple ha apostado fuerte por el almacenamiento en la nube. Todos los usuarios Apple cuentan con 5 GB de almacenamiento gratuito.
En primer lugar tenemos que configurar las capacidades iCloud de nuestra aplicación.

- Accedemos a las propiedades del target de nuestra aplicación y en capabilities ponemos a “on” iCloud.
Aunque por defecto no aparecen seleccionados, debemos seleccionar todos los servicios: Key-value storage, iCloud Documents y CloudKit.
- Accedemos a la configuración de la aplicación en Apple y activamos iCloud. La primera vez el indicador estará en amarillo porque tenemos que terminar la configuración.

Editamos la definición de la aplicación en Apple para terminar la configuración de iCloud.

Seleccionamos CloudKit, que es el modo actual de uso de iCloud. Pulsamos en Edit para crear los contenedores. Un contenedor es similar a una carpeta del sistema de ficheros. Creamos un contenedor, tenemos que darle un nombre y un identificador único usando la nomenclatura de dominio inversa.

Una vez terminada la configuración ya podemos realizar pequeños cambios en el código de nuestra aplicación ejemplo de CoreData para que use iCloud en lugar de solo almacenamiento local.
- En la inicialización de Core Data, cuando creamos la NSPersistentStore indicamos que ahora tendrá que sincronizarse de forma automática con una copia que estará en iCloud. Para realizar esto le indicamos en qué contenedor estará la copia.
[ NSNumbernumberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[ NSNumbernumberWithBool:YES], NSInferMappingModelAutomaticallyOption,
@"iCloud.com.TheOrganization.CoreData2",NSPersistentStoreUbiquitousContainerIdentifierKey, @"CoreData1",NSPersistentStoreUbiquitousContentNameKey,nil];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration: nilURL:storeUrl options:options error:&error];
Ahora al arrancar la aplicación debemos obtener estos dos mensajes en la ventana Output. Es importante realizar las pruebas con el dispositivo, ya que con el emulador no funcionan de forma correcta.
2016-03-25 13:32:54.091 CoreData2[3826:1802455] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](898): CoreData: Ubiquity: mobileD4406686-F8CA-414A-9EDF-8222AACB3382:CoreData1
Using local storage: 1 for new NSFileManager current token <e97487ef c61cd4cb 4f80bf40 3739bdf3 bdd657b1>
Al cabo de un segundo aparecerá este:
2016-03-25 13:32:59.970 CoreData2[3826:1802496] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](898): CoreData: Ubiquity: mobileD4406686-F8CA-414A-9EDF-8222AACB3382:CoreData1
Using local storage: 0 for new NSFileManager current token <e97487ef c61cd4cb 4f80bf40 3739bdf3 bdd657b1>
El segundo mensaje indica que ya se ha iniciado la sincronización con iCloud
Notificaciones:
Debemos añadirnos como observadores de las notificaciones NSPersistentStoreDidImportUbiquitousContentChangesNotification en el NSNotificationCenter por defecto de nuestra aplicación para que los cambios realizados desde otro dispositivo se muestren en tiempo real en el nuestro.
Recordemos que cada aplicación tiene un NSNotificacionCenter. Esta notificación se genera de forma local dado que se recibe después de que se ha actualizado la información en el soporte físico local, en este caso SQLite, con la información recibida desde el iCloud. Lo que hacemos es actualizar el modelo que tenemos en memoria usando mergeChangesFromContextDidSaveNotification
addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:self.m_managedObjectContext.persistentStoreCoordinator
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification*note) {
[self.m_managedObjectContext performBlock: ^{
[self.m_managedObjectContext mergeChangesFromContextDidSaveNotification:note];
}];
}];
Si usamos NSBatchDeleteRequest se producen fallos en la sincronización de iCloud. Tendremos que usar el borrado cargando los objetos en memoria.
5.6 Información sensible encriptada, keychain
Los servicios keychain proporcionan almacenamiento local seguro de información como nombres de usuarios, claves, etc. Keychain permite compartir estos datos seguros entre diferentes aplicaciones del mismo fabricante. Esta es la característica más importante de keychain, ya que desde iOS9 todos los ficheros se encuentran encriptados.
Las operaciones sobre el keychain reciben un diccionario NSMutableDictionary como parámetro. En este diccionario introducimos los parámetros necesarios para la realización de cada operación.
Podéis encontrar un listado de las constantes usadas para definir estos parámetros en: https://developer.apple.com/documentation/security/keychain-services
Creación:
NSString*value =@"hello";
OSStatus error;
NSMutableDictionary*query = [ NSMutableDictionary dictionary];
- Especificamos el tipo de contenido (kSecClass) que vamos a guardar en el servicio keychain. En este caso un password (kSecClassGenericPassword)
- Ahora indicamos la clave (kSecAttrAccount)
- Depués indicamos cuándo será accesible el valor (kSecAttrAccessible). Le indicamos que solo cuando el dispositivo esté desbloqueado (kSecAttrAccessibleWhenUnlocked)
- Indicamos el valor (kSecValueData)
- Finalmente llamamos a la función SecItemAdd
error = SecItemAdd((CFDictionaryRef)CFBridgingRetain(query), NULL);
if(error != errSecSuccess){
NSLog(@"Error %d",error);
}
Usamos de forma intensiva CFBridgingRelease y CFBridgingRetain. Esto es necesario porque la gestión de memoria de la librería de seguridad y en general de Core Foundation no es la misma que la gestión de memoria de nuestra aplicación escrita en Objective-C. Lo que hacemos es indicar como el ARC (Automatic Reference Counting) debe comportarse con punteros no ARC ( https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html).
Consulta:
NSString*key =@"password";
NSString*value =@"";
- Especificamos el tipo de contenido (kSecClass) que queremos consultar en el servicio keychain. En este caso un password (kSecClassGenericPassword)
- Ahora indicamos la clave (kSecAttrAccount):
- Indicamos que el resultado lo queremos obtener como una copia de la información (kSecReturnData). No queremos un puntero.
- Ejecuta la consulta.
CFDataRef attributes;
OSStatus error = SecItemCopyMatching((CFDictionaryRef)CFBridgingRetain(query), (CFTypeRef *)&attributes);
- Realiza la conversión de tipos de punteros entre punteros Objective-C y punteros no Objective-C (de la librería de seguridad).
NSString*stringToReturn =nil;
if (error == errSecSuccess) {
value = [[NSString alloc] initWithData:dataFromKeychain encoding:NSUTF8StringEncoding];
}
Modificar clave:
NSString*key =@"password";
NSString*value =@"1234";
[query setObject:(id)CFBridgingRelease(kSecClassGenericPassword) forKey:(id)CFBridgingRelease(kSecClass)];
[query setObject: key forKey:( id)CFBridgingRelease(kSecAttrAccount)];
[query setObject:(id)CFBridgingRelease(kSecAttrAccessibleWhenUnlocked) forKey:(id)CFBridgingRelease(kSecAttrAccessible)];
- Indicamos el conjunto de atributos que queremos modificar en un NSDictionary que pasamos como parámetro a la función SecItemUpdate. En este caso solo queremos modificar el valor.
NSDictionary*attributesToUpdate = [ NSDictionarydictionaryWithObject:[value dataUsingEncoding:NSUTF8StringEncoding]
forKey:( id)CFBridgingRelease(kSecValueData)];
error = SecItemUpdate((CFDictionaryRef)CFBridgingRetain(query), (CFDictionaryRef)CFBridgingRetain(attributesToUpdate));
Borrar clave:
- Indicamos la clave que queremos borrar
NSMutableDictionary*query = [ NSMutableDictionary dictionary];
[query setObject:(id)CFBridgingRelease(kSecClassGenericPassword) forKey:(id)CFBridgingRelease(kSecClass)];
[query setObject:key forKey:( id)CFBridgingRelease(kSecAttrAccount)];
- Ejecutamos la función SecItemDelete
Posteriores consultas devolverán error.