文章目录
- 前言
- 12.理解消息转发机制
- 消息转发
- 动态方法解析
- 动态方法解析的前提
- 备援接受者
- 完整的消息转发
- 消息转发全部流程
- 要点总结
- 13.用“方法调配技术”调试“黑盒方法”
- 方法调配
- 动态消息派发系统和IMP
- 如何交换方法实现
- 要点总结
- 14.理解“类对象”的用意
- 在类的继承体系中查询类型信息
- 要点总结
- 总结
前言
- 继续学习本书第二章内容
12.理解消息转发机制
- 之前强调了消息是如何传递下去的,这一条深入理解一下在某些出现问题的时刻系统是如何解决问题的。
消息转发
- 某个对象收到了无法解读的消息之后会发生什么情况?这就是OC的消息转发机制。
- 对于在编译期向类发出的无法解读的消息之后不会报错,因为可以在运行期继续向类里面添加方法,所以在编译时期出现了对象无法解读的消息就会启动消息转转发机制。
- 消息转发分为两大阶段,第一阶段先征询接收者,所属的类,看其是否能动态添加方法,处理当前这个“未知的选择子”,这叫做“动态方法解析”。第二阶段涉及“完整的消息转发机制”。
动态方法解析
- 对象在收到了无法解读的消息,首先调用其所属类的下列类方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
}
该方法的参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有机会新增一个处理此选择子的方法,假如尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另一个方法,该方法与“resolveInstanceMethod:”类似,叫做“resolveClassMethod”。
动态方法解析的前提
- 对于上述的消息转发第一步,前提是我们相关的实现代码已经写好了,只需要等着运行时的时候插入类里面即可在属性那一条的@dynamic属性就是利用该方案实现的
备援接受者
- 在第一步还是没有找到写好的方法之后,当前接受者还有第二次机会处理未知的选择子,在这一步里运行期的系统会询问接受者能不能找到其他接受者处理该消息,这里也有一个方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
;
}
- 这个方法的参数代表未知的选择子,若当前接受者能找到备援对象,则将其返回,否则返回nil。这个方法其实也实现在了某个对象内部,可能存在其他一些列对象,该对象可以经过此方法处理了某些选择子的相关对象返回。
- 需要注意的是这一步转发的消息我们是无法进行操作的,如需要操作,需要进行下一步,完整的消息转发机制
完整的消息转发
- 对于消息转发机制,如果到了这一步就必须启动完整的消息转发机制。
- 首先创建NSInvocation对象,把与尚未处理的那条消息有关的全部细节都封于其中。此对象包含选择子、目标及参数。在触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。此步骤会调用下列方法来转发消息:
- (void)forwardInvocation:(NSInvocation *)anInvocation
- 这个方法的实现方式🈶️2种,一种是只需要改变调用目标,和备援接受者方法实现的等效,第二种则是在触发消息前,先以某种方式改变消息内容,比如追加另一个参数,或者改变选择子等
- 实现此方法时,若发现某调用操作不应由本类来处理,则需调用超类的同名方法。这样的话,继承体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用NSObject类的方法,那么该方法还会继而调用“doesNotRecognizeSelector:”以抛出异常,此异常表明选择子最终未能得到处理。
- (void)doesNotRecognizeSelector:(SEL)aSelector
- 这些方法都在最大的NSObject里面实现
消息转发全部流程
- 一张图总结
- ⚠️注意:往往越简单越好,能在第一步解决就不要去第二步,能改动目标实现就不要去执行第三步。
要点总结
- 若对象无法响应某个选择子,则进入消息转发流程。
- 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
- 对象可以吧其无法解读的某些选择子转交给其他对象来处理。
- 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。
13.用“方法调配技术”调试“黑盒方法”
- 黑盒机制,不少人都有理解,就是一个方法你知道怎么用,如何用,并且常常都能使用它但是对于该方法的内部机制却不知道是什么样子,这一条就介绍了如何利用现有的方法去调试黑盒方法变成我们所能用的。
方法调配
- 对于OC对象收到消息之后使用何种方法在运行期进行解析,与给定的选择子名称相对应的方法是可以在运行期改变的!这是OC语言强大的特性,我们就可以不知道源代码并且不需要通过继承子类复写方法来改变某个类本身的功能,这样一来新功能能够在本类实例化的所有实例里面生效,而不是仅限于覆写了相关方法的那些子类的实例。叫做方法调配
动态消息派发系统和IMP
- 类的方法列表会把选择子的名称映射到相关的方法实现上面,使得动态消息派发系统能够根据此找到应该调用的方法,这些方法均以函数指针的形式表示,这种指针叫做IMP,原型如下
id (*IMP) (id, SEL, ...)
- 例如NSString类可以响应自己所带的选择子,它们的关系类似于key 和 Value .
- OC运行期提供的几个方法都能操作这张表,开发者可以向其中新增选择子,也可以改变某选择子所对应的方法实现,还可以交选择子所映射到的指针,我们可以经过操作改变类的方法表
- 也就是说我们无需修改子类覆写方法,只需要修改方法表的布局,就会反映到程序所有NSString实例上
如何交换方法实现
- 我们添加新功能的本质就是修改之前的方法实现,也就是重写写一个方法实现然后实现交换。
- 方法交换函数
- 也就是说实现方法重写需要实现方法交换,实现方法交换就需要获取方法实现。这并不拗口
- 方法交换实例
要点总结
- 在运行期,可以向类中新增或替换选择子所对应的方法实现。
- 使用另一份实现来替换原有的方法实现,这道工序叫做“方法调配”,开发者常用此技术向原有实现中添加新功能。
- 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。
14.理解“类对象”的用意
在类的继承体系中查询类型信息
- 这一条是我看的比较迷茫的一条,我总结出了我自己认为比较重要的。
- 在OC里,之前说过存在类族模式,所以这里衍生出了两种方法。
isMemberOfClass
和isKindOfClass
-
“isMemberOfClass:”
能够判断出对象是否为某个特定类的实例(只有与其出创建的类型相同时才返回YES),而“isKindOfClass
:”则能够判断出对象是否为某类或其派生类的实例。
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
BOOL bool1 = [mutableDict isMemberOfClass:[NSDictionary class]];
BOOL bool2 = [mutableDict isMemberOfClass:[NSMutableDictionary class]];
BOOL bool3 = [mutableDict isKindOfClass:[NSDictionary class]];
BOOL bool4 = [mutableDict isKindOfClass:[NSArray class]];
- 这里和书上有些区别在bool2
- 书上的bool2 是yes,我的理解是NO,在前面我们学过类族,里面分了很多实际类,打印一下
- 确实不是一个类,但是bool3时yes 因为mutableDIct隶属于NSMutableDIctionary的子类
要点总结
- 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。
- 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
- 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。
总结
- 第二章学的比较慢,每天学二,三条,有的东西还是需要手打到代码上鉴别一下,组长说书的内容有些老化,但这些知识还是很重要的,iOS路任重而道远。⛽️