《Effective Objective-C 2.0》 阅读笔记 2

12. 理解消息转发(message forwarding)

紧接着第11条的消息传递机制,如果对象无法解读接收到的消息时就会启动消息转发机制。

向类发送其无法解读的消息时,编译期不会报错,只有运行期才可以检查出来。

消息转发有两个阶段:

(一)动态方法解析

对象收到无法解读的消息后,首先调用类的方法:

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

使用此方法的前提是相关方法的实现代码已经写好,只等着运行的时候动态插在类里面

表示这个类是否能新增一个实例方法来处理这个选择器。

(二)完整的消息转发机制

(1)备援接收者

当前接收者还有第二次机会能处理未知的选择器,对应的方法如下:

- (id)forwardingTargetForSelector:(SEL)selector;

(2)完整消息转发

首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中,包含选择器、目标以及参数。消息派发系统(message-dispatch system)将调用下面方法来转发消息:

- (void)forwardInvocation:(NSInvocation *)invocation;

实现此方法较为有用的方式是:在出发消息前,先以某种方式改变消息内容,比如追加另外一个参数或者改变选择器等。如果某调用操作不由本类处理,需要调用超类的同名方法,直至NSObject。最后还是不能调用方法,就抛出异常“doesNotRecognizeSelector”。

综上全部的消息转发流程可见下图:

消息转发全流程

13. method swizzling(方法混合)

此方法堪称经典的“黑魔法”。

类似于C++的虚函数表,OC的类也有方法列表,列表项会将选择器的名称映射到方法的实现指针上。比如NSString的几个常用方法映射情况:

NSString 方法映射表

OC Runtime系统方法可以操作这个表,具体地,开发者可以向其中增加选择器或者交换选择器的实现方法。经过几次改变后的映射表变成如下:

变换后的NSString方法映射表

可见method swizzling可以无需编写子类就可以实现如此强大的功能。

具体的交换代码逻辑如下:

Method originalMethod = class_getInstanceMethod([NSString Class],@selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString Class],@selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);

实际应用中,仅仅交换两个方法的实现是意义不大的,一般我们使用这种手段来为方法新增一些功能,比如埋点。我们需要知道一共调用多少次lowercaseString, 如果我们没个调用的地方都去埋点,就很麻烦也很容易漏掉统计,这时候我们就可以用方法混合技术简单实现。

我们先做一个NSString 的分类

@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;
- @end

@implementation NSString (EOCMyAdditions)
- (NSString *)eoc_myLowercaseString {
    NSString *lowercase = [self eoc_myLowercaseString];
    NSLog(@"%@",lowercase);
    return lowercase;
}
@end

注意,这里会让人误解为死循环调用,其实不是,在调用此方法前,已经做过"lowercaseString"与"eoc_lowercaseString"方法的交换了,此时"[self eoc_myLowercaseString]"实际上会调用的是“lowercaseString“。

至此,所有NSString实例调用lowercaseString方法都会输出日志。

14. 理解"类对象"

这个关于OC类对象继承体系与元类的知识点在很多地方都会讲到,详细知识点直接去参考runtime中关于Class类的相关定义。

本节的重点有几个:

1)类型信息查询(introspection,内省),在运行时起检查对象的类型。

isMemberOfClass可以判断出对象是否是某个特定类的实例; isKindOfClass可以判断出对象是否是某类或者其派生类的实例。还可以使用==操作符来判断对象是否是某类的实例([obj class] == [EOCSomaClass Class])

我们应该尽可能使用内省的方法而非直接比较类对象方法来判断,因为前者可以正确处理那些使用了消息传递机制的对象。比如代理对象(NSProxy),在此代理对象上调用class方法返回的是代理对象本身而非接受代理的对象类;然而使用isKindOfClass,代理对象就会吧这条消息转发给接受代理的对象。

2)类对象结构

OC中每个对象结构体的首个成员是Class类的变量,该变量定义了对象所属的类,称为"is a"指针。Class结构体存放类的“元数据”,其首个变量也是isa指针,说明Class本身也是Objective-C对象,这个isa表示类对象是一个“元类”类型的对象。类方法就放在元类中,类似于实例方法放在类对象中一样。

每个类仅有一个类对象,每个类对象仅有一个元类

类继承体系与元类

15. 用前缀避免命名空间冲突

OC没有namespace机制,需要前缀命名法避免重复符号。

避免命名空间冲突的场景主要有:

1) 类名,一般加上公司、App有关联的前缀名。

2)纯C函数与全局变量,因为它们算作顶级符号,会与其他地方定义的函数或变量重名。

3)使用第三方库(a)再次开发自己的第三方库(b)时,别人在使用你发布的第三方库(b)的应用程序里可能也引
入了第三方库(a),此时应该将b中引入的a库代码上都加上你自己的前缀。

16. 提供全能初始化方法

可为对象提供必要信息以便其完成工作的初始化方法叫做“全能初始化方法”。

一个类可能有很多个初始化方法,必须有一个全能初始化方法,让其他所有初始化方法都调用它。

如果子类的全能初始化方法与超类的方法名称不同,那么应该覆写超类的全能初始化方法。如果超类的全能初始化方法不适用于子类,可以覆写超类的方法并在其中抛出异常。

17. 实现description方法

在打印对象信息时用到:

NSLog(@"%@",obj);

可以自定义上述的输出格式。

NSObject还有一个debugDescription方法,此方法是为了自定义控制台po命令时的对象信息输出格式。
我们可能不想把类名与指针地址等信息放在普通的描述信息中,确又想在调试时候能看到他们,就可以实现两个描述方法。NSArray就这这么做的。

18. 尽量使用不可变对象

实际编码时,尽量把对外公布的属性设为只读,而且只在确有必要时才将属性对外公布。当然,我们可以在类的内部实现中再次将这些属性设置为读写的。

在表示各种collection的属性时,可以设为不可变的,然后提供修改方法操作这个collection,内部维持一个可变的collection,返回其拷贝给外部。

19. 使用清晰协调的命名方式

OC方法名一般相对比较冗长,但是却可以相对清晰完整地表达含义,向日常句子一样。

使用OC规范的驼峰命名法。

坚持统一风格。

20. 为私有方法名加前缀

这样容易将私有方法与公有方法区别开来,方便调试。

建议使用p_前缀。

不要用一个单一的下划线做前缀,这是苹果公司预留的。

21. 理解OC错误模型。

OC不像Java那样频繁地抛出异常,因为OC很难做到“异常安全”。在ARC模式下,抛出异常后,那些本应该在作用域末尾释放的对象就不能自动释放了。非ARC模式下,即使在抛出异常之前手动释放资源,但是如果释放的资源太多或者代码执行路径很复杂时,就会使代码和乱。

可以设置编译器标志来实现异常安全代码,即打开-fobjc-arc-exceptions。但这样会引入额外代码,在不抛出异常时,也会执行代码。

所以只有发生了导致crash的严重错误时,才使用异常。

其他不严重的情况,可以使用delegate来处理NSError对象。

22. 理解NSCopying协议

自定义对象要支持拷贝操作,需要实现NSCopying协议,并覆写

- (id)copyWithZone:(NSZone*)zone;

方法,这里的NSZone是历史遗留问题,现在都是default zone,不用管。

同理,如果要获取可变版本的拷贝时,需要遵守NSMutableCopying协议并覆写

- (id)mutableCopyWithZone:(NSZone*)zone;

方法。

拷贝时,有深拷贝与浅拷贝之分。

深拷贝: 在拷贝对象本身时,将其底层数据也一并复制过去。
浅拷贝: 只拷贝对象本身,不复制其中数据。

Foundation框架中的所有collection类默认情况下都执行浅拷贝

NSSet 有一个深拷贝的初始化方法:

- (id)initWithSet:(NSArray *)array copyItems:(BOOL)copyItmes;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容