CoreDataで多層にデータを組んだ時の2段目のNSFetchedResultsController

これはとてもはまった。はまったけれど、Coredataへの理解がまた一段と進んだ気がするが、文章で記録する自信がないので、Sourceを書いておく。

Game に対して、Setが複数作成されるようなデータ構成をイメージする。テニスのGameに何セットも行うようなイメージ。

そこで、Game.m, Game.h, Set.m, Set.hはそれぞれこんな感じ。

Game.m

#import "Game.h"
#import "Set.h"

@implementation Game

@dynamic gameDate;
@dynamic gameTitle;
@dynamic set;

@end

Game.h

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

@class Set;

@interface Game : NSManagedObject

@property (nonatomic, retain) NSDate * gameDate;
@property (nonatomic, retain) NSString * gameTitle;
@property (nonatomic, retain) NSSet *set;
@end

@interface Game (CoreDataGeneratedAccessors)

- (void)addSetObject:(Set *)value;
- (void)removeSetObject:(Set *)value;
- (void)addSet:(NSSet *)values;
- (void)removeSet:(NSSet *)values;

@end

Set.m

#import "Set.h"
#import "Score.h"


@implementation Set

@dynamic tempp;
@dynamic score;
@dynamic game;

@end

Set.h

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

@class Score;

@interface Set : NSManagedObject

@property (nonatomic, retain) NSString * tempp;
@property (nonatomic, retain) NSSet    * score;
@property (nonatomic, retain) Game     *game;

@end

@interface Set (CoreDataGeneratedAccessors)

- (void)addScoreObject:(Score *)value;
- (void)removeScoreObject:(Score *)value;

@end

で、Gameを決めたあとのSetをNSFetchedResultsControllerする際、NSPredicateをどうしていいのかで悩んだ。結論としては以下のとおり。

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"game == %@", self.gameItem];
    [fetchRequest setPredicate:predicate];

self.gameItemは、Gameクラスのインスタンス。つまり、Gameのインスタンスが決まっていれば、SetのRelationShipで指定したgameでNSPredicateを作ればいいらしい。

いやはや、こういうことが書いてある情報が全然なくて、しんどかったけど、わかればスッキリした。。
念のため、NSFetchedResultsController全体を記録しておく。

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

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Set" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
    
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
    
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"tempp" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];
    
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"game == %@", self.gameItem];
    [fetchRequest setPredicate:predicate];

    // 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:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.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.
	    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
	    abort();
	}

    return _fetchedResultsController;
}

※なお、temppはその名の通り。適当。NSSortDescriptorがどうしても必要らしくて作った。