我已经有几个星期了。我在许多论坛上搜索了每个重复的回复,我实现了一些正常的方法,但仍然无法正常工作。
所以为了给你一些上下文我正在研究一个配方应用程序,它从网上废弃html配方并将其存储在核心数据中,简单吧?当客户端要求添加对iCloud Sync I的支持时,虽然在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和类别,因为许多人都知道这个问题。
在阅读了几个关于如何删除它们的线程后,我使用了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,它们将被复制,并在开头用最旧的命令排序,然后迭代并将它们保存在字典中,其名称为key,所以只要找到另一个具有相同名称的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将重新合并并使用正确的实例和不同的日期值更新数据库应该是。
答案 0 :(得分:0)
我看不到你的代码有什么明显错误,但我建议使用UUID而不是日期来订购你的副本。但这不太可能与你所看到的有关。
老实说,我认为核心数据真的搞砸了。 (例如,似乎有3种菜肴类别。)
如果我尝试删除云数据文件,并且没有给它时间彻底删除所有设备中的文件,我在使用Core Data同步时遇到了此类问题。您最终会在那里使用旧的事务日志,这会触发要插入的额外对象。
Core Data也尝试自行处理所有合并。如何发生这种情况是任何人的猜测。
核心数据+ iCloud有点不寻常,因为它是唯一没有全局身份概念的同步框架之一。实际上有很好的理由让Apple没有这样做,这在这里讨论太微妙,但它确实让开发人员感到困难。合并后的重复数据删除是一个丑陋的解决方案IMO。您的商店必须先失效才能再次生效。
我更喜欢Wasabi Sync,TICDS和Ensembles等框架的方法,这些框架都有全局标识的概念,因此不需要重复数据删除。
(披露:我创立并开发了Ensembles框架)
答案 1 :(得分:0)
也避免使用此
NSMutableDictionary * uniqueMainCatDic = [NSMutableDictionary dictionary];
宁愿使用
NSMutableDictionary * uniqueMainCatDic = [[NSMutableDictionary alloc] init];
如果你总是分配可变字典,我认为重复的怪异可能会消失。我花了几周的时间来弄明白这一点 - 不确定它是不是一个错误。