读EffectiveObjective-C2.0(第三条、第四条
第三条:多用字面量语法,少用与之等价的方法
-
NSString
,NSNumber
,NSArray
,NSDictionary
都可以使用字面量的方式创建一个对象。相比于使用alloc及init方法创建对象简单了许多。字面量语法可以缩短代码长度,十分易读。
NSString *someString = @"Effective Objective-C";
-
字面数值(NSNumber):有些时候需要将整数、浮点数、布尔值封装成OC对象。这种情况下可以使用
NSNumber
类,该类提供了多种类型的包装方法。如果不使用字面量,那么就需要按照下面的方式进行对象包装:
NSNumber *someNumber = [NSNumber numberWithInt:1];
如果使用字面量,就会变得十分的简洁
NSNumber *someNumber = @1;
- 能够使用
NSNumber
表示的字面量有很多:
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.1;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
- 字面量语法也适用于表达式
int x = 5;
float y = 6.32;
NSNumber *expressionNumber = @(x * y);
- 字面数组(NSArray):如果不使用字面量语法来创建数组,代码如下:
NSArray *animals = [NSArray arrayWithObjects:@"dog", @"cat", "mouse", @"badger", nil];
使用字面量语法创建数组:简单、易操作
NSArray *animals = @[@"dog", @"cat", @"mouse", @"badger"];
- 通过下标获取数组中的元素,不使用字面量语法:
NSString *dog = [animals objectAtIndex:0];
使用字面量语法:
NSString *dog = animals[0];
这种方式明显更加简单,和其他语言获取元素方式相似。
注意:使用字面量语法创建的数组中的元素不能为nil,如果数组中的元素为nil,就会抛出异常
NSString *object1 = @"dog";
NSString *object2 = @"cat";
NSString *object3 = @"mouse";
NSArray *array1 = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSLog(@"%@", array1);
NSArray *array2 = @[object1, object2, object3];
NSLog(@"%@", array2);
上述代码:二者打印的结果是一致的,没有任何问题。
- 如果将object2指向nil。array1不会抛出异常,array1只会打印object1,因为
arrayWithObjects:
这种方式创建的数组当发现元素为nil的时候,方法会提前结束。 - array2会抛出异常,并终止程序的运行。二者对比来说,字面量语法创建数组更加安全,帮助我们及时的发现数组中的元素存在nil的情况。而前者的方式,不容易发现数组中的元素存在nil的情况。
NSString *object1 = @"dog";
NSString *object2 = nil;
NSString *object3 = @"mouse";
NSArray *array1 = [NSArray arrayWithObjects:object1, object2, object3, nil];
NSLog(@"%@", array1);
NSArray *array2 = @[object1, object2, object3];
NSLog(@"%@", array2);
- 字面量字典(NSDictionary):是一种映射型数据结构,相当于其他语言中的Map。可向其中添加键值对。
- 使用非字面量语法创建字典:这种方式创建是**Value -> Key **,和通常情况理解的键值对相反,不是很方便。
NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];
- 使用字面量语法创建字典:明显比上面的方式清晰,简洁
NSDictionary *personData = @{
@"firstName": @"Matt",
@"lastName": @"Galloway",
@"age": @28
};
注意:字典和数组中的元素都必须是对象类型,与数组一样,使用字面量语法创建的字典,如果遇到nil,便会抛出异常,而
dictionaryWithObjectsAndKeys:
方法会在遇到nil之前的时候停下。
- 字典也可以向数组一样通过字面语法进行元素的访问,字面量语法更加方便
NSString *lastName = [personData objectForKey:@"lastName"]; // 非字面语法
NSString *lastName = personData[@"lastName"]; // 字面量语法
第四条:多用类型常量,少用#define预处理指令
编写代码时经常使用到常量。例如:设置视图的动画时长,而且动画时长在多个位置都会使用到。通常会把时长抽取为常量,在需要的位置直接调用即可,方便管理。
我们也许会使用这种预处理指令来实现:
- 使用这种预处理指令的方式会把源代码中的
ANIMATION_DURATION
字符串替换为0.3。这可能是我们想要的一种结果,但是这样定义出来的常量没有类型信息。动画时间与时间相关,但是在代码中没有明确指出类型。 - 预处理过程会把碰到的所有
ANIMATION_DURATION
一律替换成0.3,这样的话,假设此指令声明在某个头文件中,那么所有引入这个头文件的代码,其ANIMATION_DURATION
都会被替换。
#define ANIMATION_DURATION 0.3
- 要解决此问题,可以使用类型常量:
- 这种定义方式包含类型信息,清晰的描述了常量的含义
static const NSTimeInterval kAnimationDuration = 0.3;
-
注意常量的名称。常用的命名法是:如果常量局限于某一个编译单元(实现文件)之内,则在前面加字母k;如果常量在类之外的多个文件可见,则通常以类名为前缀
-
定义常量的位置很重要,不要在头文件中声明预处理指令和static const 定义的常量。当多个地方引入该头文件的时候,会将定义的常量引入,而常量不一定就是被需要的,而且,常量的名称有可能互相冲突。如果要定义全局的常量,最好加上前缀,因为OC没有命名空间的概念,不加前缀很可能导致命名的冲突。
-
如果不打算公开某个常量,应该将其定义在使用该常量的实现文件中。比如说执行动画的时间一般情况下都是时间常量即可。
// EOCAnimatedView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface EOCAnimatedView : UIView
- (void)animate;
@end
NS_ASSUME_NONNULL_END
// EOCAnimatedView.m
#import "EOCAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implementation EOCAnimatedView
- (void)animate {
[UIView animateWithDuration:kAnimationDuration animations:^{
// do something
}];
}
@end
- 变量一定要使用
static
和const
声明。- 使用
const
修饰符声明的变量,是不允许修改的,编译器会报错。 - 使用
static
修饰符声明的变量,只能在定义次变量的实现文件(.m)文件中可见。
- 使用
所以kAnimationDuration
变量的作用域,只在EOCAnimatedView.m
中。
- 如果声明此变量的时候没有加
static
,编译器会为它创建一个外部符号。此时在另一个实现文件中也声明了相同的变量,那么编译器就会报错。
duplicate symbol '_kAnimationDuration' in:
ViewController.o
EOCAnimatedView.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
所以,同时使用static
和const
声明一个变量,不会创建外部符号,也保证了变量不可以被修改,还包好了类型信息,好处非常多的。
-
有的时候我们不得不需要公开某个常量。比如说使用
NSNotificationCenter
的通知处理,要在多个文件中定义相同的Key
。这种常量,需要放在全局符号表中,以便可以在定义该常量的实现文件之外使用。通常情况下,我的做法如下:- 创建一个const文件(名称可以自己起),包含声明文件和实现文件。
- 变量在头文件中声明在实现文件中定义
// const.h extern NSString *const EOCStringConstant; //!< 注释内容 // const.m NSString *const EOCStringConstant = @"Value";
- 编译器看到头文件中的
extern
关键字,就知道如何在引入头文件的代码中处理该常量了。这个关键字告诉编译器,在全局符号表中将会有一个名字叫做EOCStringConstant
的符号。
-
因为符号要放在全局符号表中,命名要格外注意。通常用相关的类名作为前缀。类似系统定义的
UIApplicationDidEnterBackgroundNotification
这种方式。见名知意。