4
votes

I've got a Core Data property that I'm trying to set at runtime, with a value derived from another property. However, curiously, the custom accessor I built never even seems to get called.

The property, seasonNameVisible, only gets called from a predicate with terms from a search field, as shown here:

// Add our search predicates
for (NSString *term in searchTerms) {
    NSPredicate *searchTermPredicate = [NSPredicate predicateWithFormat:@"(episodeName contains[cd] %@) OR (fromSeason.seasonNameVisible contains[cd] %@) OR (fromSeason.fromSeries.seriesName contains[cd] %@)", term, term, term];
    [subPredicates addObject:searchTermPredicate];
}

If I change that property to seasonName, that part of the predicate returns a result just fine, so I'm not suspecting the predicate or any of the other search code.

My plan is to derive the seasonNameVisible NSString from seasonName at runtime. So, I've modified the Season NSManagedObject subclass to override the accessor and setter, using primitive accessors. But as far as I can tell, my accessor never gets called.

Here is the header/interface:

//
//  Season.h
//

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

@class Episode, Series;

@interface Season : NSManagedObject

@property (nonatomic, retain) NSNumber * seasonIndex;
@property (nonatomic, retain) NSString * seasonName;
@property (nonatomic, retain) NSString * seasonNameVisible;
@property (nonatomic, retain) NSSet *episodes;
@property (nonatomic, retain) Series *fromSeries;

@property (nonatomic, retain) NSString * primitiveSeasonName;
@property (nonatomic, retain) NSString * primitiveSeasonNameVisible;
@end

@interface Season (CoreDataGeneratedAccessors)

- (void)addEpisodesObject:(Episode *)value;
- (void)removeEpisodesObject:(Episode *)value;
- (void)addEpisodes:(NSSet *)values;
- (void)removeEpisodes:(NSSet *)values;

@end

// my additions
@interface Season (PrimitiveAccessors)
- (NSString *)primitiveSeasonName;
- (NSString *)primitiveSeasonNameVisible;
@end

...and the implementation:

//
//  Season.m
//

#import "Season.h"
#import "Episode.h"
#import "Series.h"


@implementation Season

@dynamic seasonIndex;
@dynamic seasonName;
@dynamic seasonNameVisible;
@dynamic episodes;
@dynamic fromSeries;

// my additions
@dynamic primitiveSeasonName;
@dynamic primitiveSeasonNameVisible;

- (NSString *)seasonNameVisible
{
    NSString *visible;
    [self willAccessValueForKey:@"seasonNameVisible"];
    visible = [self primitiveValueForKey:@"seasonNameVisible"];
    [self didAccessValueForKey:@"seasonNameVisible"];

    if (visible != nil) {
        return visible;
    } else {
        [self willAccessValueForKey:@"seasonName"];
        visible = [[self primitiveValueForKey:@"seasonName"] substringFromIndex:2];
        [self didAccessValueForKey:@"seasonName"];
        [self setSeasonNameVisible:visible];
        return visible;
    }
}

- (void)setSeasonNameVisible:(NSString *)seasonNameVisible 
{
    [self willChangeValueForKey:@"seasonNameVisible"];
    [self setPrimitiveValue:seasonNameVisible forKey:@"seasonNameVisible"];
    [self didChangeValueForKey:@"seasonNameVisible"];
}

@end

I've read Apple's docs, and searched around for help with custom accessor methods on StackOverflow, and I think I have the code right (this is the first time I've ever tried to use primitive accessors, or to override an NSManagedObject method, so I'm a tad out of my usual depth), but even when I put a breakpoint on it, it never seems to get called.

1
Now that I look over things again, I suspect I don't need to declare the primitive properties if I'm calling everything with KVC, but that still doesn't change the fact that it's not getting called.bobtiki
The predicate is being used for a fetch request, for a search view, and it is persistent, because, as described in your answer below, you can't fetch with that. The issue is more that seasonNameVisible is returning nil and my custom accessor isn't being called to fill it.bobtiki
I guess jrturton's answer has now been deleted.bobtiki
Im pretty sure fetch requests don't go through your objects to obtain values, and if they did, it would be the primitive value (so not setting off the KVO notifications). If you access the seasonNameVisible property for a single object (e.g display it in a table view cell), do you get the right result?jrturton
Is this a SQLLite store? I believe the fetches are going directly there, not through accessors. Maybe instead override the setter for seasonName to set seasonNameVisible?Hunter

1 Answers

1
votes

You're asking core data to fetch based on a lazily loaded attribute. I dont think this is going to work. You either need to set your seasonNameVisible when you set the season name or, which may make more sense, separate out the storage of your season name into whatever is the prefix (that you are removing to give the visible name) and whatever is the real name.

After the additional information in the comments, I would advise the following:

  • split your season into two attributes, a season number (int) and season name
  • sort your fetch request on season number
  • section name key path is a new read-only property on your object, which returns a string made of the number and name

The error you are seeing in your comment is normal when altering a fetch request used with an FRC. Delete the app from the simulator and re-build and it will go away, or use a nil cache while developing. The FRC stores its cache permanently (eg between runs) so any changes upset it.

Section name key path can be any key path or property name you like, as long as the sorting is the same. From the docs for NSFetchedResultsController:

sectionNameKeyPath

A key path on result objects that returns the section name. Pass nil to indicate that the controller should generate a single section. The section name is used to pre-compute the section information. If this key path is not the same as that specified by the first sort descriptor in fetchRequest, they must generate the same relative orderings. For example, the first sort descriptor in fetchRequest might specify the key for a persistent property; sectionNameKeyPath might specify a key for a transient property derived from the persistent property.

So, you'd have a Season object with two persistent attributes, seasonNumber and seasonName. You'd sort your fetch request by season number. Your section name key path (for a fetch presumably on episodes, which have a season relationship) would be @"season.seasonSectionName", implemented as follows - no changes in your managed object model, just changes to your Season object:

Season.h:

@property(nonatomic,readonly) NSString *seasonSectionName;

Season.m:

-(NSString*)seasonSectionName
{
    return [NSString stringWithFormat:@"%d - %@",self.seasonNumber,self.seasonName];
}

All you're really doing is decorating the season number with another property.