ios - 手动调用viewWillAppear()时出错(Objective-C)

时间:2017-06-14 13:26:58

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

我试图在我的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吗?感谢

2 个答案:

答案 0 :(得分:3)

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

    [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];
    }];
    

答案 1 :(得分:2)

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

解压缩您正在运行的代码进行更新(看起来您已经在loadStudents中进行了更新)并调用该方法而不是viewWillAppear

请参阅https://developer.apple.com/documentation/uikit/uiviewcontroller