Effective OBJC-2.0笔记:
OBJC使用动态绑定的消息结构,在运行时才会检查对象类型,接收一条消息之后,究竟应执行哪些代码,由运行期环境决定。
除非明确需要,否则不要在头文件中import其它头文件,可以使用向前申明来引入其他类,在实现文件中再import那些类的头文件,这样可以尽量降低类之间的耦合,减少编译时间。
有时无法使用向前声明,比如要声明某个类遵循一项协议,这种情况下尽量把该类遵循某协议的这条声明移至分类中,如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
多使用字面亮语法来创建字符串,数值,数组,字典,简明扼要,用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。
不要使用预处理指令定义常量,这样定义出来的常量不含类型信息,编译器只会在编译前据此执行查找与替换操作,即使有人重新定义了常量值,编译器也不会产生警告,导致应用程序中的常量不一致。编译器将会替换所有引入预处理指令所在文件的文件中的常量值。
若常量只在某编译单元内可见,则使用static const来定义,由于此类常量不在全局符号表中,所以无须为其名称添加类命前缀,使用kxxx命名即可。
若常量需要在全局使用,则在头文件中使用extern来声明,并在实现文件中定义其值,这种常量要出现在全局符号表中,所以其名称用相关类命坐前缀。
用枚举表示状态码,选项等。如果多个枚举选项可以同时使用,用NS_Option定义枚举,方便通过按位或组合各枚举值。在处理枚举值的switch分支中不使用default,确保各枚举值都可以被正确处理。
使用@property语法来定义对象所封装的数据。尽量不要直接访问实例变量,而应该通过属性(存取方法)来访问。使用了属性后,编译器会自动编写访问这些属性所需的方法和实例变量,这个过程称为“自动合成”,在编译期执行。也可以在类的实现文件中通过@synthesize语法来指定实例变量的名字。若不想编译器自动合成存取方法,可以在实现文件中使用@dynamic xxx来阻止编译器自动创建实例变量和存取方法。
属性特质(nonatomic,automic,copy,strong,assign)会影响编译器所生成的存取方法。分为四类:
原子性
读写权限
内存管理语义:赋值时,应该保留新值还是复制或者直接赋值给实例变量就好。assign:针对简单数据类型进行简单的赋值操作。strong:该属性定义了一种拥有关系,设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。weak:该属性定义了一种非拥有关系,为这种属性设置新值时,设置方法既不保留新值,也不释放旧值,在该属性指向的对象遭到摧毁时,属性值会被清空。copy:设置方法并不保留新值,而是将其拷贝并赋值。若是自己实现存取方法,应该保证其具备相关属性所声明的特质。例如:如果将某个属性声明为copy,那么就应该在设置方法中拷贝相关对象,否则会产生bug。
方法名:指定存取方法的方法名。
开发iOS程序时应该使用nonatomic属性,防止性能瓶颈,采取其他方式实现同步锁,实现线程安全。
在对象内部读取数据时,应该直接通过实例变量来读取,而写入数据时,通过属性来写。因为访问实例变量不通过方法查找,效率比较高,但是写入数据需要观察数据的变化和属性的特质,所以用属性来访问。在初始化方法及dealloc方法中,应该直接通过实例变量来读写数据。惰性初始化配置某数据时,通过属性来读取数据。
若要检测自定义对象的等同性,提供”isEqual:”hash”方法。相同对象必须具有相同的哈希值,但是两个哈希值相同的对象不一定相等。编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。当对象放入集合后,不应该改变对象的内容或哈希值了,集合根据对象的哈希值分装到不同的组中,若放入到集合后对象的哈希值被改变了,会导致程序错误。
类族模式可以把实现细节隐藏在一套简单的公共接口后面。尽量使用类族模式隐藏类的实例细节,无须关系创建出来的对象具体属于哪个子类。从类族的公共抽象基类中继承子类时,应当覆盖基类文档中指明需要覆盖的方法。
可以通过关联对象机制来把两个对象连起来。定义关联对象时可以指定内存管理语义,用以模仿定义属性时所采用的拥有关系与非拥有关系。
oc使用动态绑定机制来向对象传递消息,对象接收到消息后,由运行期决定调用哪个方法,可以在程序运行时改变。消息由接收者,选择子和参数构成。给某对象的全部消息都要由动态消息派发系统来处理,该系统会查出对应的方法,并执行相应的代码。
当对象接收到无法解读的消息后,就会启动“消息转发机制”,提前编写的代码会执行预定的逻辑,避免程序崩溃。消息转发分为两大阶段,第一阶段先征询接收者所属的类,看其是否能动态添加方法,已处理当前未知选择子,该步骤为“动态方法解析”,第二阶段涉及完整的消息转发机制,征询接收者看看有没有其他对象能处理这条消息,若有则运行期系统会把消息转给那个对象,于是消息转发过程结束,若没有,则启动完整的消息转发机制,运行期系统会把消息有关的全部细节都疯撞到 NSInvocation中,给接收者最后一次机会,处理未知选择子。
运行期,可以通过方法调配(method-swizzling)向类中新增或替换选择子所对应方法的实现。
每个实例都有一个指向Class对象的指针,用以表明其类型,这些Class对象则构成了类的继承体系。如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
选择与你的公司,应用程序或二者皆有关联的名称作为类名的前缀,并在所有代码中使用这一前缀,若自己所开发的程序库中用到了第三方库,则应为其中的名称都加上前缀。
如果创建类实例的方式不止一种,那么这个类就会有很多个初始化方法,选定一个作为全能初始化方法,为对象提供必要信息以便完成初始化,其他初始化方法都来调用它。若全能初始化方法与超类不同,则需复写超类中的对应方法。如果超类的初始化方法不适用于子类,应该在子类中覆写这个超类方法,并在其中抛出异常。
实现对象的description方法返回一个有意义的字符串,用以描述该实例。若想在调试时打印出更详尽的对象描述信息,则应实现debugDescription方法。
尽量创建不可变属性的类,可以在头文件申明为只读属性,在分类中扩展为可读可写属性。不要把可变的集合作为属性公开,而应提供相关的方法,以方法的形式更改对象内部的可变对象。
如果方法的返回值是新创建的,那么方法名的首个词应是返回值的类型。如果方法要在当前对象上执行操作,那么就应该包含动词,若执行操作时还需要参数,则应该在动词后面加上一个或多个名词。避免使用简称。如果某方法返回非属性的布尔值,应该根据其功能,选用has或is当前缀。
如果要从其他框架中继承子类,务必遵循其命名惯例,前缀与后缀。若要创建自定义的委托协议,其名称中应该包含委托方的名称,后面再跟上delegate一词。
给私有方法的名称加上前缀p_,这样更容易将其同公共方法区分开.
只有发生了可使整个程序崩溃的严重错误时,才使用异常,发生错误时,可以指派给委托方法来处理错误或者把错误信息放在NSError对象里,经由输出参数返回给调用者。
若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。如果自定义的对象分为可变版本和不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议。复制对象时需决定采用浅拷贝还是深拷贝一般情况下应尽量执行浅拷贝,如果需要深拷贝,那么考虑新增一个专门执行深拷贝的方法。
当某对象需要从另外一个对象中获取数据时,可以使用委托模式,又称数据源协议。如果有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。
使用分类机制把类的实现代码分成易于管理的小块。
向第三方类中添加分类时,总应给其分类名及方法加上专用的前缀。
dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的键值观测或NSNotificationCenter等通知。如果对象持有文件描述符等系统资源,那么应该专门指定一个方法来释放此资源,这样的类要和其使用者约定,用完资源后必须调用close方法,不建议在dealloc方法中调用方法或属性的存取,因为此时的对象已处于正在回收的状态了。
将某些引用设为weak,可避免出现保留环。weak指向的对象被回收时,weak指针指向nil,unsafe_unretained指向的对象被回收后,unsafe_unretained对象指向原地址,继续访问该指针会导致程序异常退出。
主队列以及中枢派发队列都在维护自己的自动释放池,自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里,合理运用自动释放池,可降低应用程序的内存峰值。
开启僵尸对象后,运行期系统会把所有已经回收的实例转化为特殊的僵尸对象,而不会真正回收他们,这种对象所在的核心内存无法重用,因此不可能遭到复写,僵尸对象收到消息后,会抛出异常,准确地描述发送过来的消息,并描述了回收之前的那个对象。
开启僵尸对象环境设置后,运行期系统会把对象的dealloc方法调配,最后得到一个类名为_NSZombie_OriginalClass的NSZombie类的复制,给僵尸对象发送消息后,系统可由此知道该对象原来所属的类,假如把所有僵尸对象都归到NSZombie类中,那原来的类名就丢了,创建新类的工作由运行期函数objc_duplicateClass()来完成,会把整个_NSZombie_类结构拷贝一份,并赋予其新的名字。僵尸类并未实现任何方法,所以发给它的全部消息都要经过完整的消息转发机制,在消息转发机制中,forwarding是核心,调试程序时,它首先要做的事情就包括检查接受消息的对象所属的类名,若名称前缀包含_NSZombie_,则表明消息接收者时僵尸对象,需要特殊处理,打印一条特殊消息后应用程序就终止了。