Bridge casts for collection elements - ios

I'm using a vendor API that returns a CFDictionaryRef that can contain a variety of CF object types and which the caller needs to CFRelease. I'd like to cast it to an NSDictionary to more easily work with it, and want to make sure I understand I'm handling the elements correctly in terms of casting.
It looks to me like toll-free bridged types (e.g. CFString, CFNumber) are just handled by NSDictionary and I can just get the NS types as I would if they'd been Obj-C types all along (I'm guessing there's a bridge cast going on under the covers).
For a non-toll-free bridged type (e.g. CFHost) it looks like I can bridge cast the result from -valueForKey: into the CF type and go from there, though I'm not positive if I need to release that value or not.
Here's some sample code that illustrates the problem. Is this the right way to handle things?
// Caller is responsible for releasing returned value
//
+ (CFDictionaryRef)someCFCreateFunctionFromVendor
{
CFMutableDictionaryRef cfdict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(cfdict, CFSTR("string1"), CFSTR("value"));
int i = 42;
CFDictionarySetValue(cfdict, CFSTR("int1"), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i));
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, CFSTR("myhost"));
CFDictionarySetValue(cfdict, CFSTR("host1"), host);
return cfdict;
}
+ (void)myMethod
{
NSDictionary *dict = CFBridgingRelease([self someCFCreateFunctionFromVendor]);
for (NSString *key in [dict allKeys]) {
id value = [dict valueForKey:key];
NSLog(#"%# class: %#", key, [value class]);
if ([value isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)value;
NSLog(#"%# is an NSString with value %#", key, str);
} else if ([value isKindOfClass:[NSNumber class]]) {
NSNumber *num = (NSNumber *)value;
NSLog(#"%# is an NSNumber with value %#", key, num);
} else if ([value isKindOfClass:[NSHost class]]) {
NSLog(#"%# is an NSHost", key); // never hit because no toll-free bridge to NSHost
} else {
NSLog(#"%# is an unexpected class", key);
}
// Sample handling of non-toll-free bridged type
if ([key isEqualToString:#"host1"]) {
CFHostRef host = (__bridge CFHostRef)value;
NSArray *names = (__bridge NSArray *)(CFHostGetNames(host, false));
NSLog(#"host1 names: %#", names);
// I don't think I need to CFRelease(host) because ARC is still handling value
}
}
}
Output...
string1 class: __NSCFConstantString
string1 is an NSString with value strvalue
int1 class: __NSCFNumber
int1 is an NSNumber with value 42
host1 class: __NSCFType
host1 is an unexpected class
host1 names: ( myhost )

Ironically, the only error in your code is in the Core Foundation, non-ARC stuff. Your +someCFCreateFunctionFromVendor method calls CFNumberCreate() and CFHostCreateWithName(), both of which are Create functions, but doesn't CFRelease() the objects after adding them to the dictionary. It should. Otherwise, it's a leak. The static analyzer would catch that, by the way. (For the CFNumber, that means you have to split creation into a separate line so you can reference the object after adding it to the dictionary.)
You must not release the host object in +myMethod because that code doesn't receive ownership of it. It has nothing to do with ARC. You wouldn't release it there in MRR (manual retain/release) code, either.

Related

Objective-C data type issue

I'm not that strong with Objective-C, so this is probably a simple issue. I cannot understand why the the last line in the error completion block is causing an exception:
- (void)sendInappropriateNewsfeedComment:(NSString *)comment newsfeedEventId:(NSString *)newsfeedEventId completion:(void (^)(NSString *, NSInteger))completion error:(void (^)(NSString *, NSInteger))error {
PAInappropriateNewsFeedRequest *inappropriateNewsfeedRequest = [[PAInappropriateNewsFeedRequest alloc] initWithComment:comment newsfeedEventId:newsfeedEventId];
[inappropriateNewsfeedRequest executeWithCompletionBlock:^(id obj) {
completion(#"SUCCESS", (NSInteger)1);
} error:^(NSError *e, id obj) {
NSString * message = [obj objectForKey:#"message"];
error(message, [obj integerForKey:#"code"]);
}];
}
I've also attached a screenshot showing that the "obj" object has a key called "code" that is of type "(long)-1".
What is the proper way to declare the error block and pass the "-1" value back to the call site?
Simply because NSDictionary has no method called integerForKey. That's what "unrecognized selector" means. Selector is basically a method name.
The fact that this can be even compiled is caused by using id for the parameter type. You can call anything on id but it will crash your app if the method does not exist. You should cast obj to a proper type as soon as possible.
NSDictionary *dictionary = (NSDictionary *) obj;
NSString *message = dictionary[#"message"];
NSNumber *code = dictionary[#"code"];
If obj can be a different type, you should make sure to check [obj isKindOfClass:NSDictionary.self] before casting.
The whole solution taking Sulthan's suggestions into account might looks something like this
typedef void (^NewFeedCompletion)(NSString *, NSInteger);
typedef void (^NewsFeedError)(NSString *, NSInteger);
- (void) sendInappropriateNewsfeedComment: (NSString *)comment
newsfeedEventId: (NSString *)newsfeedEventId
completion: (NewFeedCompletion) completion
error: (NewsFeedError) error
{
PAInappropriateNewsFeedRequest *inappropriateNewsfeedRequest = [[PAInappropriateNewsFeedRequest alloc] initWithComment:comment newsfeedEventId:newsfeedEventId];
[inappropriateNewsfeedRequest executeWithCompletionBlock: ^(id obj) {
completion(#"SUCCESS", (NSInteger)1);
} error:^(NSError *e, id obj) {
assert([obj isKindOfClass: NSDictionary.class])
NSDictionary *errorDictionary = (NSDictionary *) obj;
NSString *message = [errorDictionary objectForKey: #"message"];
NSNumber *code = [errorDictionary objectForKey: #"code"]
error(message, [code integerValue]);
}];
}

Parsing id to NSString

When parsing API responses, sometimes I can not rely on strings being embedded in quotation marks. ID's are a good example of this, where some API's will send the numerical ID as a string while some will send it as a number.
What is a good practice when parsing such a value? If I simply parse it to an NSString like so:
NSString *myID = (NSString *)message["myID"];
I can end up with an NSString object that somehow contains (long)123.
And using stringValue would cause issues when the value is actually already sent as a string (since NSString does not have a stringValue function).
A way that works, but is somewhat ugly, is this:
id myID = (NSString *)message["myID"];
if ([myID respondsToSelector:#selector(stringValue)])
{
myID = [myID stringValue];
}
You could do something like:
id myID = message["myID"];
if ([myID isKindOfClass:[NSString class]]) { ... }
else { ... }
As long as this logic is encapsulated inside data parser and is opaque for your api users (i.e. they will always get a string) any approach is fine, e.g.:
- (NSString*)parseID:(NSDictionary*)message {
id rawID = message["myID"];
if ([rawID isKindOfClass:[NSString class]]){
return rawID;
} else if ([rawID isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)rawID stringValue];
} else {
// We might still want to handle this case.
NSAssert(false, #"Unexpected id type");
return nil;
}
}
Alternative is to define stringValue in extension, so any possible objet will respond to selector:
#implementation NSString(JSONStringParsing)
- (NSString *)stringValue {
return [self copy];
}
#end
Why not just use description?
NSArray *objects = #[
#NSIntegerMin,
#NSIntegerMax,
#"123456789"
];
for (id object in objects) {
NSString *stringObject = [object description];
NSLog(#"%# -> %# | %#", [object className], [stringObject className], stringObject);
}

How to determine what kind of data is stored in NSUserDefaults

I am currently storing an NSUInteger in NSUserDefaults to persist data, but I need to move over to an NSObject that is NSCoding compliant and that will be stored as NSData. Is there a way to determine whether I am storing an int or an object in my key so that I can accommodate users who have or have not migrated from one persistence type to the other? I know objectForKey returns an id, so is this good enough?
- (id) returnStuff {
NSData *data = [myUserDefaults objectForKey:myKey];
if([data isKindOfClass:[NSData class]) {
// then it must be an archived object
return (desiredObjectClass*)[NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else {
NSUInteger returnInt = [myUserDefaults integerForKey:myKey];
return [NSNumber numberWithInteger: returnInt];
}
You can use isKindOfClass: to treat NSData specially, as you have done, except I wouldn't bother unboxing the integer only to rebox it. The following method handles and returns any type.
Note: there is no need to cast the unarchived data, as the compiler won't care as you are returning id.
- (id)stuffForKey:(NSString *)key {
id value = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if ([value isKindOfClass:[NSData class]]) {
// then it must be an archived object
NSData *dataValue = (NSData *)value;
return [NSKeyedUnarchiver unarchiveObjectWithData:dataValue];
}
return value;
}

How to check if json object contains <null>?

I am getting a Json from server by making a network request in my app.I am getting <null> value for some keys in Json object.My app gets crashed if this type of response is received.Please tell me how can i validate>?
I have tried this but it does not work all time.
if(!(user_post.username==(id)[NSNull null]) )
{
user_post.username=[dict_user_info objectForKey:#"name"];
if(user_post.username!=nil)
{
ser_post.username=[dict_user_info objectForKey:#"name"];
}
else
{
user_post.username=#"Username";
}
}
Consider testing the value for null so your program won't crash. Like this:
if([dict_user_info objectForKey:#"name"] != [NSNull null])
{
ser_post.username=[dict_user_info objectForKey:#"name"];
}
Create a Category of NSDictionary and add following method in it, which replaces null value with empty string for each key in dictionary.
- (NSDictionary *)dictionaryByReplacingNullsWithStrings
{
const NSMutableDictionary *replaced = [self mutableCopy];
const id nul = [NSNull null];
const NSString *blank = #"";
for(NSString *key in self) {
const id object = [self objectForKey:key];
if(object == nul || object == NULL) {
//pointer comparison is way faster than -isKindOfClass:
//since [NSNull null] is a singleton, they'll all point to the same
//location in memory.
[replaced setObject:blank
forKey:key];
}
}
return [replaced copy];
}
Usage :
[yourJSONDictionary dictionaryByReplacingNullsWithStrings];
Read more about Category in iOS Tutorial 1 and Tutorial 2
yourJsonObject = [myDic valueforkey#"key"];
if(yourJsonObject != [NSNull null])
{
//not null
}
** you can also check whether object exist or not
if(yourJsonObject)
{
//exist
}
I think you've confused your logic. I am trying to stay true to your code, but let me know if the following is not what you intended:
if (dict_user_info[#"name"] != nil && [dict_user_info[#"name"] isKindOfClass:[NSNull class]] == NO) {
user_post.username = dict_user_info[#"name"];
if (user_post.username != nil) {
ser_post.username = user_post.username;
} else {
user_post.username = #"Username";
}
}
These are a couple of methods I wrote for my projects, try them :
/*!
* #brief Makes sure the object is not NSNull or NSCFNumber, if YES, converts them to NSString
* #discussion Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON and expect it to be an NSString, pass it through this method just to make sure thats the case.
* #param str The object that is supposed to be a string
* #return The object cleaned of unacceptable values
*/
+ (NSString *)cleanedJsonString:(id)str
{
NSString *formattedstr;
formattedstr = (str == [NSNull null]) ? #"" : str;
if ([str isKindOfClass:[NSNumber class]]) {
NSNumber *num = (NSNumber*) str;
formattedstr = [NSString stringWithFormat:#"%#",num];
}
return formattedstr;
}
/*!
* #brief Makes Sure the object is not NSNull
* #param obj Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON ( NSArray, NSDictionary or NSString), pass it through this method just to make sure thats the case.
* #return The object cleaned of unacceptable values
*/
+ (id)cleanedObject:(id)obj
{
return (obj == [NSNull null]) ? nil : obj;
}
/*!
* #brief A JSON cleaning function for NSArray Objects.
* #discussion Sometimes JSON responses can contain NSNull objects, which does not play well with Obj-C. So when you access a value from a JSON and expect it to be an NSArray, pass it through this method just to make sure thats the case. This method first checks if the object itself is NSNull. If not, then it traverses the array objects and cleans them too.
* #param arr The Objects thats supposed to be an NSArray
* #return The NSNull Cleaned object
*/
+ (NSArray *)cleanedJsonArray:(id)arr
{
if (arr == [NSNull null]) {
return [[NSArray alloc] init];
}
else
{
NSMutableArray *arrM = [(NSArray*)arr mutableCopy];
int i=0;
for (id __strong orb in arrM)
{
if (orb == [NSNull null])
{
[arrM removeObjectAtIndex:i];;
}
i++;
}
return arrM;
}
}
Just pass a JSON string, array or object to the appropriate method and the method will clean it for you.
Do yourself a favour and write a method that handles this and put it into an extension. Like
- (NSString*)jsonStringForKey:(NSString*)key
{
id result = self [key];
if (result == nil || result == [NSNull null]) return nil;
if ([result isKindOfClass:[NSString class]]) return result;
NSLog (#"Key %#: Expected string, got %#", key, result);
return nil;
}
You might even add some code that accepts NSNumber* results and turns them into strings, if that is what your server returns (some poster here had the problem that his server returned dress sizes as numbers like 40 or strings like "40-42" which makes something like this useful).
And then your code becomes one readable line
user_post.username = [dict_user_info jsonStringForKey:#"name"] ?: #"username";
I actually use several slightly different methods depending on whether I expect null, expect no value, expect an empty string or not, which gives me warnings when my assumptions are wrong (but always returns something that doesn't break).
try this:
if(!(user_post.username == (NSString *)[NSNull null]) )

How do I get the datatype of the value given the key of NSDictionary?

I have a NSDictionary and what to get the datatype for value given the key
is it possible?
You could check the variants you accept by
id objectValue = [dictionary valueForKey:#"SomeKey"];
if ([objectValue isKindOfClass:[NSString class]]) {
//Object is a NSString
} else if ([objectValue isKindOfClass:[NSArray class]]) {
//Object is a NSArray
} else if ([objectValue isKindOfClass:[NSDictionary class]]) {
//Object is a NSDictionary
} else if ([objectValue isKindOfClass:[NSNumber class]]) {
//Object is a NSNumber
}
And so on.. In this pattern just handle all the types your app supports. Ignore values your app doesn't support by this pattern or just fail gracefully in another way when you don't support the datatype of the value
To just figure out what class it is (to debug the application for example) you can do:
NSString *className = NSStringFromClass([objectValue class]);
you could use NSStringFromClass to get the type, or failing that you could use isKindOfClass:
NSDictionary *dictionary = #{
#"string": #"Something",
#"number": #(1),
#"null": [NSNull null],
#"custom": [[CustomType alloc] init]
};
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"type = %#", NSStringFromClass([obj class]));
}];
output:
type = CustomType
type = NSNull
type = __NSCFNumber
type = __NSCFConstantString

Resources