提起NSMethodSignature
不得不联想到消息转发机制
当给一个对象发送消息,找不到方法时会走消息转发三步
1 resolveInstanceMethod
或(resolveClassMethod
)允许用户在此时为该 Class
动态添加实现。如果有实现了,则调用并返回YES
,那么重新开始objc_msgSend
流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod
。如果仍没实现,走第二步。
2 forwardingTargetForSelector
尝试找到一个能响应该消息的对象。如果获取到,则直接把消息转发给它,返回非nil
对象。否则返回nil
,继续下面的动作。注意,这里不要返回self
,否则会形成死循环
3 methodSignatureForSelector
尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector
抛出异常。如果能获取,则返回非nil
:创建一个 NSlnvocation
并传给forwardInvocation:
。调用forwardInvocation:
方法,将第3步获取到的方法签名包装成Invocation
传入,如何处理就在这里面了,并返回非nil
调用doesNotRecognizeSelector:
,默认的实现是抛出异常。如果第3步没能获得一个方法签名,执行该步骤。
从NSInvocation
开始,NSInvocation
本质就是一个创建方法(消息),将方法具体化的一个类。NSInvocation
能设置Target
,参数,返回值和方法名称。
NSInvocation
应用 (NSInvocation
可以去构建一个方法然后转发这个方法)但是NSInvocation
的创建需要方法签名来创建,首先要得到方法签名。
NSMethodSignature
提供给外面的API不多
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
这个方法时根据ObjCTypes
生成一个方法签名,它是一个是字符串数组,该数组包含了方法的类型编码。那如何获取这些类型编码呢,有两种方式
1:可以参考官方文档 Type Encodings。
2:使用 @encode()计算。
NSLog(@"id Type encoding -->%s",@encode(BOOL));
// 输出结果 id Type encoding -->B
NSLog(@"id Type encoding -->%s",@encode(id));
...
// 输出结果 id Type encoding -->@
不过一般不会用此方法手动创建一个方法签名的。消息转发第三步需要手动创建,一般会重写NSObject
的
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
来获取实例和类的方法签名。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
/*手动创建签名 但是尽量少使用 因为容易创建错误 可以按照这个规则来创建
https://blog.csdn.net/ssirreplaceable/article/details/53376915
//写法例子
//例子"v@:@"
//v@:@ v 返回值类型void;@ id类型,执行sel的对象;: SEL;@ 参数
//例子"@@:"
//@ 返回值类型id;@ id类型,执行sel的对象;: SEL
*/
//如果返回为nil则进行手动创建签名
if ([super methodSignatureForSelector:aSelector]==nil) {
NSMethodSignature * sign = [NSMethodSignature signatureWithObjCTypes:"v@:"];
return sign;
}
return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
上方的方法如果调用返回有签名 则进入消息转发最后一步
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 创建备用对象
LhProxyClass *proxy = [LhProxyClass new];
SEL sel = anInvocation.selector;
//判断备用对象是否可以响应传递进来等待响应的SEL
if ([LhProxyClass respondsToSelector:sel]) {
[anInvocation invokeWithTarget:proxy];
}else {
//如果备用对象不能响应,则抛出异常
[self doesNotRecognizeSelector:sel];
}
}
延伸:
最后附上消息转发防crash
Demo。
demo
处理消息转发考虑到如果第一步resolveInstanceMethod
需要在类的本身上动态添加它本身不存在的方法,这些方法对于该类本身来说冗余
forwardInvocation
可以通过NSInvocation
的形式将消息转发给多个对象,但是其开销较大,需要创建新的NSInvocation
对象,并且forwardInvocation
的函数经常被使用者调用,来做多层消息转发选择机制,不适合多次重写
所以在第二步 forwardingTargetForSelector
将消息转发给一个对象,开销较小,并且被重写的概率较低,适合重写
动态创建一个桩类ProxyClass
, hook NSObject
转到ProxyClass
+ (void)openAvoidUnrecognizedCrash {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//区分线上
#ifdef DEBUG//调试状态,关闭防护功能
#else//发布状态,打开防护功能
swizzleMethod([self class], @selector(forwardingTargetForSelector:), @selector(swizzled_forwardingTargetForSelector:));
#endif
});
}
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
// 重写消息转发方法
- (id)swizzled_forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorStr = NSStringFromSelector(aSelector);
// 做一次类的判断,只对 UIResponder 和 NSNull 有效 ==== 白名单
if ([[self class] isSubclassOfClass: NSClassFromString(@"UIResponder")] ||
[self isKindOfClass: [NSNull class]]) {
NSLog(@"PROTECTOR: -[%@ %@]", [self class], selectorStr);
NSLog(@"PROTECTOR: unrecognized selector \"%@\" sent to instance: %p", selectorStr, self);
// 查看调用栈
NSLog(@"PROTECTOR: call stack: %@", [NSThread callStackSymbols]);
// 上报服务器 report sever
// 对保护器插入该方法的实现
id instance = [[LhProxyClass alloc] init];
return instance;
}
else
{
return [self swizzled_forwardingTargetForSelector:aSelector];
}
}
添加对应的Selector,用一个通用的返回0的函数来实现该SEL的IMP
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static id sharedInstance;
dispatch_once(&onceToken, ^{
sharedInstance = [[super allocWithZone:NULL] init];
});
return sharedInstance;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [LhProxyClass sharedInstance];
}
id ForwardingTarget_dynamicMethod(id self, SEL _cmd) {
return [NSNull null];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
class_addMethod(self.class, sel, (IMP)ForwardingTarget_dynamicMethod, "@@:");
[super resolveInstanceMethod:sel];
return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
id result = [super forwardingTargetForSelector:aSelector];
return result;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
id result = [super methodSignatureForSelector:aSelector];
return result;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[super forwardInvocation:anInvocation];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
[super doesNotRecognizeSelector:aSelector]; // crash
}
网友评论