iOS 8 données de base et mises à jour par lots

Core Data existe depuis de nombreuses années sous OS X et Apple n'a pas tardé à l'intégrer à iOS. Bien que le framework ne reçoive pas autant d'attention que les extensions ou les transferts, il continue d'évoluer d'année en année. Cette année, avec la sortie d'iOS 8 et d'OS X Yosemite, ce n'est pas différent.

Apple a introduit quelques nouvelles fonctionnalités dans l'infrastructure Core Data, mais les plus remarquables sont les mises à jour par lots et l'extraction asynchrone. Les développeurs réclament ces fonctionnalités depuis de nombreuses années et Apple a enfin trouvé le moyen de les intégrer à Core Data. Dans ce tutoriel, je vais vous montrer comment fonctionnent les mises à jour par lots et ce qu'elles signifient pour l'infrastructure Core Data..

1. Le problème

Les données de base sont excellentes pour la gestion des graphiques d'objets. Même les graphiques d'objet complexes comportant de nombreuses entités et relations ne posent pas vraiment problème pour Core Data. Cependant, Core Data présente quelques faiblesses et la mise à jour d'un grand nombre d'enregistrements en fait partie..

Le problème est facile à comprendre. Chaque fois que vous mettez à jour un enregistrement, Core Data charge l’enregistrement en mémoire, le met à jour et enregistre les modifications dans le magasin persistant, une base de données SQLite par exemple..

Si Core Data doit mettre à jour un grand nombre d'enregistrements, il doit charger chaque enregistrement en mémoire, le mettre à jour et envoyer les modifications au magasin persistant. Si le nombre d'enregistrements est trop important, iOS va simplement renflouer en raison d'un manque de ressources. Même si un périphérique sous OS X peut disposer des ressources pour exécuter la requête, il sera lent et consomme beaucoup de mémoire..

Une autre approche consiste à mettre à jour les enregistrements par lots, mais cela prend également beaucoup de temps et de ressources. Sur iOS 7, il s'agit de la seule option disponible pour les développeurs iOS. Ce n'est plus le cas sur iOS 8.

2. La solution

Sur iOS 8 et OS X Yosemite, il est possible de parler directement au magasin persistant et de lui dire ce que vous souhaitez modifier. Cela implique généralement la mise à jour d'un attribut ou la suppression d'un certain nombre d'enregistrements. Apple appelle cette fonctionnalité mises à jour par lots.

Il y a cependant un certain nombre de pièges à surveiller. Core Data fait beaucoup de choses pour vous et vous ne le réaliserez peut-être pas tant que vous n'utiliserez pas les mises à jour par lots. La validation est un bon exemple. Étant donné que Core Data effectue des mises à jour par lots directement sur le magasin persistant, tel qu'une base de données SQLite, Core Data ne peut effectuer aucune validation sur les données que vous insérez. Cela signifie que vous devez vous assurer de ne pas ajouter de données non valides au magasin persistant..

Quand utiliseriez-vous les mises à jour par lots? Apple recommande d’utiliser cette fonctionnalité uniquement si l’approche traditionnelle prend trop de temps et de ressources. Si vous devez marquer des centaines ou des milliers d'e-mails comme lus, les mises à jour par lot constituent la meilleure solution sur iOS 8 et OS X Yosemite..

3. Comment ça marche?

Pour illustrer le fonctionnement des mises à jour par lots, il est conseillé de revenir à Done, une application simple de base de données qui gère une liste de tâches. Nous allons ajouter un bouton à la barre de navigation qui marque chaque élément de la liste comme terminé..

Étape 1: Configuration du projet

Téléchargez ou clonez le projet à partir de GitHub et ouvrez-le dans Xcode 6. Exécutez l'application dans le simulateur iOS et ajoutez quelques tâches à effectuer..

Étape 2: Créer un élément de bouton de barre

Ouvrir TSPViewController.m et déclarer une propriété, checkAllButton, de type UIBarButtonItem dans l'extension de classe privée en haut.

@interface TSPViewController ()  @property (strong, nonatomic) NSFetchedResultsController * fetchedResultsController; @property (strong, nonatomic) UIBarButtonItem * checkAllButton; @property (strong, nonatomic) NSIndexPath * sélection; @fin

Initialiser le bouton dans la barre viewDidLoad méthode du TSPViewController classe et le définir comme élément du bouton de la barre de gauche de l'élément de navigation.

// Bouton Initialiser tout vérifier self.checkAllButton = [[UIBarButtonItem alloc]] initWithTitle: @ style "Check All": UIBarButtonItemStyleBordered cible: self action: @selector (checkAll :)]; // Configurer l'élément de navigation self.navigationItem.leftBarButtonItem = self.checkAllButton;

Étape 3: Mettre en œuvre vérifie tout: Méthode

le vérifie tout: La méthode est assez facile, mais il y a quelques mises en garde. Jetez un oeil à sa mise en œuvre ci-dessous.

- (void) checkAll: (id) expéditeur // Créer une entité Description NSEntityDescription * entityDescription = [NSEntityDescription entityForName: @ "TSPItem" inManagedObjectContext: self.managedObjectContext]; // Initialisation de la demande de mise à jour par lot NSBatchUpdateRequest * batchUpdateRequest = [[Allocation NSBatchUpdateRequest]] initWithEntity: entityDescription]; // Configurer la demande de mise à jour par lot [batchUpdateRequest setResultType: NSUpdatedObjectIDsResultType]; [batchUpdateRequest setPropertiesToUpdate: @ @ "done": @YES]; // Exécuter une demande par lot NSError * batchUpdateRequestError = nil; NSBatchUpdateResult * batchUpdateResult = (NSBatchUpdateResult *) [self.managedObjectContext executeRequest: erreur batchUpdateRequest: & batchUpdateRequestError]; if (batchUpdateRequestError) NSLog (@ "Impossible d'exécuter la demande de mise à jour par lot."); NSLog (@ "% @,% @", batchUpdateRequestError, batchUpdateRequestError.localizedDescription);  else // Extraire les ID d'objets NSArray * objectIDs = batchUpdateResult.result; for (NSManagedObjectID * objectID dans objectIDs) // Transforme les objets gérés en défauts NSManagedObject * managedObject = [self.managedObjectContext objectWithID: objectID]; if (managedObject) [self.managedObjectContext refreshObject: managedObject mergeChanges: NO];  // Effectuer la récupération NSError * fetchError = nil; [self.fetchedResultsController performFetch: & fetchError]; if (fetchError) NSLog (@ "Impossible d'effectuer l'extraction."); NSLog (@ "% @,% @", fetchError, fetchError.localizedDescription); 

Créer une demande de lot

Nous commençons par créer un NSEntityDescription par exemple pour le TSPItem entité et l'utiliser pour initialiser un NSBatchUpdateRequest objet.

// Création de la description de l'entité NSEntityDescription * entityDescription = [NSEntityDescription entityForName: @ "TSPItem" inManagedObjectContext: self.managedObjectContext]; // Initialisation de la demande de mise à jour par lot NSBatchUpdateRequest * batchUpdateRequest = [[Allocation NSBatchUpdateRequest]] initWithEntity: entityDescription];

Nous définissons le type de résultat de la demande de mise à jour par lot sur NSUpdatedObjectIDsResultType, ce qui signifie que le résultat de la demande de mise à jour par lots sera un tableau contenant les ID d'objet, des instances du NSManagedObjectID classe, des enregistrements qui ont été modifiés par la demande de mise à jour par lots.

// Configurer la demande de mise à jour par lot [batchUpdateRequest setResultType: NSUpdatedObjectIDsResultType];

Nous peuplons également le propertiesToUpdate propriété de la demande de mise à jour par lots. Pour cet exemple, nous définissons propertiesToUpdate à un NSDictionary contenant une clé, @"terminé", avec valeur @OUI. Cela signifie simplement que chaque TSPItem enregistrement sera mis à terminé, qui est exactement ce que nous voulons.

// Configurer la demande de mise à jour par lot [batchUpdateRequest setPropertiesToUpdate: @ @ "done": @YES];

Exécuter la demande de mise à jour par lot

Même si les mises à jour par lots contournent le contexte de l'objet géré, l'exécution d'une demande de mise à jour par lots est effectuée en appelant executeRequest: error: sur un NSManagedObjectContext exemple. Le premier argument est une instance de NSPersistentStoreRequest classe. Pour exécuter une mise à jour par lot, nous passons la demande de mise à jour par lot que nous venons de créer. Cela fonctionne ailerons depuis le NSBatchUpdateRequest la classe est un NSPersistentStoreRequest sous-classe. Le deuxième argument est un pointeur sur un NSError objet.

// Exécuter une demande par lot NSError * batchUpdateRequestError = nil; NSBatchUpdateResult * batchUpdateResult = (NSBatchUpdateResult *) [self.managedObjectContext executeRequest: erreur batchUpdateRequest: & batchUpdateRequestError];

Mise à jour du contexte d'objet géré

Comme je l'ai mentionné précédemment, les mises à jour par lots contournent le contexte de l'objet géré. Cela donne aux mises à jour par lots leur puissance et leur vitesse, mais cela signifie également que le contexte de l'objet géré n'est pas au courant des modifications apportées. Pour remédier à ce problème, nous devons faire deux choses:

  • transformer les objets gérés mis à jour par la mise à jour par lots en défauts
  • demande au contrôleur de résultats récupéré d'effectuer une récupération pour mettre à jour l'interface utilisateur

C’est ce que nous faisons dans les prochaines lignes du vérifie tout: méthode. Nous vérifions d’abord si la demande de mise à jour par lot a abouti en vérifiant la batchUpdateRequestError pour néant. En cas de succès, nous extrayons le tableau de NSManagedObjectID cas de la NSBatchUpdateResult objet.

// Extraire les ID d'objet NSArray * objectIDs = batchUpdateResult.result;

Nous parcourons ensuite le objectID tableau et demander le contexte d'objet géré pour le correspondant NSManagedObject exemple. Si le contexte de l'objet géré renvoie un objet géré valide, nous le transformons en erreur en appelant refreshObject: mergeChanges:, en passant l'objet géré comme premier argument. Pour forcer l'objet géré dans une faute, nous passons NON comme deuxième argument.

// Extraire les ID d'objet NSArray * objectIDs = batchUpdateResult.result; for (NSManagedObjectID * objectID dans objectIDs) // Transforme les objets gérés en défauts NSManagedObject * managedObject = [self.managedObjectContext objectWithID: objectID]; if (managedObject) [self.managedObjectContext refreshObject: managedObject mergeChanges: NO]; 

Récupération des enregistrements mis à jour

La dernière étape consiste à indiquer au contrôleur de résultats récupéré d'effectuer une récupération pour mettre à jour l'interface utilisateur. Si cela échoue, nous enregistrons l'erreur correspondante.

// Effectuer la récupération NSError * fetchError = nil; [self.fetchedResultsController performFetch: & fetchError]; if (fetchError) NSLog (@ "Impossible d'effectuer l'extraction."); NSLog (@ "% @,% @", fetchError, fetchError.localizedDescription); 

Bien que cela puisse sembler fastidieux et assez complexe pour une opération facile, gardez à l'esprit que nous contournons la pile de données de base. En d’autres termes, nous devons nous occuper de certaines tâches d’entretien qui sont habituellement effectuées pour nous par Core Data..

Étape 4: Construire et exécuter

Générez le projet et exécutez l'application dans iOS Simulator ou sur un périphérique physique. Cliquez ou appuyez sur le bouton à droite pour vérifier chaque tâche de la liste..

Conclusion

Les mises à jour par lots sont un excellent ajout au cadre de données de base. Non seulement il répond à un besoin des développeurs depuis de nombreuses années, mais il est facile à mettre en œuvre tant que vous respectez quelques règles de base. Dans le prochain didacticiel, nous examinerons de plus près l'extraction asynchrone, une autre nouvelle fonctionnalité du framework Core Data..