第3条:多用字面量语法,少用与之等价的方法
本条要点:(作者总结)
- 应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
- 应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
- 用字面量语法创建数组或字典时,若值中有 nil,则会抛出异常。因此,务必确保值里不含 nil。
编写 Objective-C 程序时,总会用到某几个类,它们属于 Foundation 框架。虽然从技术上来说,不用 Foundation 框架也能写出 Objective-C 代码,但实际上却经常要用到此框架。
这个几个类是 NSString、NSNumber、NSArray、NSDictionary。从类名上即可看出各自所表示的数据结构。
Objective-C 以语法繁杂著称。不过从 Objective-C 1.0 起,有一种非常简单的方式能创建 NSString 对象。这就是“字符串字面量”(string literal),其语法如下:
1 NSString *someString = @"Effective Objective-C 2.0";
如果不用这种语法的话,就要以常见的 alloc 及 init 方法来分配并初始化 NSString 对象了。在版本较新的编译器中,也能用这种字面量语法来声明 NSNumber、NSArray、NSDictionary 类的实例。使用字面量语法(literal syntax)可以缩短源代码长度,使其更为易读。
字面数值
有时需要把整数、浮点数、布尔值封入 Objective-C 对象中。这种情况下可以用 NSNumber 类,该类可处理多种类型的数值。若是不用字面量,那么就需要按下述方式创建实例:
1 NSNumber *someNumber = [NSNumber numberWithInt:1];
上面这行代码创建了一个数字,将其值设为整数 1。然而使用字面量能令代码更为整洁:
1 NSNumber *someNumber = @1;
可以看到,字面量语法更为精简。不过它还有很多好处。能够以 NSNumber 实例表示的所有数据类型都可使用该语法。例如:
1 NSNumber *intNumber = @1; 2 NSNumber *floatNumber = @2.5f; 3 NSNumber *doubleNumber = @3.14159; 4 NSNumber *boolNumber = @YES; 5 NSNumber *charNumber = @'a';
字面量语法也适用于下述表达式:
1 int x = 5; 2 float y = 6.32f; 3 NSNumber *expressionNumber = @(x * y);
以字面量来表示数值十分有用。这样做可以令 NSNumber 对象变的整洁,因为声明中只包含数值,而没有多余的语法成分。
字面量数组
数组是常用的数据结构。如果不使用字面量语法,那么就要这样来创建数组:
1 NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
而使用字面量语法来创建则是:
1 NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
上面这种做法不仅简单,而且还利于操作数组。数组的常见操作就是取某个下标所对应的对象,这用字面量来做更为容易。如果不用字面量,那么通常会用“objectAtIndex:” 方法:
1 NSString *dog = [animals objectAtIndex:1];
若使用字面量,则是:
1 NSString *dog = [animals objectAtIndex:1];
这也叫做“取下标”操作(subscripting),与使用字面量语法的其他情况一样,这种方式也更为简洁、更易理解,而且与其他语言中依下标来访问数组元素时所用的语法类似。
不过,用字面量语法创建数组时要注意,若数组元素对象中有 nil,则会抛出异常,因为字面量语法实际上是一种“语法糖”,其效果等于是先创建了一个数组,然后把方括号内的所有对象都加到这个数组中。
在改用字面量语法来创建数组是就会遇到这个问题。下面这段代码分别以两种语法创建数组:
1 id object1 = /* ... */; 2 id object2 = /* ... */; 3 id object3 = /* ... */; 4 5 NSArray *arrayA = [NSArray arrayWithObjects:object1, object2, object3, nil]; 6 NSArray *arrayB = @[object1, object2, object3];
如果 object1 与 object3 都指向有效的 Objective-C 对象,而 object2 是 nil,那么字面量语法创建数组 arrayB 时会抛出异常。arrayA 虽然能创建出来,但是其中却只含有 object1 一个对象。原因在于,“arrayWithObjects:”方法会依次处理各个参数,直到发现 nil 为止,由于 object2 是 nil,所以该方法会提前结束。
这个微妙的差别表明,使用字面量语法更为安全。抛出异常令程序终止执行,这比创建好数组之后才发现元素个数少了要好。向数组中插入 nil 通常说明程序有错,而通过异常可以更快的发现这个错误。
字面量字典
“字典”(Dictionary) 是一种映射型数据结构,可向其中添加键值对。与数组一样, Objective-C 代码也经常用到字典。其创建方式如下:
1 NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];
这样写令人困惑,因为其顺序是<对象>,<键>,<对象>,<键>。这与通常理解的顺序相反,我们一般认为是把“键”映射到“对象”。因此这种写法不容易读懂。如果改用字面量语法,就清晰多了:
1 NSDictionary *personData = @{@"firstName": @"matt", @"lastName": @"Galloway", @"age": @28};
上面这种语法更简明,而且键出现在对象之前,理解起来比较顺畅。此范例代码还说明了使用字面量数值的好处。字典中的对象和键必须都是 Objective-C 对象,所以不能把整数 28 直接放进去,而要将其封装在 NSNumber 实例中才行。使用字面量语法很容易就能做到这一点,只需要给数字前加一个 @ 字符即可。
与数组一样,用字面量语法创建字典时也有一个问题,那就是一旦值为 nil,便会抛出异常。不过基于同样的原因,这也是个好事。假如在创建字典时不小心用了空值对象,那么“dictionaryWithObjectsAndKeys:” 方法就会在首个 nil 之前停下,并抛出异常,这有助于查错。
字典也可以像数组那样用字面量语法访问。按照特定键访问其值的传统做法是:
1 NSString *lastName = [personData objectForKey:@"lastName"];
与之等效的字面量语法则是:
1 NSString *lastName = personData[@"lastName"];
这样写也省去了冗赘的语法,令此代码简单易读。
可变数组与字典
通过取下标操作,可以访问数组中某个下标或字典中某个键所对应的元素。如果数组与字典对象是可变的(mutable),那么也能通过下标修改其中的元素值。修改可变数组与字典内容的标准做法是:
1 [mutableArray replaceObjectAtIndex:1 withObject:@"dog"]; 2 [mutableDictionary setObject:@"Galloway" forkey: @"lastName"];
若换用取下标操作来写,则是:
1 mutableArray[1] = @"dog"; 2 mutableDictionary[@"lastName"] = @"Galloway";
局限性
字面量语法有个小小的限制,就是除了字符串以外,所创建出来的对象必须属于 Foundation 框架才行。如果自定义了这些类的子类,则无法用字面量语法创建其对象。要想创建自定义子类的实例,必须采用“非字面量语法”(nonliteral syntax)。然而,由于 NSArray、NSDictionary、NSNumber 都是也已定型的“子族”(class cluster),因此很少有人会从其中自定义子类,真要那样做也比较麻烦。而且一般来说,标准的实现已经很好了,无须再改动。创建字符串时可以使用自定义的子类,然而必须要修改编译器的选项才行。除非你明白这样做的后果,否则不鼓励使用此选项,用 NSString 就足够了。
使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable)。若想要可变版本的对象,则需要复制一份:
1 NSMutableArray *mutable = [@[@1, @2, @3, @4, @5] mutableCopy];
这么做会多调用一个方法,而且还要再创建一个对象,不过使用字面量语法所带来的好处还是多于上述缺点的。
END