使用 Core Data 和 Ensembles (iCloud) 删除近似重复项
Removing near duplicates with Core Data and Ensembles (iCloud)
总结
我的问题是我想在我的基于核心数据的 iOS 项目中删除几乎重复的项目,该项目使用 Ensembles 与 iCloud 同步。
- 与 iCloud 的同步在我的应用程序中基本上运行良好。
- 问题是当用户在他的持久存储被 Ensembles(连接到 iCloud)吸取之前在多个设备上创建相似的对象时。
- 这会产生近乎重复的结果,这实际上是正确的。
- 我删除这些重复项的方法似乎不起作用。
详细问题
用户可以在连接到 iCloud 之前在不同的设备上创建 NSManagedObjects
。假设他有一个名为 Car
的 NSManagedObject
,它与一个名为 Person
的 NSManagedObject
有 "To One" 关系,在 return 中有一个 [=159] =] 与 Car
的关系。这看起来像这样:
好吧,假设用户有两个设备,他在每个设备上创建了两个 NSManagedObjects
。一个名为 "Audi" 的 Car
和一个名为 "Raphael" 的 Person
。两者通过关系联系在一起。在另一台设备上,他创建了一个名为 "BMW" 的 Car
和另一个名为 "Raphael" 的 Person
。也是相互联系的。现在用户在每个设备上都有两个相似的对象:两个 Person
对象都命名为 "Raphael."
我的问题是,用户在同步后最终会在每台设备上拥有两个名为 "Raphael" 的 Person
对象。
这实际上是正确的,因为当用户获取他的持久性存储时,对象会获得它们的唯一标识符(以标识 Ensembles 中的对象)。对象实际上是不同的。但这是我想要解决的问题。
我的做法
我实现了这个委托方法并删除了 reparationContext 中的重复项。
- (BOOL)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble
shouldSaveMergedChangesInManagedObjectContext:(NSManagedObjectContext*)savingContext
reparationManagedObjectContext(NSManagedObjectContext *)reparationContext {
[reparationContext performBlockAndWait:^{
// Find duplicates
// Change relationships and only use the inserted Person object (the one from iCloud)
// Delete local Person object
[reparationContext save:nil];
}
return YES;
}
基本上这似乎在合并来自第一台设备的数据的第二台设备上运行良好。但不幸的是,即使在reparationContext中删除了,本地人似乎仍然同步到iCloud。
这会导致中断状态,因为第一台设备随后还会合并来自第二台设备的更改,并再次替换已在第二台设备上删除的人员。一些同步之后,这个人终于在汽车关系中失踪,应用程序抛出同步错误。
重现问题的步骤
步骤 1(设备 1)
- 创建对象
- 数据:汽车"Audi" -> 人"Raphael (Device 1)"
步骤 2(设备 2)
- 创建对象
- 数据:汽车"BMW" -> 人"Raphael (Device 2)"
步骤 3(设备 1)
- 从商店中提取数据
- 连接到 iCloud
- 将数据发送到 iCloud
- 数据:汽车"Audi" -> 人"Raphael (Device 1)"
步骤 4(设备 2)
- 从商店中提取数据
- 连接到 iCloud
- 从 iCloud 合并数据
- 将设备 2 中的本地人替换为设备 1 中插入的人
- 从设备 2 中删除本地人
- 将数据发送到 iCloud
- 数据:
汽车 "Audi" -> 人 "Raphael (Device 1)"
汽车 "BMW" -> 人 "Raphael (Device 1)"
步骤 5(设备 1)
- 从 iCloud 合并数据
- 用设备 2 的插入人员替换设备 1 的本地人员(这不应该发生)
- 从设备 1 中删除本地人(这不应该发生)
- 将数据发送到 iCloud
- 预期数据:
汽车 "Audi" -> 人 "Raphael (Device 1)"
汽车 "BMW" -> 人 "Raphael (Device 1)"
- 实际数据:
汽车 "Audi" -> 人 "Raphael (Device 2)"
汽车 "BMW" -> 人 "Raphael (Device 2)"
实际上本地人对象 "Raphael (Device 2)" 在第 4 步中被删除了,但它似乎仍被发送到 iCloud,因为在第 5 步中它作为 savingContext.insertedObjects
中的插入从 shouldSaveMergedChangesInManagedObjectContext
委托方法。
据我了解,Ensembles 首先从 iCloud 中提取更改,通过委托方法询问用户是否一切都符合预期,然后合并到持久存储中并在合并后将增量发送到 iCloud。
我是不是做错了什么?或者这是 Ensembles 的错误?
我认为您的 "reparationContext" 处理程序有问题,您删除了本地对象并保留了远程对象。另一台设备会做同样的事情,但在这边反之亦然,然后删除错误的对象。修复方法必须是确定性的。所以也许你可以通过 uniqueID 或其他东西对两个人进行排序,并始终删除第一个。然后所有设备都会做同样的事情,应该没有 ping-pong 同步恢复已删除的数据。
有拉斯提到的问题。你必须小心,始终确定性地做事。按唯一 ID 排序是一种方法。
就我个人而言,我会选择其他两种方式之一来处理此问题:
- 合并完成后进行重复数据删除(再次确保它是确定性的)
- 使用精心挑选的全局标识符为您控制重复数据删除。
例如,您可以使用唯一 ID Raphael
。您唯一需要注意的是,当您在同一台机器上创建另一个 Raphael 时,它被称为 Raphael_1
(或其他)。
如果您的唯一 ID 很可能是唯一的(例如名字 + 姓氏不太可能冲突),Ensembles 会自动合并不同设备上的人。
总结
我的问题是我想在我的基于核心数据的 iOS 项目中删除几乎重复的项目,该项目使用 Ensembles 与 iCloud 同步。
- 与 iCloud 的同步在我的应用程序中基本上运行良好。
- 问题是当用户在他的持久存储被 Ensembles(连接到 iCloud)吸取之前在多个设备上创建相似的对象时。
- 这会产生近乎重复的结果,这实际上是正确的。
- 我删除这些重复项的方法似乎不起作用。
详细问题
用户可以在连接到 iCloud 之前在不同的设备上创建 NSManagedObjects
。假设他有一个名为 Car
的 NSManagedObject
,它与一个名为 Person
的 NSManagedObject
有 "To One" 关系,在 return 中有一个 [=159] =] 与 Car
的关系。这看起来像这样:
好吧,假设用户有两个设备,他在每个设备上创建了两个 NSManagedObjects
。一个名为 "Audi" 的 Car
和一个名为 "Raphael" 的 Person
。两者通过关系联系在一起。在另一台设备上,他创建了一个名为 "BMW" 的 Car
和另一个名为 "Raphael" 的 Person
。也是相互联系的。现在用户在每个设备上都有两个相似的对象:两个 Person
对象都命名为 "Raphael."
我的问题是,用户在同步后最终会在每台设备上拥有两个名为 "Raphael" 的 Person
对象。
这实际上是正确的,因为当用户获取他的持久性存储时,对象会获得它们的唯一标识符(以标识 Ensembles 中的对象)。对象实际上是不同的。但这是我想要解决的问题。
我的做法
我实现了这个委托方法并删除了 reparationContext 中的重复项。
- (BOOL)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble
shouldSaveMergedChangesInManagedObjectContext:(NSManagedObjectContext*)savingContext
reparationManagedObjectContext(NSManagedObjectContext *)reparationContext {
[reparationContext performBlockAndWait:^{
// Find duplicates
// Change relationships and only use the inserted Person object (the one from iCloud)
// Delete local Person object
[reparationContext save:nil];
}
return YES;
}
基本上这似乎在合并来自第一台设备的数据的第二台设备上运行良好。但不幸的是,即使在reparationContext中删除了,本地人似乎仍然同步到iCloud。
这会导致中断状态,因为第一台设备随后还会合并来自第二台设备的更改,并再次替换已在第二台设备上删除的人员。一些同步之后,这个人终于在汽车关系中失踪,应用程序抛出同步错误。
重现问题的步骤
步骤 1(设备 1)
- 创建对象
- 数据:汽车"Audi" -> 人"Raphael (Device 1)"
步骤 2(设备 2)
- 创建对象
- 数据:汽车"BMW" -> 人"Raphael (Device 2)"
步骤 3(设备 1)
- 从商店中提取数据
- 连接到 iCloud
- 将数据发送到 iCloud
- 数据:汽车"Audi" -> 人"Raphael (Device 1)"
步骤 4(设备 2)
- 从商店中提取数据
- 连接到 iCloud
- 从 iCloud 合并数据
- 将设备 2 中的本地人替换为设备 1 中插入的人
- 从设备 2 中删除本地人
- 将数据发送到 iCloud
- 数据:
汽车 "Audi" -> 人 "Raphael (Device 1)"
汽车 "BMW" -> 人 "Raphael (Device 1)"
步骤 5(设备 1)
- 从 iCloud 合并数据
- 用设备 2 的插入人员替换设备 1 的本地人员(这不应该发生)
- 从设备 1 中删除本地人(这不应该发生)
- 将数据发送到 iCloud
- 预期数据:
汽车 "Audi" -> 人 "Raphael (Device 1)"
汽车 "BMW" -> 人 "Raphael (Device 1)" - 实际数据:
汽车 "Audi" -> 人 "Raphael (Device 2)"
汽车 "BMW" -> 人 "Raphael (Device 2)"
实际上本地人对象 "Raphael (Device 2)" 在第 4 步中被删除了,但它似乎仍被发送到 iCloud,因为在第 5 步中它作为 savingContext.insertedObjects
中的插入从 shouldSaveMergedChangesInManagedObjectContext
委托方法。
据我了解,Ensembles 首先从 iCloud 中提取更改,通过委托方法询问用户是否一切都符合预期,然后合并到持久存储中并在合并后将增量发送到 iCloud。
我是不是做错了什么?或者这是 Ensembles 的错误?
我认为您的 "reparationContext" 处理程序有问题,您删除了本地对象并保留了远程对象。另一台设备会做同样的事情,但在这边反之亦然,然后删除错误的对象。修复方法必须是确定性的。所以也许你可以通过 uniqueID 或其他东西对两个人进行排序,并始终删除第一个。然后所有设备都会做同样的事情,应该没有 ping-pong 同步恢复已删除的数据。
有拉斯提到的问题。你必须小心,始终确定性地做事。按唯一 ID 排序是一种方法。
就我个人而言,我会选择其他两种方式之一来处理此问题:
- 合并完成后进行重复数据删除(再次确保它是确定性的)
- 使用精心挑选的全局标识符为您控制重复数据删除。
例如,您可以使用唯一 ID Raphael
。您唯一需要注意的是,当您在同一台机器上创建另一个 Raphael 时,它被称为 Raphael_1
(或其他)。
如果您的唯一 ID 很可能是唯一的(例如名字 + 姓氏不太可能冲突),Ensembles 会自动合并不同设备上的人。