1

我正在尝试在核心数据中保存一对多关系。需要用户决定是否需要将新的子列表对象附加到新的父对象。在另一种情况下,现有数据库条目用作父对象。在某些情况下保存后,应用程序会崩溃。

最终编辑:对不起,如果你介意我保留所有的编辑,我仍然会。启蒙的过程相当曲折。毕竟我开始认为这是一个数据冲突......再次感谢汤姆,他帮助我指出了正确的方向:我仍在使用关系对核心数据实体进行排序和分组NSFetchedResultsController。我现在已经为我的实体类编写了一个有效的compare:方法,到目前为止我所看到的它正在工作。我即将为我的问题写一个答案。我仍将非常感谢您就此提供的任何信息警告

编辑 3:保存过程和用户​​警报似乎与问题无关。我现在放大了 NSFetchedResultsController 以及我使用关系(“考试”)作为sectionNameKeyPath. 我现在将尝试compare:在我的Examination实体类中编写一个类别中的方法。如果这也不起作用,Image除了关系之外,我还必须在我的实体类中写入一个可比较的值,并将其用于部分。大家同意了吗?

编辑 1:只有在询问用户是否要进行新检查并回答“是”后,才会发生崩溃。当没有用户提示时也输入相同的方法(当事实决定创建新检查时(没有检查存在 = 是,现有检查未超时 = 否)。在这些情况下,不会发生错误. 一定是在alert view打开的时候view完成加载,然后collection view和它的NSFetchedResultsController一起玩。

编辑 2:感谢Tom,这是调用堆栈。我认为这无关紧要,但视图控制器在集合视图中显示图像,每次检查的图像部分递减。所以 NSFetchedResultsController 的 section key 和 sort 描述符都在使用 MOC 更改通知发送后的检查。使我的应用程序崩溃的不是保存:它是 NSSortDescriptor(或者,公平地说,我使用所有这些的方式)。 调用堆栈的图像


NSFetchedResultsController 的代码:

#pragma mark - NSFetchedResultsController

- (NSFetchedResultsController *)fetchedResultsController
{
    if (m_fetchedResultsController != nil) {
        return m_fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass([Image class]) inManagedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext]];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key, identical sort to section key path must be first criterion
    NSSortDescriptor *examinationSortDescriptor = [[NSSortDescriptor alloc] initWithKey:kexaminationSortDescriptor ascending:NO];
    NSSortDescriptor *editDateSortDescriptor = [[NSSortDescriptor alloc] initWithKey:keditDateSortDescriptor ascending:NO];

    NSArray *sortDescriptors =[[NSArray alloc] initWithObjects:examinationSortDescriptor, editDateSortDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[HLSModelManager currentModelManager] managedObjectContext] sectionNameKeyPath:kSectionNameKeyPath cacheName:NSStringFromClass([Image class])];
    aFetchedResultsController.delegate = self;
    m_fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        HLSLoggerFatal(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return m_fetchedResultsController;
}

#pragma mark - NSFetchedResultsControllerDelegate - optional

/* Asks the delegate to return the corresponding section index entry for a given section name.  Does not enable NSFetchedResultsController change tracking.
 If this method isn't implemented by the delegate, the default implementation returns the capitalized first letter of the section name (seee NSFetchedResultsController sectionIndexTitleForSectionName:)
 Only needed if a section index is used.
 */
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName
{
    return sectionName;
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // In the simplest, most efficient, case, reload the table view.
    [[self collectionView] reloadData];
}

/* THE OTHER DELEGATE METHODS ARE ONLY FOR UITableView! */

保存检查(现有或新的)和新图像的代码:

-(BOOL)saveNewImage
{
    BOOL done = NO;

    // remove observer for notification after alert
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kExaminationTimedoutAlertDone object:nil];

    Examination * currentExamination = [self getCurrentExamination];

    if ([self userWantsNewExamination] == YES)
    { // if an examination was found
        if (currentExamination != nil)
        { // if the found examination is not closed yet
            if ([currentExamination endDate] == nil)
            { // close examination & save!
                [currentExamination closeExamination];
                NSError *savingError = nil;
                [HLSModelManager saveCurrentModelContext:(&savingError)];

                if (savingError != nil)
                {
                    HLSLoggerFatal(@"Failed to save old, closed examination: %@, %@", savingError, [savingError userInfo]);
                    return NO;
                }
            }
        }
        currentExamination = nil;
    }

    // the examination to be saved, either new or old
    Examination * theExamination = nil;

    // now, whether user wants new examination or no current examination was found - new examination will be created
    if (currentExamination == nil)
    {
        // create new examination
        theExamination = [Examination insert];
        if (theExamination == nil)
        {
            HLSLoggerFatal(@"Failed to create new examination object.");
            currentExamination = nil;
            return NO;
        }

        // set new examinations data
        [theExamination setStartDate: [NSDate date]];
    }
    else
    {
        theExamination = currentExamination;
    }

    if (theExamination == nil)
    { // no image without examination!
        HLSLoggerFatal(@"No valid examination object.");
        return NO;
    }

    Image *newImage = [Image insert];

    if (newImage != nil)
    {
        // get users last name from app delegate
        AppDelegate * myAppDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];

        // set image data
        [newImage setEditUser: [[myAppDelegate user] lastName]];
        [newImage setEditDate: [NSDate date]];
        [newImage setExamination: theExamination];
        [newImage setImage: [self stillImage]];
        [newImage createImageThumbnail];

        // update edit data
        [theExamination setEditUser: [[myAppDelegate user] lastName]];
        [theExamination setEditDate: [NSDate date]];
        // unnecessary! CoreData does it automatically! [theExamination addImagesObject:newImage];

        //! Important: save all changes in one go!
        NSError *savingError = nil;
        [HLSModelManager saveCurrentModelContext:(&savingError)];

        if (savingError != nil)
        {
            HLSLoggerFatal(@"Failed to save new image + the examination: %@, %@", savingError, [savingError userInfo]);
        }
        else
        {
            // reload data into table view
            [[self collectionView] reloadData];
            return YES;
        }
    }
    else
    {
        HLSLoggerFatal(@"Failed to create new image object.");
        return NO;
    }

    return done;
}

错误:

2013-05-22 17:03:48.803 MyApp[11410:907] -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0
2013-05-22 17:03:48.809 MyApp[11410:907] CoreData: error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  -[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0 with userInfo (null)
2013-05-22 17:03:48.828 MyApp[11410:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Examination compare:]: unrecognized selector sent to instance 0x1e5e73b0'

这里也是实体类文件:

//
//  Examination.h
//  MyApp
//

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Image;

@interface Examination : NSManagedObject

@property (nonatomic, retain) NSDate * editDate;
@property (nonatomic, retain) NSString * editUser;
@property (nonatomic, retain) NSDate * endDate;
@property (nonatomic, retain) NSDate * startDate;
@property (nonatomic, retain) NSSet *images;
@end

@interface Examination (CoreDataGeneratedAccessors)

- (void)addImagesObject:(Image *)value;
- (void)removeImagesObject:(Image *)value;
- (void)addImages:(NSSet *)values;
- (void)removeImages:(NSSet *)values;

@end

//
//  Examination.m
//  MyApp
//

#import "Examination.h"
#import "Image.h"

@implementation Examination

@dynamic editDate;
@dynamic editUser;
@dynamic endDate;
@dynamic startDate;
@dynamic images;

@end
4

1 回答 1

1

此错误与将数据保存到 MOC 无关。

因为新图像数据的保存是在前一个视图控制器的 prepareForSegue 中触发的,并且用户警报为下一个视图控制器提供了完成加载的时间,同时还创建了 NSFetchedResultsController 及其与其委托的连接,因此在保存到 MOC 的临时上下文,并且仅在用户发出警报之后。

NSFetchedResultsController 仅在这种情况下才开始监听 MOC 的变化。似乎如果它收到 MOC 更改的警报,它将仅获取更改,然后才需要将新数据与现有数据进行比较。非常欢迎您提供有关此方面的更多信息!

然后,因为我已经为关系设置了排序描述符(以及sectionNameKeyPath),并且没有提供对核心数据实体类中的实体对象进行排序的方法,所以 NSFetchedResultsController 无法继续。回想起来,一切似乎都是那么轻松自然,我真的开始怀疑我的解决方案是否简单......

我发现有趣的是,它可以在没有更改干扰的情况下一次性获取初始数据。毕竟它使用的是相同的 NSSortDescriptor。有任何想法吗?

这是我的解决方案:

//
//  MyCategoryExamination.m
//  MyApp
//

#import "MyCategoryExamination.h"

@implementation Examination (MyCategoryExamination)

- (NSComparisonResult)compare:(Examination *)anotherExamination;
{
    return [[self startDate] compare:[anotherExamination startDate]];
}

@end

请告诉我这是否有问题。

于 2013-05-23T09:40:17.070 回答