CFPropertyListCreateDeepCopy fails to process array / dictionary containing NSNull - objective-c

For some reason this sample code works:
NSArray *immutable = #[ #"a", #"b", #"c" ];
NSMutableArray *mutable = (__bridge id)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFArrayRef)immutable, kCFPropertyListMutableContainers);
and this code produces nil as a result of the conversion:
NSArray *immutable = #[ #"a", [NSNull null], #"c" ];
NSMutableArray *mutable = (__bridge id)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFArrayRef)immutable, kCFPropertyListMutableContainers);
I tried to find any mention of NSNull not being allowed when using this function. I have a suspicion that it has something to do with the way method examines whether property is mutable or not, but I can't really back that up with facts.
Any ideas?

As kind people from apple developer forum pointed out the issue is that Property List Structure is rather strict about data types it can work with. NSNull is not one of allowed ones.
From apple docs:
Property lists are constructed from the basic Core Foundation types CFString, CFNumber, CFBoolean, CFDate, and CFData.

Related

Objective-C: Boxing VS Literal

Which construct should I use, why? I'll make an example with NSArray. The same apply to other "boxed" objects, such as NSDictionary, NSNumber...
NSArray *arr1 = #[object1, object2, object3];
or
NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil];
The two constructs produce identical effect for NSArray and NSDictionary objects. The difference is that the first syntax is not available prior to the 2012 version of the compiler, while arrayWithObjects: works with all versions of the language.
If you are not planning for your code to compile with older versions of the complier, the new syntax is gives you better readability, especially for NSNumber objects. Compare
NSArray *arr = #[#1, #2, #3];
vs.
NSArray *arr = [NSArray arrayWithObjects:
[NSNumber numberWithInteger:1]
, [NSNumber numberWithInteger:2]
, [NSNumber numberWithInteger:3]
, nil
];
Neither NSArray nor NSDictionary literals are "boxed". Boxing refers to when you make objects out of scalars or non-object types. Cocoa arrays and dictionaries are always objects.
Boxing is the relevant terminology for NSNumber, though. For example, 1 is an int, a scalar type (not an object). #1 is an NSNumber, the result of boxing 1. However, technically, I'd say that [NSNumber numberWithInt:1] is also boxing. It's just that #1 is a more convenient syntax.
There is a difference between:
NSArray *arr1 = #[object1, object2, object3];
and
NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil];
In the first, if any of object1, object2, or object3 hold nil, you will get a runtime exception. In the second, any nil just terminates the argument list. So, if object2 is nil, arr2 will end up as a single-element array holding only object1. NSArray will never even get around to considering object3 because, as far as it's concerned, the argument list was terminated after the first element.
Rarely, you can make legitimate use of this behavior (in which case, I'd clearly make note of it in the comments), but most often this is unexpected and undesirable behavior. It's a particularly pernicious sort of bug.

Objective-C at sign and curly braces, #{ ... } what does it mean?

I have this line in Objective-C.
NSMutableArray *mutableArray;
[mutableArray addObject:#{ #"Something" : aObject, #"Otherthing" : anotherObject }];
What does the #{ ... } part do exactly? It is an object, but it seems to create some kind of key, value pair on the fly.
It is creating NSDictionary object as you said. Syntax is simple
NSDictionary* dictionary = #{key: object, key: object};
In your example, keys are objects of NSString class. It is important to remember that dictionary copies keys and retains values.
These are called Literals. Apple LLVM Compiler 4.0 and above can use this.
In your question, the expression creates a dictionary
NSDictionary *settings = #{ AVEncoderAudioQualityKey : #(AVAudioQualityMax) };
Similarly arrays which were created using NSArray arrayWithArray and other similar methods, can now be done easily
NSArray *array = #[ #"Hello", #"World"];
and you will not even need the nil sentinel.
More details here: http://clang.llvm.org/docs/ObjectiveCLiterals.html
The #{ ... } syntax is a shorthand way of creating a NSDictionary introduced as part of Modern Objective-C. The syntax #{#"key1": object1, #"key2": object2} is just a shorthand for more verbose methods like [NSDictionary dictionaryWithObjectsAndKeys:] among a few others.

Retrieving NSArray From an NSDictionary

I am trying to get an array full of my data, I keep getting an BAD_ACCESS error when I run this though at the calling the array which I have not included here but I even commented that code out and tried just calling it to the log and still get the BAD_ACCESS error. The array is stored in a dictionary that contains a one key that is a number. I am not sure what I am doing wrong here.
ISData *is = [[ISData alloc] init];
NSDictionary *dic = [is getData:#"right" : isNumber];
NSArray *array = [[NSArray alloc] initWithArray:[dic valueForKey:#"2"]];
NSString *out = [array objectAtIndex:0];
How the dictionary is created:
NSNumber* key = [NSNumber numberWithInt:isNumber];
NSArray *values = [NSArray arrayWithObjects:[NSString stringWithUTF8String:name], [NSString stringWithUTF8String:desc], [NSString stringWithUTF8String:intent], nil];
[dic setObject:values forKey:key];
You don't say exactly where it crashes, and you don't have an obvious crashing bug here, so it's hard to diagnose your actual issue. This could be a memory management thing that's outside the code you've presented. But a couple things are going on here that are suspicious:
You should never have a bare [MyClass alloc] without the -init call. Your init should call super's init, which is responsible for setting up the new object.
Your -valueForKey: should be -objectForKey:. The difference is probably unimportant in this case, but the former is used for "KVC" coding, which you're not using. If you set it as object, get it as object.
Your #"2" as the key into the dictionary doesn't match your input, which is an NSNumber. NSNumbers are not string versions of numbers, so you're unlikely to find any value there. Instead, use the same [NSNumber numberWithInt:2] pattern.
It is most likely that your array is empty. You can try print NSLog(#"count = %d", array.count); to see if that's the case.
If your dic is set up in the second block of code, then what's that NSDictionary *dic = [is getData:...] thing in the first block?
And is there a reason you cannot set up your array directly? Is there a reason for you to use a dictionary when it has only one key?

How does NSDictionary handle NIL objects?

Consider the code below. In essence, we get 2 strings, then we add these values to the NSDictionary.
However, i hit a weird bug. When fbAccessTokenKey is 0x0 (or nil), then twitterToken would not be added as well.
NSString *fbAccessTokenKey=[[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken=[[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSDictionary *params= [[NSDictionary alloc] initWithObjectsAndKeys:
fbAccessTokenKey, #"fb_access_token",
twitterToken, #"twitter_access_token",
nil
];
Why is this happening, and what is a good way of resolving this?
nil is used as a 'sentinel' for marking the "end of arguments" list. If twitterToken was nil, the runtime would go through your arguments, and once it got to twitterToken, it would think that it was up to the end of your list of objects and keys. This is due to the way that C/Obj-C is implemented when it comes to list arguments.
The alternative safe way to do it is to use an NSMutableDictionary, and check to see if your values are non-nil, then add them to the mutable dictionary like this:
NSString *fbAccessTokenKey = [[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken = [[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (fbAccessTokenKey) [params setObject:fbAccessTokenKey forKey:#"fb_access_token"];
if (twitterToken) [params setObject:twitterToken forKey:#"twitter_access_token"];
For more technical info, there's a good article on Cocoa with Love: http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
You can use the NSNull object.
Documentation here:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNull_Class/Reference/Reference.html
Rather than initializing with initWithObjectAndKeys. Why not instantiate an NSMutableDictionary and then add the key value pairs (or not if the key is null)?
NSMutableDictionary * params = [[NSMutableDictionary alloc] init];
if (fbAccessTokenKey)
[params setObject:fbAccessTokenKey forKey:#"fb_access_token];
// ... etc
You could cast it back to an NSDictionary later if you want to keep it immutable from that point.
Update
Just a note in response to Josh's comment, I should clarify that of course the cast will not magically convert the params NSMutableDictionary to an NSDictionary. But if you are passing it to code which requires an NSDictionary, the cast will let you treat it as such.
Josh's comment includes this code:
NSMutableDictionary * md = [[NSMutableDictionary alloc] init];
NSDictionary * d = (NSDictionary *)md;
[d setObject:#"Yes I am" forKey:#"Still mutable?"];
NSLog(#"%#", d); // Prints { "Still mutable?" = Yes I am; }
This will generate the following compiler warning (and for me, with warnings generating errors, a compile error):
file:blah.m: error: Semantic Issue: 'NSDictionary' may not respond to 'setObject:forKey:'

Sorting a mutable array using a dictionary key

I am trying to create a simple mutable array with a single key ("dayCounter") that I intend to use for sorting. I've read loads of examples on line, but no joy.
So I create this array. Note the first entry is a NSDictionary object. (The other objects are text)
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
I save the array in a plist and everything looks great after the load.
However, when I come to sort the array, the program crashes. I have tried every combination of the following:
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dayCounter" ascending:YES];
[cumArray sortUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
Do I need a dictionary item to act as a key? Can I sort on the first object any easier? Any help is much appreciated.
Sometimes using too many nested expressions can obscure what's really going on. For example, the 'simple' mutable array you created actually contains a nested mutable array, rather than directly containing the dictionaries you're trying to sort.
So instead of this:
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
try doing this
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter]
forKey:#"dayCounter"]
NSArray *objs = [dailyArray objectAtIndex:x];
NSDictionary *dict2 = [objs objectAtIndex:0];
NSDictionary *dict3 = [objs objectAtIndex:1];
NSDictionary *dict4 = [objs objectAtIndex:2];
// Note: You might want to temporarily log the values of dict2 - 4 here to make sure they're
// really dictionaries, and that they all actually contain the key 'dayCounter'.
cumArray = [NSMutableArray arrayWithObjects:dict1, dict2, dict3, dict4, nil];
Assuming that you really have a mutable array of dictionaries, each of which contains the key dayCounter, the sort descriptor you showed in your example should work just fine.
Your setup makes no sense. You are saying yourself that only the first object in the array is a dictionary that contains the key `#"dayCounter" ("The other objects are text"). How is it supposed to be sorted if only one object contains the sort criteria?
You need to sort the array with a method, like - (NSComparisunResult)compareDict
If you have to compare 2 dictionaries and determine which one should be ordered above the other ( NSOrderedAscending ) then you need to "extend" NSDictionary:
#interface NSDictionary (SortingAdditions) {}
- (NSComparisonResult)compareTo:(NSDictionary *)other;
#end
#implementation NSDictionary (SortingAddictions)
- (NSComparisonResult)compareTo:(NSDictionary *)other
{
if( [self count] > [other count] )
{ return NSOrderedAscending; }
}
#end
This method will sort NSDictionaries according to the amount of objects that they contain.
Other values you can return here are: NSOrderedDescending and NSOrderedSame.
Then you can sort the mutable array with:
[SomeMutableArray sortUsingSelector:#selector(compareTo:)];
Keep in mind that every object in the array will need to be an NSDictionary, otherwise you will get an exception: unrecognized selector sent to instance blabla
You can do the same thing for any type of object, if the array contains both NSStrings, NSNumbers and NSDictionaries you should take a different approach