前言:第一次阅读此书大概是一年半之前,在网上找到电子版,也就临时看了一晚上,之后就没有再涉猎。八月份决定抽出半个月左右的时间认真阅读下这本书(但是由于这两周一直在准备公司项目的新版本,实际只读完了前五章)。读后受益匪浅,今天抽空把之前做的笔记整理一部分出来,做个记录与分享。
对象所占内存总是分配在堆空间(heap space)中,而绝不会分配在栈(stack)上,不能在栈中分配OC对象;
有时会遇到定义里不含*的变量,他们可能会使用栈空间(stack space)。这些变量所保存的不是OC对象,比如CGRect,是一个结构体;
创建对象需要额外开销,例如分配和释放内存;
如果在一个类文件中,只需要知道另一类的类名,而不需要用到该类接口的情况下,尽量使用前置声明@class而不是#import“”引入头文件,将引入头文件的时机尽量延后,这样可以减少编译时间,也可以避免循环引用,导致两个类中有一个类无法被编译。
多用字面量语法
1、缩减代码长度,简便整洁
2、数组和字典使用字面量语法,当集合内对象为空时,会抛出异常,这样会更安全多用类型常量,少用#define预处理指令
1、 预处理指令定义的常量没有类型信息
2、尽量在实现文件而不要在声明文件中声明预处理指令凡是需要以按位或操作来组合的枚举都应该使用NS_OPTIONS定义,不需要互相组合的枚举,应使用NS_ENUM来定义,这两个宏具备后向兼容能力都是用#define预处理指令来定义的。
原因:在C++的编译模式下,认为按位运算的运算结果的数据类型应该是枚举的底层数据类型,而且C++不允许将这个底层类型“隐式转换”为枚举类型本身。如果使用NS_ENUM宏来定义组合枚举,则有可能会报错在switch语句中,若用枚举来定义状态机,最好不要有default分支。这样的话,如果之后又加了一种状态,编译器就会发出警告,提示新加入的状态并未在switch语句中进行处理,可以保证代码的准确性。否则就会自动进入default分支进行处理,不会发出警告。
在iOS开发中,几乎所有属性都声明为nonatomic,这样做的历史原因是:在iOS开发中使用同步锁开销较大,会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”,若要实现线程安全的操作,还需要采用更为深层的锁定机制才行。
“collection”与“set”在中文里都叫做“集合”,前者是Array、Dictionary、set等数据结构的总称。
对象等同性
“==”操作符比较的是两个指针本身,而不是指针所指的对象,准确率较低;
“isEqualToString”比“isEqual”执行速度快,后者还要进行其他操作;
基本数据类型判断等同性使用:“==”
判断等同性优先使用:“isEqualToString/Array/Dictionary” > "isEqual" > "=="
- 学会使用类族模式
系统框架中有许多类族,特别是collection类,如数组,字典等。使用“类族模式”的主要思想是通过继承,隐藏基类的内部实现细节,开放公共接口,方便调用,便于维护;
例如在项目工程中为所有包含列表视图的viewController类创建父类ListViewController,通过继承来统一接口,统一管理,简化代码;
在子类越来越多时,类族的优势会越来越明显,开发人员无需关心其他问题,只需要在基类的实现方法中增加该子类的初始化接口即可。而各种属性定义,方法实现从基类的公共接口处调用;
关联对象
设置关联对象:objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
获取相应的关联对象: objc_getAssociatedObject(<#id object#>, <#const void *key#>)
删除所有的关联对象:objc_removeAssociatedObjects(<#id object#>)
注:由于设置关联对象时用的key是“不透明指针”,就是不局限于某种特定类型的指针,若想使两个键匹配到同一个值,必须是完全相同的指针,所以在设置静态全局变量时,通常使用静态全局变量做key,也可以使用选择器等,保证指针相等性。
一般只有在其他办法行不通的情况下才会使用关联对象,因为滥用关联对象可能会导致“保留环”,使代码失控,程序崩溃C语言使用“静态绑定”,在编译期就能决定在运行时所应调用的函数
在OC中,如果向某对象传递消息,就会使用动态绑定机制来决定需要调用的方法
向某一对象发送消息,编译器看到此消息后,会将其转换为一条标准的C语言函数调用,所调用的函数为消息传递的核心函数:
objc_msgSend();
iOS 8.0之后调用方式:((void (*)(id,SEL,id、、、))objc_msgSend)(self,@selector(selector),object1、、、);
objc_msgSend函数执行方法调用的顺序:
在接受者所在类的方法列表中,查找与选择器名称相同的方法,进行调用;
查找未果--》向接受者所属类的父类递进查找;
查找未果--》进行消息转发
在匹配成功后,objc_msgSend会将匹配结果缓存在“快速映射表”中,再次调用时速度会大大加快用方法调配技术互换两个方法的实现:
Method method1 = class_getInstanceMethod([self class], @selector(testMethodWithInfo:));
Method method2 = class_getInstanceMethod([self class], @selector(test));
method_exchangeImplementations(method1, method2);
系统在调用method1时就会执行method2,调用method2时调用method1.
通过这一方案,可以为那些“完全不知道其具体实现的”黑盒方法添加日志记录功能,非常有助于程序调试,单一般也只在调试程序时用到这一方案。Apple宣称其保留所有“两字母前缀”的权利,所以为了避免出现“重名符号错误”,在应用程序中声明类名时都应该添加“三字母前缀”,如所在公司是阿里,想要自定义一个PayView类,则应给其命名为ALiPayView。
给私有方法的名称加上前缀,例如p_(p代表private),使其与公共方法区分开;
不要单用一个下划线做前缀,这种方式是苹果公司预留方式,容易产生冲突;浅拷贝之后的内容与原始内容均指向相同对象,而深拷贝之后的内容所指的对象是原始内容中相关对象的一份拷贝。即浅拷贝后改变拷贝对象的值,原始内容也会随之改变,但是深拷贝后改变拷贝对象的值,原始内容不会发生改变。
无论当前实例是否可变,若需获取其可变版本的拷贝,则使用mutableCopy,若需获取其不可变版本的拷贝,则使用copy
对于不可变的NSArray与可变的NSMutableArray来说,下列关系总成立:
[NSMutableArray copy] =>NSArray
[NSArray mutableCopy] =>NSMutableArray当类的实现中方法过多,导致代码冗余时,可以通过“分类”的方法,将类中的方法按需分类,划入几个分区中,便于管理与维护。
在编写程序库用作分享时,如果有一些方法,它们不是公共API的一部分,然而却很适合在程序库之内使用,这时我们应该创建private分类,当程序库的某些地方需要用到这些方法时,就导入此分类头文件,并且设置头文件不随程序库一并公开即可。
向第三方类(公共API,如NSString)添加分类时:
1、总应给其名称加上你专用的前缀,避免类名冲突;
2、总应给其中的方法名加上你专用的前缀,避免方法重写覆盖;
注:如果在项目中给第三方类创建了多个分类,并且都声明并实现了方法名相同的方法,则在程序中调用该方法时,实现的是最后一个创建的分类中的方法。
3、在分类中不要定义属性保留环(循环引用):当两个或两个以上对象呈环状相互引用时,因为循环中的对象其引用计数不会降为0,所以内存无法释放,会导致内存泄漏情况。
可用下列修饰符来改变局部变量与实例变量的语义:
__strong: 默认语义,保留此值。
__unsafe_unretained: 不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经被收回了。
__weak: 不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空。
__autoreleasing: 把对象“按引用传递”给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放。
所以在程序调用block语法时,因为block会自动保留其所捕获的全部对象,而这其中如果有某个对象又保留了block块本身(如self),就可能会导致循环引用(保留环)。而使用__weak来修饰self时,weakSelf的引用计数会自动清空,就不会导致保留环的产生。
ARC只负责管理oc对象的内存,要注意的是:CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease。
给对象配置过的观测行为都应该在dealloc中注销,ARC会自动执行父类的dealloc方法,所以在ARC下实现dealloc方法时,不需要手动调用[super dealloc]。
系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量NSZombieEnable可开启此功能。
系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使对象变为僵尸对象。僵尸类能够响应所有的选择器,响应方式为:打印一条包含消息内容及其接收者的消息,然后终止应用程序。