必备知识架构-①语言-消息转发
pod search CJBaseHelper
库的使用方法,见我简书中的以下文章:
《CJHook》
pod search CJHook
在调用原始方法之前插入额外的执行逻辑
1、使用场景
日志记录与监控: 在方法执行前后添加日志记录,可以帮助开发者监控应用的行为,特别是在调试和性能分析时。
1
2
3
4
5
6
7
8
9
10- (void)swizzleMethod:(Class)class withSelector:(SEL)selector {
Method originalMethod = class_getInstanceMethod(class, selector);
IMP originalIMP = method_getImplementation(originalMethod);
IMP newIMP = imp_implementationWithBlock(^(id self) {
NSLog(@"Before method execution");
((void(*)(id, SEL))originalIMP)(self, selector);
NSLog(@"After method execution");
});
method_setImplementation(originalMethod, newIMP);
}
2、调用
以在进入个人主页前,需要先判断是否登录为例
常规:使用Util式、使用宏
1 | - (void)goMineHomePage { |
改进(改进后发现,其实可读性不高,所以还不如不这么改):
1 | // LoginCheckUtil.m |
3、内部核心代码
1 | // 在调用原始方法之前插入额外的执行逻辑 |
- 什么时候会报
unrecognized selector的异常
当调用该对象上某个方法,而该对象上没有实现这个方法的时候。可以通过
消息转发进行解决,流程见下图
image
OC在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
但是在这之前,OC的运行时会给出三次拯救程序崩溃的机会Method resolution(消息动态解析)
OC运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则,运行时就会移到下一步,消息转发(Message Forwarding)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 如果是执行 run 函数,就动态解析,指定新的 IMP
if (sel == NSSelectorFromString(@"run:")) {
// class: 给哪个类添加方法
// SEL: 添加哪个方法
// IMP: 方法实现 => 函数 => 函数入口 => 函数名
// type: 方法类型:void用v来表示,id参数用@来表示,SEL用:来表示
class_addMethod(self, sel, (IMP)runMethod, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//新的 run 函数
void runMethod(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
Fast forwarding(消息接受者重定向)
如果目标对象实现了-forwardingTargetForSelector:,Runtime这时就会调用这个方法,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点1
2
3
4
5
6
7
8// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == ) {
return [[Person alloc] init];
// 返回 Person 对象,让 Person 对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}- Normal forwarding(消息重定向)
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 从 anInvocation 中获取消息
SEL sel = anInvocation.selector;
if (sel == NSSelectorFromString(@"run:")) {
// 1\. 指定当前类的一个方法作为IMP
// anInvocation.selector = @selector(readBook:);
// [anInvocation invoke];
// 2\. 指定其他类来执行这个IMP
Person *p = [[Person alloc] init];
// 判断 Person 对象方法是否可以响应 sel
if([p respondsToSelector:sel]) {
// 若可以响应,则将消息转发给其他对象处理
[anInvocation invokeWithTarget:p];
} else {
// 若仍然无法响应,则报错:找不到响应方法
[self doesNotRecognizeSelector:sel];
}
}else{
[super forwardInvocation:anInvocation];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
[super doesNotRecognizeSelector:aSelector];
}既然
-forwardingTargetForSelector:和-forwardInvocation:都可以将消息转发给其他对象处理,那么两者的区别在哪?
区别就在于-forwardingTargetForSelector:只能将消息转发给一个对象。而-forwardInvocation:可以把消息存储,在你觉得合适的时机转发出去,或者不处理这个消息。修改消息的target,selector,参数等。将消息转发给多个对象- Normal forwarding(消息重定向)

