第1章 熟悉Objective-C
第1条:了解objective-c的起源
消息与函数调用之间的区别:
实用函数调用的语言,则由编译器决定。如果调用的函数是多态的,那么在运行时就要按照“虚方法表”来查出到底应该执行哪个函数的实现。
而使用消息结构的语言,其运行时所执行的代码由运行环境来决定。才用消息结构的语言,不论是否多态,总是在运行才会去查找所要执行的方法。实际在,编辑器甚至不关心接受消息的对象是何种类型。接受消息的对象问题也要在运行时处理,其过程叫做“动态绑定”。
第2条:在类的头文件中尽量少引入其他头文件
1.除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前申明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。
2.有时无法使用向前申明,比如要申明某个类遵循的一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。
第3条:多用字面量语法,少用与之等价的方法
如:NSNumber *someNumber = [NSNumber numberWithInt:1];
NSArray *animals = @[@“cat”,@“dog”,@“mouse”];
NSString *dog = animals[1];
NSDictionary *personData =@{@“firstName”:@“Matt”,@“lastName”:@“galloway”,@“age”,@28};
NSString *lastName = personData[@“lastName”];
局限性:字体量语法有个小小的限制,就是出了字符串以外,所创建出来的对象必须属于Foundation框架。
1、应该使用字面量语法来创建字符串、数值、数组、字典。与创建此类对象的常规方法相比,这么做更加简明扼要。
2、应该通过取下标操作来访问数组下标或字典中的键所对应的元素。
3、用字面量语法创建数组或字典时,若值中有nil,则会抛出异常。因此,务必确保值不含nil。
第4条:用枚举表示状态、选项、状态码
1、不要用预处理指令定义常量。这样定义出来的常量不含类型信息,编译器只是会在编译前据此执行查找与替换操作。即使有人重新定义了常量值,编译器也不会产生警告信息,这将导致应用程序中的常量值不一致。
2、在实现文件中使用static const来定义“只在编译单元内可见的常量(transiation-unit-specific constant)”。由于此类常量不在全局符号表中,所要无须为其名称加前缀。
3、在头文件中使用extern来声明全局常量,并在相关实现文件中定义其值。这种常量要出现在全局符号中,所以其名称应加以区隔,通常用与子相关的类名做前缀。
第5条:用枚举表示状态、选项、状态码
1、应该用枚举来表示状态机的状态,传递给方法的选项以及状态码等值,给这些值起个易懂的名字。
2、如果把传递给某个方法的选项表示为枚举类型,而多个选项又可同时使用,那么就将各选项值定义为2的幂,以便通过按位或操作将其组合起来。
3、用NS_ENUM与NS_OPTIONS的宏定义枚举类型,并指明其底层数据类型。这样做可以确保枚举是用开发者所选的底层数据类型实现出来的,而不会采用编译器所选的类型。
4、在处理枚举类型的switch语句中不要实现default分支。这样的话,加入新枚举后,编译器就会提示开发者(发出警告),switch语句并未出来所有枚举。
第2章 对象、消息、运行期
第6条:理解“属性”这一概念
1.可以用@property语法来定义对象中所封装的数据。
2.通过“特质”来指定存储数据所需的正确语义。copy并不保留新值,而是将其“拷贝”,当copy的原值改变时,它还不改变。
3.在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。
4.开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能。
第7条:理解“属性”这一概念
1、在对象内部读取数据时,应该直接通过实例变量来读(读取效率高),而写入数据时,则如果该属性来写(内存管理语义得以贯彻)。
2、在初始化方法中,总是应该直接通过实例变量来读写数据(因为子类可能覆盖设置方法,通过设置方法调用子类方法则会报错提醒)。
3、有时会使用惰性初始化技术配置某份数据,这种情况下说,需要如果属性来读取数据。
直接访问实例变量的优势:
1)不经过Objective-c的“方法派发”(method dispatch)步骤,所以直接访问实例变量的速度快;
2)不会调用其“设置方法”,绕过了为相关属性所定义的“内存管理语义”;
3)不会触发“键值观测”(KVO)通知。
而属性访问有助于排查与之相关的错误,因为可以给获取get和set方法设置断点,监控该属性的调用者及其访问时机。
第8条:理解“对象等同性”这一概念
1.若想检测对象的等同性,请提供“isEqual”与hash方法。
2.相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
3.不要盲目地遂个检测每条属性,而是应该依照具体需求来制定检测方案。
4.编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。
5.如果某对象放入collection之后改变其内容,得小心它的隐患。
NSMutableSet *set = [NSMutableSet new];
NSMutableArray *arrayA = [@[@1,@2]mutableCopy];
[set addObject:arrayA];
NSLog(@"set = %@",set);
NSMutableArray *arrayB = [@[@1,@2]mutableCopy];
[set addObject:arrayB];
NSLog(@"set = %@",set);//1,2
NSMutableArray *arrayC = [@[@1]mutableCopy];
[set addObject:arrayC];//{1},{1,2}
NSLog(@"set = %@",set);
[arrayC addObject:@2];
NSLog(@"set = %@",set);{1,2},{1,2}
NSSet *setB = [set copy];
NSLog(@"setB = %@",setB);//1,2
第9条:以“类族模式”(class cluster)隐藏实现细节
1.类族模式可以把实现细节隐藏在一套简单的公共的接口后面。
2.系统框架中经常使用类族。
3.从类族的公共抽象基类中继承子类时要当心(比如某些抽象类,子类必须覆盖一些方法),若有开发文档,则应首选阅读。
第10条:在既有类中使用关联对象存放自定义数据
1.可以通过“关联对象”机制来把两个对象连起来;
2.定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”和“非拥有关系”;
3.只有在其他做法不可行时才选用关联对象,因为这种做法通常会引入难于查找的bug。
第11条:理解objc_msgSend的作用
1、消息由接受者、选择子(select)及参数构成。给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method)。
2、发给某对象的全部消息都要由“动态消息派发系统”(dynamic message dispatch system)来处理,该系统会查出对应的方法,并执行其代码。
第12条:理解消息转发机制
1、若对象无法响应某个子选择子(select),则进入消息转发流程;
2、通过运行期的动态方法解析功能,我们可以在需要用到某个方法时在将其加入类中。
3、对象可以把其无法解读的某些选择子转交给其他对象来处理。
4、经过上述两步之后,如果还是没办法处理选择子,那么久启动完整的消息转发机制。
第13条:用“方法调配技术”调试“黑盒方法”
1、在运行期,可以向类中新增或替换选择子所对应的方法实现;
2、使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
3、一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。
第14条:理解“类对象”的用意
“isMemberOfClass:”能够判断出对象是否为某个特定类的实例,而“isKindOfClass:”则能够判断出对象是否为某类或其派生类的实例。
例如:NSMutableDictionary *dict =[NSMutableDictionary new];
[dict isMemberOfClass:[NSDcitionary class]];//NO
[dict isMemberOfClass:[NSMutableDictionary class]];//YES
[dict isKindOfClass:[NSDcitionary class]];//YES
[dict isKindOfClass:[NSMutableDictionary class]];//NO
1、每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
2、如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
3、尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。