Setting date in core data with KVC - objective-c

This has me a little mystified.
For some reason I can set a date via a property, but trying to set it the same way using KVC causes an error.
I'm trying to make my code more generic, so I'd really like to be able to set my dates via KVC, given my NSManagedObject definitions as they are below.
This isn't my project, and it's a very large project, so I need to work within what's already there.
I read this, but it doesn't quite address this particular issue...
Why is an NSDate in a Core Data managed Object converted to NSTimeInterval?
In the core data modeler...
NSManagedObject SomeClass
updateDate Date
In code...
NSManagedObject SomeClass
#property (nonatomic) NSTimeInterval updateDate
So in a method, I've seen code that does this when setting through the property, which works just fine, but isn't generic.
someClass.updateDate = [NSDate date] timeIntervalSinceReferenceDate
I can't do that through KVC becaus setValue: expects an ID, so it fails with this compile time error 'Sending NSTimeInterval to parameter of incompatible type 'id_Nullable'' when the same is done through KVC
[someClass setValue:[NSDate date].timeIntervalSinceReferenceDate forKey:#"updateDate"];
Suggestions for fixing this so I can use KVC to set my dates.
Thanks a lot

If you pass an NSNumber to setValue:forKey:, it is automatically unboxed if the property is a primitive type. So you should be able to do [someClass setValue:#([[NSDate date] timeIntervalSince1970]) forKey:#"updateDate"].

Related

Is it safe to assign NULL to an NSDate*?

I'm writing a game that includes a Quest class, which has a completionDate property of type NSDate *, which records the date and time that the player completed the quest. It's declaration (in Quest.h) is:
#property (nonatomic) NSDate *completionDate;
I figured it would be reasonable, in a case where the player had not yet completed a quest, to assign the quest's completionDate property a value of NULL to indicate this. However, when I do:
quest.completionDate = NULL;
XCode (11.1) gives a warning on that line of code: Null passed to a callee that requires a non-null argument.
Despite the warning, my code seems to work as intended: Doing a check like the following to determine whether or not the quest has been completed seems to work fine:
if (quest.completionDate == NULL) { ... }
Researching this, I found similar questions like Xcode 7, Obj-C, "Null passed to a callee that requires a non-null argument", with answers suggesting just using the init method to initialize an object to a default state. However, in the case of NSDate, init initializes the date to "now" (the current date/time), so that won't work for what I'm trying to do.
My question: Is there any reason not to assign NULL to an NSDate *?
Add the nullable property attribute to your completionDate.
#property (nonatomic, nullable) NSDate *completionDate;
To learn more about declaring nullable types, see this Apple blog post Nullability and Objective-C.

Understanding ObjC bad message examples?

I'm currently reading The Big Nerd Ranch Guide on Objective C programming and I'm having trouble understanding a section in Chapter 13, Objects which is providing bad examples of Messages:
NSDate *now = [NSDate date];
double seconds = [now timeIntervalSince1970];
This is firstly a correct example of using messages in Objc. The first line creates a pointer to an NSDate object using the variable now and the NSDate type declaration. It is now an instance of NSDate. It was explained to me that if you are to print now, it will display the output of the NSDate date method. This is where things start to get a little weird for me. It seems odd that you would have a variable pointing both to an instance, and a function output if printed. But that's okay. Moving on.
The second line creates a variable called seconds, which is of type "double" and is going to obtain a value that the timeIntervalSince1970 method of NSDate instance now outputs.
Here is where the author introduces examples of bad messages:
double testSeconds = [NSDate timeIntervalSince1970];
NSDate *testNow = [now date];
He explains the errors as follows:
First Line:
"The Error is clear, the receiver in this message send is the NSDate
class, so the selector should be the name of an NSDate class method.
This selector is not."
Ok. This makes sense. An invalid method. Next. .
Second Line:
"This error is less clear: It is telling you that NSDate has no
instance method whose name matches the date selector."
Wait, what? Isn't now a pointer to an instance of NSDate? Shouldn't you be able to call the date method from an instance of NSDate? I don't get it.
The book does not explain any more than what I've quoted above, so I'm sure it's something stupid and basic I'm not getting. I hope this isn't too specific or unhelpful to others. I'll delete the submission if asked. Thank you.
The variable now points to an instance of NSDate. The method date is not defined on instances of NSDate but on the class itself. Hence date is a class method and not an instance method. Unlike other programming languages Objective-C does not inherit class methods to their class instances.
Vice versa instance methods cannot be called on classes. This said timeIntervalSince1970 cannot be called on the class NSDate as this method is an instance method. This is due to the circumstances that instances usually manage an instance state. Instance methods do operate on this instance state, namely reading and modifying their instance variables. Back to your example: The instance method timeIntervalSince1970 calculates the difference between 01/01/1970 and a concrete, instantiated date. So if you would be able to call timeIntervalSince1970 on class level there's no chance to calculate a difference of dates as the class NSDate doesn't carry any date information (the instance state!) such as the day, month, year and the time.
To sum up: Instance methods cannot be called on classes. This isn't supported by any programing language I am aware of. Calling class methods on instances is however supported by some programing language although there's typically no need for doing so and sometimes it even lowers code readability. However Objective-C doesn't support those calls neither.

Why do [NSDate distantPast] and [NSDate distantFuture] methods return id?

I'm wondering why do [NSDate distantPast] and [NSDate distantFuture] methods' return types are of type id? Why don't these methods return an NSDate pointer?
Because polymorphism is just valid in one way.
Let's say that you subclass NSDate and that you want to override that method. You must use the same signature so you'll do it with this signature:
-(NSDate*) distantPast;
But you'll not be able to assign the result of the expression to a pointer of the subclass type, and you'll need to downcast the result:
NSDateSubclass* ptr= (NSDateSubclass*)[someDateSubclassInstance distantPast];
// Downcasting is necessary here, it would give a warning or syntax error otherwise.
Even if you are sure that the returned object is a subclass of NSDate, you need to downcast the result. This is why all the methods that returns created objects, are declared to return an id.
NSDate is an abstract superclass, and distantPast or distantFuture return private subclasses of NSDate, and not an NSDate per se.
I think that question is also like asking what should you return on an init method? An id or a pointer to our class.
Because it would be the same on that case you can consider it's some kind of standardization on Apple's part for class methods.
There is no particular reason for this. The other poster pointed out that these methods might return a private subclass of NSDate, but a subclass of NSDate is still an NSDate. I'm guessing that these methods are old enough (i.e. NextStep, before Mac OS) that the reasons for the id return type is historical, and "lost to antiquity". (If you look at these methods in the Mac OS docs, it says they were defined in Mac OS 10.0)
I bet the old school approach was to use anonymous object pointers everywhere.

Difference between dot syntax and valueForKey

Please note that update 3 is probably most relevant
Im setting a NSTimeInterval property of a managed object with an nsdate object using setValue:forKey:
When i attempt to get the value I get weird stuff, at runtime this
NSLog(#"[managedObject valueForKey:#\"startTime\"] : %#, [NSDate dateWithTimeIntervalSince1970:[managedObject startTime]]: %#",
[managedObject valueForKey:#"startTime"],[NSDate dateWithTimeIntervalSince1970:[managedObject startTime]]);
Returns
[managedObject valueForKey:#"startTime"] : 2012-07-14 08:13:05 +0000,
[NSDate dateWithTimeIntervalSince1970:[managedObject startTime]]: 1981-07-14 08:13:05 +0000
Update 1
The value returned by [managedObject valueForKey:#"startTime"] is correct. However I would prefer to use [NSDate dateWithTimeIntervalSince1970:[managedObject startTime]] or something similar so that it is more strongly typed.
I believe [managedObject startTime] returns an incorrect value => 363954111.000000 .
However i set it with something like this:
managedObject setValue:1342261311 forKey:#"startTime"
It is worth noting that I am unsure whether this is incorrect because [managedObject valueForKey:#"startTime"] returns a correct NSDate object.
Update 2
I've logged the double values returned by KVC and . syntax.
managedObject.startTime = 363954111.000000
valueForKey timeIntervalSince1970 = 1342261311.000000
Update 3
Okay, I've set up a test, start time is set like this entity.startTime = [[NSDate dateWithTimeIntervalSince1970:1342261311] timeIntervalSince1970]; and end time is set like this [entity setValue:[NSDate dateWithTimeIntervalSince1970:1342261311] forKey:#"endTime"];
When i write them to log i get this start = 1342261311.000000, end = 363954111.000000
It seems that the NSDate object is being unwrapped incorrectly, has anyone seen this before?
The problem here is that valueForKey: is intended to be used with object values, in fact it returns an id.
As a convenience, valueForKey: wraps primitive types (such as integers and doubles) in their NSNumber counterparts.
The reason you see two different values is that valueForKey: returns an id, which essentially is a pointer to the position in memory where the NSNumber happens to be stored. Your code then just takes this arbitrary memory address and somehow interprets it as a double and then constructs an NSDate out of that.
Calling the startTime accessor method directly, on the other hand, returns the double without any further ado.
If you want to use valueForKey:, you can do something like this to get the real value:
NSTimeInterval tiv = [[managedObject valueForKey:#"startTime"] doubleValue];
and then work from there.
I am actually a bit surprised that the compiler doesn't emit a warning about this. Apple's latest compilers have become quite adept at catching problems like this one.
It was a problem with the difference in epochs. NSDate uses Jan 1 2001 as an epoch. So when I was getting the value I was using the unix epoch (1970). That gave me a difference in values.
When KVC unwraps and wraps NSTimeInterval with a NSDate object it uses the NSDate 2001 epoch.
So instead of using dateWithTimeIntervalSince1970
I used dateWithTimeIntervalSinceReferenceDate when getting the value.
NSTimeInterval is a typdef of a double
typedef double NSTimeInterval;
You can not store scalars directly in core data but you either have to wrap them in a NSNumber or in your case it may be easier to use a NSDate.
If startTime is a NSTimeInterval (and not an NSDate), you are comparing two different things there, a double and an NSDAte object.
[managedObject valueForKey:#"startTime"] will return you an NSTimeInterval, a primitive (which you should print with %f by the way).
[NSDate dateWithTimeIntervalSince1970:[managedObject startTime]] will return you a NSDate.
If you really want to comare the two, you should use [NSDate dateWithTimeIntervalSince1970:[managedObject valueForKey:#"startTime"]] to properly compare two NSDate objects.

Show NSDecimalNumber as currency in IB

I have a core data entity with a property amount, which is a NSDecimalNumber. For this property the entity's class has a method defined with an NSDecimalNumber as argument.
In Interface Builder I bound a table column to this property (using an NSArrayController) and on the column's cell I put an NSNumberFormatter. The formatter is configured in IB as 'currency'.
Now, when I try to enter a number, I get the following error:
-[NSCFNumber decimalNumberBySubtracting:]: unrecognized selector sent to instance 0x1001d5590
Apparently my setter method is receiving a regular NSNumber rather than an NSDecimalNumber. Can I configure my formatter differently, perhaps in code rather than IB, or is the only option to add an additional setter with an NSNumber as argument?
Core Data doesn't store NSDecimalNumber, only NSNumber. NSNumber doesn't have the method that gets called.
You either need to change the entities definition to use NSNumber or build your own NSValueTransformer to store the NSDecimalNumber in Core Data.
Please look here for more details about properties: http://developer.apple.com/library/ios/ipad/#documentation/Cocoa/Conceptual/CoreData/Articles/cdMOM.html%23//apple_ref/doc/uid/TP40002328-SW1
It's not clear to me exactly where your issue is. Either the formatter is giving you a straight NSNumber instead of a NSDecimalNumber or the core data is.
To make the NSNumberFormatter give you NSDecimalNumbers, use the method -setGeneratesDecimalNumbers:
To make the data store give you NSDecimalNumbers, make sure the relevant attribute in the model is set to "decimal number".