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



我正在尝试在我的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 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"];
}

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

谢谢
  1. 核心数据不是线程安全的。 您不能只从任何线程访问任何上下文。 您应该将persistentStoreCoordinatorviewContext视为只读,并且仅从主线程读取它。 对核心数据的所有更改都应经过performBackgroundTask并使用传递给它的上下文。
  2. 你不能只是删除核心数据下的文件,然后期望东西能工作。 首先,在删除文件时,有许多其他托管对象上下文读取或写入。可以将第二个核心数据设置为对某些将存储在单独文件中的实体使用外部存储。 第三,iOS 使用 WAL 模式进行 SQLite 日记,并且可能存在(可能很大的)日志文件用于任何核心数据持久存储。
  3. 要更新核心数据更改的UI,您应该使用NSFetchedResultsController。 确保在核心数据设置中设置persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES
  4. 不要保留任何指向托管对象的指针。 可以在您不知情的情况下从上下文中删除它们,然后访问它们将导致崩溃。 而是使用 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];
}];

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

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

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

最新更新