CoreData,子MOC在单独的线程上,意外:错误:NULL _cd_rawData但是对象没有变成错误

时间:2014-03-24 18:43:59

标签: objective-c xcode macos core-data nspersistentstore

好的,我有点迷失了这个,我正在尝试使用第二个ManagedObjectContext运行后台核心数据操作,其类型设置为NSPrivateQueueConcurrencyType,并且因上述错误而失败。

我有一个NSOperation的自定义子类,它正在传递NSArray字符串,而PersistentStoreCoordinator来自主线程,然后创建自己的ManagedObjectContext,运行查询并执行和操作。

以下是该类的代码:

//
//  ProcessProfanity.m
//  Hashtag Live Desktop
//
//  Created by Gareth Jeanne on 24/03/2014.
//  Copyright (c) 2014 Gareth Jeanne. All rights reserved.
//

#import "ProcessProfanity.h"
#import "Tweet.h"

static const int ImportBatchSize = 250;

@interface ProcessProfanity ()
@property (nonatomic, copy) NSArray* badWords;
@property (nonatomic, strong) NSManagedObjectContext* backgroundContext;
@property (nonatomic, strong) NSPersistentStoreCoordinator* persistentStoreCoordinator;
@end

@implementation ProcessProfanity


{

}


- (id)initWithStore:(NSPersistentStoreCoordinator*)store badWords:(NSArray*)words
{
self = [super init];
if(self) {
    self.persistentStoreCoordinator = store;
    self.badWords = words;
}
return self;
}


- (void)main
{
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
_backgroundContext.undoManager = nil;
[_backgroundContext performBlockAndWait:^
{
    [self import];
}];
}

- (void)import
{

//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];

//Setup the Request
[request setEntity:[NSEntityDescription entityForName:@"Tweet" inManagedObjectContext:self.backgroundContext]];

NSError *error = nil;

//Create an array from the returned objects
NSArray* tweetsToProcess = [self.backgroundContext executeFetchRequest:request error:&error];
NSAssert2(tweetsToProcess != nil && error == nil, @"Error fetching events: %@\n%@", [error localizedDescription], [error userInfo]);

for (Tweet* tweetToCheck in tweetsToProcess){
    __block NSString *result = nil;
    [self.badWords indexOfObjectWithOptions:NSEnumerationConcurrent
                                   passingTest:^(NSString *obj, NSUInteger idx, BOOL *stop)
     {
         if (tweetToCheck){
             if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound)
             {
                 result = obj;
                 *stop = YES;
                 //return YES;
             }
         }
         return NO;
     }];

    if (!result){
        //DDLogVerbose(@"The post does not contain any of the words from the naughty list");
        if(tweetToCheck){
            tweetToCheck.profanity = [NSNumber numberWithBool:false];
        }
    }
    else{
        if(tweetToCheck){
            //DDLogVerbose(@"The string contains '%@' from the the naughty list", result);
            tweetToCheck.profanity = [NSNumber numberWithBool:true];
        }
    }

}
[self.backgroundContext save:NULL];
}

@end

这就是我所说的:

-(void)checkForProfanity{

if(!self.operationQueue){
self.operationQueue = [[NSOperationQueue alloc] init];
}

NSArray* termsToPass = [self.filterTerms copy];
ProcessProfanity* operation = [[ProcessProfanity alloc] initWithStore:self.persistentStoreCoordinator badWords:termsToPass];
[self.operationQueue addOperation:operation];


}

修改1

特定的行我似乎得到了错误,或者至少在Xcode破坏的地方是:

if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound)

我已经设法将其缩小了一点,包含搜索字符串的术语列表的NSArray可能非常大,可能超过1,000个NSStrings。如果我用这个大小的数组进行测试,我就会遇到问题。但是,如果我将数组减少到大约15 NSStrings,我不会得到错误,所以我不认为这必然是一个线程相关的问题,我想知道数组是否在主线程中被释放。我修改了代码以进行深层复制,然后修改__block副本,如下所示,但它似乎没有帮助。

self.badWords = [[NSArray alloc] initWithArray:words copyItems:YES];

for (Tweet* tweetToCheck in tweetsToProcess){
    __block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];
    __block NSString *result = nil;
    [array indexOfObjectWithOptions:NSEnumerationConcurrent

事实上,在Xcode中断的地方,如果我是PO数组,我得到一个找不到对象的消息,但是如果我得到结果,我正确得到一个返回的对象为nil。

修改2

所以我做了以下更改,没有任何变化:

使NSArray强大而非复制:

@property (nonatomic, strong) NSArray* badWords;

分配时将其作为副本:

self.badWords = [[NSArray alloc] initWithArray:words copyItems:YES];

在处理对象的实际方法中创建了一个带有___block声明的NSArray的本地副本:

__block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];

这肯定意味着它会在ProcessProfanity对象的生命周期中徘徊吗?

我希望能够从块内的断点处获取数组吗?

2 个答案:

答案 0 :(得分:4)

在此实例中,错误消息“error:NULL _cd_rawData但对象未变为错误”表示您正在访问其上下文之外的托管对象。基本上你的fetch会将你持久存储中的所有推文作为错误返回。尝试访问托管对象上的属性后,Core Data将触发故障并从存储中获取完整对象。

通过使用indexOfObjectWithOptions:passingTest:选项调用NSArray方法NSEnumerationConcurrent,暗示您希望对数组中的元素执行异步执行。关键字concurrent表示可以使用多个线程对数组元素进行操作。

在您的上下文中,这意味着访问此块中的托管对象可能会导致在拥有该对象的托管对象上下文的不同线程上访问它。因此,当您在条件检查中访问tweetToCheck.text - if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound)时,Core Data会从持久性存储中获取该托管对象,并将其返回给不属于托管对象上下文线程的线程。 / p>

此外,没有必要使用方法indexOfObjectWithOptions:passingTest:,因为您实际上并不对此操作的结果感兴趣。

在我看来,使用NSSet可能更方便,因为您只是在测试亵渎性词语中是否存在给定的推文词。引用NSSet的documentation:“当元素的顺序不重要时,可以使用集合作为数组的替代,并且在测试集合中是否包含对象时的性能是一个考虑因素”。显然,这似乎符合您的标准。

所以你的init看起来像:

 -(id)initWithStore:(NSPersistentStoreCoordinator*)store 
           badWords:(NSSet*)badWords
{
   self = [super init];
   if(self) {
     self.persistentStoreCoordinator = store;
     self.badWords = [words copy];
   }
   return self;
}

由于您只对更新尚未标记为亵渎的推文感兴趣,因此您可能只想获取未被标记为亵渎的推文:

//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];

//Setup the Request
[request setEntity:[NSEntityDescription entityForName:@"Tweet" inManagedObjectContext:self.backgroundContext]];
[request setPredicate:[NSPredicate predicateWithFormat:@"profanity = NO"]];

既然你有一系列不亵渎的推文,你可以遍历你的推文并检查每个单词是否含有亵渎词。您需要处理的唯一事情是如何将您的推文分成单词(忽略逗号和感叹号等)。然后对于每个单词,您将需要删除变音符号并且可能忽略该情况。因此,您最终会遇到以下人员:

if([self.badWords containsObject:badWordString]) {
    currentTweet.profanity = [NSNumber numberWithBOOL:YES];
}

请记住,您可以在NSSet上运行谓词,这样您就可以实际执行大小写和变音符号不敏感查询:

NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"SELF = %@[cd]",wordToCheck];
BOOL foundABadWord = ([[[self.badWords filteredSetUsingPredicate:searchPredicate] allObjects] count] > 0);

您可能想要考虑的另一件事是删除推文中的重复字词,您实际上并不想多次执行相同的检查。因此,根据您如何找到性能,您可以将推文的每个单词放入NSSet中,只需对推文中的唯一单词运行查询:

if([[self.badWords intersectsSet:tweetDividedIntoWordsSet]) {
    //we have a profane tweet here!
}

您选择的实施取决于您,但假设您在应用中仅使用英语,那么您肯定会想要运行案例和变音符号不敏感的搜索。

修改

最后要注意的是,无论你尝试多少,人们将始终是检测亵渎或侮辱性语言的最佳方法。我鼓励你阅读这篇关于检测亵渎的SO帖子 - How do you implement a good profanity filter?

答案 1 :(得分:1)

好的,所以仍然不太清楚发生了什么,但我遵循Daniels的建议并重新编写了indexOfObjectWithOptions方法,现在它正在运行。为了完整性,所以它有希望帮助别人,这就是我最终做的事情。

    DDLogInfo(@"Processing posts to check for bad language");
for (Tweet* tweetToCheck in tweetsToProcess){
    __block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];
    __block NSString *result = nil;

    NSRange tmprange;
    for(NSString *string in array) {
        tmprange = [tweetToCheck.text rangeOfString:[NSString stringWithFormat:@" %@ ", string]];
        if (tmprange.location != NSNotFound) {
            result = string;
            DDLogVerbose(@"Naughty Word Found: %@", string);
            break;
        }
    }

    if (!result){
        //DDLogVerbose(@"The post does not contain any of the words from the naughty list");
        if(tweetToCheck){
            tweetToCheck.profanity = [NSNumber numberWithBool:false];
        }
    }
    else{
        if(tweetToCheck){
            //DDLogVerbose(@"The string contains '%@' from the the naughty list", result);
            tweetToCheck.profanity = [NSNumber numberWithBool:true];
        }
    }