在应用程序委托中添加持久性存储(启用iCloud)时发生崩溃
我将开始更新,以帮助那些寻求使用这个作为他们自己的个人代码的参考。
最新的更新
- 我相当肯定,我find了一种方法,一旦他们停止了对方的谈话,重新同步设备。 我将在下面更新我的答案和所有的细节。 我希望大家都觉得这很有帮助。 经过近两个月的摸索才能确定这一点。 所以请参考和分享与其他人有类似的问题,让设备再次通过iCloud相互交谈。 我花了很长的时间才弄明白这一切,所以我非常乐意尽可能地让更多的开发者从创build自己的make-shift修复程序中解脱出来。
另外帮助正确设置
- 我发现在更新应用程序后,与该帐户关联的iCloud数据可能会导致崩溃,因为iCloud数据将尝试立即合并到设备中(设备尚未设置其持久性存储)。 我现在增加了
@property (nonatomic, readwrite) BOOL unlocked;
到AppDelegate.h
和@synthesize unlocked;
到AppDelegate.m
。 然后,我更改了我的- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
方法以及我的- (void)mergeChangesFrom_iCloud
方法,这两个方法将在下面显示(中间为持久性存储设置,下方为iCloud合并方法)。 本质上,我告诉应用程序,以防止iCloud合并数据,直到应用程序build立其持久存储。 否则,您会看到由于无法读取的故障导致的应用程序崩溃。
以下是我如何设置我的persistentStoreCoordinator:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (__persistentStoreCoordinator != nil) { return __persistentStoreCoordinator; } // here is where you declare the persistent store is not prepared; self.unlocked = NO; NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Maintain_My_Car.sqlite"]; __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSDictionary *options = nil; NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil]; NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"]; if (coreDataCloudContent.length != 0) { // iCloud enabled; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent]; options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, @"<bundleIdentifier>.store", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, nil]; } else { // iCloud not enabled; options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; } NSError *error = nil; [psc lock]; if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { NSLog(@"bad things %@ %@", error, [error userInfo]); abort(); } [psc unlock]; // the store is now prepared and ready for iCloud to import data; self.unlocked = YES; dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"iCloud persistent store added"); [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); }); return __persistentStoreCoordinator; }
<myAppKey>
和<bundleIdentifier>
是实际的值。 我只是为了分享这个代码而掩盖它们。
我知道有些人仍然有麻烦,可能会使用这个问题作为如何build立自己的iCloud核心数据应用程序的参考,所以我想更新这个,只要我改变我的个人代码,确保你们所有人都可以使用适合我的代码。 在此更新中,我将初始cloudURL从[fileManager URLForUbiquityContainerIdentifier:@"<TeamIdentifier>.<bundleIdentifier>"]
更改为[fileManager URLForUbiquityContainerIdentifier:nil]
,确保从授权文件收集容器信息。
其他方法 _notificationArray
定义如下: @property (nonatomice, strong) NSMutableArray *notificationArray;
@synthesize notificationArray = _notificationArray;
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification { if (self.unlocked) { NSManagedObjectContext *moc = [self managedObjectContext]; if (self.notificationArray.count != 0) { for (NSNotification *note in _notificationArray) { [moc performBlock:^{ [self mergeiCloudChanges:note forContext:moc]; }]; } [_notificationArray removeAllObjects]; [moc performBlock:^{ [self mergeiCloudChanges:notification forContext:moc]; }]; } else { [moc performBlock:^{ [self mergeiCloudChanges:notification forContext:moc]; }]; } } else { if (_notificationArray == nil) { _notificationArray = [[NSMutableArray alloc] init]; } [_notificationArray addObject:notification]; } } - (void)resetStore { [self saveContext]; __persistentStoreCoordinator = nil; __managedObjectContext = nil; // reset the managedObjectContext for your program as you would in application:didFinishLaunchingWithOptions: myMainView.managedObjectContext = [self managedObjectContext]; // the example above will rebuild the MOC and PSC for you with the new parameters in mind; }
然后是mergeiCloudChanges:forContext:
方法:
- (void)mergeiCloudChanges:(NSNotification *)note forContext:(NSManagedObjectContext *)moc { // below are a few logs you can run to see what is being done and when; NSLog(@"insert %@", [[note userInfo] valueForKey:@"inserted"]); NSLog(@"delete %@", [[note userInfo] valueForKey:@"deleted"]); NSLog(@"update %@", [[note userInfo] valueForKey:@"updated"]); [moc mergeChangesFromContextDidSaveNotification:note]; NSNotification *refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self userInfo:[note userInfo]]; [[NSNotificationCenter defaultCenter] postNotification:refreshNotification]; // do any additional work here; }
最初的问题
-
在iOS 5.0.1上使用iCloud,我偶尔会遇到有关持久存储的错误。 我会继续用新的信息来更新这个信息,但是到目前为止,我提供的解决scheme是我能够使应用程序再次正常工作的唯一方式(不幸的是,jlstrecker的解决scheme不适用于我)一旦我开始看到错误,这是以下内容:
-NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error :: CoreData:Ubiquity:尝试读取无处不在的URL的错误URL:file://localhost/private/var/mobile/Library/Mobile%20Documents/./data/。 错误:错误域= LibrarianErrorDomain代码= 1“操作无法完成(LibrarianErrorDomain错误1 – 无法启动项目下载)”UserInfo = 0x176000 {NSURL = file:// localhost / private / var / mobile / Library /Mobile%20Documents/./data/,NSDescription =无法启动项目下载。}
对于我的生活,我不知道为什么我突然看到这一点,或者如何让它停止。 我已经从这两个设备删除了应用程序,删除了之前在它们之间进行同步的iCloud数据,并删除了有关应用程序的备份中的所有数据。 我重新启动了Xcode,重新启动了这两个设备,清理了Xcode项目,但没有任何东西阻止了错误的出现。 我以前从来没有见过这个错误,并没有find任何在线如何把它closures的运气。
该应用程序崩溃在这里:
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { NSLog(@"bad things %@ %@", error, [error userInfo]); abort(); }
日志不会被打中,也不会中止。 我只是看到上面的错误,应用程序本身变得没有反应。 如果有人能帮助我指出正确的方向,我会非常感激。
以前的问题/问题
-
即使从testing版更新到5.0.1的公开版本,这似乎仍然继续。 最后一次发生在我改变我的托pipe上下文数据模型后。 考虑到我还没有发布的应用程序,我没有打扰合并模型的新版本。 我刚刚删除并重新安装了我的设备上的应用程序,但它拒绝配合存储在iCloud容器中的数据,我的意思是我收到一个错误,商店无法下载项目。 我想这是由于冲突的数据模型types,这是非常有道理的。 所以看来你只需要摆脱在iCloud容器内的数据,而不会摆脱容器。 删除iCloud数据似乎杀了所有的东西,实质上禁用容器和应用程序ID。 由于看起来比较简单,所以我尝试按照jlstrecker的build议创build一个新的容器,但不幸的是,这根本没有帮助。 所以再一次,我不得不经过我在回答中列出的步骤,这又是一个窍门。 但考虑到每次创build新的应用程序ID和更新configuration文件是多么烦人,我认为最好是更新我所学到的内容,以潜在地缩小原因并获得更快的解决scheme。
通过iCloud>存储和备份>pipe理存储,然后删除应用程序似乎是清空数据的最佳解决scheme,但这样做似乎会损坏容器,导致上述错误。 成功完成后,无论我删除应用程序多less次,然后将其重新安装到设备上(使其看起来像第一次出现在设备上,并希望重新创build容器),我永远无法显示该应用程序再次在文档和数据列表中。 这有点关系,如果这意味着任何人从他们的iCloud中删除数据,就意味着iCloud不会再为该应用程序工作。 到目前为止,我只是在应用程序上使用开发概要文件,所以也许使用分发概要文件可能会有所不同,但在确定任何内容之前,我必须先进行testing。
我希望这些新的更新可以帮助任何可能在设置商店时遇到麻烦的人。 到目前为止,它一直在为我工作。 如果我find更好的修复方法,或者只是让这个过程变得更加简单的话,我一定会更新。
更新的答案重新同步您的设备几个月的修补已经让我找出什么(我相信)的根源问题是。 这个问题一直让设备在失去同步之后再次相互交谈。 我不能确定是什么原因导致这种情况,但是我怀疑是事务日志损坏,或者(更可能)日志本身的容器被重新创build。 这就像设备发布更改容器A和设备B相同,而不是发布到容器C,他们可以读/写日志。
现在我们知道了这个问题,这是一个解决scheme。 更多的修补工作引导我到以下。 我有一个名为resetiCloudSync:(BOOL)isSource
的方法resetiCloudSync:(BOOL)isSource
,这是在我原来的问题上面的方法的修改版本。
- (void)resetiCloudSync:(BOOL)isSource { NSLog(@"reset sync source %d", isSource); NSManagedObjectContext *moc = self.managedObjectContext; if (isSource) { // remove data from app's cloud account, then repopulate with copy of existing data; // find your log transaction container; NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSString *coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"store"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent]; NSError *error = nil; // remove the old log transaction container and it's logs; [[NSFileManager defaultManager] removeItemAtURL:cloudURL error:&error]; // rebuild the container to insert the "new" data into; if ([[NSFileManager defaultManager] createFileAtPath:coreDataCloudContent contents:nil attributes:nil]) { // this will differ for everyone else. here i set up an array that stores the core data objects that are to-many relationships; NSArray *keyArray = [NSArray arrayWithObjects:@"addedFields", @"mileages", @"parts", @"repairEvents", nil]; // create a request to temporarily store the objects you need to replicate; // my heirarchy starts with vehicles as parent entities with many attributes and relationships (both to-one and to-many); // as this format is a mix of just about everything, it works great for example purposes; NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Vehicle" inManagedObjectContext:moc]; [request setEntity:entity]; NSError *error = nil; NSArray *vehicles = [moc executeFetchRequest:request error:&error]; for (NSManagedObject *object in vehicles) { NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:object.entity.name inManagedObjectContext:moc]; // check regular values; for (NSString *key in object.entity.attributesByName.allKeys) { [newObject setValue:[object valueForKey:key] forKey:key]; } // check relationships; NSMutableSet *relSet = [[NSMutableSet alloc] init]; for (NSString *key in object.entity.relationshipsByName.allKeys) { [relSet removeAllObjects]; // check to see relationship exists; if ([object valueForKey:key] != nil) { // check to see if relationship is to-many; if ([keyArray containsObject:key]) { for (NSManagedObject *toManyObject in [object valueForKey:key]) { [relSet addObject:toManyObject]; } } else { [relSet addObject:[object valueForKey:key]]; } // cycle through objects; for (NSManagedObject *subObject in relSet) { NSManagedObject *newSubObject = [NSEntityDescription insertNewObjectForEntityForName:subObject.entity.name inManagedObjectContext:moc]; // check sub values; for (NSString *subKey in subObject.entity.attributesByName.allKeys) { NSLog(@"subkey %@", subKey); [newSubObject setValue:[subObject valueForKey:subKey] forKey:subKey]; } // check sub relationships; for (NSString *subRel in subObject.entity.relationshipsByName.allKeys) { NSLog(@"sub relationship %@", subRel); // set up any additional checks if necessary; [newSubObject setValue:newObject forKey:subRel]; } } } } [moc deleteObject:object]; } [self resetStore]; } } else { // here we remove all data from the current device to populate with data pushed to cloud from other device; for (NSManagedObject *object in moc.registeredObjects) { [moc deleteObject:object]; } } [[[UIAlertView alloc] initWithTitle:@"Sync has been reset" message:nil delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil] show]; }
在这个代码中,我有两条截然不同的path。 一种是不同步的设备,需要从源设备导入数据。 所有这些path都会清除内存,为应用程序中的数据做好准备。
其他( isSource = YES
)path,做了很多事情。 通常,它会删除损坏的容器。 然后它创build一个新的容器(日志有一个地方居住)。 最后,它search父实体并复制它们。 它所做的是用应该在那里的信息重新填充事务日志容器。 那么你需要删除原来的实体,所以你没有重复。 最后,重置持久化存储以“刷新”应用程序的核心数据,并更新所有视图和fetchedResultsControllers
。
我可以certificate,这是奇妙的作品。 我已经清除了几个月没有与主设备isSource = NO
设备( isSource = NO
)的数据。 然后,我从主设备中推送数据,并愉快地观看,因为我的所有数据都在几秒钟内出现。
再次,请随时参考和分享给任何和所有与iCloud同步出现问题的人。
回答原来的问题,在iOS 5.1出来后不再受影响,这会在您的设置中删除应用程序的iCloud存储后修复崩溃
在尝试了许多小时之后,我试着创build一个新的应用程序ID,更新了应用程序的关联configuration文件,围绕iCloud容器字段进行了更改,以匹配新的configuration文件,并且所有工作都重新开始。 我仍然不知道为什么发生这种情况,但似乎与该应用程序ID关联的iCloud存储被损坏?
所以底线是如果这发生在其他人身上,请按照以下步骤操作,并且您应该很好:
- 在Provisioning Portal中创build一个新的App ID。
- find与应用关联的configuration文件。 点击编辑 – >修改,然后将应用程序ID更改为您刚创build的那个。
- 提交更改,然后将Xcode中的现有configuration文件replace为刚创build的configuration文件。
- 更改
<bundleIdentifier>
所有实例以适应新的App ID(这些将位于您的主应用“摘要”页面中,iCloud Containers和iCloud Key-Value Store的权利以及您在创build持久性存储的AppDelegate文件中在我的代码上面)。 - 重新启动Xcode,因为您更改了有关configuration文件的信息(否则会投诉并拒绝在设备上运行)。
- 确保新configuration文件位于您希望安装应用程序的设备上,然后构build并运行。 一切都应该在这一点上工作得很好。
另一个解释:类似的情况发生在我testing设备在iOS 6.0.1和6.1testing版2。
它不是像@Slev所说的完全固定在iOS 5.1中。 一台设备在尝试访问iCloud上的持久性存储时会完全冻结大约80秒,但绝不会实际访问存储在那里的信息。
我相信这是由于设备操作系统中的日志文件损坏。 在设备上删除应用程序或iCloud数据并没有解决冻结/无法访问iCloud商店的问题。
我发现的一个修复是reset all settings
(擦除所有内容)。 settings->general->reset.
只有这样,我才能在该设备上再次使用我的应用访问iCloud上的数据。 我希望这有助于其他任何人来到这里寻找一个非常令人沮丧的错误的解决scheme。
在5.0.1 beta 1和5.0.0上使用一个设备时出现此错误。
我通过更改iCloud容器的名称摆脱了错误。 在“iCloud容器”列表中,第一个必须与您的应用ID匹配,但可以添加具有不同名称的其他容器。
(像slev的更改应用程序ID的解决scheme,这只是一个很好的解决scheme,如果应用程序尚未发布。)
对不起,我也遇到了同样的问题,当你说应用程序崩溃时,因为iCloud数据会立即尝试合并到设备中(设备还没有build立持久性存储),但是我可以不明白你怎么解决这个问题。
你的代码是:
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification { if (self.unlocked) { NSManagedObjectContext *moc = [self managedObjectContext]; [moc performBlock:^{ [self mergeiCloudChanges:notification forContext:moc]; }]; } }
我还没有尝试过,但看着这个代码,我想知道什么发生在“解锁”是错误的通知。 你会放松他们吗?
有一个while循环testing“解锁”属性并花费一些时间直到属性成为真的不是更好吗?
我希望你能理解我非常不好的英语… :)谢谢
戴夫
更新:
每个人都应该看看WWDC 2012的iCloud核心数据会话227。他们提供的源代码是基于iCloud的解决scheme的绝佳起点。 真的花时间去看看他们在做什么。 您需要填写一些漏洞,例如将对象从一个商店复制到另一个商店并重复。 这就是说,我不再使用migratePersistentStore
方法,如下所述在本地和iCloud商店之间移动。
我原来的答案是:
Slev要求我发布一些代码,以便将本地副本从iCloud迁移到iCloud,然后再返回。 此代码是实验性的,不应在生产中使用。 这里仅供参考,供大家分享和推广。 一旦苹果发布一个适当的参考应用程序,你应该咨询你的模式和做法。
-(void) onChangeiCloudSync { YourAppDelegate* appDelegate = (YourAppDelegate*) [[UIApplication sharedApplication] delegate]; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([iCloudUtility iCloudEnabled]) { NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"]; NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil]; NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"]; cloudURL = [NSURL fileURLWithPath:coreDataCloudContent]; // The API to turn on Core Data iCloud support here. NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@"com.yourcompany.yourapp.coredata", NSPersistentStoreUbiquitousContentNameKey, cloudURL, NSPersistentStoreUbiquitousContentURLKey, [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,nil]; NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0]; NSError* error; if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error]) { NSLog(@"Error migrating data: %@, %@", error, [error userInfo]); //abort(); } [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"] error:nil]; [appDelegate resetStore]; } else { NSURL *storeUrl = [[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp.sqlite"]; // The API to turn on Core Data iCloud support here. NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; NSPersistentStore* store = [appDelegate.persistentStoreCoordinator.persistentStores objectAtIndex:0]; NSError* error; if (![appDelegate.persistentStoreCoordinator migratePersistentStore:store toURL:storeUrl options:options withType:NSSQLiteStoreType error:&error]) { NSLog(@"Error migrating data: %@, %@", error, [error userInfo]); //abort(); } [fileManager removeItemAtURL:[[appDelegate applicationDocumentsDirectory] URLByAppendingPathComponent:@"YourApp2.sqlite"] error:nil]; [appDelegate resetStore]; } }
- 最佳实践 – 您自己的项目/应用程序的NSError域和代码
- 核心数据,当NSFetchRequest返回NSDictionaryResultType时如何获取NSManagedObject的ObjectId?
- 执行caching时,NSFetchedResultsController崩溃:使用caching时
- 核心数据:NSP预测多对多关系。 (“这里不允许的多键”)
- 具有谓词的NSFetchedResultsController忽略从不同NSManagedObjectContext合并的更改
- 我应该如何将UIImage存储在我的Core Data数据库中?
- 有没有办法实例化NSManagedObject而不插入它?