开发实战程序员@IT·互联网

读书笔记--Objective-C 2.0 编写高质量的iOS代

2018-06-05  本文已影响74人  西风颂

第一章:熟悉Objective-C

1、OC语言的起源:

消息和函数的区别:使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器来决定;如果代码中调用的函数是多态,那么在运行时就要按照“虚方法表”来查出到底应该执行那个函数实现;而采用消息结构的语言,不论是否多态,总是在运行时才会去查找索要执行的方法;实际上,编译器甚至不关心接受消息的对象时何种类型;接收消息的对象问题也要在运行时处理;其过程叫做“动态绑定”

要点:

2.在类的头文件中尽量少引入其它头文件

要点:

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

字面量语法有个小限制,就是除了字符串以外,所创建出来的对象必须属于Foundation框架才行;
要点:

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

常用的命名法是:若常量局限于某“编译单元(也就是“实现文件”之内)”,则在前面加字母k,若常量在类之外可见,则通常以类名为前缀;在声明变量时不加static,则编译器会为它创建一个额“外部符号”,此时若是另一个编译单元中也声明了同名变量,那么编译器就抛出一条错误信息;既声明为static,又声明为const,那么编译器根本不会创建符号;
要点:

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

要点:

第二章:对象、消息、运行时

6.理解“属性”这一概念

@synthesize 编译器自动编写访问这些属性所需的方法,此过程叫做“自动合成”;
@dynamic 告诉编译器不要自动创建实现属性所用的实例变量,也不要为其创建存取方法;

属性可以拥有的特质:
原子性:默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性。如果属性具备nonatomic特质,则不使用同步锁。请注意,尽管没有名为“atomic”的特质,但是仍然可以在属性特质中写明这一点,编译器不会报错,若是自己定义的存取方法,那么就应该遵从与属性特质相符的原子性;(在iOS中使用同步锁的开销较大,这会带来性能问题,一般情况下并不要求属性必须是“原子性”,因此这并不能保证“线程安全”,若要实现“线程安全”的操作,还需采用更为深层的锁定机制才行)
读/写权限:readwrite/readonly
内存管理语义:
assign :设置方法只会执行针对“纯量类型”的简单赋值操作
strong :此特质表明该属性定义了一种“拥有关系”。为这种属性设置新值时,设置方法会被先保留新值,并释放旧值,然后再将新值设置上去;
week :此特质表明该属性定义了一种“非拥有关系”。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会被清空;
Unsafe_unretained :此特质的语义和assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象遭到摧毁时,属性值不会自动清空,这一点与weak有区别;
copy :此特质所表达的所属关系与strong类似,然而设置方法并不保留新值,而是将其“拷贝”。

方法名:可以为属性设置自定义的存取方法名

要点:

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

直接访问和属性访问的区别:

  1. 由于不经过OC的“方法派发”步骤,所以直接访问实例变量的速度当然比较快;在这种情况下,编译器所生成的代码会直接访问保存对象实例变量的那块内存;
  2. 直接访问实例变量时,不会调用“设置方法”,这就绕过了为相关属性所定义的“内存管理语义”。如:如果在ARC下直接访问一个声明为copy的属性,那么并不会拷贝该属性,只会保留新值并释放旧值;
  3. 直接访问实例变量,就不会触发“键值观察”通知(KVO);
  4. 通过属性来访问有助于排查与之相关的错误,因为给“获取/设置方法”中新增断点监控,监控该属性的调用者及其访问时机;

要点:

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

==操作符比较的是两个指针本身,而不是其所指的对象;
isEqual:来判断两个对象的等同性;

若两个对象相等,则其哈希码也相等,但是两个哈希码相同的对象却未必相等

要点:

9.以“类族模式”隐藏实现细节

系统框架中有很多类族。大部分集合类都是类族;在使用NSArray的alloc方法来获取实例时,该方法首先会分配一个属于某类的实例,此实例充当“占位数组”。该数组稍后会转为另一个类的实例,而那个类则是NSArray的实体子类;

想cocoa中NSArray这样的类族来书,还是有办法新增子类的,但是需要遵守以下几条规则:
1.子类应该继承自类族中的抽象基类;
若要编写NSArray类族的子类,则需令其继承自不可变数组的基类或可变数组的基类;
2.子类应该定义自己的数据存储方式;
开发者编写NSArray子类时,经常在这个问题上受阻;子类必须用一个实例变量来存放数组中的对象;这似乎和大家预想的不一样,我们以为NSArray自己肯定会保存那些对象,所以在子类中就无需再存一份了,但是大家要记住,NSArrzy本身只不过是包在其他隐藏对象外面的壳,它仅仅定义了所有数组都需要具备的一些接口;对于这个自定义的数组子类来说,可以用NSArray来保存其实例;
3.子类应当覆写超类文档中指明需要覆写的方法;

要点:

10.在既有类中使用关联对象存放自定义数据

要点:

11.理解objc_msgSend的作用

objc_msgSend通过接收者所属的类搜寻其“方法列表”,如果能找到与选择子名称相符的方法,就调至其实现代码;若是找不到就沿着继承体系继续向上查找,等找到合适的方法之后再跳转;再找到匹配的方法之后objc_msgSend会将匹配结果缓存在“快速映射表”里面,每个类都有这样的一块缓存,若是稍后还向该类发送与选择子相同的消息,那么执行起来就很快了;当然这种“快速执行路径”还是不如“静态绑定的函数调用操作”那么迅速,不过只要把选择子缓存起来,也就不会慢很多了,实际上消息派发并非应用程序的瓶颈所在;如果最终还是没有找到相符的方法,那就执行“消息转发”操作;

其他“边界情况”则需要交由OC运行环境中的另一些函数来处理:
objc_msgSend_stret 如果待发送的消息返回结构体,那么可交由此函数处理;只有当CPU的寄存器能够容纳的下消息返回类型时,这个函数才能处理此消息;若是返回值无法容纳与CPU寄存器中(比如说返回的结构体太大了),那么就由另一个函数执行派发;此时哪个函数会通过分配在栈上的某个变量来处理消息所返回的结构体;

objc_msgSend_fpret 如果消息返回的是浮点数,那么可交由此函数处理,在某些结构的CPU中调用函数时,需要对“浮点数寄存器”做特殊处理,也就是说通常所用的objc_msgSend在这种情况下并不合适,这个函数时为了处理x86等架构CPU中某些令人稍觉惊讶的奇怪情况;

objc_msgSendSuper 如果要给超类发消息,那么就交由此函数处理;也有另外两个与objc_msgSend_stret 和 objc_msgSend_fpret 等效的函数,用于处理发给super的相应消息;

每个类中都有一张表格,其中的指针都会指向这种函数,而选择子的名称就是查表是所用的key;objc_msgSend等函数正是通过这张表格来寻找应该执行的方法并跳至其实现的;请注意:原型的样子和objc_msgSend函数很像,这不是巧合,而是为了利用“尾调用优化”技术,令“跳至方法实现”这一操作变得更简单些;
如果某函数的最后一项操作是调用另外一个函数,那么就可以运用“尾调用优化”技术;编译器会生成跳转至另一函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”;只有当某函数的最后一个操作仅仅是调用其他函数而不会将其返回值另做他用时,才能执行“尾调用优化”;这项优化对objc_msgSend非常关键,不这么做的话,那么每次调用OC方法之前,都需要调用objc_msgSend函数准备“帧栈”;若是不优化,还会过早的发生“栈溢出”现象;

要点:

12.理解消息转发机制

动态方法解析
备援接收者
完整的消息转发
消息转发全流程

要点:

13.用“方法调配技术”调试“黑盒方法”

要点:

14.理解“类对象”的用意

要点:

第三章 接口和API设计

15.用前缀避免命名空间冲突

要点:

16.提供“全能初始化方法”

要点:

17.实现description方法

要点:

18.尽量使用不可变对象

要点:

19.使用清晰而协调的命名方法

要点:

20.为私有方法名前加前缀

21.要理解OC错误模型

NSError对象封装了三条信息:
1.Error domain(错误范围,其类型为字符串)
错误发生的范围,也就是产生错误的根源,通常用一个持有全局变量来定义;比如说:“处理URL的子系统”在从URL中解析或取得数据时如果出错了,那么就会使用NSURLErrorDomain来表示错误范围;
2.Error code(错误码,其类型为整数)
独有的错误代码,用以指明在某个范围内具体发生了何种错误;某个特定范围内可能会发生一系列相关错误,这些错误情况通常采用enum来定义;
3.User info(用户信息,其状态为字典)
有关此错误的额外信息,其中或许包含一段“本地化的描述”,或许还含有导致该错误发生的另外一个错误,经由此种信息,可将相关错误串成一条“错误链”;
要点:

22.理解NSCopying协议

要点:

第四章 协议与分类

23.通过委托与数据源协议进行对象间通信

要点:

24.将类的实现代码分散到便于管理的数个分类之中

要点:

25.总是为第三方类的分类名称加前缀

要点:

26.勿在分类中声明属性

要点:

27.使用“分类”隐藏实现细节

要点:

28.通过协议提供匿名对象

要点:

第五章 内存管理

29.理解引用计数

引用计数工作原理
通过hash表记录程序中每个对象的引用计数,当引用计数为0的时候,此对象才会被释放;

属性存取方法中的内存管理
旧址release新值retain

自动释放池
调用release会立刻递减对象的保留计数,然后有时候可以不调用它,改为调用autorelease,此方法会在稍后递减计数,通常是在下一次“事件循环”时递减,不过也可能执行得更早些;
自动释放池中的释放操作要等到下一次事件循环时才会执行,由此可见,autorelease能延长对象生命周期,使其在跨越方法调用边界后依然可以存活一段时间;

保留环(循环引用)

要点:

30.以ARC简化引用计数

使用ARC时必须遵循的方法命名规则
若方法名以下列词语开头,则其返回的对象归调用者所有:
alloc、new、copy、mutableCopy,则其返回的对象归调用者所有;归调用者所有的意思是:调用上述四种方法的那段代码要负责释放方法所返回的对象;也就是说,这些对象的保留计数是正值,而调用了这四种方法的那段代码要讲其中一次保留操作抵消掉;如果还有其他对象保留此对象,并对其调用autorelease,那么保留计数的值可能比1大,这也就是retainCount方法不太有用的原因之一;

ARC可以执行一些手工操作很难甚至无法完成的优化;例如在编译期ARC会把能够互相抵消的retain、release、autorelease操作约简;移出多余的操作;

变量的内存管理语义
__strong:默认语义,保留此值;
__unsafe_unretained:不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了;
__weak:不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空;
__autoreleaseing:把对象“按引用传递”给方法时,使用这个特殊的修饰符;此值在方法返回时自动释放;

要点:

31.在dealloc方法中只释放引用并解除监听

要点:

32.编写“异常安全代码”时留意内存管理问题

要点:

33.以弱引用避免保留换

要点:

34.以“自动释放池块”降低内存峰值

要点:

35.用“僵尸对象”调试内存管理问题

要点:

36.不要使用retainCount

要点:

第六章 块与大中枢派发

37.理解“块”这一概念

块的内部机构
块本身也是对象,在存放块对象的内存区域中,首个变量是指向class对象的指针,该指针叫做isa;其余内存里含有块对象正常运转所需的各种信息;

在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码;函数原型至少要接受一个void *型的参数,此参数代表块;块其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态;而改用块之后,则可以把原来用标准C语言特性所编写的代码封装成简明且易用的接口;

descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针;辅助函数在拷贝及丢弃块对象时运行,其中会执行一些操作;

要点:

38.为常用的块类型创建typedef

要点:

39.用handler块降低代码分散程度

要点:

40.用块应用其所属对象时不要出现保留环

要点:

41.多用派发队列,少用同步锁

要点:

42.多用GCD,少用performSelector系列方法

要点:

43.掌握GCD及操作队列的使用时机

NSOperationQueue:
取消某个操作;
指定操作间的依赖关系
通过键值观测机制监控NSOperation对象的属性
指定操作的优先级
重用NSOperation对象

要点:

44.通过Dispatch Group 机制,根据系统资源状况来执行任务

要点:

45.使用dispatch_once来执行只需执行一次的线程安全代码

要点:

46.不要使用dispatch_get_current_queue

iOS6.0起就弃用了此函数
要点:

第七章 系统框架

47.熟悉系统框架

要点:

48.多用块枚举,少用for循环

要点:

49.对自定义其内存管理语义的collection使用无缝桥接
要点:

50.构建缓存时选用NSCache而非NSDictionary

NSCache当系统资源要消耗尽时,它可以自动删减缓存,如果采用普通的字典,那么就要自己编写挂钩,在系统发出低内存通知时手工删减缓存,而NSCache则会自动删减,由于其实Foundation框架的一部分,所以与开发者相比,它能在更深的层面上插入挂钩;此外,NSCache还会先行删减“最久未使用的”对象;若想自己编写代码来为字典添加此功能,则会十分复杂;

NSCache对象不拷贝键的原因在于:很多时候,键都是由不支持拷贝操作的对象来充当的,因此NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便;另外NSCache是线程安全的;而字典则不具备;

要点:

51.精简initialize与load的实现代码

区别:
1.前者是惰性加载
2.前者执行是在线程安全的环境下执行
3.子类未实现,而超类实现了就运行超类的实现代码

要点:

52.别忘了NSTimer会保留其目标对象

要点:

上一篇 下一篇

猜你喜欢

热点阅读