Should I prefer to use literal syntax or constructors for creating dictionaries and arrays?
Asked Answered
P

3

3

I am reading through the iOS Developer Guide to get familiarized with the Objective-C language and currently I am having a little confusion on the topic of Container Literals and Subscript Notation as it pertains to creating objects like NSDictionary.

I understand that there are several ways to create NSDictionary objects including Key-Value encoding (dictionaryWithObjects:forKeys: and dictionaryWithObjectsAndKeys:, or their corresponding initializers). Source Link.

From my understanding there are two main ways to do this and then there is another way which is by using container literals, demonstrated here:

NSDictionary *myDictionary = @{
   @"name" : NSUserName(),
   @"date" : [NSDate date],
   @"processInfo" : [NSProcessInfo processInfo]
};

Which is the best way to use? Is there any benefit in using the Container Literal technique over the previous two or is it just a convenience thing for programmers?

I am under the impression that it is also just another easier way to code things like arrays. Is this true or is there something that I'm missing here? Are these techniques just a matter of personal preference?

Padova answered 21/9, 2012 at 18:7 Comment(3)
Also there is NSNumber * numberWithInt = @7; NSNumber * numberWithBool = @YES;Whistling
Much belated reopen vote: this isn't a "matter of style" opinion question, as there are real consequences to choosing to use literals vs. the most commonly otherwise used array/dictionary initializers.Gatian
@Gatian thanks for your reply!Padova
G
12

I disagree with the other answers posted thus far: almost all the time, it's better to use the new container literal syntax than to use constructors. They help with code correctness, and there's not really that much to worry about for compatibility.

Code Correctness

Container literals are indeed syntactic sugar, but specifically they map to the "safe" constructor methods +[NSArray arrayWithObjects:count:] and +NSDictionary dictionaryWithObjects:forKeys:count:. Constructing an array or dictionary using one of these methods directly isn't all that convenient, so many programmers find it simpler to use arrayWithObjects: and dictionaryWithObjectsAndKeys:. However, the latter methods have a nasty pitfall: since the argument list must be terminated with nil, you can find yourself with unexpected array/dictionary contents if you pass nil where you intend to pass an object.

For example, say you're setting up a dictionary mapping the properties of one of your model objects (maybe you're going to send it as JSON?):

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
    person.name, @"name", person.title, @"title", person.address, @"address", 
    nil];

If this code runs into a Person for whom no title has been set, the resulting dictionary will be missing the @"address"key and its value. You could spend hours tracking down why some fraction of the people in your database are missing addresses (and even see the code above and tear your hair out wondering why it's not working when c'mon, I'm setting it right there!). Many of us have.

By contrast, if you use the literal form like this:

NSDictionary *dictionary = @{
    @"name": person.name, @"title": person.title, @"address": person.address };

It will be expanded to something like this:

id objects[] = { person.name, person.title, person.address };
id keys[] = { @"name", @"title", @"address" };
NSUInteger count = sizeof(objects) / sizeof(keys);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
                                                       forKeys:keys
                                                         count:count];                          

And if person.name or person.title returns nil, this method will throw an exception instead of silently creating data you don't want. (Either way you'll have to decide how you want your code to handle nil titles, but this way you'll catch the problem sooner.) And sure, you could write this "safer" form yourself instead of using the equivalent syntactic sugar, but are you sure you won't just fall back on the habit of writing dictionaryWithObjectsAndKeys: because it's shorter?

Compatibility

The code generated by container literals (and number literals and boxed expressions, for that matter) uses no new API, so you can compile it with Xcode 4.4 or newer (or Clang 3.1 or newer directly) and deploy to any version of Foundation. You do need to consider compatibility if your source code will also be used with older compilers or GNUStep, however. (Though it sounds like GNUStep is good with Clang now, too.)

And it's not part of the question, but since it's on a related subject: the same is "sort of" true for the new object subscripting syntax. That does use new methods only defined on Mac OS X 10.6 and iOS 6.0... but those methods are provided by libarclite. (You know, the library that gets linked in when you try to deploy ARC code back to iOS 4.3 or Mac OS X 10.6 -- it's not just for ARC anymore!) So all you need to do is declare them in a header, link ARCLite if you're not already, and you're good to go.

Gatian answered 23/9, 2012 at 21:9 Comment(1)
new best answer chosen for question.Padova
M
2

There's no "best way". Use whichever is the best for a particular use case. For example, if you want your app to be portable (i. e. the logic that requires Foundation only and not UIKit can run on other platforms as well, like Mac OS X or Linux with GNUstep, etc.) then avoid using the literal syntax - they're not very portable. If you need it to work on iOS only, then use them, because they're convenient.

Also, these notations are only syntactic sugar - that is, they map to method names (as far as I know, exactly to the two methods you mentioned in your question), so they don't have any effect on performance, the behavior of the algorithm, etc.

And yes, you guessed it right: the same applies to the new subscripting syntax - for NSArray, it invokes - objectAtSubscriptedIndex:.

Milewski answered 21/9, 2012 at 18:26 Comment(5)
The subscripting is actually transformed into objectAtSubscriptedIndex: and objectForKeyedSubscript:, not objectAtIndex: and objectForKey:. Don't ask me why. (This is what prevents that piece of the new syntax from being used with an Apple SDK older than iOS 6/OS X 10.8.)Partizan
@JoshCaswell Thanks, fixed. (So, unfortunately enough, I was right about portability...)Milewski
@H2CO3: It's easy enough to add support yourself: https://mcmap.net/q/443035/-compiler-error-quot-expected-method-not-found-quot-when-using-subscript-on-nsarrayPartizan
@JoshCaswell of course, that's an obvious solution, but not sure everybody will do it...Milewski
+1 for "no best way" - however in practice constructors are both more easily ported and also more readable.Unforgettable
C
1

You can use them on GNU/Linux with GNUstep and clang. In most of my cases GNUstep works with clang much better than all versions of gcc. (Sorry I should just edit the other answer, I am new to this)

Chemulpo answered 22/9, 2012 at 16:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.