开发者

NSDictionary stringForKey: and Casting [NSNull null] to NSString*

开发者 https://www.devze.com 2023-01-07 23:41 出处:网络
I was recently told casting [NSNull null] to (NSString*) was \"terrible\". However, -[NSDictionary stringForKey:] method will return [NSNull null] if the key is not in the dictionary, and if I don\'

I was recently told casting [NSNull null] to (NSString*) was "terrible".

However, -[NSDictionary stringForKey:] method will return [NSNull null] if the key is not in the dictionary, and if I don't do the cast the compiler yells at me.

Am I missing something?

EDIT:

My mistake... and I think I might be beginning to see the problem...

As everyone has pointed out, there is no stringForKey: method on NSDictionary, and yes, I was thinking about the user defaults when I asked the question... so I went back and looked at what I was doing... here it is:

NSString * s开发者_运维问答omeValue = (NSString*)[myDictionary objectForKey:@"key"];
if (someValue == (NSString*)[NSNull null]) ...

if I don't do the cast in this case, I get the following error:

warning: comparison of distinct Objective-C types 'struct NSNull *' and 'struct NSString *' lacks a cast

Casting the value "solves" the problem, and I'd rather not write 10 lines where one will do...

Is this bad? Where will I run into problems?

ALSO:

The dictionaries are coming from a JSON library... and NULLs are valid values in this case, so perhaps it's not that NSDictionary returns them if the key is missing, but rather that the key is, in fact, there, and the value is actually null.


NSDictionary does not return instances of NSNull unless you or some other source put them there. As well, NSDictionary does not respond to -stringForKey:. Are you perhaps thinking of NSUserDefaults?

If you have an instance of NSDictionary and all you want to do is get a string from it, you could try something like the following code.

NSString *MyGetString(NSDictionary *dict, id key, NSString *fallback) {
    id result = [dict objectForKey: key];

    if (!result)
        result = fallback;
    else if (![result isKindOfClass: [NSString class]])
        result = [result description];

    return result;
}

Pass a fallback string as fallback--the function will use that if no object is associated with the key key.


as Jonathan's code shows, it's better if you use isKindOfClass: method. It'll protect you even if you change your method of retrieving the object, etc. and is a more accurate check.

Also remember that [someValue isKindOfClass:[NSString class]] returns YES only when someValue is not nil AND it's an NSString which means it's also enough for testing nil values.


The cast is "bad" because NSNull is not an NSString. Casting does not change the actual type of the object pointed to, it only changes the compiler's, um, attitude to it.

In the very particular case shown, where the only thing you're going to do with the object is a direct test for pointer equality, your approach will work, but it's a source of potential future errors. This sort of hacky cast to silence compiler warnings makes one uneasy to look at and should be avoided if there's a better way.

The real problem, of course, is the previous cast, where the NSObject* from objectForKey: is cast straight to an NSString* with no questions asked. At present you can get away with it because you think you're guaranteed to get only strings or NSNull, but what happens if the JSON library changes or just fails to honour that contract? You'd really be better off doing something like:

NSObject* someValue = [myDictionary objectForKey:@"key"];
NSString* someStr = nil;
if (someValue == [NSNull null])
   ...
else if ([someValue isKindOfClass:[NSString class]])
   someStr = (NSString*) someValue;
....


You can use this category extension to wrap this logic up. it's on github

NSDictionary+Verified.h:

//  NSDictionary+Verified.h
//
//  Created by alexruperez on 08/05/13.
//  Copyright (c) 2013 alexruperez. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSDictionary (Verified)

- (id)verifiedObjectForKey:(id)aKey;

@end

NSDictionary+Verified.m:

NSDictionary+Verified.m

#import "NSDictionary+Verified.h"

@implementation NSDictionary (Verified)

- (id)verifiedObjectForKey:(id)aKey
{
    if ([self objectForKey:aKey] && ![[self objectForKey:aKey] isKindOfClass:[NSNull class]]) return [self objectForKey:aKey];
    return nil;
}

@end


A good test for NULL checking, especially when testing parsed results from network APIs (JSON especially) is the isEqual: method. I test for both existence, and for a usable value using...

NSString *someValue = [response objectForKey:MyAPIKeySomeValue];
if (!!someValue && ![someValue isEqual:[NSNull null]]) {
    // optionally test for class type
    // use someValue
}
else {
    // error out for required values; skip for optional values
}

On a more general note, if you're expecting JSON in a specific format, and the API is not meeting that requirement, you could consider that a candidate for wrapping in a @try/@catch. You can then handle the abnormal/illegal response format, and eliminate hard crashes for invalid data formats.

If the format is loosely defined, you could allow your parsing routines to fail more gracefully by constructing a meaningful NSError.

0

精彩评论

暂无评论...
验证码 换一张
取 消