iOS开发------runtime之动态添加方法(动态决议,请求转发)

来源:互联网 发布:世界卫生组织官网数据 编辑:程序博客网 时间:2024/05/17 18:19

RunTime中实例变量调用方法的步骤:

1、在该实例变量的方法缓存列表中查找方法,如果找到执行.

2、如果没找到,会去类结构中的相应方法列表中进行查找,如果找到执行.

3、如果方法列表没有找到该方法,那么就从父类中进行1、2部操作.

4、如果直到根类仍然没有找到方法,那么就会报错:unrecognized selector sent to instance 0x1005046c0.


也就是说

1、重写父类的方法,本质上不是覆盖了父类的方法,只不过是在本类中找到相应的方法,不再去父类中查找方法而已

2、super关键字并不是指父类,作用是 跳过此类直接从父类中进行查找方法


动态决议以及请求转发就是拦截上述过程,让其在runtime中运行相应的方法


动态决议

实现下面的两个方法
//对类对象进行决议+ (BOOL)resolveClassMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);//对实例对象进行决议+ (BOOL)resolveInstanceMethod:(SEL)sel __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

返回值以及参数:
返回值:表示动态决议的成功与否
参数:sel 需要进行动态决议的方法

添加方法
/** *  运行时添加方法的函数 * *  @param cls   需要动态决议的类 *  @param name  需要动态决议的方法 *  @param imp   需要执行方法的指针(本人理解为函数指针) *  @param types 类型字符串,后面详细说 * *  @return 添加的结果 */(BOOL)class_addMethod(Class cls,SEL name,IMP imp,const char * types)


用代码来实现,更加简明,建立一个测试类RunTimeClass1:NSObject,不实现任何的方法:

在main中实现下面方法
RunTimeTextClass1 * class1 = [[RunTimeTextClass1 alloc]init];[class1 performSelector:@selector(Text1) withObject:@"哈哈哈哈"];

因为类中根本没有这个方法,所以结果相信也都知道crash了,因为找不到这个方法的内存地址
RunTime[1047:62333] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RunTimeTextClass1 Text1]: unrecognized selector sent to instance 0x100107540'

那么在RunTimeClass1中实现下面方法,需要导入头文件:<objc/runtime.h>
//实例变量的动态决议,类方法的动态决议是一样的+(BOOL)resolveInstanceMethod:(SEL)sel{    if ([NSStringFromSelector(sel) isEqualToString:@"Text1"])    {        NSLog(@"RunTimeTextClass1 实例对象动态决议!");                class_addMethod([self class], sel, (IMP)test1, "v@:@");    }        return [super resolveInstanceMethod:sel];}


       实现决议的方法test1,楼主使用C实现的,如果是用Objc实现的,可以通过方法:+(IMP)instanceMethodForSelector:(SEL)aSelector来转换,Objc的方法是至少有id self,SEL _cmd两个参数的普通C语言的函数,必须要有这两个参数
void test1(id self,SEL _cmd,NSString *str){    NSLog(@"%@",str);}

再来解释一下types这个字符串:
v  :表示函数的返回值void
@:表示参数id self
:  :表示SEL对象
@:表示后面的参数str:NSString

下面是苹果官方规定的符号:



接下来在运行一下,结果就不报错了,并执行了我们决议后的方法



请求转发

实现下面两个方法
//调用不存在的方法的时候重定向到一个有该方法的对象- (id)forwardingTargetForSelector:(SEL)aSelector __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);//将调用不存在的方法封装成NSInvocation对象传出,做完处理调用invokeWithTarget:方法触发- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");

举个例子来看:
在RunTimeTextClass1中调用-text1方法,但是在.h与.m中都没有实现这个方法,也不给他实现动态决议方法,调用方法text1
RunTimeTextClass1 * class1 = [[RunTimeTextClass1 alloc]init];[class1 performSelector:@selector(text1)];

实现-forwardingTargetForSelector:方法来完成请求转发

然后在RunTimeTextClass1中实现第一个方法:
-(id)forwardingTargetForSelector:(SEL)aSelector{    return [[Text alloc]init];}

下面是Text的实现文件
////  Text.m//  RunTime////  Created by YueWen on 16/2/24.//  Copyright © 2016年 YueWen. All rights reserved.//#import "Text.h"@implementation Text-(void)text1{    NSLog(@"我是Text!");}@end

运行结果:运行了Text1的text1方法,如果Text1或者Text1父类中中没有实现该方发,那么程序依旧会crash



实现forwardingInvocation:方法来完成请求转发

首先实现这个方法:
-(void)forwardInvocation:(NSInvocation *)anInvocation{    Text * text = [[Text alloc]init];        SEL selector = anInvocation.selector;        if ([text respondsToSelector:selector])//如果有这个方法    {        [anInvocation invokeWithTarget:text];//触发方法    }        else    {        [super forwardInvocation:anInvocation];    }}

但是用上面的方法来实现,还需要实现一个方法,为这个类对象进行方法签名:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{    return [Text instanceMethodSignatureForSelector:aSelector];}

这样再来看看效果,和上面的效果是一样的:


如果动态决议和请求转发都实现了,那么动态决议的优先级要高于请求转发.
0 0
原创粉丝点击