第一章 熟悉Objective-C
第1条:了解OC起源
消息结构,运行时所执行代码有运行时环境决定,而函数调用,则有编译器决定。
第2条:类的头文件中尽量少引用其他头文件
向前声明 @class 的好处:
1、是延迟引入,减少类的使用者所需的引入的头文件数量
2、解决类之间的相互引用
第3条:多用字面量语法,少用与之等价的方法
NSNumber *number = [NSNumber numberWithInt:1];
NSNumber *number = @1;
优势:
1、简单、易读、防Nil;特别是NSArray、NSDictionary生成时遇到nil会报错,可以提前排查出问题
局限:
2、生成的是不变量,如果需要可变量,需要mutbleCopy;
3、也紧紧局限Foudation框架
第4条:多用类型常量,少用#define预处理指令
类型常量,用static const 声明 = #define
预处理定义出来的常量不包含类型信息,编译器只是会在编译前根据此执行查找与替换。即使有人重新定义常量值,编译器也不会警告,这样会导致常量不一样。
第5条:用枚举表示状态,选项,状态码
第二章 对象,消息,运行机制
第6条:熟悉“属性”
第7条:在对象内部尽量直接访问实例变量
在对象内部,读实例变量时用下划线,写时用存取方法(属性)来写;
直接访问
1、直接访问速度更快,无需方法派发
2、初始化,dealloc用下划线读写数据。
3、直接访问,不会调用设置方法,copy属性,不会拷贝属性,而且保留新值释放旧值
4、不能KVO
5、不便于调试
存取方法:
1、init中不要用存取方法,防止子类覆盖
2、惰性初始化一定要用存取方法
第8条:理解“对象等同性”
== 判断指针是否相等, isEqualTo判断类型、属性和hash值 (isEqual会根据类,进行方法分发,工厂方法),在复写isEqual方法时,需要注意其他情况调用super
关于hash值:如果collection类型的属性,直接写死固定值,会造成该固定值的对应的value变多,而影响性能。如果通过整体求hash,也出现中间变量,存在性能损耗。可以多每个属性求hash,在进行与或处理
注意:把对象放入collection之后,改变其内容会造成很严重的后果
第9条:以“类簇模式”隐藏实现细节
Cocoa里面很多类族实现,这种工厂方式的实现,因此不能用[subA class] == [A class]的方式进行判断,应该使用类型查询方式isKindOfClass进行类型判断。
第10条:在既有类中使用关联对象存放自定义数据
objc_setAssociateObject(id object , const void *key , id value , objc_AssociationPolicy policy)
object:被关联的对象;
key:唯一key;
value:关联的对象;
police:状态内存策略(copy,retain)
与Dictionary比较 设置关联对象的key一般是“不透明指针”,所以用静态全局变量作为key;同时要指定内存管理语义,用于模仿拥有 和 非拥有关系
objc_getAssociateObject
objc_removeAssociateObject
不需要主动调用remove来移除关联对象,一般调用objc_setAssociateObject来设置为nil,objc_removeAssociateObject会将所有的关联对象都移除。
第11条:理解objc_msgSend的作用
objc_msgSend(id self , SEL cmd , ...) 参数可变的函数
消息由接受者、选择子、参数组成,给对象发送消息,相当于对象调用方法
每个类都有一张函数调用表,key为选择子,value为实际调用的函数值。尾调用优化技术,使跳转更加简单:直接跳转,不需要调用堆栈,进行优化。
方法列表若是没有找到,进入消息转发,三次挽救机会:
1、+ (BOOL)resolveInstanceMethod:(SEL)sel
2、- (id)forwardingTargetForSelector:(SEL)aSelector
3、- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
尾递归优化?
尾递归的判断标准时:函数运行的最后一步是否调用自身,而不是是否在函数最后一行调用自身。
优化:不需要保存自身(外尾)的调用帧(即调用栈)。
第12条:理解消息转发机制
1、动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
2、备援接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
3、完整的消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
第13条:用“方法调配技术”调试“黑盒方法”
使用动态消息转发,不用通过继承子类,覆写子类方法实现新功能。使用另一份实现替换原有的方法实现。
操作选择器映射表,可以新增选择子,添加新功能,无需编写子类,只需修改“方法表”布局。
交换方法实现:
void method_exchangeImplementations(Method m1,Method m2)
获得方法实现:
Method class_getInstanceMethod(class aclass , SEl aSelector)
通过这样的方式,就可以为既有的方法增加新功能,一般为黑盒方法增加日志记录功能,有助于调试。
第14条:理解“类对象”
isMemberOfClass 判断对象是否为某个对象特点定类的实例
isKindOfClass 判断对象是否为某类或其派生类的实例
尽量使用类型信息查询方法来确定对象的类型,而不要直接比较类对象,因为某些对象实现了消息转发。
第三章 接口与API设计
第15条:用前缀避免命名空间冲突
在自己开发的程序中用到了第三方库,则应为其中的名称加上前缀。(前三个字母都大写)
第16条:提供“全能初始化方法”
designated initializer OR Initializer from NSCoding;子类与超类不同,子类需要覆盖,超类需要在方法中写Assert。
第17条:实现description方法 和 debugDescription方法
第18条:尽量使用不可变的对象
1、.h 中readonly, .m中readwrite,
但是在对象外面还可以通过KVC的方式(setValue forKey)进行更改(hack);更加brutal 是通过类型查询信息找到对应实例变量在内存中的偏移量,从而进行设置
2、可变的collection,应该通过相关方法,修改可变对象
第19条:使用清晰而协调的命名方式
驼峰命名法
第20条:为私有方法加前缀
不要用下划线开头,那是苹果公司的。
第21条:理解OC错误模型
ARC不是异常安全的,抛出异常,未释放的对象不能自动释放。如果想“异常安全”,增加-fobc-arc-exception标志;
严重错误,抛出NSException;不严重用nil,0、NSError
第22条:理解NSCoding协议
若想令自己的对象具有拷贝功能,需要实现NSCoding协议,实现copyWithZone:(NSZone *)zone方法。
如果对象分为可变与不可变两种版本,即要同时实现NSCoping和MutableCopying协议。
复制对象一般进行浅拷贝,深拷贝可单独写一个方法。深拷贝会将底层数据一起拷贝,包括实例变量。
第四章 协议与分类
第23条:通过委托和数据源协议进行对象间通信
1、把需要处理的事件方法定义成协议
2、对象从另外一个对象获取数据时,定义成数据源协议
3、若有必要,可实现含有位段的结构体,将委托对象是否响应相关协议缓存其中,(直接在setDelegate方法中进行缓存
第24条:将类的实现分散至各个便于管理的分类中
1、划分成不同的功能区
2、调试方便,因为分类名会出现在类名后面;
3、私有的可以考虑private分类
第25条:为第三方的分类名称加前缀
1、如果二个分类提供的方法重名,后编译分类方法会覆盖前面分类方法,分类编译顺序与添加到工程中的顺序有关;
2、如果方法名相同,分类会覆盖苹果自带的方法
第26条:勿在分类属性中声明属性
1、在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性,虽然技术上可行。
2、如果声明,会出现warning,原因是分类中无法合成与声明属性相关的变量,所以需要在分类中实现存取方法,并且实现中声明为@dynamic,意思就运行时在提供。当然关联对象也可以实现这种需求,但是仍然建议只在分类中提供方法,
3、把封装的数据所用的全部属性都定义在主接口里。
第27条:使用“class-continuation分类”隐藏实现细节
为什么要有这种分类:因为可以定义方法和实例变量,
隐藏实例方法和方法,也可以避免不必要头文件的引入,特别是对于OC++而言,引入的C++头文件。这样.h中进行向前声明,避免引入不必要的头文件,
也可以将类遵循的协议放在class-continuation,但是向前声明delegate却会有警告,因为引入.h文件,编译器看不到协议的定义及包含的方法。
为什么可以定义方法和实例变量:因为ABI机制,我们无须知道对象大小也可以使用。
第28条 通过协议提供匿名对象
1、如果具体类型不重要,只是能响应特定方法,那么可使用匿名对象表示,声明为id类型,来隐藏类型名称
第五章 内存管理
第29条:理解引用计数
第30条:ARC简化引用计数
第31条 在dealloc中只释放非OC对象引用并解除监听
运行时会在适当的时候调用dealloc,不要主动调用dealloc,但是手动 需要最后调用 super dealloc
1、开销较大或者系统内资源(比如文件描述符、套接字、大块内存等)不在dealloc中进行,应该单独提供方法进行释放,还有一个原因是系统并不保证每个创建出来的对象dealloc都会执行,也可以考虑在Appdelegate中种植方法执行清理,防止内存泄露
2、在dealloc不建议调用其他函数,防止调用过程中对象已经销毁。也不要调用属性的存取方法,因为有人会对其进行覆盖,也可能处于KVO下。(这个存取方法待讨论)
3、self.tableView.delegate 设置为nil
第32条:编写异常代码时,留意内存管理
MRC时,可以将内存释放写在finnal里面(提问:为什么不能写在try 和 catch 里面)【因为在写在try中,抛异常会跳到catch中,内存泄漏】,但是变量就必须放在块的外面
ARC时:不会自动处理try catch的内存管理,因为ARC不能调用release,所以需要很多样板代码,进行跟踪清理对象,影响运行时性能。因为ios认为因为异常而终止程序,内存管理也就没有必要了。
1、通过-fobjc-arc-exceptions进行开启安全异常处理,默认情况是关闭的。OC++模式是默认开启的
2、建议通过NSError方式进行错误捕捉。
第33条:以弱引用避免保留环
第34条:以“autoreleasepool”降低内存峰值
第35条:僵尸对象调试,内存管理问题
当对象已经释放,但是还没被覆盖时,调用这块内存会正常工作,但是存在很大风险。Cocoa:系统在回收对象时,可以不真的将其回收,而是把它转化为僵尸对象。NSZombieEnabled设置为yes,或者在scheme中勾选。
僵尸类是从NSZombie模板复制出来的,并且可以保留原类名字,不采用继承的方式,是因为效率因素的考虑。
其实现原理是:
修改对象的isa指针,让它指向僵尸类,使对象变成僵尸对象,僵尸类能影响所有的选择子:打印相关消息,并且终止程序。
创建新类,并且转化为僵尸对象
第36条:不要使用retainCount
第六章 块与大中枢派发
第37条:理解“块”
第38条:为块创建typedef
return_type (^block_name)(parameters)
优点:
1、定义变量一样定义block
2、重构时如果给Block多增加参数,那么只需修改相应的块签名(typedef),其他引用block的地方也会自动报错,避免遗漏
第39条:用handler块降低代码分散程度
1、使用delegate 会使代码结构过于分散,可以直接只用回调块,使块和相关对象放在一起,避免通过delegate透传数据
2、通过handler,增加队列参数,决定放在哪个队列上。
3、Error块和Succes块 放在一起,可以处理返回结果中数据异常的情况 VS Error 和 Success分开处理,更加清晰。
第40条:用块引用其所属性避免循环引用
1、设计API时,可以考虑在调用完complete的块之后,将环中的某个对象设置为nil,解除环,避免API调用者没有处理保留环的问题。也可以通过weakify的方法。
2、API调用者可以通过weakify的方法,解除保留环的问题;
第41条:多用派发队列,少用同步锁
派发队列更加单实现同步语义。
GCD之前,有2种方法:
1、同步块 @synchrnoized(someObject)一般是对self创建锁,但是其中会涉及与self无关的代码,降低代码效率。
2、NSLock对象,通过[_lock lock] [lock unlock]加锁,解锁;NSRecuresiveLock 递归锁
同一个锁的同步块,顺序执行
通过atomic 属性同步,这是通过synchrnoized的方式实现的?
1、同步和异步派发结合可以实现加锁机制一样的同步问题,但是却不阻塞异步派发的进程,但是仍然无法正确同步
2、使用同步队列及栅栏块可以令同步行为更加高效。
3、异步派发,需要拷贝块,因此异步派发不一定会比同步快,需要考虑拷贝块与执行块的时间
第42条:多用GCD,少用performSelector系列方法
1、会发生warning,导致内存泄漏,因为编译器不知道调用什么选择子,方法签名、返回值,无法通过ARC对返回值进行管理。
2、选择子太过局限,返回类型(void或者id,不能是struct)和参数都有局限。
3、如果把任务放在指定线程执行,用GCD和块,毕竟块可以捕获外部变量。
第43条:掌握GCD和操作队列的使用时机
NSOperation 好处:
1、取消操作 2、指定依赖关系 3、通过KVO监控NSoperation对象的属性 4、指定操作的优先级 5、可以复用NSOperation对象
NSNotifationCenter 就是使用的操作队列
第44条:使用dispatch group进行任务分组
1、dispatch_group_async 包含block,用于回调
2、dispatch_group_enter && dispatch_group_leave
dispatch_group_wait (阻塞)使用表示group可以阻塞的时间;dispatch_group_notify(不阻塞),使用group结束的回调。
dispatch_apply 用于重复执行的次数
第45条:dispatch_once 只执行一次、线程安全
1、之前通过@synchronized(self) 创建单例,比dispatch-once慢二倍
2、需要一个标记,标记声明为static 或者global,目的是标记都相同
第46条 不要使用dispatch_get_current_queue
dispatch_get_current_queue 已经废弃,只做调试用
第七章 系统框架
第47条:熟悉系统框架
第48条:多用枚举块,少用for循环
枚举方式:
1.for循环
2.NSEnumerator 遍历
3.快速遍历、块枚举
4.块枚举,支持GCD来并发执行遍历操作
typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
NSEnumerationConcurrent = (1UL << 0),
NSEnumerationReverse = (1UL << 1),
};
如果提前知道collection对象类型,应修改块签名,指出对象具体的类型
第49条:对自定义其内容管理语义的collection使用无缝桥接
第50条:构建缓存时选用NSCache,而非NSDictionary
第51条:精简initialize与load实现代码
第52条:NSTimer会保留其目标对象