iCloud Sync - 核心数据重复条目(绝望的帮助)

时间:2014-02-06 17:24:34

标签: ios core-data ios7 icloud duplicate-removal

我已经有几个星期了。我在许多论坛上搜索了每个重复的回复,我实现了一些正常的方法,但仍然无法正常工作。

所以为了给你一些上下文我正在研究一个配方应用程序,它从网上废弃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属性,请告诉我因为我已经尝试了很多没有运气,应该有一个更好的方法。

提前致谢!

  1. 更新:
  2. 最后我终于设法解决了我的问题,因为听起来我从iCloud获得了重复的实例!这就是日期是一样的。我刚刚添加了一个if来检查两个日期是否相同然后不删除MainCategory,所以下次打开你的应用时Core Data将重新合并并使用正确的实例和不同的日期值更新数据库应该是。

2 个答案:

答案 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];

如果你总是分配可变字典,我认为重复的怪异可能会消失。我花了几周的时间来弄明白这一点 - 不确定它是不是一个错误。