在iOS中如果调用一个不存在的方法我们如何补救可以让我们的程序不报错?可能有的同学会觉得闹着玩儿了,方法不定义还想正常调用,这是我们的正常逻辑,但iOS号称动态语言怎么会没点黑科技,今天就让我们来好好说道说道。
在iOS中有三个环节可以进行补救,第一个环节就是resolveInstanceMethod阶段,如果被调用的方法已经定义并存在,则方法会被直接调用不会触发任何环节,为什么会这样了,这跟Objective-c自身的结构有关,在查看objc的源码可以发现其内部结构中定义了缓存实例方法的结构体,具体objc的内容不在此具体详聊,感兴趣的朋友可以自行去查看相关资料,objc的方法在调用的时候会首先从本地的缓存中去寻找方法是否已经定义,如果已经定义则执行已经定义的方法,如果在本地缓存中找不到则会继续在父类中继续寻找,如此当所有的父类中都找不到方法的定义时,则会告诉调用者找不到需要调用的方法,你有什么要补充的,这时候就会触发resolveInstanceMethod 方法,在该方法中入参是一个SEL的方法指针,调用者根据方法指针进行判断是否是目标函数,然后就可以进行第一部的补救措施,我们可以通过class_addMethod 方法动态的指定需要触发的函数,这里需要注意两点:
1.方法的实现可以是纯OC的方法定义,也可以是C的方法定义,这两者是稍微有点区别
OC的写法
IMP imp = class_getMethodImplementation([self class], @selector(helloGoodbye:));
class_addMethod([self class],sel, imp,"v@:@");
C的写法
class_addMethod([self class],sel, (IMP)sayGoodBye,"v@:@");
2.需要注意v@:@ Type Encoding 这是对OC中方法签名的抽象描述,其中 v:返回值, @:调用者对象,:代表方法,@是入参类型,具体其他的符号说明参见苹果官方文档https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
resolveInstanceMethod返回值为YES表示方法可以继续向下传播,如果返回NO,则表示到此为止,方法处理完成
对于第一阶段的补救可能不一定能够满足我们的需求,这时候我们可以进行第二阶段的补救,怎么补救,就那些需要实现forwardingTargetForSelector方法,该方法可以将我们的目标方法代理到其他的实现类中,以满足一些特定的需要,具体参考代码
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
if (aSelector == @selector(sayBye:)){
return [People new];
}
return [super forwardingTargetForSelector:aSelector];
// return nil;
}
返回值为nil则表示这个阶段我处理不了,继续向下传播寻求第三阶段的补救措施。
至于最后一步的补救措施就是所谓的兜底方案,如果这个阶段还不能处理那就只能抛出异常了,在这个阶段的处理相对于前面两步要稍显复杂,需要先处理方法签名,然后在进行转发或者自行处理,在anInvocation参数中包涵了方法的签名以及参数,在进行参数处理的时候需要注意参数索引,0个参数是当前调用者,第一个是方法签名,2个才是具体的入参,如果还有其他参数可以以此类推进行尝试。
纵观三个阶段的补救措施发现,越靠前,越容易拦截,越往后处理起来越复杂,看完整片文章后发现,原来objc还有这种骚操作,其实对于这类的黑科技objc中还有很多,后续我们可以一一进行解说。
下面是整个列子的代码
//
// ViewController.m
// protocal
//
// Created by june on 2019/3/6.
// Copyright © 2019年 zj. All rights reserved.
//
#import "ViewController.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@interface People : NSObject
@end
@implementation People
-(void) sayBye:(NSString *)name {
NSLog(@"bye bye %@",name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:@selector(sayBye:) withObject:@"张三"];
// [self sayBye:@"zhangsan"];
}
//-(void) sayGoodBye:(NSString *)name {
// NSLog(@"bye bye %@",name);
//}
- (void) helloGoodbye:(NSString *) name{
NSLog(@"==========ddddd %@",name);
}
void sayGoodBye(id obj,IMP cmd,NSString *name){
NSLog(@"=========%@",name);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod");
if(@selector(sayBye:) == sel){
// return YES;
// IMP imp = class_getMethodImplementation([self class], @selector(helloGoodbye:));
// class_addMethod([self class],sel, imp,"v@:@");
class_addMethod([self class],sel, (IMP)sayGoodBye,"v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
// return YES;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
// NSLog(@"forwardingTargetForSelector");
// if (aSelector == @selector(sayBye:)){
// return [People new];
// }
// return [super forwardingTargetForSelector:aSelector];
return nil;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
SEL sel = anInvocation.selector;
People *people = [People new];
// if([people respondsToSelector:sel]){
// [anInvocation invokeWithTarget:people];
// }else{
NSString *arg;
[anInvocation getArgument:&arg atIndex:2];
[self helloGoodbye:arg];
// }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"methodSignatureForSelector");
if([NSStringFromSelector(aSelector) isEqualToString:@"sayBye:"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
@end
网友评论