使用 NSPrivateQueueConcurrencyType 和自定义 setter 的核心数据延迟加载不起作用

Core Data Lazy Loading with NSPrivateQueueConcurrencyType and custom setter not working

问题: 当 NSManaged object 时,使用后台线程获取托管 object 不会延迟加载 NSManaged object相关的有一个自定义 setter。在具有主并发类型的主线程上执行提取没有问题。这是为什么?

解决方法: 如果我在关系 object 上创建自定义 getter 并检查是否为零,我可以强制 NSManaged object 通过调用其他没有自定义 setter 方法的变量来加载。

背景 核心数据布局非常简单。我管理了一个游戏 object 和一个管理了回合 object。回合object与游戏object是一对一的关系。我总是获取游戏 object 以访问转弯 object。 TurnImp 和 GameImp 是继承自 Game 和 Turn object 的实现 类,因此我没有将 getter/setter 方法放入自动生成的代码中。

代码

The Fetch

//
//Stick command on background
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
    //
    //Load Game 
    //
    AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    CoreDataHelper *coreDataHelper = appDelegate.coreDataHelper;
    NSManagedObjectContext *childMOC = [coreDataHelper createChildManagedObjectContext];

    //the request
    NSFetchRequest *fetchRequest = [NSFetchRequest new];

    //the object entity we want
    NSEntityDescription *entity = [NSEntityDescription entityForName:GAMEIMP_GAME inManagedObjectContext:childMOC];
    [fetchRequest setEntity:entity];

    //the predicate rules, the what
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"gameId == %@", @"1404110671234567"];
    [fetchRequest setPredicate:predicate];

    //the sorting rules
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:GAMEIMP_OBJECT_ID ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    //Fetch results
    NSFetchedResultsController *resultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:childMOC sectionNameKeyPath:nil cacheName:nil];
    NSError *error;
    BOOL success = [resultsController performFetch:&error];
    GameImp *game;
    if (success) {
        game = [resultsController.fetchedObjects objectAtIndex:0];
    } else {
        NSLog(@"Unable to get game. Error: %@", error);
    }
    TurnImp *turnImp = game.turn;

    //Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
    int lastRoundReward = [turnImp.lastRoundReward intValue];

    //Work around, call custom getter method. Now 3 is returned.
    lastRoundReward = [turnImp getLastRoundReward];
 }

本子MOC创作

-(NSManagedObjectContext*) createChildManagedObjectContext {
   NSManagedObjectContext *childMOC = [[NSManagedObjectContext alloc]    initWithConcurrencyType:NSPrivateQueueConcurrencyType];
   childMOC.parentContext = self.mainManagedObjectContext;

   return childMOC;
}

TurnImp Header

@interface TurnImp : Turn

@property(atomic) BOOL isValid;
- (void) setLastRoundReward: (int) lastRoundReward;
- (int) getLastRoundReward;
@end

TurnImp M

@implementation TurnImp

@synthesize isValid;
@synthesize lastRoundReward = _lastRoundReward;

/**
 * Set the last round reward
 * @param -
 * @return -
 */
- (void) setLastRoundReward: (int) lastRoundReward {
    _lastRoundReward = [NSNumber numberWithInt:lastRoundReward];
}

/**
 * Get the int value of lastRoundReward
*/
- (int) getLastRoundReward {
    //Note - HACK! Lazy loading not working, try another member
    if (self.lastRoundReward == nil) {
        //Force load
        NSString *objectId = self.objectId;
    }
    return [self.lastRoundReward intValue];
}

将 childMoc 更改为 mainMoc 即可。 MainMoc 代码

 //create the main MOC
 _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

修复并发问题后的更多内容

[childMOC performBlock:^{

        // Execute the fetch on the childMOC and do your other work.
        NSError *error;
        NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
        if (results == nil) {
            // Handle error
        } else if (results.count == 1) {
            GameImp *game = [results firstObject];
            TurnImp *turnImp = game.turn;

            //Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
            int lastRoundReward = [turnImp.lastRoundReward intValue];

            //Work around, call variable objectId (not same as ObjectId)
            NSString *objectId = turnImp.objectId;
            //not it's 3...
            lastRoundReward = [turnImp.lastRoundReward intValue];

        }
    }];

变通

我从 TurnImp 中删除了以下内容,它按预期方式处理关系。

@synthesize lastRoundReward = _lastRoundReward;

首先,我必须承认我不知道你的问题陈述是什么意思 - 关系的延迟加载到底应该做什么?

但是,快速浏览一下您的代码就会发现您正在使用 NSPrivateQueueConcurrencyType 创建 MOC,但您没有将其使用正确地包装在适当的 performBlock 调用中。

当您明显违反核心数据并发准则时,您就是在危险的水域中玩耍,并且会出现未定义的行为。

此外,为什么要创建 NSFetchedResultsController 的实例只是为了执行提取?那太过分了。只需使用获取请求。像这样...

[childMOC performBlock:^{
    // Execute the fetch on the childMOC and do your other work.
    NSError *error;
    NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
    if (result == nil) {
        // Handle error
    } else if (results.count == 1) {
        GameImp *game = [results firstObject];
        TurnImp *turnImp = game.turn;
        int lastRoundReward = [turn2.lastRoundReward intValue];
    }
}];