【问题标题】:Core Data: error: Exception was caught during Core Data change processing核心数据:错误:核心数据更改处理期间捕获到异常
【发布时间】:2014-11-06 20:49:35
【问题描述】:

我已经有这个问题好几天了,这真的很令人沮丧。我一直在检查我的代码,一遍又一遍地尝试不同的东西并一直遇到同样的问题。只有 50% 的情况不会发生这种情况总是。这使得它更难..

问题,

我正在将 3 个 csv 文件中的数据解析到我的核心数据,其中 2 个文件解析总是顺利进行,但中间/第二个文件总是发生崩溃的地方,因此,这将是该文件和 managedObjectContext 类的地址对于这个文件。

错误信息

CoreData: error: Serious application error.  
Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  
-[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2014-09-12 11:27:06.115 AppName[210:3907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'

因此,在我的 FetchData 课程中,我尝试以不同的方式解决问题。

  • 首先,我更改了 .csv 文件并将 N/A 插入到所有空的字段/单元格中。
  • 其次,我正在检查我的 FetchData 类,如果它没有任何值,请保存 N/A。
  • 第三,在我触发解析数据的视图控制器中,我现在在我的核心数据中为这 3 个实体分离了三个不同的属性。

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContextGI;

@property (nonatomic, strong) NSManagedObjectContext *managedObjectContextVA;

这可能有点疯狂或其他什么,但我真的需要解决这个问题,所以尝试任何可能的解决方案或方法总是好的,我认为。

ViewController 调用函数来执行解析..

//at the beginning of my model
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

-(IBAction)myLoadingTask:(id)sender{

       dispatch_async(kBgQueue, ^{

           NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

           NSString *savedValue = @"";

           if([[userDefaults stringForKey:@"dataFetched"] length] > 0){                            
               savedValue = [userDefaults stringForKey:@"dataFetched"];
           }

           // if the csv files data hasn't been fetch it, then fetch it
           if([savedValue length] == 0){

               FetchData *fd = [[FetchData alloc] initWithManagedContext:self.managedObjectContext];

               // fetching benefits data
               [fd beginParser];

               FetchGIBillData *fdGI = [[FetchGIBillData alloc] initWithManagedContext:self.managedObjectContextGI];

               // fetching gi bill data
               [fdGI beginParser];

               FetchVAPhones *fdVA = [[FetchVAPhones alloc] initWithManagedContext:self.managedObjectContextVA];

               // fetching va phones
               [fdVA beginParser];

               NSString *valueToSave = @"saved";
               [[NSUserDefaults standardUserDefaults] setObject:valueToSave forKey:@"dataFetched"];
               [[NSUserDefaults standardUserDefaults] synchronize];

           }

       });
}

这是我的 Core Data 模型函数等等.. 我已经检查它是否为空保存 N/A 等等.. 我所有的我实体中的属性是 strings

#define GIBILL_FILENAME @"gi_bill_data"

int numOfEntries;
- (id)initWithManagedContext:(NSManagedObjectContext*)managedObjectContext
{
    self.managedObjectContext = managedObjectContext;
    arrayOfRecords = [[NSMutableArray alloc] init];
    numOfEntries=0;
    return self;
}


- (void) beginParser
{
    if (self.managedObjectContext == nil){
        // Error: Must pass in NSManagedObjectContext
        return;
    }

    NSString *filePath = [[NSBundle mainBundle] pathForResource:GIBILL_FILENAME ofType:@"csv"];
    NSInputStream *stream = [NSInputStream inputStreamWithFileAtPath:filePath];
    NSStringEncoding encoding = NSUTF8StringEncoding;//NSWindowsCP1250StringEncoding;

    CHCSVParser *parser = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:&encoding delimiter:','];
    parser.delegate = self;

    [parser parse];

    // uncomment to update data x amount of dates
    //[self checkDateForRefreshCSV:parser];

}

** 这是保存的地方!! *

#pragma mark - Data Add
/**
 * addRows
 * @param parser: the CHCSV parser that will parse if required refresh
 * @brief: add the row to ths managedObjectContent DB. All values saved.
 */
- (void) addRows:(CHCSVParser *)parser
{
    int i = -1;
    if ([arrayOfRecords count] == 0) return;
    GIBill *data = [NSEntityDescription
                      insertNewObjectForEntityForName:@"GIBill"
                      inManagedObjectContext:self.managedObjectContext];

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.facility_code = [arrayOfRecords objectAtIndex:i];
    else
        data.facility_code = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.institution = [arrayOfRecords objectAtIndex:i];
    else
        data.institution = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.city = [arrayOfRecords objectAtIndex:i];
    else
        data.city = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.state = [arrayOfRecords objectAtIndex:i];
    else
        data.state = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.country = [arrayOfRecords objectAtIndex:i];
    else
        data.country = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.bah = [arrayOfRecords objectAtIndex:i];
    else
        data.bah = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.poe = [arrayOfRecords objectAtIndex:i];
    else
        data.poe = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.yr = [arrayOfRecords objectAtIndex:i];
    else
        data.yr = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.gibill = [arrayOfRecords objectAtIndex:i];
    else
        data.gibill = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 0)
        data.cross = [arrayOfRecords objectAtIndex:i];
    else
        data.cross = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.grad_rate = [arrayOfRecords objectAtIndex:i];
    else
        data.grad_rate = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.grad_rate_rank = [arrayOfRecords objectAtIndex:i];
    else
        data.grad_rate_rank = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.default_rate = [arrayOfRecords objectAtIndex:i];
    else
        data.default_rate = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.avg_stu_loan_debt = [arrayOfRecords objectAtIndex:i];
    else
        data.avg_stu_loan_debt = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.avg_stu_loan_debt_rank = [arrayOfRecords objectAtIndex:i];
    else
        data.avg_stu_loan_debt_rank = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.indicator_group = [arrayOfRecords objectAtIndex:i];
    else
        data.indicator_group = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.salary = [arrayOfRecords objectAtIndex:i];
    else
        data.salary = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.zip = [arrayOfRecords objectAtIndex:i];
    else
        data.zip = @"N/A";

    if([[arrayOfRecords objectAtIndex:++i] length] > 2)
        data.ope = [arrayOfRecords objectAtIndex:i];
    else
        data.ope = @"N/A";

    NSError *error;

    [self.managedObjectContext save:&error];

}

好吧,我发布了我认为该问题最相关的代码。如果需要其他内容或有关问题的更多详细信息,请告诉我,我会提供它..

提前致谢!

【问题讨论】:

  • 添加异常断点并检查它在哪里插入null。
  • 你正在使用旧的 Core Data 并发模型跨线程访问这些上下文,这会导致很多问题。
  • 旧的核心数据并发模型是什么意思? @quellish
  • 您正在使用线程限制。当您创建 NSManagedObjectContexts 时,您使用的是“init”,而不是带有 NSPrivateQueueConcurrencyType 或 NSMainQueueConcurrencyType 参数的“initWithConcurrencyType:”。这意味着您的 NSManagedObjectContext 只能在创建它的线程中使用。您在一个地方创建上下文,然后将其传递给使用并发调度队列的东西 - 这将根据需要创建尽可能多的线程。线程约束并发模型已经过时了一段时间。
  • 哦!!!!我应该在我的 bg 线程中创建它吗? @quellish

标签: ios objective-c csv core-data


【解决方案1】:

下面这行拯救了我的一天:

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

您只需将并发类型设置为私有。这样它可以在私有队列上同时对数据库执行多个操作。

【讨论】:

  • 这可以在你的 appDelegate 和所有其他核心数据函数中找到。对于 Swift,它是这一行:var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)。这篇文章作者的所有功劳。出色的捕获和节省时间。
【解决方案2】:

嗯,整个问题是创建NSManagedObjectContextMain Thread 中的所有内容,然后在Background Thread 中访问或使用它。

所以,我刚刚关注了this post,现在一切都完美无缺:)

非常感谢 cmets,它确实让我朝着正确的方向前进,这完全是我找到问题所需要的。

谢谢!

在 AppDelegate.h 中

+ (NSManagedObjectContext *)mainQueueContext;
+ (NSManagedObjectContext *)privateQueueContext;

然后,在我的 AppDelegate.m

#pragma mark - Singleton Access

+ (NSManagedObjectContext *)mainQueueContext
{
    return [self mainQueueContext];
}

+ (NSManagedObjectContext *)privateQueueContext
{
    return [self privateQueueContext];
}

#pragma mark - Getters

- (NSManagedObjectContext *)mainQueueContext
{
    if (!_mainQueueContext) {
        _mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        _mainQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }

    return _mainQueueContext;
}

- (NSManagedObjectContext *)privateQueueContext
{
    if (!_privateQueueContext) {
        _privateQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _privateQueueContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
    }

    return _privateQueueContext;
}

- (id)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextDidSavePrivateQueueContext:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:[self privateQueueContext]];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(contextDidSaveMainQueueContext:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:[self mainQueueContext]];
    }
    return self;
}

#pragma mark - Notifications

- (void)contextDidSavePrivateQueueContext:(NSNotification *)notification
{
    @synchronized(self) {
        [self.mainQueueContext performBlock:^{
            [self.mainQueueContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

- (void)contextDidSaveMainQueueContext:(NSNotification *)notification
{
    @synchronized(self) {
        [self.privateQueueContext performBlock:^{
            [self.privateQueueContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

最后,在我的 ViewController 中,我将工作发送到后台..

// dont forget the macro
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

dispatch_async(kBgQueue, ^{

    id delegate = [[UIApplication sharedApplication] delegate];
    self.managedObjectContext = [delegate privateQueueContext];

    // do something in the background with your managedObjectContext!!!!

 });

【讨论】:

  • 你为什么要创建公共方法? + (NSManagedObjectContext *)privateQueueContext 因为您正在使用实例方法 id delegate = [[UIApplication sharedApplication] delegate]; self.managedObjectContext = [delegate privateQueueContext];
  • 在 swift 3 中怎么样?
  • @Taprolano 你在 swift 3 中发现了吗?
【解决方案3】:

对于使用 Swift 3 的人:

var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)

【讨论】:

    【解决方案4】:

    在 Swift 4 中我使用这个模板:

    // MARK: - Core Data stack
    
    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "Model")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
    
                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    lazy var viewContext: NSManagedObjectContext = {
        return self.persistentContainer.viewContext
    }()
    
    lazy var cacheContext: NSManagedObjectContext = {
        return self.persistentContainer.newBackgroundContext()
    }()
    
    lazy var updateContext: NSManagedObjectContext = {
        let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        _updateContext.parent = self.viewContext
        return _updateContext
    }()
    

    和 updateContext 用于更新操作。

    【讨论】:

    • 你能解释一下为什么 privateQueueConcurrencyType 是 updateContext 所必需的,以及你是如何使用它的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    • 2013-01-26
    相关资源
    最近更新 更多