- Item 1: Familiarize Yourself with Objective-C's Roots
- Item 2: Minimize Importing Headers in Headers
- Item 3: Prefer Literal Syntax over the Equivalent Methods
- Item 4: Prefer Typed Constants to Preprocessor #define
- Item 5: Use Enumerations for States, Options, and Status Codes
Item 3: Prefer Literal Syntax over the Equivalent Methods
While using Objective-C, you will come across a few classes all the time. They are all part of the Foundation framework. Although technically, you do not have to use Foundation to write Objective-C code, you usually do in practice. The classes are NSString, NSNumber, NSArray, and NSDictionary. The data structures that each represent are self-explanatory.
Objective-C is well known for having a verbose syntax. That’s true. However, ever since Objective-C 1.0, there has been a very simple way to create an NSString object. It is known as a string literal and looks like this:
NSString
*someString =@"Effective Objective-C 2.0"
;
Without this type of syntax, creating an NSString object would require allocating and initializing an NSString object in the usual alloc and then init method call. Fortunately, this syntax, known as literals, has been extended in recent versions of the compiler to cover NSNumber, NSArray, and NSDictionary instances as well. Using the literal syntax reduces source code size and makes it much easier to read.
Literal Numbers
Sometimes, you need to wrap an integer, floating-point, or Boolean value in an Objective-C object. You do so by using the NSNumber class, which can handle a range of number types. Without literals, you create an instance like this:
NSNumber
*someNumber = [NSNumber
numberWithInt
:1
];
This creates a number that is set to the integer 1. However, using literals makes this cleaner:
NSNumber
*someNumber =@
1
;
As you can see, the literal syntax is much more concise. But there’s more to it than that. The syntax also covers all the other types of data that NSNumber instances can represent. For example:
NSNumber
*intNumber =@
1
;NSNumber
*floatNumber =@
2.5f
;NSNumber
*doubleNumber =@
3.14159
;NSNumber
*boolNumber =@
YES
;NSNumber
*charNumber =@
'a'
;
The literal syntax also works for expressions:
int
x =5
;float
y =6.32f
;NSNumber
*expressionNumber =@
(x * y
)
;
Making use of literals for numbers is extremely useful. Doing so makes using NSNumber objects much clearer, as the bulk of the declaration is the value rather than superfluous syntax.
Literal Arrays
Arrays are a commonly used data structure. Before literals, you would create an array as follows:
NSArray
*animals = [NSArray
arrayWithObjects
:@"cat"
,@"dog"
,@"mouse"
,@"badger"
, nil];
Using literals, however, requires only the following syntax:
NSArray
*animals =@[@"cat"
,@"dog"
,@"mouse"
,@"badger"]
;
But even though this is a much simpler syntax, there’s more to it than that with arrays. A common operation on an array is to get the object at a certain index. This also is made easier using literals. Usually, you would use the objectAtIndex: method:
NSString
*dog = [animalsobjectAtIndex
:1
];
With literals, it’s a matter of doing the following:
NSString
*dog = animals[1
];
This is known as subscripting, and just like the rest of the literal syntax, it is more concise and much easier to see what’s being done. Moreover, it looks very similar to the way arrays are indexed in other languages.
However, you need to be aware of one thing when creating arrays using the literal syntax. If any of the objects is nil, an exception is thrown, since literal syntax is really just syntactic sugar around creating an array and then adding all the objects within the square brackets. The exception you get looks like this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'
This brings to light a common problem when switching to using literals. The following code creates two arrays, one in each syntax:
id
object1 =/* ... */
;id
object2 =/* ... */
;id
object3 =/* ... */
;NSArray
*arrayA = [NSArray
arrayWithObjects
: object1, object2, object3, nil];NSArray
*arrayB =@[
object1, object2, object3]
;
Now consider the scenario in which object1 and object3 point to valid Objective-C objects, but object2 is nil. The literal array, arrayB, will cause the exception to be thrown. However, arrayA will still be created but will contain only object1. The reason is that the arrayWithObjects: method looks through the variadic arguments until it hits nil, which is sooner than expected.
This subtle difference means that literals are much safer. It’s much better that an exception is thrown, causing a probable application crash, rather than creating an array having fewer than the expected number of objects in it. A programmer error most likely caused nil to be inserted into the array, and the exception means that the bug can be found more easily.
Literal Dictionaries
Dictionaries provide a map data structure in which you add key-value pairs. Like arrays, dictionaries are commonly used in Objective-C code. Creating one used to look like this:
NSDictionary
*personData = [NSDictionary
dictionaryWithObjectsAndKeys
:@"Matt"
,@"firstName"
,@"Galloway"
,@"lastName"
, [NSNumber
numberWithInt:28
],@"age"
, nil];
This is rather confusing, because the order is <object>, <key>, <object>, <key>, and so on. However, you usually think about dictionaries the other way round, as in key to object. Therefore, it doesn’t read particularly well. However, literals once again make the syntax much clearer:
NSDictionary
*personData =@{@"firstName"
:@"Matt"
,@"lastName"
:@"Galloway"
,@"age"
:@
28
}
;
This is much more concise, and the keys are before the objects, just as you’d expect. Also note that the literal number in the example shows where literal numbers are useful. The objects and keys have to all be Objective-C objects, so you couldn’t store the integer 28; instead, it must be wrapped in an NSNumber instance. But the literal syntax means that it’s simply one extra character.
Just like arrays, the literal syntax for dictionaries suffers from an exception being thrown if any values are nil. However, for the same reason, this is a good thing. It means that instead of creating a dictionary with missing values, owing to the dictionaryWithObjectsAndKeys: method stopping at the first nil, an exception is thrown.
Also similar to arrays, dictionaries can be accessed using literal syntax. The old way of accessing a value for a certain key is as follows:
NSString
*lastName = [personDataobjectForKey
:@"lastName"
];
The equivalent literal syntax is:
NSString
*lastName = personData[@"lastName"
];
Once again, the amount of superfluous syntax is reduced, leaving an easy-to-read line of code.
Mutable Arrays and Dictionaries
In the same way that you can access indexes in an array or keys in a dictionary through subscripting, you can also set them if the object is mutable. Setting through the normal methods on mutable arrays and dictionaries looks like this:
[mutableArrayreplaceObjectAtIndex
:1
withObject:@"dog"
]; [mutableDictionarysetObject
:@"Galloway"
forKey
:@"lastName"
];
Setting through subscripting looks like this:
mutableArray[1
] =@"dog"
; mutableDictionary[@"lastName"
] =@"Galloway"
;
Limitations
A minor limitation with the literal syntax is that with the exception of strings, the class of the created object must be the one from the Foundation framework. There’s no way to specify your own custom subclass that should be created instead. If you wanted to create an instance of your own custom subclass, you’d need to use the nonliteral syntax. However, since NSArray, NSDictionary, and NSNumber are class clusters (see Item 9), they are rarely subclassed, as it’s nontrivial to do so. Also, the standard implementations are usually good enough. Strings can use a custom class, but it must be changed through a compiler option. Use of this option is discouraged because unless you know what you are doing, you will always want to use NSString anyway.
Also, in the case of strings, arrays, and dictionaries, only immutable variants can be created with the literal syntax. If a mutable variant is required, a mutable copy must be taken, like so:
NSMutableArray
*mutable = [@
[@1
, @
2
, @
3
, @
4
, @
5
]
mutableCopy
];
This adds an extra method call, and an extra object is created, but the benefits of using the literal syntax outweigh these disadvantages.
Things to Remember
- Use the literal syntax to create strings, numbers, arrays, and dictionaries. It is clearer and more succinct than creating them using the normal object-creation methods.
- Access indexes of an array or keys in a dictionary through the subscripting methods.
- Attempting to insert nil into an array or dictionary with literal syntax will cause an exception to be thrown. Therefore, always ensure that such values cannot be nil.