条目更新但未保存时,核心数据会重复

时间:2013-08-05 14:16:46

标签: objective-c cocoa-touch core-data uimanageddocument

我正在尝试完成Assignment 6 from course CS193P of Paul Hegarty。总之,这是一个用于浏览从Flickr下载的照片的iOS应用程序。该应用程序有两个选项卡:

  • 第一个标签用于按标签浏览照片。单击标记时,将列出具有此标记集的照片。单击照片时,将显示该照片。所有这些都是通过嵌入在导航控制器中的表视图实现的,最后是用于显示的UIScrollView。
  • 第二个标签用于通过表视图浏览最近查看过的照片。

照片和标签信息存储在Core Data中。此数据通过NSFetchedResultsController显示在表格中。

这是我的问题:只要我不更新Core Data对象,一切都很好。当我更新对象时(例如,在照片上设置lastViewed属性,以便我可以在“最近”选项卡中显示它),相应的照片将在下一个“表视图刷新”中从Flickr再次下载,从而导致重复输入表视图。 经过长时间的调试后,我终于发现了这个问题,但我无法解释原因:这是因为Core Data对象的更新没有明确保存更改。

我已阅读Core Data Programming Guide以及不同的班级参考文档,但我没有找到答案。

以下是用户想要显示Photo lastViewed属性时更新的代码。如果我取消注释行[[SharedDocument sharedInstance] saveDocument],一切都按预期工作。如果我对其进行评论,则下次刷新时将再次下载已查看的照片,而核心数据中已存在该照片。

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];

    // - Variable check discarded for a readability purpose -

    if ([segue.identifier isEqualToString:@"setImageURL:"])
    {
        Photo *photo = [self.fetchedResultsController objectAtIndexPath:indexPath];

        // Update the last viewed date property to now
        photo.lastViewed = [NSDate dateWithTimeIntervalSinceNow:0];

        // If I uncomment the line below, the issue disappears:
        // [[SharedDocument sharedInstance] saveDocument];

        if ([segue.destinationViewController respondsToSelector:@selector(setImageURL:)])
        {
            // Prepare the next VC
        }
    }
}

共享NSManagedObjectContext。以下是共享对象的代码:

@interface SharedDocument()
@property (strong, nonatomic)  UIManagedDocument * document;
@end

@implementation SharedDocument

+ (SharedDocument *) sharedInstance
{...} // Returns the shared instance

- (UIManagedDocument *) document
{...} // Lazy instantiation

- (void) useDocumentWithBlock:(void (^)(BOOL success))completionHandler
{...} // Create or open the document

- (void) saveDocument
{
    [self.document saveToURL:self.document.fileURL
            forSaveOperation:UIDocumentSaveForOverwriting
           completionHandler:nil];
}

更新

关于Flickr照片的注意事项:可从Flickr下载50张照片。这是一个有限集,即不会添加或更新新照片。因此,当我刷新表格视图时,不应下载新的照片。

以这种方式创建Photo对象(这是来自NSManagedObject子类的类别):

+ (Photo *) photoWithFlickrInfo:(NSDictionary *)photoDictionary
         inManagedObjectContext:(NSManagedObjectContext *)context
{    
    Photo * photo = nil;

    // Check whether the photo already exists
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Photo"];
    NSString *pred = [NSString stringWithFormat:@"uniqueId = %@",
                      [photoDictionary[FLICKR_PHOTO_ID] description]];
    request.predicate = [NSPredicate predicateWithFormat:pred];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"uniqueId"
                                                                     ascending:YES];
    request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];

    NSError *error = nil;

    NSArray *matches = [context executeFetchRequest:request error:&error];

    if (!matches || ([matches count] > 1) || error)
    {
        // Abnormal
        NSLog(@"Error accessing database: %@ (%d matches)", [error description], [matches count]);
    }
    else if (0 == [matches count])
    {
        // Create the photo
        photo = [NSEntityDescription insertNewObjectForEntityForName:@"Photo"
                                              inManagedObjectContext:context];

        photo.uniqueId = photoDictionary[FLICKR_PHOTO_ID];
        photo.title    = [photoDictionary[FLICKR_PHOTO_TITLE] description];
        photo.comment  = [[photoDictionary valueForKeyPath:FLICKR_PHOTO_DESCRIPTION] description];

        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            photo.imageURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                  format:FlickrPhotoFormatOriginal] absoluteString];
        }
        else
        {
            // iPhone
            photo.imageURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                  format:FlickrPhotoFormatLarge] absoluteString];

        }

        photo.thumbnailURL = [[FlickrFetcher urlForPhoto:photoDictionary
                                                  format:FlickrPhotoFormatSquare] absoluteString];

        photo.section = [photo.title substringToIndex:1];

        // Update the category / tag
        for (NSString * category in [photoDictionary[FLICKR_TAGS] componentsSeparatedByString:@" "])
        {            
            // Ignore a couple of categories
            if ([@[@"cs193pspot", @"portrait", @"landscape"] containsObject:[category lowercaseString]])
                continue;

            Tag *tag = [Tag withName:[category capitalizedString] forPhoto:photo inManagedObjectContext:context];

            [photo addTagsObject:tag];
        }

        NSArray *allTags = [[photo.tags allObjects] sortedArrayUsingComparator:^NSComparisonResult(Tag * t1, Tag * t2) {
            return [t1.name compare:t2.name];
        }];

        photo.tagString = ((Tag *) [allTags objectAtIndex:0]).name;
        NSLog(@"PhotoTagString: %@", photo.tagString);

        // Update the specific 'All' tag
        Tag * allTag = [Tag withName:@"All" forPhoto:photo inManagedObjectContext:context];
        [photo addTagsObject:allTag];

        NSLog(@"[CORE DATA] Photo created: %@ with %d tags", photo.uniqueId, [photo.tags count]);
    }
    else
    {
        // Only one entry
        photo = [matches lastObject];

        NSLog(@"[CORE DATA] Photo accessed: %@", photo.uniqueId);
    }

    return photo;
}

我希望我的解释足够明确。告诉我你是否需要更多信息来理解这个问题(这是我的第一篇文章,我还是一个年轻的padawan: - )

提前多多感谢,

弗洛里安

1 个答案:

答案 0 :(得分:1)

我无法从上面的代码片段中看到您最初如何创建Photo NSManagedObject,但听起来非常像是您遇到了永久性的对象ID问题。

使用UIManagedDocument时,存在一个问题,即在保存时没有自动完成此问题,问题通常表现为在下次应用启动之前取消新创建的对象失败。这是因为即使在保存之后它也会运行初始临时对象ID(通常在保存时,您会期望为您创建和分配永久对象ID)。我猜这就是为什么它一直有效,直到你保存,但不是之后。

如果在将您的托管对象插入核心数据后,您可以调用以下内容:

BOOL success = [context obtainPermanentIDsForObjects:@[newEntity] error:&error];

它将创建永久ID,您可以正常保存文档,任何提取都应该找到新对象。

作为脚注,与原始问题完全无关,如果要插入一批对象,以这种方式单独获取ID效率非常低,在这种情况下更好地传递一组新的托管对象。但这可能只会影响人们为新店铺播种或进行其他批量插入。