2009-08-05 6 views
10

Je travaille sur une application iPhone qui récupère un certain nombre d'objets à partir d'une base de données. J'aimerais les stocker en utilisant des données de base, mais j'ai des problèmes avec mes relations.Ajout d'objets uniques aux données de base

Un détail contient un nombre quelconque de POI (points d'intérêt). Lorsque je récupère un ensemble de POI sur le serveur, ils contiennent un ID de détail. Pour associer le POI au détail (par ID), mon processus est le suivant: Interrogez le ManagedObjectContext pour le detailID. Si ce détail existe, ajoutez-y le poi. Si ce n'est pas le cas, créez le détail (il a d'autres propriétés qui seront peuplées paresseusement).

Le problème avec ceci est la performance. L'exécution de requêtes constantes sur les données de base est lente, au point que l'ajout d'une liste de 150 POI prend une minute grâce aux multiples relations impliquées.

Dans mon ancien modèle, avant que les données de base (divers objets de cache NSDictionary) ce processus était super rapide (chercher une clé dans un dictionnaire, puis créez si elle n'existe pas)

J'ai plus de relations que juste celui-ci, mais à peu près tout le monde doit faire cette vérification (certains sont nombreux à beaucoup, et ils ont un vrai problème).

Est-ce que quelqu'un a des suggestions pour aider? Je pourrais effectuer moins de requêtes (en cherchant un certain nombre d'ID différents), mais je ne suis pas sûr de savoir combien cela va aider.

Certains code:

 POI *poi = [NSEntityDescription 
       insertNewObjectForEntityForName:@"POI" 
       inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]]; 

    poi.POIid = [attributeDict objectForKey:kAttributeID]; 
    poi.detailId = [attributeDict objectForKey:kAttributeDetailID]; 
    Detail *detail = [self findDetailForID:poi.POIid]; 
    if(detail == nil) 
    { 
     detail = [NSEntityDescription 
        insertNewObjectForEntityForName:@"Detail" 
        inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]]; 
     detail.title = poi.POIid; 
     detail.subtitle = @""; 
     detail.detailType = [attributeDict objectForKey:kAttributeType]; 
    } 





-(Detail*)findDetailForID:(NSString*)detailID { 
NSManagedObjectContext *moc = [[UIApplication sharedApplication].delegate managedObjectContext]; 
NSEntityDescription *entityDescription = [NSEntityDescription 
              entityForName:@"Detail" inManagedObjectContext:moc]; 
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; 
[request setEntity:entityDescription]; 

NSPredicate *predicate = [NSPredicate predicateWithFormat: 
          @"detailid == %@", detailID]; 
[request setPredicate:predicate]; 
NSLog(@"%@", [predicate description]); 

NSError *error; 
NSArray *array = [moc executeFetchRequest:request error:&error]; 
if (array == nil || [array count] != 1) 
{ 
     // Deal with error... 
    return nil; 
} 
return [array objectAtIndex:0]; 
} 
+1

Expliquez vos objets plus en détail et publiez une partie du code que vous utilisez. Il est presque 100% probable qu'il existe un moyen de faire ce que vous faites déjà beaucoup plus vite. – Sneakyness

Répondre

3

Cette page fournit une aide sur optimisation des performances: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1

Bien que pas très efficace, pourquoi ne pas simplement les construire en mémoire avec un NSDictionary? Lisez tout de Core Data dans un NSDictionary, puis fusionnez vos données, en remplaçant tout dans les données de base.

+0

Idée intéressante. Le modèle serait alors, chaque fois que créer des objets 1. Récupérer la plage possible d'objets liés, stocker dans NSDictionary avec la clé 2. Si l'ID existe, ajouter la relation à cet objet 3. Si l'ID ne pas, ajouter le nouvel objet à la fois le dictionnaire et les données de base. Ajoutez la relation. – Dimitri

4

J'ai tout fait pour que ça fonctionne très bien, grâce à Norman, qui m'a mis sur la bonne voie. Je posterai mon cours d'aide ici pour les autres.

Fondamentalement, ma classe d'assistance recherchera si un objet NSManagedObject existe pour certains ID, et peut le créer pour certains ID. Cela s'exécute assez rapidement pour moi, avec 1000 opérations de recherche/création prenant environ 2 secondes sur mon iPhone (j'ai aussi fait quelques autres choses là-bas, pure find/create est probablement plus rapide). Il le fait en mettant en cache un dictionnaire de tous les NSManagedObjects et en vérifiant ce cache plutôt que d'exécuter une nouvelle requête NSFetchRequest.

Quelques modifications qui pourraient aider à accélérer les choses encore plus loin: 1. Obtenir seulement des propriétés sélectionnées pour les NSManagedObjects 2. Seulement obtenir la propriété d'identifiant pour le NSManagedObject dans un dictionnaire, au lieu de l'objet entier. Dans mes tests de performance, la requête unique n'était pas la partie lente (mais avec seulement 1 000 éléments, je m'attendrais à ce que ce soit rapide). La partie lente était la création des articles.

#import "CoreDataUniquer.h" 


@implementation CoreDataUniquer 

    //the identifying property is the field on the NSManagedObject that will be used to look up our custom identifier 
-(id)initWithEntityName:(NSString*)newEntityName andIdentifyingProperty:(NSString*)newIdProp 
{ 
    self = [super init]; 
    if (self != nil) { 
     entityName = [newEntityName retain]; 
     identifyingProperty = [newIdProp retain]; 
    } 
    return self; 
} 

-(NSManagedObject*)findObjectForID:(NSString*)identifier 
{ 
    if(identifier == nil) 
    { 
     return nil; 
    } 
    if(!objectList) 
    { 
     NSManagedObjectContext *moc = [(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]; 
     NSEntityDescription *entityDescription = [NSEntityDescription 
                entityForName:entityName inManagedObjectContext:moc]; 
     NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease]; 
     [request setEntity:entityDescription]; 

     NSError *error; 
     NSArray *array = [moc executeFetchRequest:request error:&error]; 
     objectList = [[NSMutableDictionary dictionary] retain]; 
     for (NSManagedObject* p in array) { 
      NSString* itemId = [p valueForKey:identifyingProperty]; 
      [objectList setObject:p forKey:itemId]; 
     } 
    } 
    NSManagedObject* returnedObject = [objectList objectForKey:identifier]; 
    return returnedObject; 
} 
-(NSManagedObject*)createObjectForID:(NSString*)identifier 
{ 

    NSManagedObject* returnedObject = [NSEntityDescription 
             insertNewObjectForEntityForName:entityName 
             inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]]; 
    [returnedObject setValue:identifier forKey:identifyingProperty]; 
    [objectList setObject:returnedObject forKey:identifier]; 
    return returnedObject; 
} 


- (void) dealloc 
{ 
    DESTROY(entityName); 
    DESTROY(identifyingProperty); 
    [super dealloc]; 
} 

@end 
+0

Aussi, assurez-vous de cocher l'option "index" sur votre propriété d'identifiant. – Dimitri

+2

Merci! Voulez-vous également publier le fichier .h? – ma11hew28

+0

Vous essayez de trouver l'entité avec la valeur d'attribut en vérifiant toutes les entités du magasin. Vous accédez à tous ces entites et il coz changer l'état d'erreur en requête réelle. Je pense que vous devez utiliser le prédicat et la limitation pour NSFetchRequest et exécuter la requête. si count> 0, cela signifie que l'entité avec l'attribut existe déjà dans le stockage persistant. Après cela, vous pouvez accéder à ce résultat de demande pour mettre à jour votre entité. –

6

Vérifiez la section intitulée "Lot Failles" sur la page intitulée "Performance Core Data" dans Guide de programmation des données de base de Xcode que Norman liée à sa réponse.

Seulement aller chercher ces managedObjects dont ids sont IN une collection (NSSet, NSArray, NSDictionary) de ids des objets renvoyés par le serveur peut être encore plus efficace.

NSSet *oids = [[NSSet alloc] initWithObjects:@"oid1", @"oid2", ..., nil]; 
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"oid IN %@", oids]; 
[oids release]; 

MISE À JOUR: J'ai travaillé cette astuce dans une solution pour la acani usersView. Fondamentalement, après avoir téléchargé un JSON response of users, l'iPhone utilise le popular open source JSON framework pour analyser la réponse dans un NSArray de NSDictionary objets, chacun représentant un utilisateur. Ensuite, il fait un NSArray de leurs uids et fait un chargement par lots sur les données de base pour voir si l'un d'entre eux existe déjà sur l'iPhone. Sinon, il l'insère. Si c'est le cas, il met à jour ceux qui existent seulement si leur attribut updated est plus ancien que celui du serveur.

Questions connexes