38

我将开始更新它以帮助那些寻求将其用作自己个人代码的参考的人。

最新更新

  • 我很确定我已经找到了一种方法,可以在设备停止相互通信后重新同步设备。我将在下面使用所有详细信息更新我的答案。我非常希望你们都觉得这很有帮助。花了将近 2 个月的反复试验才弄清楚这一点。因此,请参考并与其他遇到类似问题的人分享,让设备再次通过 iCloud 相互交谈。我花了很长时间才弄清楚这一切,所以我很高兴尽可能多地让其他开发人员免于创建自己的临时修复程序。

另一个有助于正确设置的补充

  • 我发现在更新具有与帐户关联的 iCloud 数据的应用程序后,打开它可能会导致崩溃,因为 iCloud 数据会尝试立即合并到设备中(设备尚未设置其持久存储)。我现在添加了 @property (nonatomic, readwrite) BOOL unlocked;toAppDelegate.h@synthesize unlocked;to 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 的 Core Data 应用程序的参考,因此我想在更改我的个人代码时更新它,以确保你们所有人都可以使用适合我的代码。在此更新中,我将初始 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 时出错:file://localhost/private/var/mobile/Library/Mobile%20Documents/./data/。错误:Error Domain=LibrarianErrorDomain Code=1“操作无法完成。(LibrarianErrorDomain 错误 1 ​​- 无法启动项目下载。)”UserInfo=0x176000 {NSURL=file://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();
    
    }
    

    日志永远不会被命中,也不会中止。我只是看到上面的错误,应用程序本身变得无响应。如果有人能帮助我指出正确的方向,我将不胜感激。

以前的问题/问题

  • 即使在从 beta 版更新到 5.0.1 的公开版本之后,这种情况似乎仍在继续。上一次发生在我身上是在更改了我的托管上下文数据模型之后。考虑到我还没有发布应用程序,我没有费心合并模型的新版本。我刚刚在我的设备上删除并重新安装了该应用程序,但它拒绝与存储在 iCloud 容器中的数据合作,我的意思是我收到了一个错误,即商店无法下载项目。我想这是由于数据模型类型冲突造成的,这很有意义。所以看起来你只需要摆脱 iCloud 容器中的数据而不摆脱容器。删除 iCloud 数据似乎会扼杀一切,实质上是禁用容器和 App ID。因为看起来比较简单,我尝试按照 jlstrecker 的建议创建一个新容器,但不幸的是,这根本没有帮助。所以再一次,我必须完成我在答案中概述的步骤,这又成功了。但是考虑到每次都必须创建新的 App ID 和更新配置文件是多么烦人,我认为最好更新我所学到的知识,以缩小原因并找到更快的解决方案。

    通过 iCloud > Storage & Backup > Manage Storage,然后删除应用程序似乎是清空数据的最佳解决方案,但这样做似乎会损坏容器,导致上述错误。成功完成此操作后,无论我删除应用程序并将其重新安装到设备多少次(使其看起来像是第一次出现在设备上并希望重新创建容器),我永远无法让应用程序显示再次在文档和数据列表中。这有点令人担忧,这是否意味着任何从 iCloud 中删除数据的人都意味着 iCloud 将不再适用于该应用程序。到目前为止,我只在应用程序上使用开发配置文件,因此使用分发配置文件可能会有所不同,但我必须先测试一下,然后才能确定。

我希望这些新的更新可以帮助任何在设置商店时遇到困难的人。到目前为止,它对我来说效果很好。如果我找到更好的修复或任何使过程更加流畅的东西,我一定会更新更多。

4

5 回答 5

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 容器字段以匹配新的配置文件,一切都恢复了。我仍然不知道为什么会发生这种情况,但似乎与该 App ID 关联的 iCloud 存储已损坏?

所以底线是如果这发生在其他人身上,请按照以下步骤操作,你应该会很好:

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

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

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

我相信这是由于设备操作系统中的日志文件损坏所致。删除设备上的应用程序或 iCloud 数据并不能解决冻结/无法访问 iCloud 商店的问题。

我发现的一个修复方法是reset all settings(也可以删除所有内容)在设备上。settings->general->reset.

只有这样我才能再次使用该设备上的应用程序访问 iCloud 上的数据。我希望这可以帮助其他任何来到这里寻找解决一个非常令人沮丧的错误的方法的人。

于 2012-11-19T23:02:44.943 回答
3

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

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

(和 slev 的修改 app ID 的方案一样,只有在 app 还没有发布的情况下,这才是一个不错的方案。)

于 2011-11-10T01:40:04.310 回答
0

更新:

每个人都应该真正看看来自 WWDC 2012 的 iCloud Core Data session 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];
    }
}
于 2012-02-19T21:14:07.857 回答
0

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

你的代码是:

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

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

有一个while循环来测试“解锁”属性并花费一些时间跨度直到该属性变为真,这不是更好吗?

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

于 2012-05-14T11:28:04.730 回答