King
King

Reputation: 59

ios - error when calling viewWillAppear() manually (Objective-C)

I'm trying to update core-data in the background of my ios app, I do this by first deleting the core-data and then adding it back. However, I need a certain segue to occur for some functions to run but when I try to do everything in the background these functions never run, unless I change the page and go back to it.

So I tried to fix this error by calling viewWillAppear() manually but I get the following error.

*** 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>

method in class where error occurs:

- (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 and viewDidDissapear in other class:

- (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"];
}

Whats causing the error, also is there a better way to approach this rather than manually calling viewDidLoad? thanks

Upvotes: 0

Views: 373

Answers (2)

Jon Rose
Jon Rose

Reputation: 8563

  1. Core-data is not thread safe. You can't just access any context from any thread. You should treat the viewContext of persistentStoreCoordinator as readonly and ONLY read it from the main thread. All changes to core data should go through performBackgroundTask and use the context that is passed to it.
  2. You can't just delete the files under core-data and expect stuff to work. First there many be other managedObjectContext reading or writing while you are deleting the file. Second core data can be set to use external storage for some entities that will be store in separate files. Third, iOS uses WAL mode for SQLite journalling and there may be (potentially large) journal files sitting around for any Core Data persistent store.
  3. To update the UI for core data change you should use a NSFetchedResultsController. make sure to set persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES in your core data setup.
  4. do not keep any pointers to managedObjects. They can be deleted from the context without you being aware and then accessing them will cause a crash. Instead use a fetchedResultsController - even for just one object.

To delete all entities in core data:

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

Upvotes: 3

Marcel
Marcel

Reputation: 6579

You should never call the viewDidLoad, viewWillAppear, etc methods yourself. Only when you override those methods you should call them on super.

Extract the code you are running to update (which looks like you already have in loadStudents) and call that method instead of viewWillAppear.

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

Upvotes: 2

Related Questions