objective-c - CoreData,单独线程上的子 MOC,意外 : error: NULL _cd_rawData but the object is not being turned into a fault

标签 objective-c xcode macos core-data nspersistentstore

好吧,我对这个有点迷茫,我目前正在尝试使用类型设置为 NSPrivateQueueConcurrencyType 的第二个 ManagedObjectContext 运行后台核心数据操作,但由于上述错误而惨败。

我有一个 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];
}

@结束

我是这样调用它的:

-(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 个 NSString。如果我用那个大小的数组进行测试,我就会遇到问题。但是,如果我将数组减少到大约 15 个 NSString,我不会收到错误,所以我认为这不一定是与线程相关的问题,我想知道数组是否在主线程中被释放。我已经修改了代码以进行深拷贝,然后按如下方式进行 __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 数组,我得到一个对象未​​找到的消息,但如果我 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 对象的生命周期内一直存在吗?

我期望能够从 block 内的断点 PO 数组是错误的吗?

最佳答案

在这种情况下,错误消息“error: NULL _cd_rawData but the object is not being turned into a fault”表明您正在访问其上下文之外的托管对象。基本上,您的提取从您的持久存储中返回所有推文作为错误。一旦您尝试访问托管对象上的属性,Core Data 将引发错误并从存储中获取完整的对象。

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

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

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

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

所以你的 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?

关于objective-c - CoreData,单独线程上的子 MOC,意外 : error: NULL _cd_rawData but the object is not being turned into a fault,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22617977/

相关文章:

objective-c - 增加NSDate +1天方法Objective-C?

ios - 从 UIAlertView 中的 UITextField 获取文本以设置标签的文本

swift - 我在 Xcode 10.2 上的 Swift4.2 中插入 NSManagedObject 后立即收到 Xcode 构建错误

macos - Mavericks 中的 Apple Mail : Any way to change MinimumHTMLFontSize?

objective-c - 将 NSWindow 放在前面但不放在焦点上

objective-c - Objective-C - 如何实现自定义回调方法但强制执行特定参数?

ios - 如何使用 AirDrop 同时发送文本(或 URL)和图像

ios - didDisconnectPeripheral 未调用 iOS Swift 3

iphone - 不确定如何在使用 Storyboard时正确地子类化 UIApplication

xcode - ITMS-90289 - 在 Mac App Store 中使用应用程序组