5

我正在尝试在我的核心数据应用程序中实现 iCloud 同步。我在编程方面不是那么专业,这确实是我学到的一个高级主题......我发现了 Drew McCormack 的核心数据同步框架“Ensembles”。它似乎使 iCloud 同步更容易。

我将它集成到我的应用程序中,只要我将新对象添加到我的核心数据模型中,同步就可以很好地工作。但是当我删除一个对象时,它会创建重复项。然后从重复中复制。我最终有 3-4 次相同的条目(对象)......

这是为什么?我究竟做错了什么?我做了一些研究,我的猜测是全局标识符可以解决这个问题?

什么是全局标识符?我的猜测是它们有助于避免重复!?但是我该如何设置呢?我真的不知道,做了很多研究,但找不到答案。

感谢帮助!

更新: 感谢您的帮助!我阅读了自述文件和这本书,但由于我是初学者,所以对我来说并不是一切都清楚。

我想我现在了解在 Ensembles 中使用全局标识符,但我不知道我是否正确地使用它。

如果我理解正确,我必须为每个对象分配一个标识符。我可以通过将其存储在属性中来做到这一点。这个标识符可以是任何东西,只要它是唯一的并且是一个 NSString?

在我的应用程序中,用户可以存储不同的东西,比如姓名、文本、标题、日期等。该应用程序基于 Xcode 中的 Master-Detail-View 模板并使用 Core Data。我的核心数据模型只有一个具有一些属性的实体,大多数是字符串和 NSDate。没有关系什么的。如果用户点击“+”,则会创建一个新对象,并将用户输入的内容存储在属性中。

我添加全局标识符的方法是添加一个存储它的新属性。因此,当创建一个新对象时,我会

/// I did find that to use as identifier !?

NSString *taskUniqueStringKey = newManagedObject.objectID.URIRepresentation.absoluteString;

/// and store it in the attribute.

[newManagedObject setValue:taskUniqueStringKey forKey:@"coreDataObjectID"]; 

然后我用这个:

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects
{

return [objects valueForKeyPath:@"coreDataObjectID"];;

}

这似乎对我有用。但我做得对吗?这是分配全局标识符的正确位置吗?我没有 awakeFromInsert !?

如果这有效,我遇到了下一个问题。我的应用程序已经上线,用户在更新之前保存的旧条目将丢失全局标识符。我能做些什么呢?我想我已经得到了什么,什么是独一无二的,我唯一能想到的是一个在创建对象时保存 [NSDate date] 的属性。

我试图使用它,但我失败了,因为 Ensembles 只接受 NSString 而不是 NSDate!?我可以使用这个日期属性吗,这是否足够独特并且可以用作全局标识符?如果是的话,您能否给我一个代码示例,说明如何将其从日期转换为字符串?

与 Ensembles 同步效果很好。不再有重复项,您只需关闭 iCloud 并保留条目并再次打开它,它就会像应有的那样同步,而不会丢失本地存储的对象左右。合奏真的很酷!我看到一些小的奇怪行为,例如有时同步需要很长时间,有时它真的很快,如果我在两个不同的设备上在短时间内编辑东西,它会有点混乱,就像我刚刚删除的对象重新出现一样。但我想这很正常吗?如果我在不同设备上使用该应用程序之间需要一些时间,那么一切正常。

我是否理解正确,只有一种方法可以调用同步:

- (void)syncWithCompletion:(void(^)(void))completion
{
if (self.ensemble.isMerging) return;


if (!self.ensemble.isLeeched) {
    [self.ensemble leechPersistentStoreWithCompletion:^(NSError *error) {
        if (error) NSLog(@"Error in leech: %@", error);

        if (completion) completion();
    }];
}
else {
    [self.ensemble mergeWithCompletion:^(NSError *error) {

        if (completion) completion();
    }];
}

如果需要,您只需调用它?没有什么比之前没有偷取的合并,或者像“这是实际状态 - 像现在一样保存它”这样的方法吗?

应用程序中有您要同步的不同点。在应用程序启动和终止时将是一个好点。在我的应用程序中,我猜我应该同步两点:添加对象并将其保存到 Core Data 时,以及保存对对象的更改时。我还可以提供一个按钮,例如“立即同步”。这是一个好方法吗?我总是打电话吗?

[self syncWithCompletion:NULL];

另一个问题出现了。我可以从与 Ensembles 同步中排除对象吗?我的应用程序在第一次应用程序启动时将教程条目作为对象加载一次。如果可能的话,我不想同步它们?

非常感谢你的帮助!如果我可以帮助您进行德语本地化等任何事情,请告诉我!;)

4

2 回答 2

6

是的,这几乎可以肯定是由于没有为您的对象设置全局标识符,或者至少没有正确设置。

当你 leech 你的 ensemble 时,本地持久存储被导入到同步数据中。如果没有全局标识符,Ensembles 将为您的对象分配随机 ID,因此它可以跨设备跟踪它们。

当您窃取具有相同数据的第二个设备时,会出现重复。Ensembles 无法知道数据代表与其他设备上相同的逻辑对象,因此它再次分配随机 ID。实际上,它将每个设备上的对象视为完全独立的,因此在同步后所有对象都最终在您的数据集中。

解决方案是全局标识符。通过实现一个CDEPersistentStoreEnsemble委托方法,您可以为 Ensembles 提供全局 id,它可以使用它来识别不同设备上的哪些对象属于一起。

您应该为全局 ID 使用什么?通常,只是一个 UUID,但对于类似单例的对象,您只想选择一个 id。

您可以在awakeFromInsert. 您可以将全局 ID 存储在实体的属性中。(请注意,如果您正在迁移现有应用程序,您将需要在尝试获取存储以进行同步之前检查是否已生成全局 ID。)

更多详细信息请参见 GitHub 上的 READMEleanpub 上的书中

更新

要回答您的更新问题:

是的,标识符必须是一个字符串,并且是不可变的。分配后不应更改。

NSManagedObjectID 不是一个很好的全局标识符,因为它在不同的设备上会有所不同。我们真的想要跨设备全局的东西。

如果您从头开始,那么使用NSUUID是一个好方法。只需创建一个唯一的 id,并将其存储在对象中。

如果您有一个现有的应用程序,并且它一直在通过另一种机制进行同步,那么您需要想出一种方法来在每台设备上提供相同的全局标识符。一种方法是以某种方式混合对象属性。通常这会给你一个非常接近独特的价值,并且对于过渡来说已经足够了。

例如,您进行了一次快速获取,发现您的对象还没有全局 id。您遍历对象,并将全局 ID 设置为由 creationDate + 文本组成的字符串。(您甚至可以通过使用哈希来缩短它,但它可能并不那么重要。)在初始“迁移”到全局标识符之后,您只需将 UUID 用于任何新创建的对象。

请注意,您不必使用awakeFromInsert. 那只是一个方便的放置位置。只要在保存对象之前分配全局标识符就可以了。

从 an 获取字符串的最简单方法NSDate是调用该description方法,但另一种方法是获取doubleusingtimeIntervalSince1970并将其转换为字符串。(注意日期本身作为唯一标识符:通常一起创建的对象将具有相同的创建日期。)

您应该如何进行同步是正确的:您可以简单地调用syncWithCompletion:.

回答关于排除对象的问题:您不能排除单个对象,主要是因为当这些对象与同步对象有关系时,它可能会变得很棘手。您可以通过以下两种方式之一处理这些对象:

  1. 将它们放在单独的持久存储中,并将该存储添加到同一个持久存储协调器。
  2. 同步对象,但手动为它们提供全局 ID,以便在每个设备上对对象进行相同的处理。例如。您可以将全局 ID 设为“Sample1”、“Sample2”等。
于 2015-01-23T13:48:06.727 回答
3

为了整合德鲁的答案,我想这两个步骤如下。

1实现CDEPersistentStoreEnsemble委托方法(参见自述文件)

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble 
    globalIdentifiersForManagedObjects:(NSArray *)objects {
    return [objects valueForKeyPath:@"yourUniqueIdentifier"];
}

2生成NSManagedObject子类的唯一标识符

- (void)awakeFromInsert {
    [super awakeFromInsert];

    if (!self.yourUniqueIdentifier) {
        self.yourUniqueIdentifier = [[NSUUID UUID] UUIDString];
    }
}

您可以在其中awakeFromInsert初始化特殊的默认属性值,例如标识符。

例如,当您有父子上下文时,检查是必要的。否则,您将覆盖先前设置的标识符。请参阅为什么 awakeFromInsert 被调用两次?.

于 2015-01-23T14:05:22.833 回答