核心数据,在NSMutableDictionary中缓存NSManagedObjects,问题

时间:2011-03-23 09:30:41

标签: caching core-data

我正在编写一个字典应用程序,我正在尝试从字符串导入原始数据,每个字一个字符串。除此之外,原始输入字符串包含相应单词所属的词性的名称。在我的数据模型中,我有WordPartOfSpeech的单独实体,我想为输入字符串中可能存在的每个唯一词性创建一个PartOfSpeech类型的实体,并建立从Word s到相关语音段的关系。 PartOfSpeech实体与name只有一个属性,Word和一对多关系: enter image description here

我获得唯一PartOfSpeech实体的第一个实现涉及将它们缓存在可变数组中,并且每次使用谓词对其进行过滤。它奏效了,但速度很慢。我决定通过在NSDictionary中缓存PartsOfSpeech来加快速度,现在当我尝试在导入后保存数据存储区时,我收到错误“无法保存带有自己商店之外的引用的对象”。看起来问题出现在字典中,但我该如何解决呢?

这是有效的代码: (在两个代码段managedObjectContext中都是一个ivar,而processStringsInBackground:方法使用performSelectorInBackground:withObject:方法在后台线程上运行)

- (void) processStringsInBackground:(NSFetchRequest *)wordStringsReq {
    NSError *err = NULL;    
    NSFetchRequest *req = [[NSFetchRequest alloc] init];
    [req setEntity:[NSEntityDescription entityForName:@"PartOfSpeech" inManagedObjectContext:managedObjectContext]];
    err = NULL;
    NSMutableArray *selectedPartsOfSpeech = [[managedObjectContext executeFetchRequest:req error:&err] mutableCopy];
    NSPredicate *p = [NSPredicate predicateWithFormat:@"name like[c] $name"];
    //  NSPredicate *formNamePredicate = [NSPredicate predicateWithFormat:<#(NSString *)predicateFormat#>]
...
    for (int i = 0; i < count; i++){
...     
        currentPos = [self uniqueEntityWithName:@"PartOfSpeech" usingMutableArray:selectedPartsOfSpeech predicate:p andDictionary:[NSDictionary dictionaryWithObject:partOfSpeech forKey:@"name"]];
...
    }
}

- (NSManagedObject *) uniqueEntityWithName:(NSString *) entityName usingMutableArray:(NSMutableArray *)objects predicate:(NSPredicate *)aPredicate andDictionary:(NSDictionary *) params {
    NSPredicate *p = [aPredicate predicateWithSubstitutionVariables:params];
    NSArray *filteredArray = [objects filteredArrayUsingPredicate:p];
    if ([filteredArray count] > 0) {
        return [filteredArray objectAtIndex:0];
    }
    NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:managedObjectContext];
    NSArray *dicKeys = [params allKeys];
    for (NSString *key in dicKeys) {
        [newObject willChangeValueForKey:key];
        [newObject setPrimitiveValue:[params valueForKey:key] forKey:key];
        [newObject didChangeValueForKey:key];
    }
    [objects addObject:newObject];
    return newObject;
}

这里是一样的,但使用NSMutableDictionary进行缓存,后来无法保存:

- (void) processStringsInBackground:(NSFetchRequest *)wordStringsReq {
    NSError *err = NULL;    
    [req setEntity:[NSEntityDescription entityForName:@"PartOfSpeech" inManagedObjectContext:managedObjectContext]];
    NSArray *selectedPartsOfSpeech = [managedObjectContext executeFetchRequest:req error:&err];
    NSMutableDictionary *partsOfSpeechChache = [[NSMutableDictionary alloc] init];
    for (PartOfSpeech *pos in selectedPartsOfSpeech) {
        [partsOfSpeechChache setObject:pos forKey:pos.name];
    }
...
    for (int i = 0; i < count; i++){
...     
        currentPos = [self uniqueEntity:@"PartOfSpeech" withName:partOfSpeech usingDictionary:partsOfSpeechChache];
...
    }
}

- (NSManagedObject *)uniqueEntity:(NSString *) entityName withName:(NSString *) name usingDictionary:(NSMutableDictionary *) dic {
    NSManagedObject *pos = [dic objectForKey:name];
    if (pos != nil) {
        return pos;
    }
    NSManagedObject *newPos = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:managedObjectContext];
    [newPos willChangeValueForKey:@"name"];
    [newPos setPrimitiveValue:name forKey:@"name"];
    [newPos didChangeValueForKey:@"name"];

    [dic setObject:newPos forKey:name];
    return newPos;
}

你能帮我找到问题吗?

祝你好运, 提莫菲。

3 个答案:

答案 0 :(得分:1)

错误是由不共享同一持久性存储的managedObject之间形成关系引起的。你可以通过以下方式做到:

  • 使用初始化创建托管对象,而不将其插入上下文。
  • 从上下文中删除托管对象,同时将其保留在另一个对象中,例如数组,然后与它形成关系。
  • 意外创建两个Core Data堆栈,以便您有两个上下文和两个商店。
  • 在多商店环境中混淆配置。

我没有看到您提供的任何部分代码会引发问题。

答案 1 :(得分:0)

事实证明,将NSManagedContext传递给与其创建的线程不同的线程是错误的。相反,应该将NSPersistenceStroreCoordinator传递给另一个线程,并在那里创建一个新的托管对象上下文。为了将更改合并到“主”上下文中,应该保存其他线程的上下文,在主线程上完成保存时收到通知并合并更改(请参阅有关Core Data和并发的苹果文档,可以' t给你链接,因为我在Xcode中读取它。所以这里是我对我的代码所做的更改,以使其工作(仅发布更改的行):

— (void) processStringsInBackground:(NSDictionary *) params {
    NSFetchRequest *wordStringsReq = [params objectForKey:@"wordStringsReq"];
    NSPersistentStoreCoordinator *coordinator = [params objectForKey:@"coordinator"];
    NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
    [localContext setPersistentStoreCoordinator:coordinator];

managedObjectContext的所有引用都被localContext

取代

在主线程上,我这样称呼这个方法:

.......
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:req, @"wordStringsReq", persistentStoreCoordinator, @"coordinator", nil]; //the params i pass to the background method
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"NSManagingContextDidSaveChangesNotification" object:nil]; //register to receive the notification from the save
    [self performSelectorInBackground:@selector(processStringsInBackground:) withObject:dict];

} - (void) handleNotification:(NSNotification *) notific { NSLog(@"got notification, %@", [notific name]); [managedObjectContext mergeChangesFromContextDidSaveNotification:notific]; }

祝你好运!

答案 2 :(得分:0)

好的答案,虽然有点过时了。精细的文档指出主要的NSManagedObjectContext永远不应该在工作线程中使用。相反,使用&#34; main&#34;创建一个单独的NSManagedObjectContext专用于worker。 MOC作为父母,然后代替。这是相关的&#34; Concurrency&#34; “核心数据编程指南”中的页面:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Conceptual/CoreData/Concurrency.html

Snippet(Swift)

let jsonArray = … //JSON data to be imported into Core Data
let moc = … //Our primary context on the main queue

let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc

privateMOC.performBlock {
    for jsonObject in jsonArray {
        let mo = … //Managed object that matches the incoming JSON structure
        //update MO with data from the dictionary
    }
    do {
        try privateMOC.save()
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}