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

Posted

技术标签:

【中文标题】iCloud 同步 - 核心数据重复条目(绝望的帮助)【英文标题】:iCloud Sync - Core Data Duplicate Entries (Desperate Help) 【发布时间】: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/… 【参考方案1】:

我看不出您的代码有任何明显错误,但我建议您使用 UUID 而不是日期来订购您的重复代码。但这不太可能与您所看到的有关。

老实说,在我看来,Core Data 确实把事情搞砸了。 (例如,似乎也有 3 个美食类别。)

如果我尝试删除云数据文件,并且没有给它时间从所有设备上彻底删除文件,我在使用 Core Data 同步时遇到了此类问题。您最终会在那里获得旧的事务日志,这会触发插入额外的对象。

Core Data 还尝试自行处理所有合并。这是如何发生的,谁也说不准。

Core Data + iCloud 有点不寻常,因为它是唯一没有全局身份概念的同步框架之一。实际上,Apple 不这样做是有充分的理由的,这些理由太微妙了,无法在这里讨论,但这确实让开发人员感到困难。合并后重复数据删除是 IMO 一个丑陋的解决方案。您的商店必须先失效,然后才能再次生效。

我更喜欢 Wasabi Sync、TICDS 和 Ensembles 等框架的方法,它们都有全局身份的概念,因此不需要重复数据删除。

(披露:我创建并开发了 Ensembles 框架)

【讨论】:

感谢 Drew 的见解。事实上,Apple 必须在 iCloud+CoreData Sync 上继续努力,这很奇怪,而且会做出意想不到的事情。毕竟,我终于设法解决了我的问题,听起来我得到了重复的实例,这很疯狂!这就是日期是一样的。我刚刚添加了一个 if 来检查两个日期是否相同,然后不要删除它,因此下次打开应用时,Core Data 将使用正确的实例和不同的日期值更新数据库。【参考方案2】:

也要避免使用这个

NSMutableDictionary * uniqueMainCatDic = [NSMutableDictionary dictionary];

相当使用

NSMutableDictionary * uniqueMainCatDic = [[NSMutableDictionary alloc] init];

我认为如果你总是分配可变字典,你的重复奇怪可能会消失。我花了几个星期才弄明白 - 不确定它是否是一个错误。

【讨论】:

嘿,邓肯,这确实很奇怪。鉴于现在所有项目都启用了 ARC,这两种方法都应该返回一个保留对象

以上是关于iCloud 同步 - 核心数据重复条目(绝望的帮助)的主要内容,如果未能解决你的问题,请参考以下文章

Swift - iCloud 核心数据在同步之前重复之前添加的数据

核心数据 + iCloud 同步 NSPersistentStoreDidImportUbiquitousContentChangesNotification

将 PersistentStoreCoordinator 从本地迁移到 iCloud 时数据重复

如何强制 iCloud 与核心数据进行同步?

使用 iCloud 进行核心数据同步

如何阻止核心数据同步到 iCloud