《Effective Objective-C 2.0》之《对象、消息、运行期》读书笔记

理解“属性”这一概念

要点
  • 可以用@property语法来定义对象中所封装的数据。
  • 通过“特质”来指定存储数据所需的正确语义。
  • 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。
  • 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能。
属性语法
  1. @synthesize
    系统默认语法,系统会自动生成setter和getter方法
  2. @dynamic
    系统不会自动生成setter和getter方法。如果没有实现getter和setter方法,编译的时候是不会报错的,但是运行的时候,如果使用getter和setter方法,则会Crash
    如以下代码在使用的使用就会crash。
@interface ViewController ()
@property (nonatomic, copy) NSString *firstName;
@end

@implementation ViewController
@dynamic firstName;

 - (void)viewDidLoad {
    [super viewDidLoad];
    NSString *f =  self.firstName;
    //以下代码也会crash
    //self.firstName = @"a";
}
@end
属性特质
  1. 原子性
  • nonatomic:不使用同步锁,iOS需要使用该属性
  • atomic:默认属性,iOS使用会影响性能
  1. 读写权限
  • readwrite:可以使用getter和setter方法
  • readonly:只可以使用getter方法,对外暴露接口的时候可以定义改属性,然后在实现中再定义成readwrite,用于修改
  1. 内存管理语义
  • assign:只会针对“纯量关系”(如CGFloat或NSInteger)的简单赋值操作。指向对象释放时 ,指针不会置nil
  • strong:定义一种“拥有关系”。设置方法会先保留新值,并释放旧值,然后再将新值设置上去
  • weak:定义一种“非拥有关系”。设置方法不保留新值,也不释放旧值。指向对象释放时 ,指针会置nil,与assign区分
  • unsafe_unretained:与assign相同,适用于“对象类型”,“非拥有关系”,指向对象释放是,指针不会置nil
  • copy:与strong类似。设置方法不保留新值,而是将其“拷贝”。属性类型为NSString*时,经常用此特质来保护其封装性,因为传递给设置方法的新值可能指向一个NSMutableString类的实例。这个类是NSString类的子类,表示一种可以修改其值的字符串,此时 若不拷贝字符串,那么设置完属性后,字符串的值就可能会在对象不知情的情况下更改。所以,这时就要拷贝一份“不可变”的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的”,就应该设置新属性值时拷贝一份。
方法名
  1. getter=指定“获取方法”的方法名,如
@property (nonatomic, getter=isOn, assign) BOOL on;
  1. setter=指定“设置方法”的方法名,这个不太常用。
atomic和nonatomic区别

具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性。也就是说,如果两个线程读写同一个属性,那么不论何时,总能看到有效的属性值。若是不加锁的话,那么当其中一个线程正在改写某属性时,另外一个线程也许会突然闯入,把尚未修改好的属性值读出来。发生这种情况时,线程读到的属性值可能不对。
如果开发过iOS程序,你就会发现,其中所有属性都声明为nonatomic。这样做的历史原因是:在iOS中使用同步锁的开销大,这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不并不能保证“线程安全”,若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行。例如,一个线程在连续多次读取某属性的过程中有别的线程在同时改写该值,那么即便将属性设置为atomic,也还是会读到不同的属性。因此,开发iOS程序时,一般都用使用nonatomic属性。但是在开发Mac OS X时,使用atomic属性通常不会有性能问题。

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

要点
  • 在对象内部读取数据时,应该直接通过实例变量来读,而写入数据时,则应该通过属性来写。
  • 在初始化方法以及dealloc方法中,总是应该直接通过实例变量来读写数据。
  • 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据。
直接使用实例变量和使用设置方法的区别
  • 直接访问实例变量的速度比较快,编译器所生成的代码会直接访问保存对象实例变量的那块内存
  • 直接访问实例变量时,不会调用“设置方法”,会绕过为相关属性所定义的“内存管理语义”。比如说,如果在ARC下直接访问一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值并释放旧值
  • 如果直接访问实例变量,不会触发“键值观测”(KVO)通知
  • 通过属性来访问有助于排查与之相关的错误,因为可以给“获取方法”或“设置方法”中新增“断点”,监控改属性的调用者及其访问时机
在初始化方法中如何设置属性值

这种情况下总是应该直接访问实例变量,因为子类可能会“覆写”设置方法。如果子类“覆写”设置方法,则在基类中使用该属性的设置方法,会访问到子类的设置方法。
某些情况下,却必须在初始化方法中调用设置方法:
如果待初始化的实例变量声明在超类中,而我们又无法再子类中直接访问此实例变量的话,那么就需要调用“设置方法”了。

惰性初始化

这种情况下,必须通过“获取方法”来访问属性,否则,实例变量就永远不会初始化。对于创建成本高的属性,应该在使用的时候才创建。如:

- (UILabel *)lable {
    if (!_lable) {
        _lable = [[UILabel alloc] init];
        _lable.text = @"TestLabel";
    }
    return _lable;
}

理解“对象等同性”这一概念

要点
  • 若想检测对象的等同性,请提供“isEqual:”与hash方法。
    对于对象等同性,不要使用==来判断拉个对象是否相等,该方法只会比较指针,应该使用“isEqual”方法,而NSObject对象的isEqual方法默认也只是比较指针,有特殊比较需求,需要自己重写isEqual方法
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象缺未必相同。
  • 不要盲目地逐个检测每条属性,而是应该依据具体需求来定制检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希碰撞几率低的算法。
    直接取属性的hash比使用字符串拼接效率高,类似下面代码:
 - (NSUInteger)hash {
    NSUInteger firstNameHash = [_firstName hash];
    NSUInteger lastNameHash = [_lastName hash];
    NSUInteger ageHash = _age;
    return firstNameHash ^ lastNameHash ^ ageHash;
}```

####以“类簇模式”隐藏实现细节
#####要点
* 类簇模式可以把实现细节隐藏在一套简单的公共接口后面。
* 系统框架中经常使用类簇。
* 从类簇的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。
主要通过“工厂模式”实现

####在既有类中使用关联对象存放自定义数据
#####要点
* 可以通过“关联对象”机制来把两个对象连起来。
* 定义关联对象时可制定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”。
* 只有在其他做法不可行时才应该选用关联对象,因为这种做法通常会引入难于查找的bug。

**关于*“关联对象”*在后续文章中单独讲解**

####理解objc_msgSend的作用
#####要点
* 消息由接收者、选择子以及参数构成。给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method)。
* 发给某对象的全部消息都要由“动态消息派发系统”(dynamic message dispatch system)来处理,该系统会查出对于的方法,并执行其代码。

**关于*objc_msgSend*在后续文章中单独讲解**

####理解消息转发机制
#####要点
* 若对象无法响应某个选择子,则进入消息转发流程。
* 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
* 对象可以把无法解读的某些选择子转交给其他对象来处理。
* 经过上述两步后,如果还是没有办法处理选择子,那就启动完整的消息转发机制。

**关于消息转发机制在后续文章中单独讲解**

####用“方法配调技术”调试“黑盒方法”
#####要点
* 在运行期,可以向类中新增或者替换选择子所对应方法的实现。
* 使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
* 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。

**关于*方法配调技术*在后续文章中单独讲解**

####理解“类对象”的用意
#####要点
* 每个实例都有一个指向Class对象的指针,用以表示其类型,而这些Class对象则构成了类的继承体系。
* 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
* 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

**关于*类对象*在后续文章中单独讲解**

你可能感兴趣的:(《Effective Objective-C 2.0》之《对象、消息、运行期》读书笔记)