Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(一)

第一章:熟悉 Objective-C

第1条:了解 Objective-C 语言的起源

第2条:在类的头文件中尽可能少引入其余头文件

背景:

使用 #import "ClassName.h" 能够引入其余文件的全部接口细节。git

问题:
  1. .h头文件中,在编译一个使用了某类的文件时,不须要知道这个类的所有细节,只须要知道有这个类就好。
  2. A头文件中引入B头文件,C头文件引入A头文件,就会一块儿引入B头文件的全部内容。此过程若持续下去,则要引入许多根本用不到的内容,这固然会增长编译时间。
解决办法:
  1. 使用 @class ClassName “向前声明”(forward declaring),只声明有这个类,没有具体细节,能够解决上述问题。github

    除非确实有必要,不然不要引入头文件。通常来讲,应在某个类的头文件中使用向前声明来说起别的类,并在实现文件中引入那些类的头文件。这样作能够尽可能下降类之间的耦合(coupling)。objective-c

    继承听从协议 不能使用向前声明。 有时没法使用向前声明,好比要声明某个类遵循一项协议。这种状况下,尽可能把“该类遵循某协议”的这条声明移至“分类”中。若是不行的话,就把协议单独放在一个头文件中,而后将其引入。数组

    向前声明的做用:缓存

    1. 防止引入根本用不到的内容,减小头文件细节引用。
    2. 解决两个类相互引用的问题。
  2. 将引入头文件的时机尽可能延后,只在确有须要时才引入,这样能够减小类的使用者所需引入头文件的数量。函数

第3条:多用字面量语法,少用与之等价的方法

使用字面量语法(literal syntax)能够缩减源代码的长度,使其更为易读。性能

第4条:多用类型常量,少用 #define 预处理指令

问题:

#define 定义的常量没有类型信息,编译器只会在编译前据此执行查找与替换操做。即便有人从新定义了常量值,编译器也不会产生警告信息,这将致使应用程序中的常量值不一致。优化

解决办法:
  • 在实现文件中使用 static const 来定义“只在编译单元内可见的变量”。因为此类常量不在全局符号表中,因此无须为其名称加前缀。代码实现以下:
// .h 文件
@interface 类名: 父类名
...
@end

// .m 文件
// 类内使用
static const 类型 常量名 = 常量值;

@implementation 类名
...
@end
  • 在头文件中使用 extern 来声明的全局变量,并在相关实现文件中定义其值。这种常量要出如今全局符号表中,因此其名称应加以区分,一般用与之相关的类名作前缀。
// .h 文件

// 类外可用声明
extern 类名 const 常量名;

@interface 类名: 父类名
...
@end

// .m 文件
// 类外可用声明
类名 const 常量名 = 常量;

@implementation 类名
...
@end

常量名称经常使用命名法是:编码

  1. 只在类内使用,在前面加字母 k
  2. 类外也可以使用,以类名最为前缀。

第5条:用枚举表示状态、选项、状态码

  1. 使用枚举,给这些值起个易懂的名字。
  2. 将枚举值定义为2的幂,多枚举选项能够同时使用,能够经过按位或操做进行组合。
  3. 定义枚举时,指明其底层数据类型,便于处理。
  4. 在处理枚举类型的 switch 语句中不要实现 default 分支,便于加入新枚举后,编译器报错,知道须要修改的地方。

第二章:对象、消息、运行期

第6条:理解 “属性” 这一律念

第7条:在对象内部尽可能直接访问实例变量

第8条:理解 “对象等同性” 这一律念

  1. == 操做符比较的是两个指针自己,不是其所指的对象。
  2. NSObject 协议中声明的 isEqual 方法判断两个对象的等同性。 isEqual 默认实现是:当且仅当其 “指针值” 彻底相等时,这两个对象才相等。
  3. 特定类具备等同性的断定方法。 NSString : isEqualToString: NSArray : isEqualToArray: NSDIctionary : isEqualToDictionary: 若比较的对象不是对应的类型,就会抛出异常,崩溃。
  4. 等同性断定的执行深度取决于受测对象。 对象个数相同的数组比较,对应位置上的对象均相等,数组就相等,这叫作“深度等同性断定”。 为了性能,建议尽量的下降深度。
  5. 容器中可变类的等同性断定 将某对象放入容器后,又修改其内容,那么后面的行为将很难预料,建议不要这么作。

第9条:以 “类族模式” 隐藏实现细节

类族模式:使用继承,实现多种职能的子类,父类经过设定不一样的类型来建立某种子类,执行其相应的职能。 做用:将实现细节隐藏在一套简单的公共接口后面。 须要注意的是,建立的实例的真实类型是什么,须要咱们知道atom

新增 CocoaNSArray 这样的类族的子类,须要遵照如下几条规则:

  1. 子类应该继承自类族中的抽象基类。
  2. 子类应该定义本身的数据存储方式。 NSArray 自己只是包在其余隐藏对象外面的壳,它仅仅定义了全部数组都需具有的一些接口。
  3. 子类应当覆写超类文档中指明须要覆写的方法。 在每一个抽象的基类中,都有一些子类必须覆写的方法。

第10条:在既有类中使用关联对象存放自定义数据

在对象中存放相关信息:

  1. 从对象所属的类中继承一个子类,而后修改这个子类对象。
  2. 经过“关联对象”的特性,给某对象关联许多其余对象,这些对象经过“键”来区分。
关联对象方法:
  • 以给定的键和存储策略为某对象设置关联对象值

    objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)

    参数说明:

    • object 关联的源对象。
    • key 关联的 key。一般使用静态全局变量作键。
    • value 关联 key 所对应的值。传 nil 能够清除现有的关联。
    • policy 关联的存储策略,也就是对应的内存管理语义,是一个枚举值。
      objc_AssociationPolicy 枚举值以下表: | 关联类型 | 等效的 @property 属性 | | --- | --- | OBJC_ASSOCIATION_ASSIGN | assign | OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain | OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy | OBJC_ASSOCIATION_RETAIN | retain | OBJC_ASSOCIATION_COPY | copy |
  • 根据给定的键从某对象中获取对应的关联对象值。

    objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
  • 移除指定对象的所有关联对象。

    objc_removeAssociatedObjects(id _Nonnull object)

注:只有在其余方法都行不通时才考虑使用它。如果滥用,则很快就会令代码失控,使其难于调试。

第11条:理解 objc_msgSend 的做用

OC方法调用

代码:

id returnValue = [someObject messageName: paramter];

代码说明:

  • someObject 接受者。
  • messageName 选择子。

选择子和参数合起来称为“消息”

底层C语言代码实现:

id returnValue = objc_msgSend(someObject, @selector(messageName:), paramter);
消息发送机制核心函数

原型代码:

void objc_msgSend(id self, SEL cmd, ...)

这是个“参数个数可变函数”,能接受两个或者两个以上的参数。 参数说明:

  • self 接受者。
  • cmd 选择子(方法的名字)。
  • 后续参数为消息中的那些参数,顺序不变。

具体实现:

graph TD A[objc_msgSend] -->|获取接受者和选择子| B(接受者) B -->|查找与选择子名称相符的方法| C{方法列表} C -->|找到| D[方法实现代码] C -->|未找到| E{沿着继承体向上查找} E -->|找到| F[方法实现代码] E -->|未找到| G[消息转发]

注:OC 方法调用须要不少步骤,较为耗时。objc_msgSend 会将匹配结果缓存在“快速映射表”,每一个类都有这样一块缓存。虽然仍是不如“静态绑定的函数调用操做”那么迅速,可是也不会慢不少。

边界状况:

  • objc_msgSend_stret 待发送的消息要返回结构体,就交由此函数处理。
  • objc_msgSend_fpret 待发送的消息要返回浮点数,就交由此函数处理。
  • objc_msgSendSuper 要给超类发消息,就交由此函数处理。如:[super message:parameter]
尾调用优化

Objective-C 对象的每一个方法均可以看作简单的 C 函数,其原型以下:

<return_type> Class_selector(id self, SEL _cmd, ...)

这个原型和 objc_msgSend 函数很像,是为了利用 “尾调用优化” 技术。令 “跳至方法实现” 这一操做跟简单些。

使用范围:某函数的最后一项操做仅仅是调用另外一个函数而不会将其返回值另做他用。 步骤:编译器会生成调转至另外一函数所需的指令码,不会向调用堆栈中推人新的 “栈帧”。 不优化后果:

  1. 每次调用 Objective-C 方法以前,都须要为调用 objc_msgSend 函数准备 “栈帧”,能够在 “栈踪影” 中看到。
  2. 过早的发生 “栈溢出” 现象。

第12条:理解消息转发机制

消息转发: 第一阶段:动态方法解析: 征询接收者(所属的类),看其是否能动态添加方法,以处理当前这个 “未知的选择子”。 第二阶段: 1. 备援的接收者: 请接收者看看有没有其余对象(备援的接收者)能处理这条消息。 2. 完整的消息转发机制: 运行期系统会把与消息有关的所有细节都封装到 NSInvocation 对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析

是否能新增一个实例方法来处理选择子,调用方法以下:

// 实例方法
+ (BOOL)resolveInstanceMethod:(SEL)selector
// 类方法
+ (BOOL)resolveClassMethod:(SEL)selector

使用前提:相关方法的实现代码已经写好,只等运行的时候动态插入到类里面。

动态添加方法函数以下:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

参数说明:

  • cls 添加方法的类
  • name 被添加方法的名字
  • imp 函数指针,指向待添加的方法(C语言实现)。
  • type 待添加方法的 “类型编码”
备援接收者

是否有其余对象处理这条消息,调用方法以下:

- (id)forwardingTargetForSelector:(SEL)selector

若找到备援对象,该方法返回备援对象,反之,返回 nil

注意:咱们没法操做经由这一步所转发的消息。

完整的消息转发机制

建立 NSInvocation 对象,此对象包含 选择子目标参数

消息派发调用方法以下:

- (void)forwardInvocation:(NSInvocation *)invocation

若发现某调用操做不该由本类处理,则向上寻找,直至 NSObject。若是最后调用了 NSObject 的方法,那么该方法还会继续调用 doesNotRecognizeSelector: 以抛出异常,代表选择子最终未能获得处理。

总结:

消息转发全流程以下图:

接收者在每一步中均有机会处理消息。步骤越日后,处理消息的代价就越大。

消息转发代码:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MessageSend

第13条:用 ”方法调配(method swizzling)技术“ 调试 ”黑盒方法“

函数指针(IMP):id (*IMP)(id, SEL, ...)

方法表:函数指针所组成的一个集合。

操做类的方法表:

  • 新增选择子
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    第12条已经说过了。
  • 改变某选择子所对应的方法实现
  • 交换两个选择子所映射到的指针,也就是交换两个方法实现。 方法交换函数:
    func method_exchangeImplementations(_ m1: Method, _ m2: Method)
    参数:两个待交换的方法实现 获取方法实现函数:
    Method class_getInstanceMethod(Class cls, SEL name)

这个方法能够为那些 “彻底不知道具体实现” 的黑盒方法增长日志记录功能,这很是有助于程序调试。 如果滥用,反而会令代码变的不易读懂且难于维护。

method swizzling代码:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MethodSwizzling

相关文章
相关标签/搜索