接runtime运行时介绍与运用(一)。
4.动态方法决议(或拦截调用)
首先说明一下OC方法调用顺序:
注:如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
注:如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
1).首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
2).如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
3).如果没找到,去父类指针所指向的对象中执行1,2.
4).以此类推,如果一直到根类还没找到,转向拦截调用。
5).如果没有重写拦截调用的方法,程序报错。
在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
说明:第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
第二个方法和第一个方法相似,只不过处理的是实例方法。
第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
示例:
#import@interface Person : NSObject
@property (nonatomic, assign)int age;
@property (nonatomic, copy) NSString *name;
- (void)eat;
@end
#import "Person.h"#import@implementation Person
//run方法实现
void runMethodImplementation(id self, SEL _cmd){
NSLog(@"--跑--runMethodImplementation---");
}
//NSObject
- (void)eat{
NSLog(@"----吃----");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"调用的方法:%s",__func__);
if (sel == @selector(run)) {
class_addMethod([self class], sel, (IMP)runMethodImplementation, "v@:");
return YES;
}
return NO;
}
#import "ViewController.h"#import "Person.h"#import//消息发送机制底层框架#import@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *p1 = [[Person alloc]init];
[p1 eat];
objc_msgSend(p1, sel_registerName("run"));
}
输出日志:
2017-08-14 11:41:42.715 runtime-消息发送[3581:120441] ----吃----
2017-08-14 11:41:42.715 runtime-消息发送[3581:120441] 调用的方法:+[Person resolveInstanceMethod:]
2017-08-14 11:41:42.716 runtime-消息发送[3581:120441] -----跑--runMethodImplementation-----
由于Person类并未实现-(void)run方法,所以程序理应会crash,报方法找不到unrecognized selector sent to instance 0x608000222580'。我们这里采用了拦截调用机制,所以在Person类,以及其父类中未找到时,走到了+ (BOOL)resolveInstanceMethod:(SEL)sel中,这里调用 BOOL class_addMethod(Class cls, SEL name, IMP imp,const char *types)函数,动态的增加了run方法,如果这里不增加并实现run方法,虽然程序会走到该方法中,但还是会crash,因为run方法还是没有找到。
动态增加方法的函数中参数说明:
1.参数三:IMP imp,关于该参数,作以下注解。
SEL : SEL只是方法编号。
IMP:对应函数指针,保存了方法的地址
IMP和SEL关系
每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table. Dispatch table是一张SEL和IMP的对应表.
//run方法实现
void runMethodImplementation(id self, SEL _cmd){
NSLog(@"--跑--runMethodImplementation---");
}
其实,objective-c的方法就是至少带有两个参数(self和_cmd)的普通的C函数。因此在上面的代码中提供这样一个 C 函数 runMethodImplementation,让它来充当对象方法 run 这个 selector 的动态实现
2.参数四:const char *types 表明了该函数从左到右返回值及参数的类型, 那示例中 @表示object对象。:表示selector对象
其他数据类型对应标识符:
v void
@ object对象
: selector对象
c char
i int
s short
l long
q long long
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B bool or _Bool
* string(char *)