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

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中实现下面方法,需要导入头文件:
//实例变量的动态决议,类方法的动态决议是一样的
+(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

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

iOS开发------runtime之动态添加方法(动态决议,请求转发)_第1张图片
iOS开发------runtime之动态添加方法(动态决议,请求转发)_第2张图片

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



请求转发

实现下面两个方法
//调用不存在的方法的时候重定向到一个有该方法的对象
- (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];
}

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


如果动态决议和请求转发都实现了,那么动态决议的优先级要高于请求转发.

你可能感兴趣的:(iOS,RunTime)