【问题标题】:ios - error when calling viewWillAppear() manually (Objective-C)ios - 手动调用 viewWillAppear() 时出错(Objective-C)
【发布时间】:2017-11-16 16:33:17
【问题描述】:

我正在尝试在我的 ios 应用程序的后台更新核心数据,我首先删除核心数据,然后将其添加回来。但是,我需要一定的 segue 才能运行某些功能,但是当我尝试在后台执行所有操作时,这些功能永远不会运行,除非我更改页面并返回它。

所以我尝试通过手动调用 viewWillAppear() 来修复此错误,但我收到以下错误。

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x17191c00 of class CardScanView was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x16d99c30> (
<NSKeyValueObservance 0x16dcdf20: Observer: 0x17191c00, Key path: verifyingCard, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x16d5db30>

发生错误的类中的方法:

- (void) resetDatabase {
    count++;

    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        ConDAO *con = [[ConDAO alloc] init];
        DatabaseManager *manager = [DatabaseManager sharedManager];
        NSError * error;
        NSURL * storeURL = [[[manager managedObjectContext] persistentStoreCoordinator] URLForPersistentStore:[[[[manager managedObjectContext] persistentStoreCoordinator] persistentStores] lastObject]];
        [[manager managedObjectContext] reset];//to drop pending changes
        if ([[[manager managedObjectContext] persistentStoreCoordinator] removePersistentStore:[[[[manager managedObjectContext] persistentStoreCoordinator] persistentStores] lastObject] error:&error])
        {
            // remove the file containing the data
            [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
            //recreate the store like in the  appDelegate method
            [[[manager managedObjectContext] persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
        }
        NSLog(@"*****************************");
        NSLog(@"updating");
        NSLog(@"count: %d", count);
        NSLog(@"*****************************");

        [self populateDatabase:0 con:con];


        NSTimer *timer = [NSTimer timerWithTimeInterval:60.0
                                                 target:self
                                               selector:@selector(resetDatabase)
                                               userInfo:nil repeats:NO];
        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        dispatch_async(dispatch_get_main_queue(), ^(void){
            CardScanView *card = [[CardScanView alloc] init];

            [[NSNotificationCenter defaultCenter] addObserver:card
                                                     selector:@selector(viewWillAppear:)
                                                         name:@"updated" object:nil];
            [[NSNotificationCenter defaultCenter] postNotificationName:@"updated" object:nil];

                });
        });
}

viewWillAppear 和 viewDidDissapear 在其他类中:

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    // Setup KVO for verifyingcard
    [self addObserver:self forKeyPath:@"verifyingCard" options:NSKeyValueObservingOptionNew context:nil];

    if([BluetoothTech isEqualToString:@"BLE"]){
        self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBCentralManagerOptionShowPowerAlertKey: @YES}];
    }
    else if([BluetoothTech isEqualToString:@"HID"]){
        [self.bluetoothScanTextView becomeFirstResponder];
    }
    [self loadStudents];
}


- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    // Disconnect the bluetooth peripheral device if it exists
    if(self.discoveredPeripheral != nil){
        [self.centralManager cancelPeripheralConnection:self.discoveredPeripheral];
    }
    // Remove KVO for verifyingCard
    [self removeObserver:self forKeyPath:@"verifyingCard"];
}

是什么导致了错误,还有比手动调用 viewDidLoad 更好的方法来解决这个问题吗?谢谢

【问题讨论】:

  • 每 60 秒您就完全重新初始化数据库?这是一种反模式。
  • 那我该如何更新呢?
  • 基于推送通知的增量更新是理想的选择。应避免轮询。删除所有数据并从头开始重新创建整个数据库是最坏的情况;你正在做大量的工作——消耗大量的电池寿命——只是为了重新创建你已经拥有的东西(假设数据库的内容不是每 60 秒完全改变一次)。

标签: ios objective-c xcode multithreading core-data


【解决方案1】:
  1. 核心数据不是线程安全的。您不能只从任何线程访问任何上下文。您应该将persistentStoreCoordinatorviewContext 视为只读,并且只能从主线程中读取它。对核心数据的所有更改都应通过 performBackgroundTask 并使用传递给它的上下文。
  2. 您不能只删除 core-data 下的文件,然后就指望它们能正常工作。首先,在您删除文件时,还有许多其他 managedObjectContext 读取或写入。可以将第二个核心数据设置为对将存储在单独文件中的某些实体使用外部存储。第三,iOS 对 SQLite 日志使用 WAL 模式,并且可能存在(可能很大)日志文件用于任何 Core Data 持久存储。
  3. 要更新 UI 以更改核心数据,您应该使用 NSFetchedResultsController。确保在您的核心数据设置中设置persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES
  4. 不要保留任何指向 managedObjects 的指针。它们可以在您不知情的情况下从上下文中删除,然后访问它们会导致崩溃。而是使用 fetchedResultsController - 即使只是一个对象。

删除核心数据中的所有实体:

[self.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull) {
    NSArray* entities = context.persistentStoreCoordinator.managedObjectModel.entities;
    for (NSEntityDescription* entity in entities) {
        NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entity.name];
        request.predicate = [NSPredicate predicateWithValue:YES];
        request.returnsObjectsAsFaults = YES;
        NSArray* result = [context executeFetchRequest:request error:NULL];
        for (NSManagedObject* i in result) {
            [context deleteObject:i];
        }
    }
    [context save:NULL];
}];

【讨论】:

  • 我在实现 3 时遇到了麻烦,有什么可以遵循的吗?我会在同一个函数中使用这个新代码进行更新吗?
  • 根据您的代码,我不清楚您要监控哪些对象的更改,但您应该发出 NSFetchedResultsController 将监控的 fetchRequest。将您的视图控制器设置为委托,然后至少实现controllerDidChangeContent: 并重新加载所有内容。一旦你开始工作,你应该实现其他委托方法并只更新需要更新的视图部分。
  • 问题是我并没有真正更新表格或任何东西,而是实际上是一个包含学生的字典,这似乎返回 null 直到我改变屏幕。我试图手动加载 viewWillAppear 来复制它,但没有运气。我也试过 [self.view setNeedsDisplay];但这也不起作用。有没有我可以调用的函数来做这样的事情?
  • 我可以在没有persistentContainer的情况下调用上面的delete函数吗?我的数据库管理器中没有该设置。
  • 简短回答:否。如果两个上下文同时尝试修改存储,core-data 不知道要使用哪个更改。设置mergePolicy 可以说明商店应该做什么,但那样会丢失信息。通过performBackgroundTask(内部有一个队列)对核心数据进行所有更改(包括删除)可以解决这个问题。另一种解决方案是仅使用 viewContext 而不是其他背景上下文。这是一个糟糕的解决方案,因为所有的读写都在主线程上,但对于难以处理多个上下文的初学者来说,它更简单。
【解决方案2】:

您永远不应该自己调用viewDidLoadviewWillAppear 等方法。只有当您覆盖这些方法时,您才应该在 super 上调用它们。

提取您正在运行以更新的代码(看起来您已经在 loadStudents 中拥有)并调用该方法而不是 viewWillAppear

https://developer.apple.com/documentation/uikit/uiviewcontroller

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    相关资源
    最近更新 更多