Friday, November 19, 2010

Problem with @dynamic property declarations for core data managed objects

I have just had the most frustrating debug session I can remember due, I believe, to the (recommended by Apple) use of the @dynamic declaration for NSManagedObject properties.

I have an object that is defined like this:

 @interface SomeObject : NSManagedObject {  
 @property (nonatomic, retain) NSNumber * isRead;  
 @implementation SomeObject  
 @Dynamic isRead;  

Apple tells us to use the @Dynamic declaration instead of @synthesize as a speed optimisation. What this means however is that the return type of isRead can not be relied upon to be an NSNumber, nor can it be relied upon to be the same every time!

In this particular situation the core data was being refreshed from a JSON web service if the device had an internet connection or read from disk if it was offline. The behaviour of the application depends upon the YES/NO value of isRead.

As an aside here the reason that isRead is NSNumber rather than BOOL is because when the data is read in from the JSON service it is converted to an NSDictionary and the true/false string of the web service ends up coming out as NSNumber 1/0 because only objects can be added to a dictionary and BOOL is a primitive.

when offline

2010-11-19 15:27:21.732 MyApp[1086:207] SomeObject isRead is 0
2010-11-19 15:27:21.742 MyApp[1086:207] SomeObject isRead is of type _PFCachedNumber

when Online

2010-11-19 15:30:41.415 MyApp[1111:207] SomeObject isRead is 0
2010-11-19 15:30:41.424 MyApp[1111:207] SomeObject isRead is of type NSCFBoolean

This caused a monstrous bug because we were trying to compare an NSNumber like this

 if(SomeObject.isRead == [NSNumber numberWithBool:NO]){  
  //do stuff  

Which would work if the device was online but not if there was no network. try tracking that one down! To fix it I had to reverse the polarity on the test.

 if(![SomeObject.isRead boolValue]){  
  //do stuff  

Which in all honesty is probably a better way of writing the test.


Anonymous said...

You are comparing pointers. You need to use:

if ( [SomeObject.isRead isEqualToNumber: [NSNumber numberWithBool:NO]] )