全局标识符? - iCloud + Core Data + Ensembles - 删除对象时重复

Posted

技术标签:

【中文标题】全局标识符? - iCloud + Core Data + Ensembles - 删除对象时重复【英文标题】:global identifiers? - iCloud + Core Data + Ensembles - duplicates when deleting objects 【发布时间】:2015-01-23 07:36:54 【问题描述】:

我正在尝试在我的 Core Data 应用中实现 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 同步中排除对象吗?我的应用程序在第一次应用程序启动时将教程条目作为对象加载一次。如果可以的话,我不想同步它们?

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

【问题讨论】:

我正在尝试 Ensembles,但根本无法使其同步 - 您介意分享一些设置代码吗?谢谢! 除了按照基本设置的说明进行操作之外,我真的没有做任何其他事情。集成框架,在 diddinishlaunching 中使用标识符设置集成,然后在需要时调用 syncwithcompletion。也可以使用这些 addobserver 方法。看看示例项目。这个基本项目包括你需要的一切。这很简单。 我在测试时发现的——我需要使用自定义 icloud 容器而不是默认容器来使跨设备同步工作。如果我使用默认容器,我的数据会在 icloud 中备份,但不会在不同的设备之间同步。不知道是不是必须,不太了解容器的区别,但对我来说它只适用于自定义容器。 感谢您的快速回复!还有关于自定义容器的有趣事实。您遵循的是简单同步还是惯用语? 简单同步。我只想要 icloud 而不是 Dropbox 等等......所以它有我需要的一切。 【参考方案1】:

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

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

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

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

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

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

更多详情请查看README on GitHub 和book at leanpub。

更新

回答您的更新问题:

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

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

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

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

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

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

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

您应该如何进行同步是正确的:您只需致电syncWithCompletion:

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

    将它们放在单独的持久存储中,并将该存储添加到同一个持久存储协调器中。 同步对象,但手动为它们提供全局 ID,以便在每个设备上对对象进行相同的处理。例如。您可以将全局 ID 指定为“Sample1”、“Sample2”等。

【讨论】:

一个快速的附加问题:在向实体添加属性并进行轻量级迁移后,Ensembles 是否仍在工作?我就是这样做的,我正在测试,它似乎不再同步,我认为这是因为新的核心数据模型......可能是这样吗?谢谢! 它应该适用于轻量级迁移。请注意,同步将暂停,直到所有设备都具有新模型,此时它们应该集成所有数据。每台设备都会继续捕获已保存的更改,因此应该不会丢失数据。 谢谢你的回答,和你说的一模一样……我有不同的型号,它没有同步,当我更新到相同的版本时,都再次同步了……谢谢【参考方案2】:

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

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

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

2NSManagedObject子类生成唯一标识符

- (void)awakeFromInsert 
    [super awakeFromInsert];

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

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

检查是必要的,例如,当您有父子上下文时。否则,您将覆盖先前设置的标识符。见Why is awakeFromInsert called twice?。

【讨论】:

我知道这听起来很傻,但是如果 (!self.yourUniqueIdentifier) self.yourUniqueIdentifier = [[NSUUID UUID] UUIDString]; 你会怎么写? 迅速? @JKSDEV 让 uniqueIdentifier = NSUUID().UUIDString

以上是关于全局标识符? - iCloud + Core Data + Ensembles - 删除对象时重复的主要内容,如果未能解决你的问题,请参考以下文章

处理 iCloud Core Data 错误

Core Data 和 iCloud 出错

具有不同应用标识符的 2 个应用之间的 Icloud 同步

iCloud、Core Data 和副本以及如何播种初始数据?

iOS Core Data iCloud 同步 - 可选

将 Core Data 应用程序迁移到 iCloud