Crash-proof NSDictionary - iphone

I'm looking to create a "crash-proof" NSDictionary as I'm using a JSON serializer that converts a server response into an NSDictionary. As as result, sometimes the key for the dictionary is not present. Currently, this will crash the application, however I'd rather an empty NSString was returned so I can display this in the interface.
A solution could be that I check for the key every time I access the dictionary, e.g.
if([returnedDictionary objectForKey:#"key"]){
// Display [returnedDictionary objectForKey:#"key"];
}else{
// Display #"";
}
However this soon results in bloated, hard-to-read code.
I had thought about creating a custom NSDictionary object, something like:
#interface NSSafeDictionary : NSDictionary .....
that overrides objectForKey with the above statement.
Is this a satisfactory approach?
Thanks

Are you always going to want to get strings out of your dictionary or will other objects be stored in it as well? If it's only strings, I think the easiest way around this is to construct a category on NSDictionary.
#interface NSDictionary ( EmptyStrings )
- (NSString *)stringForKey:(id)aKey;
#end
#implementation NSDictionary ( EmptyStrings )
- (NSString *)stringForKey:(id)aKey {
id object = [self objectForKey:aKey];
if (object == nil ) {
return #"";
}
if ([object isKindOfClass:[NSString class]) {
return object;
} else {
return nil;
}
}
#end

Given that it comes in over the network, I would think that you would want to sanitise the data more than just checking for empty values but if not, you don't really need to inherit from NSDictionary.
A simple utility method in your class would do the trick.
Or you could create a category on NSDictionary:
#interface NSDictionary (Safe)
-(NSString*)safeStringForKey:(NSString*)key;
#end
(I'm sure you can figure out the implementation.)

Related

Find Existence of Key in NSDictionary

How can we identify the object is available for a particular key. I have tried following:
for(NSDictionary *item in jsonArray){
if([item objectForKey:#"EventDate"])
NSLog([item objectForKey:#"EventDate"]);
}
This is getting crash the code with error:
-[__NSCFString objectForKey:]: unrecognized selector sent to instance 0x6a567b0
I have also find many posts that is showing objectForKey will return nil if a key doesn't exists. Than my question is there is also a method in NSDictionary class that is "setNilValueForKey". How is this possible that we cannot specify the NSDictionary key with nil object and also we have the method to set nil value for object in NSDictionary.
Please Suggest on first and also make me clear on second query.
1) Your jsonArray contains other types of objects than NSDictionaries, including at least one NSString. NSString doesn't respond to objectForKey: so it throws an exception when you try to call it. You'll have to look at the JSON to determine how to proceed with whatever you were doing.
2) There is an NSObject method setNilValueForKey: which is related to key-value coding. This isn't really related to NSDictionary. If you really need to represent nil in your dictionary, set [NSNull null] as the object for your key that represents nil.
Hope this helps!
Not all the objects in your array are dictionaries. You need to check what kind of data you're processing before you work on it. Something like this:
for(NSObject* item in jsonArray) {
if ([item isKindOfClass:[NSDictionary class]]) {
// do dictionary stuff
}
else if ([item isKindOfClass:[NSString class]]) {
// do string stuff
}
}
item is not a NSDictionary its a String. So check your jsonArray it may be contains only strings not dictinaries
Answer to second query
There is a non-nil object called NSNull that is built specifically to represent nils in situations where "plain" nil is not acceptable. If you replace your nils with [NSNull null] object, NSDictionary will take them. You would need to check for NSNull on the way out, though.
Refer more here
Answer to first query
for(id *item in jsonArray)
{
if([item isKindofClass:[NSDictionary class])
{
NSArray *allKeys = [item allKeys];
for(NSString *strKey in allKeys)
{
if([strKey isEqualToString:#"EventDate"])
{
// EventDate key has object
NSLog([item objectForKey:#"EventDate"]);
}
}
}
}

NSMutableArray lastObject not working in UITableView iPhone?

I want to get the last object from an NSMutableArray. When I use the code below to get the last object from the NSMutableArray it is not finding it:
if ([mutableArray lastObject])
{
NSLog(#"Last Object");
}
else
{
NSLog(#"Not a Last Object");
}
When ever I call this code the if statement is only calling for every objects from an array. Can anyone please help me to solve this?
[mutableArray lastObject] returns an id typed object which is the last object of your array. it is not a test.
To see it you should write something like :
id myLastObject = [mutableArray lastObject];
NSLog(#"my last object is : %#",myLastObject);
edit :
What do you mean by check last object ?
checking equality with the isEqual method checks if both objects are same objects, meaning only one object but you might wanna check if two objects have same properties, thus checking if properties have same values.
if ([someObject isEqual:[mutableArray lastObject]]) {
NSLog(#"Last Object");
}

Issues converting nested Plist to NSDictionaries & Arrays

Given this P-List Dictionary:
How do I get at the 3rd. Key - "Dinner" - which in itself is also a Dictionary, and parse its values correctly?
Or, should I structure this P-List differently to begin with, so I can get at everything more easily?
Here's what I got, starting by grabbing all the Keys from my 'MenuDictionary' and storing them in an Array:
// Load the Property-List file into the Dictionary:
MenuDictionary = [[NSDictionary alloc] initWithContentsOfFile:menuPath];
// Get all the Keys from the Dictionary, put 'em into a 'mealTimesArray':
mealTimesArray = [[MenuDictionary allKeys] sortedArrayUsingSelector:#selector(compare:)];
// For each meal-type KEY, grab all the Values (dishes) in it and store them in a 'mealDishesArray':
for (NSString *mealTime in mealTimesArray) {
NSArray *mealDishesArray = [MenuDictionary valueForKey:mealTime];
// Now I can iterate through the 'mealDishesArray' to access each dish at
// a time, so I can print them out or do whatever else:
for (NSString *dish in mealDishesArray) {
NSLog(#"Iterating through 'mealDishesArray' now...");
NSLog(#"Current 'dish' is: %#", dish);
The problem occurs when I get to the "Dinner" key: its a Dictionary, containing 2 Keys with 2 array Values. So how do I load its contents into a Dictionary object? More specifically, what 'init' method should I be using to load the "Dinner" contents into my new Dictionary object?
I tried this - doesn't work:
// I put this inside the first fast-enum loop:
if ([mealTime isEqualToString: #"Dinner"]) {
// init new Dictionary object (declared previously):
dinnerDictionary = [[NSDictionary alloc] initWith ???];
I'd like to init it with the contents of the "Dinner" Key, but its not a P-List file obviously, so I can't use
initWithContentsOfFile: pathName
I don't understand which of the other init methods will give me access to both the Keys and Values of "Dinner". Because even though "Dinner" is structured as a Dictionary, its currently sitting inside an Array, which doesn't regard it as a Dictionary (I think...)
I'm a little unclear about this obviously.
Or, should I be structuring my P-List differently to begin with so I can get at this nested Dinner dictionary?
Any ideas?
I think plist structure makes sense, and dealing with the contents conditionally based on class is perfectly okay, too. I would react to what's in the plist within a reasonable range of expectations, so...
// for each meal...
for (NSString *mealTime in mealTimesArray) {
// we're not sure what kind of meal we have
id mealInfo = [MenuDictionary valueForKey:mealTime];
if ([id isKindOfClass:[NSArray self]]) {
// it's an array? cast as an array and deal with the array
NSArray *mealDishesArray = (NSArray *)mealInfo;
[self handleMealArray:mealDishesArray];
} else if ([id isKindOfClass:[NSDictionary self]]) {
// it's a dictionary? that's cool, too. cast as a dictionary and deal with it
NSDictionary *mealDictionary = (NSDictionary *)mealInfo;
[self handleMealDictionary:mealDictionary];
}
}
// you've worked out to handle the array
- (void)handleMealArray:(NSArray *)mealDishesArray {
for (NSString *dish in mealDishesArray) {
NSLog(#"Iterating through 'mealDishesArray' now...");
NSLog(#"Current 'dish' is: %#", dish);
}
}
// handle the dictionary like a dictionary, realizing that it contains
// arrays, which you've already worked out how to handle
- (void)handleMealDictionary:(NSDictionary *)mealDictionary {
for (NSString *dishType in [mealDictionary allKeys]) {
NSArray *mealDishesArray = [mealDictionary valueForKey:dishType];
[self handleMealArray:mealDishesArray];
}
}
The 'problem' is with this line:
NSArray *mealDishesArray = [MenuDictionary valueForKey:mealTime];
when mealTime is 'Dinner' you are assigning mealDishesArray a value that is an NSDictionary. Thinking you have an array you then use:
for (NSString *dish in mealDishesArray)
to iterate over the elements in the array which is not going to give you what you expect for 'Dinner'. You might consider adding something like:
NSAssert ([mailDishesArray isKindOfClass: [NSArray class]], #"Expecting an array");
after your assignment to mealDishesArray.
What is the solution? Your PLIST has a totally different structure between 'Breakfast,' 'Lunch,' and 'Dinner.' Why is 'Dinner' a NSDictionary and the others are NSArray? Make them all the same type. If they can't be, then you must conditionalize your code based on:
if ([mealTime isEqualToString: #"Dinner"]) {
NSDictionary *dinnerDictionary = (NSDictionary *) [MenuDictionary valueForKey:mealTime];
/* ... */ }
You don't need to alloc anything or read anything from file; you already have a dictionary for the 'Dinner' data.

Access NSDictionary via dot notation?

Is there a way via dot notation to access the values of keys in an NSDictionary like this?
NSDictionary *returnVal = [NSDictionary dictionaryWithObjectsAndKeys:#"Saturn", #"name", #"Gas Giant", #"type", nil];
NSLog(#"VALUE: %#", [returnVal valueForKey:#"name"]); // This is how I am doing it now.
There is no dot syntax for NSDictionary, but should consider using objectForKey: instead of valueForKey:
Difference between objectForKey and valueForKey?
Not really, no.
The dot notation is a shorthand way of calling a method with that selector name. In other words, this...
NSLog(#"Hello, %#", foo.bar.name);
...is the same as this...
NSLog(#"Hello, %#", [[foo bar] name]);
When I say "same", I mean they are compiled down to the same code. It's just syntactic sugar.
A plain NSDictionary won't act that way. You could sort of fake it with Key Value Coding, which lets you call valueForKeyPath to get properties like this:
NSLog(#"Hello, %#", [foo valueForKeyPath:#"bar.name"]);
If you really wanted to be able to write foo.bar.name in your code, however, you'd have to make a custom class that overrides forwardInvocation:; this lets you catch an unknown message to an object and do something else with it besides throw an error. In this case, you could change the unknown selector to a lookup on an NSDictionary instance it contains.
But even if you did that, the compiler would probably still generate warnings unless you made header files that declared those property names to exist.
I agree with most of the answers that NSDictionary should be accessed with objectForKey: or similar methods. However it is possible to allow for dot notation access to a NSDictionary, and for learning purposes this might be interesting for someone. Also when for example your are retrieving large JSON dictionaries via AFNetworking, this method can ease the access and readability of your code.
This is my solution:
DictionaryProperties.h: (class wrapping the NSDictionary for property access)
#interface DictionaryProperties : NSObject{
NSMutableDictionary* _backingDict;
}
#property (nonatomic, strong) NSMutableDictionary* backingDict;
+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict;
#end
DictionaryProperties.m:
#import "DictionaryProperties.h"
#implementation DictionaryProperties
#synthesize backingDict = _backingDict;
- (id) initWithDictionary:(NSDictionary*)dict {
if (self) {
if ([dict isKindOfClass:[NSMutableDictionary class]]) {
self.backingDict = (id)dict;
} else {
self.backingDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
}
}
return self;
}
+ (DictionaryProperties*) allocWithDictionary:(NSDictionary*)dict {
return [[DictionaryProperties alloc] initWithDictionary:dict];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString* key = NSStringFromSelector(invocation.selector);
invocation.selector = #selector(objectForKey:);
[invocation setArgument:&key atIndex:2];
if ([self.backingDict objectForKey:key]) {
[invocation invokeWithTarget:self.backingDict];
} else {
[self doesNotRecognizeSelector:invocation.selector];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.backingDict methodSignatureForSelector:#selector(objectForKey:)];
}
#end
ExampleDictContent.h: (class declaring what is inside the dictionary)
#import "DictionaryProperties.h"
#interface ExampleDictContent : DictionaryProperties
#property (strong, nonatomic) NSString* someData;
#property (strong, nonatomic) NSString* someOtherData;
#end
#implementation ExampleDictContent
#end
Usage: (simple declaration of a dictionary, allocation of wrapper and property access)
#import "ExampleDictContent.h"
NSDictionary* d = [NSDictionary dictionaryWithObjects:NSArray arrayWithObjects:#"someData content", #"someOtherData content", nil
forKeys:NSArray arrayWithObjects:#"someData", #"someOtherData", nil];
ExampleDictContent* dictWProps = [ExampleDictContent allocWithDictionary:d];
NSLog(dictWProps.someData);
NSLog(dictWProps.someData);
This will print:
someData content
someOtherData content
So basically DictionaryProperties works as a facade for accessing the NSDictionary. It uses forwardInvocation to convert a get-property method call into a getObjectForKey: call on the dictionary. What I like about it, is that it allows for autocompletion on the dictionary, and also allows me to explicitly declare what keys I want to access (in the ExampleDictContent.h file). Note that this solution does not allow for write access to the properties, but that can be added as shown in the link below.
This solution has partly been inspired by karstenlitsche's solution. The main difference is that this solution is based on sub-classing instead of categories.
No, I don't think so.
From the reference manual.
Accessing Keys and Values
– allKeys
– allKeysForObject:
– allValues
– getObjects:andKeys:
– objectForKey:
– objectsForKeys:notFoundMarker:
– valueForKey:
That's listed as the only way to access the keys and the values. So you are doing it alright.
You would be able to access it if the keys were a public property and it was readable.
The way that you have mentioned for accessing element of dictionary is ideal way(using keys).
If you want to do something else, might be you can use-
NSArray *allValues = [returnVal allValues];
Now using this array as well you can perform tasks.
And if you want something specific then mention that, might be for that there can be some other way.
Also as NSDictionary class won't have any property defined, so dot notation is directly not possible.
No, you are doing it the correct way. In the iOS world, often the correct way is the only way. :)
If you really want dot notation (and other nice things you get with typed objects), you're going to have to stuff the dictionary representation into an object. Most commonly my interface will look like:
#interface FooBar : NSObject {
NSString *someData;
int someNumber;
}
#property (nonatomic, copy) NSString *someData;
#property (nonatomic, assign) int someNumber;
+ (FooBar *)FooBarFromDictionary:(NSDictionary *)dataDict;
#end
The implementation should be clear. Then you can
FooBar *fb = [FooBar FooBarFromDictionary:data];
NSLog(#"fb.someData = %#", fb.someData);
Technically, you can do something like this:
typedef id (^valueBlock)(id);
#interface NSDictionary(dotNotationAddons)
#property(nonatomic, readonly) valueBlock value;
#end
#implementation NSDictionary(dotNotationAddons)
-(valueBlock) value
{
return [[^(id key) {
return [self objectForKey:key];
} copy] autorelease];
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"1", #"One", #"2", #"Two", #"3", #"Three", #"4", #"Four", nil];
id value = dictionary.value(#"One");
NSLog(#"%#", value);
}
return 0;
}
I don't know if that is what you were looking for, but I hope it helps!
The answer's still no, but you can use the shorthand
myDictionary[#"key"]
instead of
[myDictionary objectForKey:#"key"]
In Swift, there is a solution that may not seem very elegant but does the trick.
It will require a typeAlias for each specific type of Dictionary and also an extension with variables (with getter/setter) for each of the expected keys in your dictionary. Not a good practice at all
It may be easier wrap your dict object in an object (class/struct) with the same treatment.
typealias MyDict = [String:AnyObject]
extension MyDict {
var key: AnyObject? {
get { return self["key"] }
set { self["key"] = newValue }
}
}
// Usage
var myDict = MyDict()
// Get the value
myDict["key"] = "value1" as AnyObject
if let str = myDict.key {
print(str) // prints "value1"
}
// Set the value
myDict.key = "value2" as AnyObject
if let str = myDict["key"] {
print(str) // prints "value2"
}

Question about deep copying example

In the Beginning iPhone 4 book, the author has this code to create a category for creating a deep copy of an NSDictionary that has an NSArray of names for each letter of the alphabet to show an example of an indexed table with a search bar.
#import "NSDictionary-MutableDeepCopy.h"
#implementation NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy];
else if ([oneValue respondsToSelector:#selector(mutableCopy)]) oneCopy = [oneValue mutableCopy];
if (oneCopy == nil)
oneCopy = [oneValue copy];
[returnDict setValue:oneCopy forKey:key];
[oneCopy release];
}
return returnDict;
}
#end
Can someone explain the for loop logic? I'm not sure what he's trying to do in seeing which value responds to which selector, and why it would be added to the dictionary. Thanks.
So, the for loop simply iterates through all the keys in the dictionary. Beforehand, we create a new dictionary called returnDict - this will be what we return.
For each key in the dictionary we want to copy, we...
Get the object stored for that key ([self valueForKey:key]), and save it into a variable called oneValue.
If oneValue implements our mutableDeepCopy method (ie, it's an NSDictionary) go call it, and assign the return value into a variable called oneCopy.
Else, we see if oneCopy implements the mutableCopy method. If it does, we put the output into the oneCopy variable.
At this point, we check to see if following steps (2) and (3) the oneCopy variable has had anything assigned to it (if (oneCopy == nil)). If it doesn't (ie, it's equal to nil) we can assume the object doesn't implement either mutableDeepCopy or mutableCopy, so we instead call a plain old copy and assign its value to oneCopy.
Add oneCopy into our returnDict dictionary using the original key.
That's the for loop - at the end of it all, we go and return the copied dictionary.
The logic in the for-loop is convoluted because the author is trying to get as mutable and as deep a copy of the entire array as possible. The code tries three different ways to satisfy this, in order of preference:
Use mutableDeepCopy if possible (if the object understands that message).
Otherwise, use mutableCopy if possible.
If all else fails, just use copy.
If the object is just plain not copiable, your code goes boom when it sends the object -copy, since no test is made for whether the object responds to -copy. This is appropriate, since trying to deep copy an array containing items that cannot be copied is definitely programmer error.