gpt4 book ai didi

core-data - UIManagedDocument 中父 NSManagedObjectContext 的后台队列更改导致合并时 NSFetchedresultsController 中的重复

转载 作者:行者123 更新时间:2023-12-04 05:53:45 26 4
gpt4 key购买 nike

好, friend 们。这个正在把我逼上墙。我有

  • UIManagedDocument 及其 2 个 MOContext(常规和父级)。
  • 运行的 UITableViewController(由 Paul Hegarty 继承为 CoreDataTableViewController)
  • NSFetchedResultsController
  • 用于与父提示访问的服务器同步的后台 GCD 队列

  • 我已经尝试了很多不同的方法,但每次都遇到问题。

    当我添加一个新的“动物”实体时,它没有问题并立即显示在 table 上。但是当我将它上传到服务器(在上传队列上)并更改它的“状态”(使用父上下文)以便它应该在上传部分时,它出现在那里但不会从未上传部分消失.

    我最终得到了我不想要的双胞胎!或者它有时甚至不能做出正确的,而只是保留错误的。

    ***但是,当应用程序关闭并重新加载时,多余的会消失。所以它只是在内存中的某个地方。我可以在商店验证一切都正确。但是 NSFetchedResultsController 没有触发 controllerDidChange... 的东西。

    这是我的 View Controller 的父类(super class)
    CoreDataTableViewController.m
    #pragma mark - Fetching

    - (void)performFetch
    {
    self.debug = 1;
    if (self.fetchedResultsController) {
    if (self.fetchedResultsController.fetchRequest.predicate) {
    if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
    } else {
    if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
    }
    NSError *error;
    [self.fetchedResultsController performFetch:&error];
    if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
    } else {
    if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    }
    [self.tableView reloadData];
    }

    - (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
    {
    NSFetchedResultsController *oldfrc = _fetchedResultsController;
    if (newfrc != oldfrc) {
    _fetchedResultsController = newfrc;
    newfrc.delegate = self;
    if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
    self.title = newfrc.fetchRequest.entity.name;
    }
    if (newfrc) {
    if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
    [self performFetch];
    } else {
    if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
    [self.tableView reloadData];
    }
    }
    }

    #pragma mark - UITableViewDataSource

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    if (self.debug) NSLog(@"fetchedResultsController returns %d sections", [[self.fetchedResultsController sections] count]);
    return [[self.fetchedResultsController sections] count];
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    }

    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
    }

    - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex: (NSInteger)index
    {
    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
    }

    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
    {
    return [self.fetchedResultsController sectionIndexTitles];
    }

    #pragma mark - NSFetchedResultsControllerDelegate

    - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
    {
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
    [self.tableView beginUpdates];
    self.beganUpdates = YES;
    }
    }

    - (void)controller:(NSFetchedResultsController *)controller
    didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex
    forChangeType:(NSFetchedResultsChangeType)type
    {
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
    switch(type)
    {
    case NSFetchedResultsChangeInsert:
    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
    break;

    case NSFetchedResultsChangeDelete:
    [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
    break;
    }
    }
    }


    - (void)controller:(NSFetchedResultsController *)controller
    didChangeObject:(id)anObject
    atIndexPath:(NSIndexPath *)indexPath
    forChangeType:(NSFetchedResultsChangeType)type
    newIndexPath:(NSIndexPath *)newIndexPath
    {
    if(self.debug) NSLog(@"controller didChangeObject: %@", anObject);
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
    {
    NSLog(@"#########Controller did change type: %d", type);
    switch(type)
    {
    case NSFetchedResultsChangeInsert:
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;

    case NSFetchedResultsChangeDelete:
    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;

    case NSFetchedResultsChangeUpdate:
    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;

    case NSFetchedResultsChangeMove:
    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
    break;
    }
    }
    }

    - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
    {
    if (self.beganUpdates) [self.tableView endUpdates];
    if (self.debug) NSLog(@"controller Did Change Content");
    }

    - (void)endSuspensionOfUpdatesDueToContextChanges
    {
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
    }

    - (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
    {
    if (suspend) {
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
    } else {
    [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
    }
    }

    @end

    这是我从它继承的特定 View Controller :
    - (NSArray *)sectionHeaderTitles
    {
    if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:@"Not Yet Uploaded", @"Uploaded But Not Featured", @"Previously Featured", nil];
    return _sectionHeaderTitles;
    }

    - (NSDictionary *)selectedEntry
    {
    if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init];
    return _selectedEntry;
    }

    - (void)setupFetchedResultsController
    {
    [self.photoDatabase.managedObjectContext setStalenessInterval:0.0];
    [self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0];
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Animal"];
    request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"unique" ascending:NO], nil];

    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:@"status" cacheName:nil];
    NSError *error;
    BOOL success = [self.fetchedResultsController performFetch:&error];
    if (!success) NSLog(@"error: %@", error);
    else [self.tableView reloadData];
    self.fetchedResultsController.delegate = self;
    }

    - (void)useDocument
    {
    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) {
    [self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
    [self setupFetchedResultsController];
    }];
    } else if (self.photoDatabase.documentState == UIDocumentStateClosed) {
    [self.photoDatabase openWithCompletionHandler:^(BOOL success) {
    [self setupFetchedResultsController];

    }];

    } else if (self.photoDatabase.documentState == UIDocumentStateNormal) {
    [self setupFetchedResultsController];
    }
    }

    - (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase
    {
    if (_photoDatabase != photoDatabase) {
    _photoDatabase = photoDatabase;
    [self useDocument];
    }
    }

    - (void)viewDidLoad
    {
    [super viewDidLoad];

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
    label.backgroundColor = [UIColor clearColor];
    label.font = [UIFont fontWithName:@"AmericanTypewriter" size:20];
    label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
    label.textAlignment = UITextAlignmentCenter;
    label.textColor = [UIColor whiteColor];
    self.navigationItem.titleView = label;
    label.text = self.navigationItem.title;
    [label sizeToFit];
    }

    - (void)viewWillAppear:(BOOL)animated
    {
    [super viewWillAppear:animated];

    // Get CoreData database made if necessary
    if (!self.photoDatabase) {
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"Default Photo Database"];
    self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url];
    NSLog(@"No existing photoDatabase so a new one was created from default photo database file.");
    }

    self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"DarkWoodBackGround.png"]];
    }


    - (void)syncWithServer
    {
    // This is done on the syncQ

    // Start the activity indicator on the nav bar
    dispatch_async(dispatch_get_main_queue(), ^{
    [self.spinner startAnimating];
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];

    [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(managedObjectContextDidSave:)
    name:NSManagedObjectContextDidSaveNotification
    object:self.photoDatabase.managedObjectContext.parentContext];
    });


    // Find new animals (status == 0)
    NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Animal"];
    newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:@"status == 0"];
    NSError *error;
    NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error];
    if ([newAnimalsArray count]) NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
    if (error) NSLog(@"fetchError: %@", error);

    // Get the existing animals from the server
    NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb];

    // In the parent context, insert downloaded animals into core data

    for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) {
    [Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext];
    // table will automatically update due to NSFetchedResultsController's observing of the NSMOC
    }

    // Upload the new animals if there are any
    if ([newAnimalsArray count] > 0) {
    NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
    for (Animal *animal in newAnimalsArray) {

    // uploadAnimal returns a number that lets us know if it was accepted by the server
    NSNumber *unique = [self uploadAnimal:animal];
    if ([unique intValue] != 0) {
    animal.unique = unique;

    // uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully
    if ([self uploadThePhotosOf:animal]){
    [self.photoDatabase.managedObjectContext performBlock:^{
    animal.status = [NSNumber numberWithInt:1];
    }];
    }
    }
    }
    }

    [self.photoDatabase.managedObjectContext.parentContext save:&error];
    if (error) NSLog(@"Saving parent context error: %@", error);
    [self performUpdate];


    // Turn the activity indicator off and replace the sync button
    dispatch_async(dispatch_get_main_queue(), ^{
    // Save the context
    [self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
    if (success)
    {
    NSLog(@"Document was saved");
    [self.photoDatabase.managedObjectContext processPendingChanges];
    } else {
    NSLog(@"Document was not saved");
    }
    }];

    [self.spinner stopAnimating];
    self.navigationItem.leftBarButtonItem = self.syncButton;
    });

    // Here it skips to the notification I got from saving the context so I can MERGE them
    }

    - (NSNumber *)uploadAnimal:(Animal *)animal
    {
    NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];
    NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation];
    NSLog(@"JSONRepresentation of %@: %@", animal.namestring, jsonStringFromAnimalMetaDictionary);
    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
    [request setPostValue:jsonStringFromAnimalMetaDictionary forKey:@"newmeta"];
    [request startSynchronous];
    NSError *error = [request error];
    NSString *response;
    if (!error) {
    response = [request responseString];
    NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0];
    return animalUnique;
    } else {
    response = [error description];
    NSLog(@"%@ got an error: %@", animal.namestring, response);
    return [NSNumber numberWithInt:0];
    }
    }

    - (BOOL)uploadThePhotosOf:(Animal *)animal
    {
    NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];

    int index = [animal.photos count];
    for (Photo *photo in animal.photos) {

    // Name the jpeg file
    NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
    NSString *imageServerPath = [NSString stringWithFormat:@"%lf-Photo.jpeg",timeInterval];

    // Update the imageServerPath
    photo.imageURL = imageServerPath;

    NSData *photoData = [[NSData alloc] initWithData:photo.image];
    NSString *photoMeta = [photo.metaDictionary JSONRepresentation];

    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
    [request addPostValue:photoMeta forKey:@"newphoto"];
    [request addData:photoData withFileName:imageServerPath andContentType:@"image/jpeg" forKey:@"filename"];
    [request setUploadProgressDelegate:self.progressView];
    [request startSynchronous];
    NSLog(@"%@ progress: %@", animal.namestring, self.progressView.progress);

    NSString *responseString = [request responseString];
    NSLog(@"uploadThePhotosOf:%@ photo at placement: %d has responseString: %@", animal.namestring, [photo.placement intValue], responseString);
    SBJsonParser *parser= [[SBJsonParser alloc] init];
    NSError *error = nil;
    id jsonObject = [parser objectWithString:responseString error:&error];
    NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0];

    // A proper response is not 0
    if ([parsedPhotoUploadResponse intValue] != 0) {
    photo.imageid = parsedPhotoUploadResponse;
    --index;
    }
    }

    // If the index spun down to 0 then it was successful
    int success = (index == 0) ? 1 : 0;
    return success;
    }

    - (NSArray *)downloadedAllAnimalsFromWeb
    {
    NSURL *downloadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];
    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL];
    [request setPostValue:@"yes" forKey:@"all"];
    request.tag = kGetHistoryRequest;
    [request startSynchronous];
    NSString *responseString = [request responseString];
    NSLog(@"downloadedAllAnimalsFromWeb responseString: %@", responseString);

    SBJsonParser *parser= [[SBJsonParser alloc] init];
    NSError *error = nil;
    id jsonObject = [parser objectWithString:responseString error:&error];
    NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject];
    return parsedDownloadedResponseStringArray;
    }

    - (void)performUpdate
    {
    NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext;
    NSSet * inserts = [context updatedObjects];

    if ([inserts count])
    {
    NSError * error = nil;

    NSLog(@"There were inserts");
    if ([context obtainPermanentIDsForObjects:[inserts allObjects]
    error:&error] == NO)
    {
    NSLog(@"BAM! %@", error);
    }
    }

    [self.photoDatabase updateChangeCount:UIDocumentChangeDone];
    }

    - (void)managedObjectContextDidSave:(NSNotification *)notification
    {

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext];

    NSLog(@"userInfo from the notification: %@", [notification userInfo]);
    // Main thread context
    NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext;

    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
    [context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
    NSLog(@"ContextDidSaveNotification was sent. MERGED");

    }


    #pragma mark - Table view data source


    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EntryCell"];

    if (!cell) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"EntryCell"];
    }

    // Configure the cell here...
    Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = animal.namestring;
    if (([animal.numberofanimals intValue] > 0) && animal.species) {
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@s", animal.species];
    } else {
    cell.detailTextLabel.text = animal.species;
    }
    return cell;
    }

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {

    NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
    Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
    // be somewhat generic here (slightly advanced usage)
    // we'll segue to ANY view controller that has a photographer @property
    if ([segue.identifier isEqualToString:@"newAnimal"]) {
    NSLog(@"self.photodatabase");
    [(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext];
    } else if ([segue.destinationViewController respondsToSelector:@selector(setAnimal:)]) {
    // use performSelector:withObject: to send without compiler checking
    // (which is acceptable here because we used introspection to be sure this is okay)
    [segue.destinationViewController performSelector:@selector(setAnimal:) withObject:animal];
    NSLog(@"animal: %@ \r\n indexPath: %@", animal, indexPath);
    }
    }

    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
    return 30;
    }

    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
    {
    return nil;
    }

    - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
    NSLog(@"header for section called for section: %d", section);
    NSLog(@"fetchedResultsController sections: %@", self.fetchedResultsController.sections);
    CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30);
    UIView *header = [[UIView alloc] initWithFrame:headerRect];
    UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)];
    if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) {
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0];
    } else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) {
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1];
    } else {
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2];
    }
    headerTitleLabel.textColor = [UIColor whiteColor];
    headerTitleLabel.font = [UIFont fontWithName:@"AmericanTypewriter" size:20];
    headerTitleLabel.backgroundColor = [UIColor clearColor];
    headerTitleLabel.alpha = 0.8;
    [header addSubview:headerTitleLabel];
    return header;
    }

    最佳答案

    任何人都不想涉足的代码太多了。

    但是,从快速检查来看,您似乎违反了 MOC 限制。具体来说,您是直接访问父上下文,而不是从它自己的线程访问。

    通常,您会启动一个新线程,然后在该线程中创建一个 MOC,使其父级成为文档的 MOC。然后做你的事情,并在新的 MOC 上调用 save。然后它会通知应该处理更新的父级。

    关于core-data - UIManagedDocument 中父 NSManagedObjectContext 的后台队列更改导致合并时 NSFetchedresultsController 中的重复,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9765050/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com