Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data predicate crashes when attribute is nil

tl;dr: type something here that won't crash if birthDate is nil:

xcdatamodel screenshot


I have an entity with a birthDate attribute and a fetched property with the following predicate, typed straight into the xcdatamodel file:

$FETCH_SOURCE.birthDate > birthDate

This happily returns a list of managed objects older than FETCH_SOURCE (the managed object where the fetch request is happening). But birthDate is optional, and if the birthDate for FETCH_SOURCE is nil...

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'can't use NULL on left hand side'

Note that it's fine if the attribute is nil for the object being compared. If I swap the arguments of the predicate:

birthDate < $FETCH_SOURCE.birthDate

...I get the same error. Trying to test it for nil:

(birthDate != nil AND birthDate < $FETCH_SOURCE.birthDate)

...gives...

'NSInvalidArgumentException', reason: 'illegal comparison with NULL'

Google hasn't heard of either of those errors so I'm thinking this is a recent state of affairs. The problem remains when it's a String being compared for equivalence, or whatever. Is there a way to fix this in the predicate so it correctly returns an empty list? Thanks.

Edit: To be clear, the question is whether it's possible to fix this crash in the predicate in the xcdatamodel file.

Update: The crash specifically happens when the Relationship fault for the NSFetchedPropertyDescription is triggered by trying to read anything about it, presumably because it doesn't attempt to run the predicate till then. It isn't nil however, and can be checked for nil without crashing.

like image 716
DenverCoder9 Avatar asked Jun 22 '15 22:06

DenverCoder9


2 Answers

I can't find any way to amend the predicate of the fetched property to avoid this error, but one workaround is to implement a custom getter for the olderHeroes fetched property. This should return an empty array if the birthDate is nil.

var olderHeroes: NSArray {
    var array : NSArray
    if self.birthDate == nil {
        array = NSArray()
    } else {
        self.willAccessValueForKey("olderHeroes")
        array = self.primitiveValueForKey("olderHeroes") as! NSArray
        self.didAccessValueForKey("olderHeroes")
    }
    return array
}

Or in Objective-C:

-(NSArray *)olderHeroes {
    NSArray *array;
    if (self.birthDate == nil) {
        array = [NSArray array];
    } else {
        [self willAccessValueForKey:@"olderHeroes"];
        array = [self primitiveValueForKey:@"olderHeroes"];
        [self didAccessValueForKey:@"olderHeroes"];
    }
    return array;
}

(These snippets go in the implementation file for the subclass of the FETCH_SOURCE entity).

like image 194
pbasdf Avatar answered Oct 22 '22 04:10

pbasdf


If you want to have results containing either a nil value or it has to be greater or smaller than a certain date use the following:

((birthDate == nil) OR (birthDate < %@))

This works for me. I hope this is what you're asking. Had a bit of trouble understanding your question.

If you're asking to check a variable you're passing in the predicate, might the following work (untested):

(($FETCH_SOURCE.birthDate == nil && birthDate == nil) OR ($FETCH_SOURCE.birthDate < birthDate))
like image 26
Orion Avatar answered Oct 22 '22 05:10

Orion