EffectiveOC的学习-1

EffectiveOC 1-8条

2016-06-03 | 阅读

1,了解Objective-C语言的起源:

OC为C语言的超集,为C添加了面向对象的特性。

要理解C中的指针和内存模型。

2.在类文件中尽量少引入其他头文件:

引入过多头文件,将接口暴露,增加耦合度,这显然是不可取的。

对于一个类B的声明中调用其他的类A,如果头文件中内容只是简单引用对象,而并不需要知道其所有的接口细节时,不应该引入类A的头文件。而使用forward declaring方式来向前声明该类:

@class A

而如果实现中必须知道接口细节时,再将头文件引入,这样,将引入头文件的时机尽量延后,只在确定需要时才引入。

循环引用会引起死循环,但是现在都是使用import进行导入,不会遇到循环引用的问题。

协议即接口,显然应该放在一个单独的头文件中。

对于不太重要的属性,可以将其放在class-continuation中,减少在头文件中的内容,以降低编译时间和降低彼此的依赖程度。

3.多用字面量语法,少用与之等价的方法。

对于Foundation框架中的NSString、NSNumber、NSArray、NSDictionary类,对于起创建,一般使用字符串字面量string literal

NSString *someString = @"a String";
NSNumber *number = @{x * y };
NSArray *animals = @[@"cat",@"dog",@"mouse"];
NSDictionary *personData = @{@"firstName":@"matt",
@"lastname":@"Galloway",
@"age":@28};
NSString *str =  personData[@"lastname"];

需要注意的是,对NSDictionary和NSArrary创建时,不应该有nil即空值,因为字面量语法实际上是一种语法糖的语法,即对一定语句的缩减版本,而这里调用的是不定参数的构造函数,相当于执行以下方法:

NSArray *animals = [NSArray array WithObjects:cat,dog,mouse,nil];

因为在不定参数的函数中以nil结尾,所以如果使用字面量语法,在参数中使用nil就会抛出异常。

4.多用类型常量,少用#define预处理指令:

C中的陋习,在C++和OC中都有了常量后,就应该使用常量了。简单来说,优点是带类型,方便检测,加 不会缩小命名空间。但在实际开发中,要根据情况来选择使用宏还是常量,宏是有好处的,写起来简单方便,而且适用范围也很广。

不打算公开某常量的话,就应该将其定义在实现文件中。

使用const static来修饰静态常量,const表示常量,static表示该变量的仅在本编译单元可见,不带static就是全局变量了。

使用extern来修饰全局常量,跟C++和C一样,现在头文件中声明,再在实现文件中定义。针对指针时,放在*号前表示地址是常量,放在*后表示地址指向内容是常量。

5.用枚举表示状态、选项、状态码:

OC中的枚举,编译器自动为枚举分配编号,一般从0开始依次递增。实现枚举所用的数据类型取决于编译器。定义枚举变量的方式:

enum ABCEnum{
 a,
 b,
 c,
};
enum ABCEnum en = a;

而OC中可以省略这个enum,通过typedef:

typedef enum ABCEnum ABCEnum;

这样,之后就可以使用枚举名来代替完整的enum ABCEnum了。

C++11中可以指明何种“底层数据类型”来保存枚举类型的变量。这样就可以提前声明枚举变量。而在不能指定底层数据类型时,无法向前声明枚举类型的原因是,编译器不清楚底层数据类型的大小,所以无法为变量分配空间,也就无法声明变量。向前声明指定数据类型如下:

enum ABCEnum : NSInteger;

当然,在定义枚举时,指定数据类型,也是在枚举名后添加冒号加数据类型。

枚举中第一个值默认是0,自定设定的值后的值就从自定的值开始依次递增:

enum ABCEnum : NSInteger{
 A , // 0
 B = 1000,
 C , // 1001
};
而一般在使用枚举类型的变量定义选项时,选项是彼此组合的,也就是用到按位或操作 时,就要设置枚举值都是合理移位后的值:
enum ABCEnum{
 A = 1 << 0,
 B = 1 << 1,
 C = 1 << 2,
};

这样可以通过或操作来组合多个选项,方便快捷。

在OC中,枚举的定义,一般使用NS_ENUMNS_OPTIONS,使用方法举例如下:

typedef NS_OPTIONS(NSInteger, NSStringDrawingOptions) {
    NSStringDrawingUsesLineFragmentOrigin = 1 << 0,
    NSStringDrawingUsesFontLeading = 1 << 1, 
    NSStringDrawingUsesDeviceMetrics = 1 << 3, 
    NSStringDrawingTruncatesLastVisibleLine  = 1 << 5,
}

使用这两个宏的目的是,指定枚举值的底层数据类型,同时兼容不同平台的不同版本的编译器。而两者的区别,在于,如果常量中进行了移位或者进行了其它位操作,就应该使用NS_OPTIONS,而没有进行位操作,就应该使用NS_ENUM

在switch中使用枚举时:

switch(abc){
	A:
	break;
	B:
	break;
	C:
	break;
}

OC中的switch可以省略case.

6. 理解 属性 property 这一特性.

属性来封装对象中的数据,这些实例变量通过access方法来访问,即 getter和setter. 编译器会自动编写getter与setter, 开发者可以通过点语法 dot syntax来访问属性. 编译器将点语法转换为调用getter和setter,也就是点语法只是简单的语法糖而已.

使用@dynamic关键字,在实现文件中,可以使编译器不自动创建getter和setter方法,且忽略编译时无法访问getter , setter的警告, 其访问方法可以在运行时,动态赋予.

属性 的attribute:

  • 原子性: nonatomic , 不设置nonatomic就是atomic原子的, 原子性极大访问降低效率.
  • 读写权限: readwrite默认 ,readonly只读: 可以在声明中声称属性是只读,然后再class-continuation中重新定义为读写.
  • 内存管理:

    • assign: 对简单数据类型,即非对象的基础数据类型,进行赋值操作.
    • strong: 属性为拥有,即__strong
    • weak: 属性未非拥有,当对象被其他人释放时,该属性值为nil.
    • unsafe_unretained: 不安全的非持有状态,与assign的区别在于前者针对对象,而后者针对基础数据类型,与weak的区别在于其不安全,weak对象释放后为nil, 而这个不安全的属性值不会清空.
    • copy: 属性为持有状态,但是持有的不是设置的对象,而是该对象的copy, 这又涉及到copy与mutablecopy, 以及深浅赋值的内容. 设置该属性的目的是 创建赋值对象的不变版本,以保存这个可能会变化的值的当前属性值. 设置的对象必须实现NSObject的copy协议.
  • 方法名: 属性可以指定方法名称,getter = gtXX , setter= stXX , 以进行定制.

个人观点,对于一般私有不用于继承的属性,为了效率,都会直接访问属性本身,因为通过getter setter方法进行访问,本身效率就很低. 但是对于getter setter方法, 其对效率的提高还是有的. 但 对于创建消耗较大的对象, 使用getter 方法来实现延迟创建, 只有用到的时候才创建该对象, 而表示固定在初始化时创建, 这样对效率的提高还是很大的.

7. 在对象内部尽量直接访问实例变量

这里继续对属性进行讨论, 方法派发method dispatch是很耗时的,与一般语言比起来,效率低太多,其他语言直接根据函数地址访问函数,而OC中,要发送消息,然后消息响应对象中寻找消息函数,然后调用.

所以,一般的做法都是,在类中直接访问实例变量, 而着重注意这些属性的初始化,以防直接访问未初始化的实例变量. 一般选择在构造函数中进行初始化,但是 如果调用setter方法进行初始化,调用的可能是子类派生的写法,所以也还是应该直接访问实例变量进行赋值.

所以说,对象内部尽量使用 直接访问, 而通过访问方法getter能够带来的速度提升,体现在 惰性初始化,延迟创建上, 对于耗费较大的对象,可以忽略调用的消耗,而强调延迟创建带来的效率提升.

8. 理解equality 这一概念.

同其他语言一样, 直接对 实例对象进行 == 判断,判断的是对象指针是否相等,即地址是否相同. 要实现真正的对象是否相同的判断, 要实现和使用NSObject 协议中的

- (BOOL) isEqual: (id) object;
- (NSUInteger) hash;

如果两个对象使用isEqual 判断相等, 那么 hash必须返回相同的值 . 但是反之不是如此,即hash值相同但是两者并不一定isEqual .

而在isEqual中对两者是否相同的判断,一般先判断两者地址是否相同,地址相同一般是相同的,然后判断对象 的 类 是否相同, 但是基类和派生类的类是不相同的, 但是两者有可能是相同的, 所以要注意这一点,进行考虑. hash 使用在collection中, 作为hash值,来进行索引. 设计一个相对合理 碰撞较少 且速度较快的 哈希算法.

而在这里还需要注意的一点是, 在容器中加入可变对象后, 会引发一些不好的效果. 首先,collection在加入对象时,记录其hash值,而不再进行维护. 对collection里面的可变对象进行修改, 如果拿这个collection与其他collection进行比较,比较的是修改前的hash. 而如果在set中,对可变对象进行修改可以导致set中含有多个相同的对象.