【发布时间】:2014-02-06 17:24:34
【问题描述】:
我已经有这个错误好几个星期了。我在许多论坛上搜索了每个关于重复的回复,我实施了一些正常的方法,但仍然无法正常工作。
所以为了给你一些背景信息,我正在开发一个食谱应用程序,它从网络上抓取 html 食谱并将其存储在核心数据中,很简单吧?好吧,当客户要求添加对 iCloud 同步的支持时,我认为这很容易,特别是在 iOS 7 上工作,它可以为您解决大部分问题。
当应用程序在应用程序中填充初始数据时,就会出现问题。我有两个相关实体,分别称为 MainCategory[e1] 和 Category[e2],它们之间存在一对多关系(e1 >> e2)。
应用程序首先启动它将创建 5 个主要类别,并且对于每个主要类别它将添加 5 个类别
+ (BOOL)initialLoad
{
DLog(@"Initial Load");
//Create main and sub categories to database
NSDictionary * categoriesDic = @{
CAT_MEAL_TYPE: @[C_STARTER,C_MAINS,C_DESSERT,C_SOUPS,C_SALAD],
CAT_INGREDIENT: @[C_BEEF,C_CHICKEN,C_PASTA,C_SALMON,C_CHOCOLATE],
CAT_CUISINE : @[C_CHINESE,C_FRENCH,C_INDIAN,C_ITALIAN,C_MOROCCAN],
CAT_SEASON : @[C_CHRISTMAS,C_SUNDAY_ROAST,C_DINNER,C_BBQ,C_NIBBLES],
CAT_DIET : @[C_WHEATFREE,C_VEGETARIAN,C_LOW_FAT,C_LOW_GI,C_DAIRY_FREE]
};
NSArray * mainCategoryKeys = @[CAT_MEAL_TYPE,CAT_INGREDIENT,CAT_CUISINE,CAT_SEASON,CAT_DIET];
for(NSString * eachMainCategoryName in mainCategoryKeys)
{
//Create Main category
MainCategory * eachMainCategory = [MainCategory mainCategoryWithName:eachMainCategoryName];
NSArray * subCategories = [categoriesDic objectForKey:eachMainCategoryName];
//Create Sub categories and adds them to main category
for(NSString * eachCategoryName in subCategories)
{
/*Category got renamed to zCategory given it's a reserver name in the framework and
can not be used */
zCategory * eachCategory = [zCategory categoryWithName:eachCategoryName];
[eachMainCategory addCategoriesObject:eachCategory];
}
}
[((AppDelegate *)[UIApplication sharedApplication].delegate) saveContext];
return TRUE;
}`
然后在保存上下文后,所有这些初始数据将与 iCloud 中的数据库同步,到目前为止一切顺利。当在第二台设备上运行相同的 initialLoad 代码并再次同步时,问题就出现了。结果是得到了双 MainCategories 和 Categories,因为你们中的许多人都知道这个问题。
在阅读了几个关于如何删除它们的线程后,我使用了 dateCreated 方法,在该方法中,您向每个实体添加一个 NSDate 属性,因此每次创建一个实例时,它都会有一个时间戳来跟踪哪个较旧,哪个较新。然后,我只需从 NSNotificationCenter 添加一个观察者,检查 iCloud 导入通知 NSPersistentStoreCoordinatorStoresDidChangeNotification 并运行 timerCheck,5 秒后将在 mainThread 上执行一个干净的重复方法。
- (void)checkTimer{
if(self.cleanTimer)
{
[self.cleanTimer invalidate];
self.cleanTimer = nil;
}//schedule timer to clean iCloud duplicates of database
self.cleanTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(cleanDuplicates:) userInfo:nil repeats:FALSE];
}
- (void)cleanDuplicates:(NSTimer*)timer{
[self performSelectorOnMainThread:@selector(cleanCron) withObject:nil waitUntilDone:TRUE];}
每次调用 checkTimer 方法以重新启动它时,我都会使计时器无效,因为当内容被更新/插入/删除时,您通常会收到几个 NSPersistentStoreCoordinatorStoresDidChangeNotification,这样我知道它会运行在所有通知都通过之后。
btw cleanCron 只是调用一个类方法 cleanDuplicates
- (void)cleanCron
{
[CTFetchCoreData cleanDuplicates];
}`
这里是非魔法发生的地方,我得到所有的 MainCategories 这将是 10,因为它们已经被复制并在开始时用最旧的排序它们,然后迭代并将它们保存在字典中,并以它们的名称作为键,因此每当它找到另一个具有相同名称的 MainCategory 时,它就会将其删除。顺便说一句,在关系 e1>e2 中有一个级联删除规则,所以每次你删除 MainCategory 项目时,它都会删除所有相关的类别,所以应该没有问题。
+ (BOOL)cleanDuplicates
{
@synchronized(self){
//Fetch mainCategories from coreData
NSArray * mainCategories = [CTFetchCoreData fetchAllMainCategories];
// Clean duplicate Main Categories
NSMutableDictionary * uniqueMainCatDic = [NSMutableDictionary dictionary];
// Sorts the array with the oldest dateCreated one
mainCategories = [mainCategories sortedArrayUsingComparator:^NSComparisonResult(MainCategory* obj1,MainCategory * obj2) {
if(obj1.dateCreated == nil || obj2.dateCreated == nil)
{
DLog(@"ERROR Date Created");
}
return [obj1.dateCreated compare:obj2.dateCreated];
}];
// if there are more than five MainCategories it procedes the clenaup
if(mainCategories.count > 5)
{
for(MainCategory* eachMainCat in mainCategories)
{
MainCategory * originalMainCat = [uniqueMainCatDic objectForKey:eachMainCat.name];
if( originalMainCat == nil)
{
DLog(@"-> %@ = %@",eachMainCat.name, eachMainCat.dateCreated);
[uniqueMainCatDic setObject:eachMainCat forKey:eachMainCat.name];
}else{
// Clean duplicate Categories
[[self managedObjectContext] deleteObject:eachMainCat];
DLog(@"x %@ = %@",eachMainCat.name, eachMainCat.dateCreated);
}
}
DLog(@"Cleaning Main Categories");
}
}
[[AppDelegate sharedInstance] saveContext];
return TRUE;
}
事实证明,在我在第二台设备上运行它之后,我会得到这个输出:
Sesame[4145:60b] -> Cuisine = 2014-02-06 16:15:38 +0000
Sesame[4145:60b] -> Meal = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Meal = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Ingredients = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Ingredients = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Cuisine = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Cuisine = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Occasion = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] -> Diet = 2014-02-06 17:15:54 +0000
Sesame[4145:60b] x Diet = 2014-02-06 17:15:54 +0000
这意味着相同的 MainCategories 将被删除,它们具有相同的时间戳!我想知道 iCloud 如何合并信息。
如果您知道除 dateCreated 属性之外的清除重复项的更好方法,请告诉我,因为我已经尝试了很多次但没有运气,应该有更好的方法。
提前致谢!
- 更新:
我终于设法解决了我的问题,这听起来很疯狂,因为我从 iCloud 获得了重复的实例!这就是日期是一样的。我刚刚添加了一个 if 来检查两个日期是否相同,然后不要删除 MainCategory,因此下次打开应用程序时,Core Data 将重新修复合并并使用正确的实例和不同的日期值更新数据库应该是的。
【问题讨论】:
-
这不是我必须处理的事情,所以我无法为您提供解决方案,但以下链接详细讨论了您的问题和解决方案(带有代码)。该公司似乎对自己采取了类似的方法但取得了成功:cutecoder.org/programming/seeding-icloud-core-data
-
谢谢马库斯,我会检查文章并让你知道!希望苹果能妥善解决这个问题
-
嗨,我刚刚发布了一些示例应用程序,它们首先检查 iCloud 以在加载种子数据之前查看文件是否已经存在 - 它可能会为您提供一些关于如何防止您的应用程序加载类别的指示第二个设备。请记住,用户应该将两台设备都设置为使用 iCloud 才能正常工作。顺便说一句,您还需要修复您正在使用的任何可能与已删除类别相关联的关系。 ossh.com.au/design-and-technology/software-development/…
标签: ios core-data ios7 icloud duplicate-removal