Performances des données de base: 6 conseils à connaître

Écrire du code de données de base en pensant aux performances aide à préparer votre application pour l’avenir. Votre base de données peut être petite au début, mais peut facilement se développer, ce qui entraîne des requêtes lentes et une expérience réduite pour l’utilisateur.

Depuis que j’ai commencé à écrire l’application Collect by WeTransfer en 2017, j’ai écrit beaucoup de code lié aux données de base, le touchant presque tous les jours. Avec des millions d’utilisateurs qui ajoutent beaucoup de contenu, l’exécution de code lié aux données de base est devenue une compétence importante de notre équipe.

Architecturer des applications SwiftUI avec MVC et Mvvmalors que vous pouvez créer une application simplement en assemblant du code, sans les meilleures pratiques et une architecture robuste, vous vous retrouverez bientôt avec du code spaghetti ingérable. Apprenez à créer des applications solides et maintenables avec moins de bugs en utilisant ce guide gratuit.

Au fil des ans, nous avons développé de nombreuses idées, que je suis heureux de partager avec vous à travers 5 conseils à connaître.

1: Utiliser un contexte d’objet géré en arrière-plan

Une chose que nous n’avons pas faite dès le début est d’utiliser un contexte d’objet géré en arrière-plan. Nous n’avons utilisé le contexte de vue que pour effectuer des tâches liées aux données de base : insertion de nouveau contenu, suppression de contenu, récupération de contenu, etc.

Au début, notre application était relativement petite. Utiliser uniquement le contexte de la vue n’était pas vraiment un problème et n’entraînait aucune pénalité de performance visible liée aux données de base. De toute évidence, une fois que notre application a commencé à se développer, nous avons réalisé que le contexte de la vue était associé à la file d’attente principale. Les requêtes lentes ont bloqué notre interface utilisateur et notre application est devenue moins réactive.

En général, la meilleure pratique consiste à effectuer un traitement de données sur une file d’attente en arrière-plan, car cela peut nécessiter beaucoup de CPU. Des exemples comme l’importation de JSON dans des données de base pourraient autrement bloquer le contexte de la vue et entraîner une absence de réponse dans l’interface utilisateur.

La solution consiste à utiliser un contexte d’objet géré en arrière-plan. Les dernières API facilitent la création d’un nouveau contexte à partir de votre conteneur persistant:

let backgroundContext = persistentContainer.newBackgroundContext()

Je recommande cette méthode sur l’initialiseur NSManagedObjectContext(concurrenyType:) car il sera automatiquement associé au NSPersistentStoreCoordinator et il sera également configuré pour consommer des émissions NSManagedObjectContextDidSave. Cela permet de synchroniser votre contexte d’arrière-plan avec le contexte de la vue.

Vous pouvez enregistrer ce contexte d’arrière-plan sur une sous-classe de conteneur persistant personnalisée. De cette façon, vous pouvez réutiliser votre contexte d’arrière-plan et vous n’avez qu’à gérer deux contextes. Cela simplifie la compréhension de votre structure de données de base et évite d’avoir plusieurs contextes désynchronisés.

Si vous n’avez qu’à utiliser le contexte d’arrière-plan à quelques endroits, vous pouvez également décider d’utiliser la méthode performBackgroundTask(_:) qui crée un contexte d’arrière-plan en place:

persistentContainer.performBackgroundTask { (backgroundContext) in // .. Core Data Code}

Cependant, cette méthode crée un nouveau NSManagedObjectContext chaque fois qu’elle est invoquée. Vous pouvez envisager d’utiliser le contexte d’arrière-plan partagé si vous expédiez plus souvent vers un contexte d’arrière-plan.

Important: Ne transmettez pas d’instances NSManagedObject entre des files d’attente

L’écriture de code de données de base multithread est beaucoup plus complexe que l’utilisation d’un contexte de vue unique. La raison en est que vous ne pouvez pas simplement passer un NSManagedObject instancié d’un contexte de vue à un contexte d’arrière-plan. Cela entraînerait un crash et une corruption potentielle des données.

Lorsqu’il est nécessaire de déplacer un objet géré d’une file d’attente à une autre, vous pouvez utiliser le NSManagedObjectID qui est thread-safe:

let managedObject = NSManagedObject(context: persistentContainer.viewContext)backgroundContext.perform { let object = try? backgroundContext.existingObject(with: managedObject.objectID)}

2: Enregistrer uniquement un contexte d’objet géré si nécessaire

Enregistrer un contexte d’objet géré valide toutes les modifications en cours dans le magasin parent du contexte. Comme vous pouvez l’imaginer, ce n’est pas une opération bon marché et elle ne doit être utilisée que si nécessaire pour assurer les performances des données de base.

Tout d’abord, il est important de vérifier s’il y a même quelque chose à sauvegarder. S’il n’y a pas de modifications à valider, il n’y a pas non plus de raison d’effectuer une sauvegarde. En créant une méthode saveIfNeeded, vous vous permettez d’intégrer facilement une vérification pour cela:

Considérez soigneusement quand enregistrer vos modifications

En plus d’utiliser saveIfNeeded au lieu de save(), vous devez également déterminer si une sauvegarde a du sens. Bien qu’un contexte puisse avoir des changements, il n’est pas toujours nécessaire de valider directement ces changements.

Par exemple, si vous avez plusieurs éléments importants dans votre base de données, vous ne voudrez peut-être les enregistrer qu’après avoir importé tous les éléments de votre contexte d’arrière-plan. Une sauvegarde est souvent suivie de mises à jour de l’interface utilisateur et plusieurs sauvegardes les unes après les autres peuvent facilement entraîner des rechargements inutiles. En outre, tenez compte du fait que les modifications enregistrées dans un contexte d’arrière-plan sont fusionnées dans le contexte de vue, bloquant également la file d’attente principale sous peu. Par conséquent, soyez conscient!

3: Récupérer uniquement ce dont vous avez besoin

La récupération des données est une tâche coûteuse et doit être aussi performante que possible pour préparer votre application à de grands ensembles de données. Le code suivant est une erreur souvent commise:

Ce code chargera tous les objets insérés en mémoire pendant qu’il est filtré directement après pour ne rester qu’avec un contenu ayant un nom.

Il est beaucoup plus performant d’utiliser des prédicats pour récupérer uniquement les objets nécessaires. Le filtre ci-dessus peut être écrit comme suit avec un NSPredicate:

Cela présente deux avantages:

  • Seuls les objets nécessaires sont chargés en mémoire
  • Vous n’avez pas besoin d’itérer sur tous les objets

Les prédicats sont très flexibles et devraient vous permettre de récupérer l’ensemble de données souhaité dans la plupart des cas tout en maintenant les performances dans les données de base.

4: Utilisez les limites de récupération

Suite à l’exemple précédent, il est important de définir des limites de récupération lorsque vous n’affichez qu’une partie de l’ensemble de données.

Par exemple, dites que vous n’avez besoin que des 3 premiers noms de tous les éléments de contenu. Dans ce cas, il serait inutile de charger tous les éléments de contenu ayant un nom en mémoire. Nous pourrions empêcher cela en définissant une limite de récupération:

Ce code ne renverra que les 3 premiers éléments de contenu ayant un nom.

5: Supprimez plusieurs objets à la fois en utilisant un NSBatchDeleteRequest

Au lieu d’itérer sur un ensemble de données en supprimant chaque objet un par un, il est souvent plus performant d’utiliser un NSBatchDeleteRequest qui s’exécute plus rapidement car il fonctionne au niveau SQL dans le magasin persistant lui-même.

Vous pouvez en savoir plus sur les demandes de suppression par lots dans mon article de blog En utilisant NSBatchDeleteRequest pour supprimer des lots dans Core Data.

6: Savoir comment déboguer le code de données de base

Comme pour tout le code que vous écrivez, il est important de savoir comment l’optimiser et le déboguer une fois qu’il ne fonctionne pas comme prévu. Il existe de nombreuses façons de déboguer qui sont mieux expliquées dans mon article de blog dédié: Débogage des données de base dans Xcode en utilisant des arguments de lancement.

Architecturer des applications SwiftUI avec MVC et Mvvmalors que vous pouvez créer une application simplement en assemblant du code, sans les meilleures pratiques et une architecture robuste, vous vous retrouverez bientôt avec du code spaghetti ingérable. Apprenez à créer des applications solides et maintenables avec moins de bugs en utilisant ce guide gratuit.

Conclusion

L’écriture d’un code de données de base performant dès le début vous aide à préparer votre application pour de futurs ensembles de données volumineux. Bien que votre application fonctionne au début, elle peut facilement ralentir une fois que votre base de données et votre modèle se développent. En utilisant un contexte d’arrière-plan, des demandes de récupération intelligente et des demandes de suppression par lots, vous rendez votre code de données de base déjà plus performant.

Si vous souhaitez améliorer vos connaissances Swift, encore plus, consultez la page de catégorie Swift. N’hésitez pas à me contacter ou à me tweeter sur Twitter si vous avez des conseils ou des commentaires supplémentaires.

Merci!



+