Protective measures for accessing arrays and dictionaries

What is the most efficient way to protect against nil values ​​in nested dictionaries and arrays?

I have an array containing dictionaries containing arrays containing dictionaries, so I have a method that wants to return something like this:

return myArray[0][@"Categories"][0][@"Content"]; 

I currently have the following, but I am wondering if there is a cleaner or more elegant way to protect these nested structures:

 if (myArray) { // Add a check for myArray.count > 0 if ([myArray[0] objectForKey:@"Categories"]) { // Add a check for myArray[@"Categories"].count > 0 if ([myArray[0][@"Categories"][0] objectForKey:@"Content"]) { return myArray[0][@"Categories"][0][@"Content"]; } } } 

Is there a better way to do this? Are there any holes that I have not connected? Thanks in advance!

EDIT: I added comments to add to checking the number of arrays. With these additions, is my access to the object in myArray [0] [@ "Categories"] [0] [@ "Content"] safe?

+4
source share
6 answers

Neither arrays nor dictionaries can be nil . Thus, you are trying to protect against the requested key / index that does not exist.

If the dictionary key does not exist like that, you just get nil back and any nil message will be ignored.

The problem arises when you have an array and you are trying to query an index that is out of scope. So what you really have to do is check the count arrays, and not check for the keys.

+4
source

As others have said, neither NSDictionary nor NSArray can contain nil values. However, they may contain an (singleton) NSNull , and NSDictionary will return nil from objectForKey if the key is not found.

In terms of a “protective” link, especially with decoded JSON, you may need a few checks.

First of all, make sure you really have the expected kind of object (some JSON providers can return an external array for one access and an external dictionary for the next) using isKindOfClass : if ([iThinkItsADict isKindOfClass:(NSDictionary class)]) { ...

Next, if you are accessing an array, make sure that the index you are going to use is within the array (bearing in mind that the array can have null entries): if (arrayIndex < someArray.count) { ...

When a value is returned from the dictionary, it may be nil if the key was not found. Thus, you can test with if (returnedValue != nil) {... (although note that if the value "nil" is used to "send" a method call, the call will succeed without an error, but returns zero / zero. Therefore, it is not necessary to check zero.

A dictionary or array can return NSNull, with which you can check with if (returnedValue != [NSNull null]) {... (Some will object that you should not use == or != To compare objects for equality, but there is only one NSNull in the application.)

+2
source

Neither NSArray nor NSDictionary can be nil . They throw an exception if you try to create them with one or insert.

+1
source

I think it’s enough to use introspection, to check whether the objects you get are arrays or dictionaries, remembering that the zero target actions are ignored and they return zero:

 if([myArray isKindOfClass: [NSArray class]] && myArray.count) { // If myArray is nil isKindOfClass: and count will return 0 if([myArray[0] isKindOfClass: [NSDictionary class]]) { if([myArray[0][@"Categories"] isKindOfClass: [NSArray class]] && [myArray[0][@"Categories"].count) { // If it doesn't contain this key, objectForKey: returns nil // and nil targeted actions are ignored. if([myArray[0][@"Categories"][0] isKindOfClass: [NSDictionary class]]) { return myArray[0][@"Categories"][0][@"Content"]; // If the key is not contained in the dictionary it will return nil } } } } 
0
source

These are the category methods that I use to protect against JSON parsing when you are not getting an array / dictionary, which the spec will make you expect.

 @implementation NSObject (TWXNSObject) - (BOOL)exists { if ([[NSNull null] isEqual:self]) return NO; return YES; } // an empty JSON dictionary comes through as an array - (BOOL)isDictionary { if (!self.exists) return NO; if (![self isKindOfClass:[NSDictionary class]]) return NO; return YES; } @end 

The method for checking access to your array will look like this:

 - (BOOL)isIndexValid:(NSUInteger)idx { if (!self.exists) return NO; if (![self isKindOfClass:[NSArray class]]) return NO; if (idx >= self.count) return NO; return YES; } 

In theory, you could redeem the implementations of the -keyKeyedSubscript and -objectAtIndexedSubscript to make these checks transparently map to the index syntax. In practice, since NSDictionary and NSArray are class clusters, it is predictable that sooner or later an unpredictable path will occur. So you better define something like

 @implementation NSDictionary (TWXNSDictionary) - (id)safeObjectForKey:(id)key { if (self.isDictionary) return [self objectForKey:key]; return nil; } @end @implementation NSArray (TWXNSArray) - (id)safeObjectAtIndex:(NSUInteger)idx { if ([self isIndexValid:idx]) return [self objectAtIndex:idx]; return nil; } @end 

and using them whenever you access data structures that come from JSON parsing or other sources that may not meet your expectations.

0
source

I think a good way is to swizzle objectAtIndexedSubscript

Like Objective-C Literals: Negative Array Subscribers

 method_exchangeImplementations( class_getInstanceMethod(self, @selector(objectAtIndexedSubscript:)), class_getInstanceMethod(self, @selector(BLC_objectAtIndexedSubscript:)) ); - (id)BLC_objectAtIndexedSubscript:(NSInteger)idx { if (idx < 0) { idx = [self count] + idx; } return [self BLC_objectAtIndexedSubscript:idx]; } 
0
source

Source: https://habr.com/ru/post/1491447/


All Articles