NSDate's initWithString: is returning nil - objective-c

I'm using Stig Brautaset's JSON framework serialize some objects, including NSDates (which are not directly supported).
I decided to use NSDate's description as the JSONFragment representation of a date (I don't care about the minor loss of precision incurred in doing so).
To extend Stig Brautaset's JSON framework to include NSDates, I defined a category:
#interface NSDate (NSDate_JSON) <JSONInitializer>
-(NSString *) JSONFragment;
#end
To recreate an NSDate (and other classes) from JSON, I defined a protocol with the following initializer:
#protocol JSONInitializer <NSObject>
-(id) initWithJSONRepresentation: (NSString *) aJSONRepresentation;
#end
I'm having issues with this initializer. In NSDate's case, it just calls initWithString:, and that's were I get into trouble: it always returns nil. This is the implementation:
#import "NSDate+JSON.h"
#implementation NSDate (NSDate_JSON)
-(NSString *) JSONFragment{
NSString *strRepr = [self description];
return [strRepr JSONFragment];
}
-(id) initWithJSONRepresentation:(NSString *)aJSONRepresentation{
return [self initWithString: aJSONRepresentation]; //returns nil!
}
#end
I'm not sure what's going on. Besides, the compiler warns me that the initWithString: method in initWithJSONRepresentation: could not be found.
Anybody knows what might be going on?
The full source code for a test case is available here.

You should always use an NSDateFormatter when attempting to convert a string into a date or vice versa. -initWithString: does exist on the Mac, but not on iOS. It requires the string to be in an extremely precise format. Using a date formatter is by far the superior solution.
And as a side note, your code would break if Apple ever decided to change the format of -[NSDate description].

Your test program is:
NSDate *d1 = [[[NSDate alloc] init] autorelease];
NSString *repr = [d1 JSONFragment];
NSDate *dd = [[[NSDate alloc] initWithString:[d1 description]] autorelease ];
NSDate *d2 = [[[NSDate alloc] initWithJSONRepresentation:repr] autorelease];
Your -JSONFragment category method is:
-(NSString *) JSONFragment{
NSString *strRepr = [self description];
return [strRepr JSONFragment];
}
What’s happening in this method is that you obtain a string representation of that date using -description, and then a JSON representation of that string using -JSONFragment.
In SBJSON, -JSONFragment returns the representation of a given object as JSON data. The JSON specification requires that strings are quoted. In your program:
NSString *repr = [d1 JSONFragment];
repr contains a string like #"\"2011-04-29 10:20:30 -0600\"". Because of the quotes, that string is not a valid string to be used with -[NSDate initWithString:].
If you change:
NSString *repr = [d1 JSONFragment];
to:
NSString *repr = [[d1 JSONFragment] JSONFragmentValue];
so that SBJSON parses that fragment and returns a string without the quotes, it should work.

The reason initWithString: can't be found is that unless you're importing Foundation and didn't show us here, your code can't see NSDate.h, so it doesn't know that initWithString: exists.
Dave's quite right about relying on description and using an NSDateFormatter instead. It doesn't seem likely that description will change, but there's no guarantee that it will continue to be valid input for initWithString: which has a strict input requirement:
A string that specifies a date and time value in the international string representation format — YYYY-MM-DD HH:MM:SS ±HHMM, where ±HHMM is a time zone offset in hours and minutes from GMT (for example, “2001-03-24 10:45:32 +0600”).
You must specify all fields of the format string, including the time zone offset, which must have a plus or minus sign prefix.
If your string differs in any way (including, as has become apparent, having quotes in it), you'll get nil.

Related

Why set types in Obj-c fast enumeration loops?

NSMutableArray *array = [[NSMutableArray alloc] init];
NSString *string = #"string";
[array addObject:string];
NSDate *date = [[NSDate alloc] init];
[array addObject:date];
for (*placeholder* stuff in array)
NSLog(#"one");
If I change placeholder to either NSString* or NSDate*, I expect to see "one", because the for loop should just ignore a non-matching type. However, the result is "one one".
Doesn't this imply that you should just have placeholder be id whatever the situation, since it doesn't seem to matter anyhow?
fast enumeration always iterates over all object in a collection. it does not filter.
The only thing that happens is, that you will have some strange casts.
if your array contains objects of differnt classes, you can determine the class for each object with isMemberOfClass:
if you would do for (NSDate *obj in array), any object in the array will be casts to NSDate, no matter if that is sense-full or not. and due to the nature of objective-c it will even work, as-long as you dont send a message that is only understandable by NSDate objects or send the object as an argument to a method that needs to receive a date object, as a cast does not change the object in anyway. A cast is just a promise you make to the compiler that you know what you are doing. Actually you also can call it a lie.
To answer your question title itself: You dont have to set the class inside the loop statement. the generic object type id is sufficient. But usually you have objects of one kind in an array — views, numbers, string, dates,…. by declaring the right class you gain some comfort like better autocompletion.
Yes, using id (or some other common ancestor class) is the correct approach, and then it's necessary to determine which type of class has been enumerated in order to handle it differently:
for (id obj in array)
{
if ([obj isMemberOfClass:[NSString class]])
{
NSString *str = (NSString *)obj;
NSLog("obj is a string: %#", str);
}
else if ([obj isMemberOfClass:[NSDate class]])
{
NSDate *date = (NSDate *)obj;
NSLog("obj is a date: %#", date);
}
}
The problem has nothing to do with fast enumeration, but with collections which can contain any type of object. The same question arises when you access an individual element of an array:
id lastObject = [array lastObject];
or
NSString *string = [array lastObject];
Which will you chose? It all depends on your code. If you're sure that array only contains strings, then in my opinion it is better to use the second choice, because you get additional type checking, autocompletion, and method matching from the compiler (i.e. you won't get warnings if you call a method that has different signatures for two different objects). The same applies to fast enumeration: if your collection can contain any kind of object, use id. If you know what it contains, use the specific type. (And the same also applies to block tests. In NSArray's method
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
if you know it only contains strings for instance, you can replace id with NSString * in the block arguments. It won't change at all the compiled code or the behavior of your application, it will only change the compiler type checking.

Constant value of NSString representation

I have a PList where I load a couple of rows of data in a dictionary. I want to add the a line like
<key>StandardValue</key>
<string>STANDARDVALUEFORCERTAININSTANCE</string>
Now when I read out the values I get a NSString. How can I get the value of the constant that I previously defined with
#define STANDARDVALUEFORCERTAININSTANCE 123
Is there a way to get the constant representation of a string? So essentially to parse it?
What you want to do isn't exactly possible. The constants created with #define only exist at compile-time, and at run time there is no way to access them by name - they have been converted to the constant value already.
One alternative that might exist is to define a number of methods that return constant values, say in a Constants class. Then, at run time, load the name of the method from the plist and call it using NSSelectorFromString() and performSelector:.
However, a possible issue with this is that for safety with performSelector: you'd have to rewrite all your constants as Objective-C objects (since performSelector: returns type id). That could be quite inconvenient.
Nevertheless, here is an example implementation of the Constants class:
#implementation Constants : NSObject
+ (NSNumber *)someValueForACertainInstance
{
return #123;
}
#end
And example usage:
NSDictionary *infoDotPlist = [[NSBundle mainBundle] infoDictionary];
NSString *selectorName = infoDotPlist[#"StandardValue"];
SEL selector = NSSelectorFromString(selectorName);
NSNumber *result = [Constants performSelector:selector];
And how the selector name would be stored in the info plist:
<key>StandardValue</key>
<string>someValueForACertainInstance</string>
You can't do it this way. I suggest a nice alternative: KVC.
You declare this variable as class instance:
#property (nonatomic,assign) int standardValueForCertainInstance;
Then you get the value with valueForKey:
NSString* key= dict[#"StandardValue"];
int value= [[self valueForKey: key] intValue];

Why does this typecast not apply itself?

I've been working on this problem for a while and can't seem to find the solution. In my app, I uniquely identify contacts from ABAddressBook by their creation date, as no two contacts can be created at the same time. In my app, the creation date is stored in a string called uid, as you can see below. I am checking to see if two contacts are the same, and I do this by performing the following conditional statement:
NSString *uid = #"some string";
if([(__bridge_transfer NSString *)ABRecordCopyValue(currentPersonRef, kABPersonCreationDateProperty) isEqualToString:uid]) {
//Do some code
}
However, when I run this code, I get the following error:
[__NSDate isEqualToString:]: unrecognized selector sent to instance 0x8c7c770
I'm pretty sure the problem is that ABRecordCopyValue() is returning an NSDate object that is not being casted to NSString by (__bridge_transfer NSString *), so when I compare it to uid using isEqualToString, it crashes. The thing is, I thought that by casting it to an NSString object, isEqualToString: would work. My question is:
Why is the (__bridge_transfer NSString *) not casting the NSDate object?
EDIT:
The answers provided have been useful, but they do not completely address my problem (well actually they do, but I have a follow-up question). In another part of my code, I run this:
NSString *uid = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
And if I NSLog uid I get this:
2012-10-28 21:55:29 +0000
So how would I compare uid in the above conditional statement if isEqualToString: doesn't work?
A cast never changes the class or type of an object. It just tells the compiler, that you are certain about the fact, that this object is of another type, than the compiler might assumes.
So if you cast a date object to a string, it will still be a date object.
In OOP you usually needs up-casting: a method wants you to pass in a object, but actually you pass in an object of a subclass. If you then need to access a property or method that is defined by the subclass, you will tell the compiler, that you have an object of the subclass by casting.
A very common example in iOS is tableview:cellForRowAtIndexPath: with subclassed cells.
In cocoa(-touch) you also know casting from toll-free bridging. see this documentation for valid casts.
The answers provided have been useful, but they do not completely
address my problem (well actually they do, but I have a follow-up
question). In another part of my code, I run this:
NSString *uid = (__bridge_transfer NSString *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
And if I NSLog uid I get this:
2012-10-28 21:55:29 +0000
You are creating a variable, that you declare to be an NSString, but actually the object you assign to it, is a date object.
NSDate *uidDate = (__bridge_transfer NSDate *)ABRecordCopyValue(currentPerson, kABPersonCreationDateProperty);
NSString *uid = [NSString stringWithFormat:uidDate];
Well, I don't know the answer to your question, but why don't you just convert the ABRecordCopyValue() to NSString, then do the isStringEqual:?
Like -
NSString *string = [NSString stringWithFormat:#"%#", ABRecordCopyValue(currentPersonRef, kABPersonCreationDateProperty)];
if([string isEqualToString: uid]){
//do your thing
}

Why this strange behavior is occurring with this code? objective-c

I have a method (the code below is a simplified version) that parses small text files:
- (void)parseFile:(NSString *)aFile
{
NSDate *date;
NSNumber *number;
NSString *desc;
NSString *txt = [NSString stringWithContentsOfFile:aFile encoding:NSUTF8StringEncoding error:nil];
for (NSString *line in [txt componentsSeparatedByString:#"\n"]) {
if ([linesubstring isEqual:#"mydate"]) {
date = [dateFormat dateFromString:strDate];
}
if ([linesubstring isEqual:#"mynumber"]) {
number = [numberFormat numberFromString:strValue];
}
if ([linesubstring isEqual:#"mydesc"]) {
desc = [line substringWithRange:NSMakeRange(0, 10)];
}
if (!date && !number && !desc) {
...do something...
}
}
}
The first problem is that date variable is being filled with the content of aFile parameter. It only assumes it's correct value, when the passes through the fist if/check.
So why? I though that date could be a reserved word and exchanged it, but with the same behavior.
The second problem is with the last if (with the nested ones). Debuging the code, i can see that xcode shows it as "out of scope", but !number fails (xcode thinks that it's valid)...
I tried other combinations, like [number isNotEqualTo:[NSNull null]] (this one throws an error EXC_BAD_ACCESS), without success.
Please, could anybody give some hints? I'm newbie with cocoa/objective-c. I'm coming from java...
TIA,
Bob
There's quite a few things wrong with the code you've provided. I'm using the answer box because there isn't enough room for this to be a comment:
With regards to your variable declarations:
NSDate *date;
NSNumber *number;
NSString *desc;
You have correctly declared them, but you have not initialised them. As they are, they could be pointing to any random garbage. This means that your test at the end of the loop…
if (!date && !number && !desc) {
...do something...
}
…may in fact always execute because date, number and desc may always be non-zero (I say may because it is actually undefined whether they are zero or non-zero). Initialise each of them to nil if you plan to determine whether they are set or not:
NSDate *date = nil;
NSNumber *number = nil;
NSString *desc = nil;
It is not always necessary to initialise variables (for example, as long as you write to it before you read from it, it is not necessary to initialise it), however some people promote the idea of initialising all variables to prevent this undefined behaviour from surfacing (I typically initialise all variables even if I overwrite the initialised value anyway).
Also, there is a variable called linesubstring but it is not declared anywhere in the code, similarly strDate, strValue are not declared anywhere either. It is important to know how these are declared and how these are used as they may similarly be pointing to garbage.

Replace array display method?

I am curious how I might override the description method that is used when you do the following (see below) for an object. I basically want to better format the output, but am unsure about how I might go about setting this up.
NSLog(#"ARRAY: %#", myArray);
many thanks
EDIT_001
Although subclassing NSArray would have worked I instead decided that I would add a category to NSArray (having not used one before) Here is what I added ...
// ------------------------------------------------------------------- **
// CATAGORY: NSArray
// ------------------------------------------------------------------- **
#interface NSArray (displayNSArray)
-(NSString*)display;
#end
#implementation NSArray (displayNSArray)
-(NSString*)display {
id eachIndex;
NSMutableString *outString = [[[NSMutableString alloc] init] autorelease];
[outString appendString:#"("];
for(eachIndex in self) {
[outString appendString:[eachIndex description]];
[outString appendString:#" "];
}
[outString insertString:#")" atIndex:[outString length]-1];
return(outString);
}
#end
gary
If you're doing this a lot, the easiest way to reformat the display of your array would be to add a new prettyPrint category to the NSArray class.
#interface NSArray ( PrettyPrintNSArray )
- (NSSTring *)prettyPrint;
#end
#implementation NSArray ( PrettyPrintNSArray )
- (NSString *)prettyPrint {
NSMutableString *outputString = [[NSMutableString alloc] init];
for( id item in self ) {
[outputString appendString:[item description]];
}
return outputString;
}
#end
Obviously you'd need to alter the for loop to get the formatting the way you want it.
I'm assuming that you myArray variable is an instance of the NSArray/NSMutableArray class.
When NSLog() encounters the # character in its format string, it calls the -description: method on the object. This is a method on the root class, NSObject from which all other Cocoa classes inherit. -description: returns an NSString allowing any object that implements this method to be passed into NSLog(#"#",anyObject) and have a nicely formatted output. The string returned can be anything you care to construct.
For your specific problem, you could subclass NSMutableArray and override the -description: method with your own implementation. Then utilise your subclass instead of NSMutableArray.
For more information on NSObject and -description: see Apple's docs.
From Formatting string objects:
NSString supports the format characters defined for the ANSI C functionprintf(), plus ‘#’ for any object. If the object responds to the descriptionWithLocale: message, NSString sends that message to retrieve the text representation, otherwise, it sends a description message.
So to customize array conversion to string you should change NSArray descriptionWithLocale: implementation. Here's an example of how you can replace object method in run-time.