在app delegate中添加持久存储(启用iCloud)时崩溃

时间:2011-11-05 17:04:42

标签: iphone core-data ios5 icloud

我将开始更新此内容,以帮助那些寻求将其用作个人代码参考的人。

最新更新

  • 我很确定我已经找到了一种方法,一旦他们停止相互通话,就可以将设备重新同步。我要更新我的 回答下面的所有细节。我完全希望你们都能找到 这很有帮助。这需要将近2个月的试验和错误 这一个。所以请参考并与其他人分享 有类似的问题让设备再次与每个人交谈 其他通过iCloud。我花了很长时间才想到这一切,所以我 我很乐意尽可能多地保存其他开发人员 必须创建自己的make-shift修复程序。

帮助正确设置的另一个补充

  • 我发现在更新了与iCloud数据相关联的应用后 使用该帐户可能会因打开iCloud而导致崩溃 数据将尝试立即合并到设备中(其中 设备尚未设置其持久存储)。我现在补充说 @property (nonatomic, readwrite) BOOL unlocked;AppDelegate.h@synthesize unlocked;AppDelegate.m。然后我将- (NSPersistentStoreCoordinator *)persistentStoreCoordinator方法更改为 以及我的- (void)mergeChangesFrom_iCloud方法,两者都有 将在下面显示(在持久存储设置的中间和 在iCloud合并方法的底部)。从本质上讲,我在说 应用程序,以防止iCloud合并数据,直到应用程序设置 它的持久存储。否则,您将看到应用程序崩溃 不可读的错误。

以下是我设置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>是实际值。我只是为了共享这段代码而屏蔽它们。

我知道有些人仍然遇到麻烦,并且可能正在使用这个问题作为如何设置他们自己的支持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时,我偶尔会收到相关错误 到持久性商店。我将继续更新此内容 我通过实验发现它的新信息,但到目前为止 我提供的解决方案是我可以让应用程序正常工作的唯一方法 再一次(不幸的是,jlstrecker的解决方案并没有起作用 我)一旦我开始看到错误,这是以下内容:

      

    -NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error ::   CoreData:Ubiquity:尝试读取ubiquity根URL时出错:   文件://localhost/private/var/mobile/Library/Mobile%20Documents/./data/。   错误:错误Domain = LibrarianErrorDomain Code = 1&#34;操作   无法完成。 (LibrarianErrorDomain错误1 - 无法执行   启动项目下载。)&#34;的UserInfo = 0x176000   {NSURL =文件://localhost/private/var/mobile/Library/Mobile%20Documents/./data/,   NSDescription =无法启动项目下载。}

    对于我的生活,我无法弄清楚为什么我会看到这一切 突然或如何让它停止。我已从两者中删除了该应用 设备,删除之前同步的iCloud数据 它们,并删除了有关应用程序的备份中的任何数据。我有 重新启动Xcode,重新启动两个设备,清理Xcode项目, 但没有任何东西可以阻止错误出现。我从未见过 之前的这个错误,并没有运气在网上找到任何东西 如何把它固定下来。

    应用程序崩溃了:

    if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
    
        NSLog(@"bad things %@ %@", error, [error userInfo]);
        abort();
    
    }
    

    日志永远不会被击中,也不会被中止。我只看到上面的错误 并且应用程序本身变得反应迟钝。如果有人能指点我 在正确的方向,我将非常感激。

以前的问题/问题

  • 即使在从测试版到更新版之后,这似乎仍在继续 公开发布5.0.1。最后一次发生在我身上的事情发生了 更改我的托管上下文数据模型。考虑到我没有 发布了应用程序,我没有打扰合并新版本的 模型。我刚刚删除并重新安装了我的设备上的应用程序,但是 然后它拒绝配合存储在iCloud中的数据 容器,我的意思是我收到了商店的错误 无法下载项目。我想这是由于数据冲突造成的 模型类型,这是完美的意义。所以看来你只需要 摆脱iCloud容器内的数据而不会摆脱 容器。删除iCloud数据似乎会杀死所有内容 off,实质上是禁用容器和App ID。看起来好像 更简单,我尝试按照jlstrecker的建议创建一个新容器, 但不幸的是,这根本没有帮助。所以我必须再次这样做 完成我在答案中概述的步骤,再次完成了 特技。但考虑到创建新App有多烦人 每次ID和更新配置文件,我认为最好 更新我所学到的可能缩小原因并得到的东西 更快的解决方案。

    通过iCloud&gt;存储&amp;备份&gt;然后管理存储 删除应用程序似乎是清空应用程序的最佳解决方案 数据,但这样做似乎腐蚀了容器,导致了 上面的错误。成功完成后,无论多少 我删除应用程序并将其重新安装到设备(以实现它 看起来像是第一次出现在设备上,希望如此 重新创建容器),我永远无法让应用程序显示在 文件&amp;数据列表再次。如果这意味着,这有点令人担忧 任何从他们的iCloud删除数据的人都意味着这一点 iCloud将不再适用于该应用程序。我只用了一个 到目前为止,应用程序上的开发配置文件,所以也许使用 分布情况可能会有所不同,但我必须这样做 在说出任何肯定的事之前先测试一下。

我希望这些新的更新可以帮助任何可能在设置商店时遇到问题的人。到目前为止,它一直对我很有用。如果我找到更好的修复方法,或者只是让这个过程变得更加无缝的任何东西,我一定会更新更新。

5 个答案:

答案 0 :(得分:13)

更新了重新同步设备的答案 几个月的修补让我弄清楚(我相信)根深蒂固的问题是什么。问题是让设备在失去同步后再次互相交谈。我不能确定是什么导致这种情况,但我怀疑是事务日志已损坏,或者(更有可能)重新创建日志容器。这就像设备A发布更改到容器A和设备B做同样的事情而不是发布到容器C,他们可以读取/写入日志。

现在我们知道了这个问题,这是一个创建解决方案的问题。更多的修补让我得到了以下内容。我有一个名为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];
}

在这段代码中,我有两条不同的路径。一种是用于不同步且需要从源设备导入数据的设备。所有这些路径都清楚了内存,以便为应该在其中的数据做好准备。

另一个(isSource = YES)路径做了很多事情。通常,它会删除损坏的容器。然后它创建一个新容器(让日志有一个驻留的位置)。最后,它搜索父实体并复制它们。这样做是使用应该存在的信息重新填充事务日志容器。然后,您需要删除原始实体,以便您没有重复项。最后,重置持久性存储以“刷新”应用程序的核心数据并更新所有视图并fetchedResultsControllers

我可以证明这很有效。我已经清除了几个月来没有与主设备(数据所在的设备)通信的设备(isSource = NO)的数据。然后,我从主设备推送数据并愉快地观看,因为我的所有数据都在几秒钟内出现。

同样,请随意引用并分享给与iCloud同步的任何人。

回答原始问题,iOS 5.1问世后不再受影响,修复了在设置中移除应用的iCloud存储空间后的崩溃

经过多长时间尝试任何事情和所有事情才能解决这个问题,我尝试创建一个新的App ID,更新应用程序的相关配置文件,在iCloud容器字段周围更改以匹配新配置文件,一切都再次运行。我仍然不知道为什么会发生这种情况,但似乎与该应用程序ID相关联的iCloud存储已损坏?

所以,如果这种情况发生在其他任何人身上,那么请按照以下步骤操作,你应该做得很好:

  1. 在Provisioning Portal中创建一个新的App ID。
  2. 查找与该应用相关联的配置文件。单击编辑 - &gt;修改,然后将应用程序ID更改为刚刚创建的应用程序ID。
  3. 提交更改,然后将Xcode中的现有配置文件替换为您刚刚创建的配置文件。
  4. 更改<bundleIdentifier>的所有实例以适应新的应用ID(这些将位于您的主应用摘要页面,iCloud容器和iCloud键值存储的权利以及您正在创建的AppDelegate文件中)持久存储,就像我上面的代码一样。)
  5. 重启Xcode,因为您更改了有关配置文件的信息(否则会抱怨并拒绝在设备上运行)。
  6. 确保新配置文件位于您要安装应用程序的设备上,然后构建并运行。在这一点上,一切都应该正常工作。

答案 1 :(得分:7)

另一个澄清:我在iOS 6.0.1和6.1 beta 2上测试设备时发生了类似的情况。

如@Slev所述,它在iOS 5.1中并未完全修复。一个设备在尝试访问iCloud上的持久存储时会完全冻结大约80秒,但实际上永远不会访问存储在那里的信息。

我认为这是由于设备操作系统中的日志文件已损坏。删除设备上的应用程序或iCloud数据无法解决冻结/无法访问iCloud存储的问题。

我发现的一个修复是reset all settings(擦除所有内容也可以)。 settings->general->reset.

只有这样我才能再次使用我的应用程序在iCloud上访问该数据。我希望这有助于其他任何人来到这里寻找一个非常令人沮丧的bug的解决方案。

答案 2 :(得分:3)

我在5.0.1 beta 1和5.0.0上使用一个设备时遇到此错误。

我通过更改iCloud容器的名称来消除错误。在iCloud容器列表中,第一个必须与您的应用程序ID匹配,但您可以添加具有不同名称的其他容器。

(就像slev改变应用ID的解决方案一样,如果应用还没有发布,这只是一个很好的解决方案。)

答案 3 :(得分:0)

<强>更新

每个人都应该真正看一下来自WWDC 2012的iCloud核心数据会话227.他们提供的源代码是基于iCloud的解决方案的优秀起点。真的花时间去完成他们正在做的事情。您需要填写一些漏洞,例如将对象从一个商店复制到另一个商店并进行重复数据删除。话虽这么说,我不再使用如下所述的migratePersistentStore方法在本地和iCloud商店之间移动。


我原来的回答:

Slev让我发布一些代码,用于将商店从本地副本迁移到iCloud,然后再返回。 此代码是实验性的,不应在生产中使用。 此处仅提供我们分享和推进的参考。一旦Apple发布了适当的参考应用程序,您可能应该参考您的模式和实践。

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

答案 4 :(得分:0)

对不起Slev,当你说应用程序崩溃时,我遇到了同样的问题,因为iCloud数据会尝试立即合并到设备中(设备尚未设置其持久存储)但我无法理解你是如何解决这个问题的。

您的代码是:

- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
    if (self.unlocked) {
       NSManagedObjectContext *moc = [self managedObjectContext];
        [moc performBlock:^{
            [self mergeiCloudChanges:notification forContext:moc];
        }];
    }
}

我还没有尝试过,但是看看这段代码,我想知道当“unlocked”为假时到达的通知会发生什么。你会松开它们吗?

使用while循环测试“unlocked”属性并花费一些时间直到属性变为真是不是更好?

我希望你能理解我非常糟糕的英语...... :) 谢谢你 戴夫