一 熟悉OC 对象 消息 运行时
1.OC消息结构语言,其运行时所应执行的代码由运行环境决定; 动态性语言。
OC语言的对象所占的内存总是分配在堆空间,绝不会分配在栈空间。 指针的大小:32位4字节,64位机8字节。
2.在类的头文件里尽量少引用其他头文件。 可以使用@class向前声明类的方法。 这可以相应的减少编译时间。
注意:类遵循某个协议,就必须引用完整,不能使用向前声明方法。 (因此也就出现了:1.要么将协议移到class-continuation里;2.如果不行,就把协议单独放到一个头文件里,然后引入。)
3.多使用字面量语法,少使用与之等价的方法
NSArray *array = [@"name", @"title"]; 而不要使用 NSArray *array = [NSArray arrayWithObjects:@"name",@"title",nil]; NSDictionary 同理。
这样子创建,就可以通过去下标的方式取对应的元素了。方便。
4.多用类型常量,少用#define 宏预处理
可以参考官方通知名称的写法。
5.用枚举表示状态、选项、状态码
首先枚举名字直接易懂。 枚举分 NS_ENUM 和 NS_OPTIONS。 具体使用:需要以按位或操作组合的都用NS_OPTIONS,不用互相组合的用NS_ENUM。
另外枚举使用switch的时候,不要加default。 这样子,新增枚举值,编译器就会给出警告信息。
6.属性 @property 自动生成set get方法。
ABI: Application Binary Interface 应用程序二进制接口。
assign: 只会执行“纯量类型(CGFloat, NSInteger)”的简单赋值操作。
strong: 定义一种拥有关系。 先保留新值,并释放旧值,然后再将新值设置上去。
copy:设置方法并不保留新值,而是将其拷贝。
weak:定义了一种“非拥有关系”。 设置方法既不保留新值,也不释放旧值。 对象摧毁时,属性也会清空nil。
unsafe_unretained: 和assign 相同,它适用于对象类型。 也是“非拥有关系”。 但是对象摧毁,不会置空nil。 这和weak的区别。
atomic:使用同步锁开销较大,会带来严重的性能问题。
7.在对象内部尽量直接访问实例变量
读取实例变量的时候才用直接访问的形式,而在设置实例变量的时候通过属性来做。
懒加载必须通过“获取方法”来访问属性。
8.对象等同性
==操作符比较的两个对象的指针本身,不是其所指的对象。 isEqual:方法判断两个对象的等同性。
如果isEqual判断两个对象相等,那么其hash方法也必返回同一个值。反之,不一定。
9.以类族的方式隐藏实现细节
参考UIButton的定义方法。 只需传入Button类型,不用管内部具体实现。系统框架方法里非常常见。
10.既有类中使用关联对象存放自定义数据
objc_setAssociatedObject()方法。 只有其他方法不行时才使用,因为这个方法通常会引入难于查找的bug。
11.理解objc_msgSend方法
在底层,所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全在运行期决定,甚至可以在程序运行时改变,这就是OC是一门真正的动态性语言。
12.理解消息转发机制
第一阶段:动态方法解析 +(BOOL) resloveInstanceMethod:(SEL)selector 在此方法里,你可以插入一个已经实现的方法。
第二阶段:完整的消息转发机制。网上有一个经典的处理流程图。 越往后处理的代价越大。
13.方法调配技术调试黑盒方法
method swizzling 方法交换。 这种方法不要乱用,否则出bug很难定位。
14.理解类对象的用意
类对象底层是Struct。 这个要掌握看看。 isa、 super_class、 *name、 version、info、instance_size.等等
isMemberOfClass:判断对象是否为某个特定类的实例。 isKindOfClass:对象是否为某个类或其派生类的实例。
第三大部分 API
15.加前缀避免命名空间冲突
注意:两字母大写前缀Apple爸爸宣称它保留使用的权利,所以我们最好使用三字母前缀。
16.提供全能初始化方法
所有对象均要初始化。 如果有多种初始化方法,搞一个全能初始化方法,所有初始化方法都调用它。
17.实现description方法
实现description方法,返回一个更有意义的字符串,用于描述该实例。 若想在调试的时候打印更详尽的对象信息,应该实现debugDescription方法。
18.尽量使用不可变对象 readonly
如果这个属性不想外部改变,就用readonly修饰。 如果外部只想调用某个属性, 可以在.h文件中修饰readonly,在.m文件里修饰为readwrite。
19.使用清晰而协调的命名方式
方法与变量使用驼峰式大小写命名方式。 方法命名不要吝啬使用长的方法名,定义的方法应该直接就可以看出其语义作用。
20.为私有方法加前缀
这样可以很好的共有方法区别开来。 不过注意:一个下划线方式(_)不要使用,Apple爸爸说这个要给它预留的。
21.Objective-C的错误类型
OC里的异常,只应该用于及其严重的错误(fatal error),不可以用其处理一般错误类型。
在错误不那么严重的情况下,可以使用代理方式来处理、传递错误。
当NSError当参数传递错误时,实际上传递的是一个指针。 (NSError )error。 指针本身又指向一个指针,那个指针指向NSError对象。 在ARC下NSError 转化为 NSError__autoreleasing 的。 即:指针所指的对象会在方法执行完毕后自动释放。 这个队形也必须自动释放,因为处理错误的方法对象不能保证可以把方法中创建的NSError释放掉,所以必须autorelease。
22.NSCopying 协议
如果想让某个类支持copy操作,那就要实现NSCopying协议,该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone; 注意是zone 不是 copy方法。
可变对象实现mutableCopyWithZone方法。 NSMutableCopying协议。
注意:可变对象使用copy,返回一个不可变的实例。
深拷贝:在拷贝对象自身时,将其底层数据也一并复制过去。 Foundation框架里所有的collection类,在默认情况下都是浅拷贝。只拷贝对象本身,而不复制器内容。
四 协议和分类
23.通过委托与数据源协议进行对象间的通信Delegate
委托模式为对象提供了一套接口。
24.将类的实现代码分散到便于管理的数个分类之中category
利用分类机制把类的实现划分为易于管理的小模块。 私用方法归入private分类中
25.总是要为第三方分类添加名称前缀
不加前缀容易覆盖原来的方法,一旦覆盖,以最后加载的一个分类为准。
26.不要在分类中声明属性
虽然说可以通过objc_setAssociatedObject: 方法添加,但是不推荐。分类目标在于扩展类的功能,而非封装数据。
把封装数据所用的全部属性都定义在主接口里。
27.使用class-continuation分类 隐藏实现细节
将就在本类使用的属性写在class-continuation分类。如果只需外部访问的属性,可以在.h里声明成readonly。
.m文件里的@interface class {} @end
28.通过协议提供匿名对象
Id<Delegate>. ID类型, 相当于匿名。。
五 内存管理
29.理解引用计数
保留计数不能说一定是某个值,应该说你执行的操作,是递增了还是递减了该计数。
XXX = nil; 悬挂指针,防止出现指向无效对象的指针,从而造成可能的crash。
autorelease:释放操作在下一次事件循环时才回收。 因此它可以延长对象的生命周期,使其跨越方法边界依然可以存活一段时间。
ARC下:不可以调用:retain release autorelease dealloc 方法
30.以ARC简化引用计数
内存泄漏:没有正确释放已经不再使用的内存。
非OC对象,比如CoreFoundation中的对象,或是有malloc()分配在堆中的内存,要手动清理。 CFRelease。
31.在dealloc方法中只释放引用并解除监听
通知一旦发给了已经回收了的对象,必然crash。
dealloc方法里面不应该调用任何其他方法,因为此时对象已经接近尾声了。
32.编写“处理异常代码”时要注意内存管理问题
在OC 中应该程序必须因异常状况而终止时才应该抛出异常。fatal error(参见第21条)。 程序即将终止,内存可以不处理。
但是如果自己非要处理异常,一定要注意try 方法内清理好对象内存。 -fobjc-arc-exceptions 在ARC下大可以捕获异常标志。 但是这样运行效率会变低。
33.用弱引用避免保留环
weak 与 unsafe_unretained 作用完全一致,然而weak修饰的属性,只要系统回收了,会自动设为nil。
34.用autoreleasepool 降低内存峰值
GCD、数组block遍历等线程都默认创建了自动释放池。每次执行“事件循环”时,就会将其清空。
autoreleasepool 可以多层嵌套, 它的作用范围就在其大括号{}之间。。 如果内存峰值不是很大,尽量不要额外建立自动释放池。
35.用“僵尸对象”调试内存管理问题 Zoombie Objects
向已经回收的对象发送消息是不安全的。 不是必crash的。 这取决于:对象所占的内存有没有被其它内容所复写。。
系统会修改对象的ISA指针,令其指向特殊的僵尸类,从而使其变成僵尸对象。NSZombie 也是一个根类,和NSObject一样。
僵尸类可以响应任何选择子,响应方式:打印一套消息内容及接收者信息,并终止程序。
36.不要使用retainCount
因为retainCount返回的是某个节点的计数值,并不能考虑到后续系统可能会清掉、释放操作。 所以它是不能准确反映计数值得。
六 块与大中枢派发
37.理解块这一概念
Block:带有自动变量(局部变量)的匿名函数。 查看Block源码,它也有一个isa指针,所以实质上block就是OC对象。
Block根据存储区域分为三种: _NSConcreteStackBlock _NSConcreteMallocBlock _NSConcreteGlobalBlock 栈、堆、程序数据(静态)Block
38.通常用typedef创建块
方便写,也方便阅读。 block表达式: ^返回值类型 参数列表 表达式
39.使用handler块降低代码分散程度
一般获取网络数据用块处理很方便,因为在块定义范围内可以直接获取块内的所有变量。
40.使用块引用其所属对象时不要出现保留环
41.多用派发队列,少用同步锁
@synchronized(self){
//会降低代码效率,公用不用一个锁的同步块,都必须按顺序执行
}
使用同步队列及栅栏块,可以令同步行为更加高效。
42.多用GCD,少用performSelector系列方法
performSelector系列方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因为ARC编译器也无法适当的插入内存管理方法。
43.掌握GCD及操作队列的使用时机
在执行后台任务时,GCD并不一定是最佳方式。 还有一个NSOperationQueue。 SD库就是用的这方式。
它的好处: 可以取消某个操作, 但是已经启动的不能取消。 可以指定操作间的依赖关系。可以指定优先级。可以通过兼职观测机制监控NSOperation对象属性。
44.dispatch_group
注意一对函数。 dispatch_group_enter dispatch_group_leave 必然成对出现。 这个主要用于函数回调完了 在执行notify方法。
45.使用dispatch_once来执行只需运行一次的线程安全代码
dispatch_once简化代码并能彻底保证线程安全。 更高效
46.不要使用dispatch_get_current_queue
此函数已经废除,最多在调试的时候使用
七 系统框架
47.熟悉系统框架
框架:将一系列代码封装成动态库,并在其中放入描述其接口的头文件, 这样做出来的东西就是框架。
48.多用枚举块,少用for循环
for in 和 block遍历方式。 block遍历方式可以根据options 反向遍历。
49.对自定义其内存管理语义的collection使用无缝桥接
__bridge
50.构建缓存是使用NSCache而非NSdictionary。
NSCache会在当系统资源耗尽时,它可以自动删除缓存。 先删除最久没使用的对象。 并且它也是线程安全的。
51.精简initialize和load的实现代码
如果分类和类都定义了load方法,先调用类的,在调用分类的。 整个应用程序在执行load时都会阻塞,所以load方法要精简。
initialize 方法是懒加载的。只有当程序用到了相关的类,才会调用。且只调用一次。 它也只应该设置类内部数据,不应该调用其它方法。
52.NSTimer会保留其目标对象
如果你的应用在iOS10之后,直接使用block方式创建就可以避免对象不释放这个问题。 也可以使用NSWeakTimer三方库解决。