第一条:了解Objective-C语言的起源
1.Objective-C语言的对象所占内存总是分配在“堆空间”中。
2.分配在堆中的内存必须直接管理,而分配在栈上用于保存变量的内存则会在其栈帧弹出时自动清理。
3.基本类型也被存放在栈空间。
第2条:在类的头文件中尽量少引用其他头文件
1.当一个类作为另一个类的属性的类型时,只需声明
@class className
有两个好处:
(1) 减少编译时间
(2)解决了两个雷相互引用的问题。
如果在各自头文件中引入对方的头文件,则会导致“循环引用”。当解析其中一个头文件时,编译器会发现它引入了另一个头文件,而那个头文件回过头来引用了第一个头文件。使用#import而非#include指令虽然不会导致死循环,但这意味着两个类里有一个无法被正确编译。
2.最好把协议单独放在一个头文件中。
3.委托协议就不能单独写一个头文件了。此时最好能在实现文件中声明此类实现了委托协议,并把这段代码放到“class-continuation”分类里。这样的话,只要在实现文件中引入包含委托协议的头文件即可。
第3条:多用字面量语法,少用与之等价的方法
1.字面量字符串
NSString *someString = @"Effective Objective-C 2.0";
2.字面数值
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159
NSNumber *boolNumber = @YES;
NSNumber *charNmuber = @'a';
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);
3.字面量数组
NSArray *animals = @[@"cat",@"dog",@"mouse",@"badger"];
NSString *dog = animals[1];
4.字面量字典
NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@28};
NSString *lastName = personData[@"lastName"];
与数组一样,用字面量语法创建字典时也有个问题,那就是一旦有值为nil,便会抛出异常
5.可变数组与字典
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
使用字面量语法创建出来的字符串、数组、字典对象都是不可变的。若想要可变版本的对象,则需复制一份
NSMutableArray *mutable = [@[@1,@2,@3,@4,@5] mutableCopy];
第4条:多用类型变量,少用#define预处理指令
1.用以下方式定义的变量包含类型信息,其好处是清楚地描述了常量的含义。
static const NSTimeInterval kAnimationDuration = 0.3;
如果试图修改由const修饰符所声明的变量,那么编译器就会报错。而static修饰符则意味着改变量仅在定义此变量的编译单元中可见(.m文件中)。
2.如果需要对外公开某个变量,应该这样定义:
// In the header file
extern NSString *const EOCStringConstant;
// In the implementation file
NSString *const EOCStringConstant = @"VALUE";
这样定义常量优于使用#define预处理指令,因为编译器会确保常量值不变。一旦在.m中定义好,即可随处使用。而采用预处理指定所定义的常量可能会无意中遭人修改,从而导致应用程序各个部分所使用的值互不相同。
第5条:用枚举表示状态、选项、状态码
1.枚举只是一种常量命名方式,某个对象所经历的各种状态就可以定义为一个简单的枚举集。
enum EOConnectionState {
EOConnectionStateDisconnected,
EOConnectionStateConnecting,
EOConnectionStateConnected,
};
// 定义变量的方式
enum EOConnectionState state = EOConnectionStateDisconnected,
若是每次不用敲入enum而只需写EOConnectionState就好了。要想这样做,则需使用typedef关键字重新定义枚举类型:
enum EOConnectionState {
EOConnectionStateDisconnected,
EOConnectionStateConnecting,
EOConnectionStateConnected,
};
typedef enum EOConnectionState EOConnectionState;
// 定义变量的方式
enum EOConnectionState state = EOConnectionStateDisconnected,
2.可以指明用何种“底层数据类型”来保存枚举类型的变量。这样做的好处是,可以向前生命枚举变量了。若不指定底层数据类型,则无法向前声明枚举类型,因为编译器不清楚底层数据类型的大小,所以在用到此类枚举时,也就不知道究竟该给变量分配多少空间。
指定底层数据类型所用的语法是:
enum EOCConectionStateConnectionState :NSInteger { /*...*/};
在向前声明时指定底层数据类型:
enum EOCConectionStateConnectionState :NSInteger;
3.不使用编译器所分配的序号,而是手工指定某个枚举成员所对应的值。
enum EOConnectionState {
EOConnectionStateDisconnected = 1,
EOConnectionStateConnecting, // 2
EOConnectionStateConnected, // 3
};
4.定义选项的时候,若这些选项可以彼此组合,各选项可通过“按位或操作符”来组合。
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
因为每个枚举值所对应的二进制表示中,只有一个二进制位的值是1。用“按位与操作符”即可判断出是否已启用某个选项:
enum UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
if(resizing & UIViewAutoresizingFlexibleWidth){
// UIViewAutoresizingFlexibleWidth is set
}
5.凡是需要按位或操作来组合的枚举都应使用NS_OPTIONS定义。若是枚举不需要相互组合,则应使用NS_ENUM来定义。
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
以上代码等价于
6.枚举用在switch语句里,最好不要有default分支。
第6条:理解属性这一概念
1.在OC中,把实例变量当做一种存储偏移量所用的“特殊变量”,交由“类对象”保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。
2.@synthesize和@dynamic
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述语法会将生成的实例变量名为为_myFirstName与_myLastName,而不再使用默认的名字。
使用@dynamic关键字,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会报错,它相信这些方法能在运行期找到。
3.具备readonly属性的属性仅拥有获取方法,只有当改属性由@synthesize实现时,编译器才会为其合成获取方法。你可以用此特质把某个属性对外公开为只读属性,然后在“class-continuation分类”中将其重新定义为读写属性。
4.内存管理语义
- assign “设置方法”只会执行针对“纯量类型”(scalar type,例如CGFloat或NSInteger等)的简单赋值操作。
- strong 此特质表明该属性定义了一种“拥有关系”。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。
- weak 此特质表明该属性定义了一种“非拥有关系”。为这种属性赋新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空。
- unsafe_unretained 此特质的语义和assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象遭到摧毁时,属性值不会自动清空,这一点与weak有区别。
- copy 此特质所表达的所属关系与strong类似,然而设置方法并不保留新值,而是将其“拷贝”。只要实现属性所用的对象是“可变的”,就应该在设置新属性值时拷贝一份。
第7条:在对象内部尽量直接访问实例变量
在写入实例变量时,通过其设置方法来做,而在读取实例变量时,则直接访问之。此办法既能提高读取操作的速度,又能控制对属性的写入操作。