Core Data predicate crashes when attribute is nil - ios

tl;dr: type something here that won't crash if birthDate is nil:
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.

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).

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))

Related

Handling possible nil date from array of objects from db which may return a date

I'm new to objective c and have been given an existing, outsourced codebase so I apologise if I am missing something very obvious!
I have a Goal data model which I have added a date to:
When making a request to the db for an array of goals, the following is returned:
As you can see, one of the array elements returns an extra KVP which is the date type I added to the xcdatamodel previously. This date refers to the date in which the goal was completed.
When iterating over this array in a for loop and checking whether the date KVP is not nil, NULL is returned for the object in which the date exists for:
([[self.goalArray objectAtIndex:i] valueForKey:#"date"] != nil) ?
[[self.goalArray objectAtIndex:i] valueForKey:#"date"] : [NSNull null]
However, the other values in the object using the same above expression with their keys return their expected values:
It's worth noting that in order to get the app to run, I change the date xcdatamodel to a string value as opposed to date for testing purposes.
I have also added this to the Goal model:
#synthesize date;
...
-(NSDate*) date {
return date;
}
-(void) setDate:(NSDate*)DateTime {
date = DateTime;
}
Is there something obvious I'm missing as I am at a wits end?
Again I apologise if this is a bad question I cannot find any clues as to how to handle objects which may or may not return a certain KVP
You should probably use objectForKey:, that's a NSDictionary function. The valueForKey: basically works on any class and returns a value and/or function result based on the key you pass it.
I can imagine something funky happening when asking for the date.
Your code should probably look somewhat like the following:
NSDictionary *goalInfo = self.goalArray[i];
//goalInfo[#"date"] - This is the short, and more readable version of 'objectForKey:'
if (goalInfo && goalInfo[#"date"]) {
return goalInfo[#"date"];
}
else {
return [NSNull null];
}

realm objectsWhere doesn't work

I'm trying to fetch realm objects using NSPredicate.
This doesn't work
RLMResults *activtyResults = [[[self getActivitySource]activities] objectsWhere:#"ID ==[c] '%#'",activityID];
activtyResults is nil
This works:
for (RealmActivity *activity in [[self getActivitySource]activities]){
if ([activity.ID isEqualToString:activityID]) {
return activity;
}
}
The activityID is a uuid: f20f6217-9cb6-4fe1-9333-8956d1252112
Why the predicate format #"ID ==[c] '%#'" doesn't work here ?Does it have anything to do with the property name being ID ?
A predicate of ID == %# would be equivalent to the imperative code you shared.
I'd expect the predicate you used to generate an error, rather than silently returning nil, due to the extraneous single quotes it contains. The lack of error combined with the nil result you're seeing suggests to me that the object you're invoking -objectsWhere: on is likely nil.

ios/objective-c: How can I prevent exception when inserting Null into core data?

I would think that the following code would not throw an exception if the value is null but is is. Am I missing a typo or is there a different way to do this?
if (![itemid isKindOfClass:[NSNull class]])
{
[record setValue:itemid forKey:#"itemid"];
}
Throws exception 'itemid NSNumber * (null) 0x14e8ebc0'
If itemid is nil (as opposed to an instance of the NSNull class), then -isKindOfClass: will return NO, and it will pass the condition and will try to set the value.
So, you probably want if (itemid && ![itemid isKindOfClass:[NSNull class]]) { ....
A message to a nil reference returns nil, or 0 or false in a numeric / bool context, regardless of the arguments to that message.

Handling null and NSDate in dictionary

I have some data stored in an object say objectA. When I print objectA it looks like:
{
employeeId = 1234;
startDate = (null);
endDate = 2013-10-01 04:59:59 +0000;
}
I am storing it in a dictionary as below:
NSDictionary *dict = #{#"employee_id" : objectA.employeeId,
#"start_date" : objectA.startDate,
#"end_date" : objectA.endDate};
I get a crash when I step over the dict code because I am trying to insert a nil object in dict. Error : attempt to insert nil object from objects[1]
Also is this the correct way to pass the date to a dictionary because when I pass this dictionary through isValidJson check, the app crashes again.
Am I doing anything wrong here? How do I handle null/nil check while passing the data to a dictionary?
If your date is nil, either don't include it in that entry in the dictionary (then when you try and read that key you will get back a nil result) or use [NSNull null] as a placeholder object (or whatever null placeholder constant you may want to use - like a specific string #"thisismynullstringconstant") and then when you read your null value from the dictionary convert it to nil.

How can I check if an object in an NSArray is NSNull?

I am getting an array with null value. Please check the structure of my array below:
(
"< null>"
)
When I'm trying to access index 0 its crashing because of
-[NSNull isEqualToString:]: unrecognized selector sent to instance 0x389cea70
Currently its crashing because of that array with a crash log:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull isEqualToString:]: unrecognized selector sent to instance 0x389cea70'
*** First throw call stack:
(0x2d9fdf53 0x3820a6af 0x2da018e7 0x2da001d3 0x2d94f598 0x1dee57 0x1dfd31 0x302f598d 0x301a03e3 0x3052aeed 0x3016728b 0x301659d3 0x3019ec41 0x3019e5e7 0x30173a25 0x30172221 0x2d9c918b 0x2d9c865b 0x2d9c6e4f 0x2d931ce7 0x2d931acb 0x3262c283 0x301d3a41 0xabb71 0xabaf8)
libc++abi.dylib: terminating with uncaught exception of type NSException
id object = myArray[0];// similar to [myArray objectAtIndex:0]
if(![object isEqual:[NSNull null]])
{
//do something if object is not equals to [NSNull null]
}
if (myArray != (id)[NSNull null])
OR
if(![myArray isKindOfClass:[NSNull class]])
Building off of Toni's answer I made a macro.
#define isNSNull(value) [value isKindOfClass:[NSNull class]]
Then to use it
if (isNSNull(dict[#"key"])) ...
Awww, guys. This is an easy one.
// if no null values have been returned.
if ([myValue class] == [NSNull class]) {
myValue = nil;
}
I'm sure there are better answers, but this one works.
I found the code for working with NSNull has the following problems:
Looks noisy and ugly.
Time consuming.
Error prone.
So I created the following category:
#interface NSObject (NSNullUnwrapping)
/**
* Unwraps NSNull to nil, if the object is NSNull, otherwise returns the object.
*/
- (id)zz_valueOrNil;
#end
With the implementation:
#implementation NSObject (NSNullUnwrapping)
- (id)zz_valueOrNil
{
return self;
}
#end
#implementation NSNull (NSNullUnwrapping)
- (id)zz_valueOrNil
{
return nil;
}
#end
It works by the following rules:
If a category is declared twice for the same Class (ie singleton instance of the Class type) then behavior is undefined. However, a method declared in a subclass is allowed to override a category method in its super-class.
This allows for more terse code:
[site setValue:[resultSet[#"main_contact"] zz_valueOrNil] forKey:#"mainContact"];
. . as opposed to having extra lines to check for NSNull. The zz_ prefix looks a little ugly but is there for safety to avoid namespace collisions.
You can use the following check:
if (myArray[0] != [NSNull null]) {
// Do your thing here
}
The reason for this can be found on Apple's official docs:
Using NSNull
The NSNull class defines a singleton object you use to represent null values in situations where nil is prohibited as a value (typically in a collection object such as an array or a dictionary).
NSNull *nullValue = [NSNull null];
NSArray *arrayWithNull = #[nullValue];
NSLog(#"arrayWithNull: %#", arrayWithNull);
// Output: "arrayWithNull: (<null>)"
It is important to appreciate that the NSNull instance is semantically different from NO or false—these both represent a logical value; the NSNull instance represents the absence of a value. The NSNull instance is semantically equivalent to nil, however it is also important to appreciate that it is not equal to nil. To test for a null object value, you must therefore make a direct object comparison.
id aValue = [arrayWithNull objectAtIndex:0];
if (aValue == nil) {
NSLog(#"equals nil");
}
else if (aValue == [NSNull null]) {
NSLog(#"equals NSNull instance");
if ([aValue isEqual:nil]) {
NSLog(#"isEqual:nil");
}
}
// Output: "equals NSNull instance"
Taken from https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/NumbersandValues/Articles/Null.html
A lot of good and interesting answers have been given already and (nealry) all of them work.
Just for completion (and the fun of it):
[NSNull null] is documented to return a singleton. Therefore
if (ob == [NSNull null]) {...}
works fine too.
However, as this is an exception I don't think that using == for comparing objects is a good idea in general. (If I'd review your code, I'd certainly comment on this).
In Swift (or bridging from Objective-C), it is possible to have NSNull and nil in an array of optionals. NSArrays can only contain objects and will never have nil, but may have NSNull. A Swift array of Any? types may contain nil, however.
let myArray: [Any?] = [nil, NSNull()] // [nil, {{NSObject}}], or [nil, <null>]
To check against NSNull, use is to check an object's type. This process is the same for Swift arrays and NSArray objects:
for obj in myArray {
if obj is NSNull {
// object is of type NSNull
} else {
// object is not of type NSNull
}
}
You can also use an if let or guard to check if your object can be casted to NSNull:
guard let _ = obj as? NSNull else {
// obj is not NSNull
continue;
}
or
if let _ = obj as? NSNull {
// obj is NSNull
}
Consider this approach:
Option 1:
NSString *str = array[0];
if ( str != (id)[NSNull null] && str.length > 0 {
// you have a valid string.
}
Option 2:
NSString *str = array[0];
str = str == (id)[NSNull null]? nil : str;
if (str.length > 0) {
// you have a valid string.
}

Resources