将唯一对象添加到Core Data

时间:2022-08-04 20:24:10

I'm working on an iPhone app that gets a number of objects from a database. I'd like to store these using Core Data, but I'm having problems with my relationships.

我正在开发一个从数据库中获取大量对象的iPhone应用程序。我想使用Core Data存储这些内容,但我的关系存在问题。

A Detail contains any number of POIs (points of interest). When I fetch a set of POI's from the server, they contain a detail ID. In order to associate the POI with the Detail (by ID), my process is as follows: Query the ManagedObjectContext for the detailID. If that detail exists, add the poi to it. If it doesn't, create the detail (it has other properties that will be populated lazily).

详细信息包含任意数量的POI(兴趣点)。当我从服务器获取一组POI时,它们包含一个详细ID。为了将POI与Detail(通过ID)相关联,我的过程如下:查询ManagedObjectContext以获取detailID。如果存在该详细信息,请将poi添加到其中。如果没有,则创建详细信息(它具有将懒惰填充的其他属性)。

The problem with this is performance. Performing constant queries to Core Data is slow, to the point where adding a list of 150 POI's takes a minute thanks to the multiple relationships involved.

这个问题是性能问题。对Core Data执行常量查询的速度很慢,因为所涉及的多个关系,添加150个POI的列表需要一分钟。

In my old model, before Core Data (various NSDictionary cache objects) this process was super fast (look up a key in a dictionary, then create it if it doesn't exist)

在我的旧模式,核心数据(不同的NSDictionary缓存对象)之前,这个过程是超级快(查找字典中的一个键,然后创建它,如果它不存在)

I have more relationships than just this one, but pretty much every one has to do this check (some are many to many, and they have a real problem).

我有更多的关系,而不仅仅是这一个,但几乎每个人都必须做这个检查(有些是很多,他们有一个真正的问题)。

Does anyone have any suggestions for how I can help this? I could perform fewer queries (by searching for a number of different ID's), but I'm not sure how much this will help.

有没有人对我如何帮助这个有任何建议?我可以执行更少的查询(通过搜索许多不同的ID),但我不确定这将有多大帮助。

Some 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];
}

3 个解决方案

#1


3  

This page provides some help on optimizing performance: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1

此页面提供了有关优化性能的一些帮助:http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1

While not very efficient, why not just build them in-memory with a NSDictionary? Read everything from Core Data into a NSDictionary then merge in your data, replacing everything in Core Data.

虽然不是很有效,但为什么不用NSDictionary在内存中构建它们呢?从Core Data读取所有内容到NSDictionary,然后合并您的数据,替换Core Data中的所有内容。

#2


6  

Check out the section titled "Batch Faulting" on the page titled "Core Data Performance" in Xcode's Core Data Programming Guide that Norman linked to in his answer.

在Xcode核心数据编程指南中标题为“核心数据性能”的页面上查看标题为“批处理错误”的部分,Norman在其答案中链接了该指南。

Only fetching those managedObjects whose ids are IN a collection (NSSet, NSArray, NSDictionary) of ids of the objects returned by the server may be even more efficient.

仅获取其ID为服务器返回的对象的ID的集合(NSSet,NSArray,NSDictionary)的那些managedObject可能更有效。

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

UPDATE: I worked this tip into a solution for the acani usersView. Basically, after downloading a JSON response of users, the iPhone uses the popular open source JSON framework to parse the response into an NSArray of NSDictionary objects, each representing a user. Then, it makes an NSArray of their uids and does a batch fetch on Core Data to see if any of them already exist on the iPhone. If not, it inserts it. If so, it updates the ones that do exist only if their updated attribute is older than that of the one from the server.

更新:我将这个技巧用于acani用户视图的解决方案。基本上,在下载用户的JSON响应之后,iPhone使用流行的开源JSON框架将响应解析为NSDrray的NSDray对象,每个对象代表一个用户。然后,它创建了他们的uid的NSArray,并在Core Data上进行批量获取,以查看它们中是否已存在任何这些。如果没有,它会插入它。如果是这样,只有当更新的属性比服务器的更新属性更新时,它才会更新确实存在的属性。

#3


4  

I've gotten all this to work really well, thanks to Norman, who put me on the right path. I'll post my helper class here for others.

我已经把这一切都搞好了,感谢Norman,他让我走上了正确的道路。我会在这里为其他人发布我的助手课程。

Basically, my helper class will look up if an NSManagedObject exists for some ID, and can create it for some ID. This executes quickly enough for me, with 1,000 find/create operations taking around 2 seconds on my iPhone (I also did a few other things there, pure find/create is likely faster).

基本上,如果某个ID存在NSManagedObject,我的帮助器类将会查找,并且可以为某个ID创建它。这对我来说足够快,我的iPhone上有1000次查找/创建操作大约需要2秒钟(我还做了一些其他的事情,纯粹的查找/创建可能更快)。

It does this by caching a dictionary of all the NSManagedObjects, and checking that cache rather than executing a new NSFetchRequest.

它通过缓存所有NSManagedObjects的字典并检查该缓存而不是执行新的NSFetchRequest来完成此操作。

A couple of modifications that could help things speed up even further: 1. Get only selected properties for the NSManagedObjects 2. Only get the identifier property for the NSManagedObject into a dictionary, instead of the whole object. In my performance testing, the single query wasn't the slow part (but with only 1,000 items, I'd expect it to be fast). The slow part was the creation of the items.

一些可以帮助事情进一步加速的修改:1。仅获取NSManagedObjects的选定属性2.仅将NSManagedObject的标识符属性转换为字典,而不是整个对象。在我的性能测试中,单个查询不是缓慢的部分(但只有1,000个项目,我希望它很快)。缓慢的部分是项目的创建。

  #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

#1


3  

This page provides some help on optimizing performance: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1

此页面提供了有关优化性能的一些帮助:http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1

While not very efficient, why not just build them in-memory with a NSDictionary? Read everything from Core Data into a NSDictionary then merge in your data, replacing everything in Core Data.

虽然不是很有效,但为什么不用NSDictionary在内存中构建它们呢?从Core Data读取所有内容到NSDictionary,然后合并您的数据,替换Core Data中的所有内容。

#2


6  

Check out the section titled "Batch Faulting" on the page titled "Core Data Performance" in Xcode's Core Data Programming Guide that Norman linked to in his answer.

在Xcode核心数据编程指南中标题为“核心数据性能”的页面上查看标题为“批处理错误”的部分,Norman在其答案中链接了该指南。

Only fetching those managedObjects whose ids are IN a collection (NSSet, NSArray, NSDictionary) of ids of the objects returned by the server may be even more efficient.

仅获取其ID为服务器返回的对象的ID的集合(NSSet,NSArray,NSDictionary)的那些managedObject可能更有效。

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

UPDATE: I worked this tip into a solution for the acani usersView. Basically, after downloading a JSON response of users, the iPhone uses the popular open source JSON framework to parse the response into an NSArray of NSDictionary objects, each representing a user. Then, it makes an NSArray of their uids and does a batch fetch on Core Data to see if any of them already exist on the iPhone. If not, it inserts it. If so, it updates the ones that do exist only if their updated attribute is older than that of the one from the server.

更新:我将这个技巧用于acani用户视图的解决方案。基本上,在下载用户的JSON响应之后,iPhone使用流行的开源JSON框架将响应解析为NSDrray的NSDray对象,每个对象代表一个用户。然后,它创建了他们的uid的NSArray,并在Core Data上进行批量获取,以查看它们中是否已存在任何这些。如果没有,它会插入它。如果是这样,只有当更新的属性比服务器的更新属性更新时,它才会更新确实存在的属性。

#3


4  

I've gotten all this to work really well, thanks to Norman, who put me on the right path. I'll post my helper class here for others.

我已经把这一切都搞好了,感谢Norman,他让我走上了正确的道路。我会在这里为其他人发布我的助手课程。

Basically, my helper class will look up if an NSManagedObject exists for some ID, and can create it for some ID. This executes quickly enough for me, with 1,000 find/create operations taking around 2 seconds on my iPhone (I also did a few other things there, pure find/create is likely faster).

基本上,如果某个ID存在NSManagedObject,我的帮助器类将会查找,并且可以为某个ID创建它。这对我来说足够快,我的iPhone上有1000次查找/创建操作大约需要2秒钟(我还做了一些其他的事情,纯粹的查找/创建可能更快)。

It does this by caching a dictionary of all the NSManagedObjects, and checking that cache rather than executing a new NSFetchRequest.

它通过缓存所有NSManagedObjects的字典并检查该缓存而不是执行新的NSFetchRequest来完成此操作。

A couple of modifications that could help things speed up even further: 1. Get only selected properties for the NSManagedObjects 2. Only get the identifier property for the NSManagedObject into a dictionary, instead of the whole object. In my performance testing, the single query wasn't the slow part (but with only 1,000 items, I'd expect it to be fast). The slow part was the creation of the items.

一些可以帮助事情进一步加速的修改:1。仅获取NSManagedObjects的选定属性2.仅将NSManagedObject的标识符属性转换为字典,而不是整个对象。在我的性能测试中,单个查询不是缓慢的部分(但只有1,000个项目,我希望它很快)。缓慢的部分是项目的创建。

  #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