I have a class that I use to setup objects in an array. In this class I have a custom "initWithDictionary", where I parse a JSON dictionary. However, as I am running into NSNull, this crashes my app. To get around this, I set up a class that handles exceptions, so when a string is NSNull, it's replace it with #"". or -1 for integers.
This is my NullExtensions class:
#interface NSNull (valueExtensions)
-(int)intValue;
-(NSString *)stringValue;
#end
#implementation NSNull (valueExtensions)
-(int)intValue {
return -1;
}
-(NSString*)stringValue {
return #"";
}
#end
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string. Only if I do the following (and the string is not null):
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
Is stringValue incorrect in this case? And if so, how do I use it properly in order to setup proper NSNull replacements?
Thx
You really really don't want to add a category to NSNull that adds such common methods. That will change the behavior of NSNull for all instances in the application, including ones created by the underlying frameworks solely for their private use.
If you need a value class that represents the notion of "value doesn't exist and therefore I'm going to return these default values instead", create a class or instance that represents exactly that.
As for why it crashes, I couldn't tell you without seeing the actual details of the crash.
And, yes, it really is THAT bad to add a category to a class that adds such a common method. All it takes is one bit of code in a plug-in or framework that does:
if ([fooMaybeNull respondsToSelector: #selector(intValue)] bar = [fooMaybeNull intValue];
Not terribly farfetched -- I have had to debug nasty crashers or misbehaviors due to exactly this kind of willy-nilly category addition.
If you are going to add methods to a class via categories, prefix your method names so as to isolate them from existing functionality. It is still fragile, but manageably so.
Instead of creating categories on NSNull, for which you would also have to add a similar category to NSString (that's why it crashes, because real strings do not respond to stringValue) - instead try creating a helper category on NSDictionary like "stringForKey" that uses the code Johan posted and returns an NSString, probably also should enforce all other types get mapped to empty strings as well.
The NSNull extensions you have written look ok to me but using a method like stringValue may be confusing since other classes like NSNumber use this.
Personally though, I think NSNull replacement in this instance is unnecessary. If you just made a quick test you can replace the NSNull where you need to. e.g.
id testObject = [parsedDictionary objectForKey:#"book_title"];
self.bookTitle = testObject==[NSNull null] ? #"" : testObject;
You are asking an NSString for its stringValue. No need to convert a string to a string.
Try this:
if (![[parsedDictionary objectForKey:#"book_title"] isKindOfClass:[NSNull class]]) {
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
} else {
self.bookTitle = #"";
}
Edit: You should not use the category on NSNull you created. You don't need it, nor should you want it. If the source for the dictionary inserts NSNull instances, go ahead and use my code above. Normally you would expect to simple have no value inserted for the key, at which time you can simple see if [parsedDictionary objectForKey:#"book_title"] returns anything.
Are you sure that the dictionary is returning [NSNull null]? By default, dictionaries return nil, not [NSNull null], when an value isn't found for a key.
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string.
That makes sense, since stringValue is not a valid method on NSString. It will work for NSValue and its subclasses, but not NSString.
Related
I am having two arrays, Namely
NMutableArray* first;
NMutableArray* second;
Now I am copying first object to the second array like
for (int i=0;i<first.count; i++)
{
[second addObject:[first objectAtIndex:i];
}
This is ok. I don't know how to access the value of the First Array. I tried like this ,
[second addObject:[[first objectAtIndex:i]name]];
I want to get the name value which is in the first object of first array. I tried using the above line, it is showing some warning. Please help me
Assuming you started with an array like this:
NSArray *array1 = #[#{#name : #"Fred"},
#{#name : #"Bill"}];
You could create a second array that contains the value of a given property of each element of the first array as follows:
NSArray *array2 = [array1 valueForKey:#"name"];
If you then logged the second array...
NSLog(#"%#", array2);
...the resulting output would be
2012-04-18 16:26:11.226 ExampleRunner[23320:707] (
Fred,
Bill
)
EDIT
Note that this will work regardless of whether the objects in the first array are instances of NSDictionary as shown in the example above, or instances of a class or classes that have a name property or instance variable (or an _name instance variable, for that matter). For more information on how and why this works, see the documentation for the NSKeyValueCoding informal protocol:
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html
The brackets are currently in the wrong place:
[second addObject:[[first objectAtIndex:i] name]];
Updated Answer:
Again, I think you should split stuff out into easy to parse lines of code:
for (id theObject in first)
{
// without an actual type, I still think the compiler might
// throw a warning on this next line of code;
// but maybe RJR III is correct and it won't warn.
// I didn't check.
NSString * nameOfObject = [theObject name];
if(nameOfObject)
{
[second addObject:nameOfObject];
}
}
Notice that I do some error checking in here as well (i.e. making sure the name is not nil).
Original Answer:
You're getting a warning because the compiler doesn't know what kind of custom object is being fetched from your call to "[first objectAtIndex: i]". In other words, it doesn't know what kind of object you're trying to get the "name" of.
Cast it to the right type and you'll get rid of the warning.
Or even better, split that one line of multiple things happening at once into two or three lines of code and make your code more readable in the process.
I sometimes like to organize IB elements into NSArrays primarily to help me organize my elements. Most often, different classes of objects make it into the same array with each other. While this is a convenient way of organization, I can't seem to wrap my head around why if I have an array like this:
NSArray *array = [NSArray arrayWithObjects:((UITextField *)textField), ((UISegmentedController *)segmentedController), nil];
Why I get "Does not respond to selector" messages when I put a for loop like this:
for (UITextField *text in array) {
[text setText:#""];
}
The for loop seems to be passed objects that are not of class UITextField.
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
EDIT Just for reference, this is how I'm handling it as of now:
for (id *object in array) {
if ([object isMemberOfClass:[UITextField class]]) {
foo();
} else if ([object isMemberOfClass:[UISegmentedController class]) {
bar();
}
}
When you do
for (UITextField *text in...
the object pointers from the array are cast to UITextField* type - so if the object isn't actually a UITextField, all sorts of weird things may happen if you try to call UITextField methods.
So instead use the id type (no * needed, btw):
for (id obj in array)
Then check the type as you do and call the appropriate methods. Or, filter the array to get only objects of a certain type, then go though that type only:
for (UITextField* text in [array filteredArrayUsingPredicate:...])
Edit: here's how to build class filter predicates:
Is it possible to filter an NSArray by class?
What is the point of declaring the object's class if all objects in the specified array are passed through the loop?
The class name is just there to let the compiler know what it should expect to find. This allows it to try to figure out what methods it should expect you to call and how you might treat the object. It's the same idea as passing in an int to a method that takes float. The method will not ignore ints - it's assuming you're passing the correct type. You're just giving this construct a little more credit than it's due:
for (UITextField *text in array)
It just doesn't have that functionality. How you're handling it now is the correct way.
Are you sure you don't get an error when you run that code? The "does not respond to selector" message is a runtime error, not a compile time error. The compiler has no idea whether the objects in the array implement -setText:, but you should certainly get an error when you actually send that message to an instance of UISegmentedControl.
Another possibility is that you've got a class called UISegmentedController that does have a -setText: method. The name of the class that implements the multi-part bar-graph-looking user interface widget is UISegmentedControl. So either the code you're showing isn't real, tested code, or you've got a class that we don't know about.
Is there a return type for "any primitive" similar to the way you can use NSObject as the return type for any object? I tried using id, but the compiler was giving me an error that it was trying to convert a primitive to a reference.
Here's what I'm trying to do. :
-(void)setObject:(NSObject*)obj forKey:(NSString*)key {
[sharedInstance.variables setObject:obj forKey:key];
}
-(NSObject*)getObjectForKey:(NSString*)key {
return [sharedInstance.variables objectForKey:key];
}
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
-(???)getValueForKey:(NSString*)key {
return [sharedInstance.variables valueForKey:key];
}
The alternative that I have though of is to use separate methods (getIntForKey, getFloatForKey, etc.) to access the values.
1) Read Key-Value Coding Article in XCode documentation - all answers are there
2) There's an object NSValue, which resembles your "NSObject". NSValue can store plain-old-data inside itself.
PS
"Scalar and Structure Support
Key-value coding provides support for scalar values and data structures by automatically wrapping, and unwrapping, of NSNumber and NSValue instance values.
Representing Data as Objects
The default implementations of valueForKey: and setValue:forKey: provide support for automatic object wrapping of the non-object data types, both scalars and structs.
Once valueForKey: has determined the specific accessor method or instance variable that is used to supply the value for the specified key, it examines the return type or the data type. If the value to be returned is not an object, an NSNumber or NSValue object is created for that value and returned in its place.
Similarly, setValue:forKey: determines the data type required by the appropriate accessor or instance variable for the specified key. If the data type is not an object, then the value is extracted from the passed object using the appropriate -Value method."
I would have thought id is the perfect candidate here too... could this just be a casting issue you're seeing?
i.e. the id implicitly implies a pointer so in my mind I see id as an objective c equivalent to the c void*
In other words where you have a NSObject* you could replace this with id such as
-(NSObject*)myMethod1
{
}
so can be done for any returned primitive with
-(id)myMethod1
{
}
i.e. not an id*
Also I expect this was just a copy/paste thing but incase it also causes issues
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:num forKey:key];
}
should probably be
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
I eventually worked through this. The ultimate solution was to have separate accessor/mutator methods per type. So now I have setIntForKey, setBoolForKey, getIntForKey, getBoolForKey, etc. The drawback is quite obvious, in that I can't call one method to set values and another to retrieve them. The advantages are numerous, however. Because the compiler knows what object or primitive type the method is expecting at compile time, I gain compile time checking for all of these methods. Additionally, I don't have to worry with casting the retrieved values to their primitive types (obviously the returned NSObjects are a different story).
I've got a function called updateTheValue() that I have called using [self updateTheValue] for a while now. Two things happened recently; I added the method calling in the viewDidLoad() method and it spit out a warning saying my class may not respond to this. Second, I want to pass objects to updateTheValue() like strings, but mostly ints, so I declared an NSObject to pass into the method. Can an int fit into an NSObject slot, or what should I use instead?
I would have posted these seperately but they seem to be related since after updating updateTheValue() to accept an NSObject every reference to this function turns out the error that my class "May not respond to -updateTheValue"
You could make your method like this:
-(void)updateTheValue:(NSObject *)anObject
// or use -(void)updateTheValue:(id)anObject
{
if ([anObject isKindOfClass:[NSString class]]) {
// Do your string handling here
}
else if ([anObject isKindOfClass:[NSNumber class]]) {
// Do your number handling here
}
}
Use it like this:
[self updateTheValue:[NSNumber numberWithInt:42]];
I'd suggest doing two different methods though, i.e. updateTheValueWithInt: and updateTheValueWithString: making it easier to read and understand.
Make sure you make the method signature visible before using them, so that the compiler knows what this does.
If you use separate methods you can use int directly without wrapping them into NSNumber objects.
First problem:
updateTheValue() must be declared before you try to call it.
You can either move the definition of function before the calls to it, or add a prototype at the top - eg, add:
(void) updateTheValue;
near the top.
Second problem:
Use an NSNumber, eg [NSNumber numberWithInt:45];
I'm attempting to create an NSArray with a grouping of string literals, however I get the compile error "Initializer element is not constant".
NSArray *currencies = [NSArray arrayWithObjects:#"Dollar", #"Euro", #"Pound", nil];
Could someone point out what I'm doing wrong, and possibly explain the error message?
New syntax for creating an array with string literals:
NSArray *currencies = #[#"Dollar", #"Euro", #"Pound"];
To fix your complication error the code must be in a method. If you want to use it statically then create a class method that follows the singleton pattern.
This isn't a problem with the NSArray creation itself (you would get the same error if you wrote [NSArray array] instead), but with where you've written it. I'm guessing this is a global or file-static NSArray. In C, that kind of variable has to have a constant initializer — meaning not a function call (or, by extension, a method call). The solution is to put the actual creation and assignment of the array into a method that will be called before you need the array, such as initialize.
It sounds like Chuck has spotted the problem. One thing you want to be aware of though in coding your solution is that you'll want to avoid storing an autoreleased instance of NSArray in a static variable. Also, a common pattern for these situations is to write a class method that creates and returns the value stored in the static variable, like so:
+ (NSArray *)currencies
{
static NSArray *_currencies;
// This will only be true the first time the method is called...
//
if (_currencies == nil)
{
_currencies = [[NSArray alloc] initWithObjects:#"Dollar", #"Euro", #"Pound", nil];
}
return _currencies;
}
Although this is old, please notice that Apple committed a new patch to the llvm project adding support for new Objective-C literal syntax for NSArray, NSDictionary and NSNumber.
See here and here
I'm a newbie in objective-c, but I think that the correct code is:
NSArray *currencies = [[NSArray alloc] initWithObjects:#"Dollar", #"Euro", #"Pound", nil];
I am not sure tho.
There's nothing wrong with that code. Are you sure the error is being produced at that line?