美文网首页iOS开发技术分享iOS Developer程序员
蜜蜂浅谈Runtime Part2(菜鸟级别)

蜜蜂浅谈Runtime Part2(菜鸟级别)

作者: 蜜锋将有小肚腩 | 来源:发表于2016-08-06 20:59 被阅读74次

这一篇,我们了解一下runtime的交换/增加/跟踪方法

我们知道通过runtime可以获取的一个类的属性列表,其实还可以获取到这个类的协议和方法列表

#pragma mark - 获取方法列表(即使是没执行的方法也能获取)
    unsigned int count;
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"ViewController 的方法有:%@", NSStringFromSelector(method_getName(method)));
    }
    
#pragma mark - 获取协议列表
    // * 这里我们写上tableview的协议
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"ViewController 的协议有%@", [NSString stringWithUTF8String:protocolName]);
    }

接下来,我们先看一个比较简单的,动态增加方法

#pragma mark - 动态增加方法
- (void)add_method
{
    /**
     (IMP)guessAnswer 意思是guessAnswer的地址指针,所以增加的方法需要用runtime函数写;
     "v@:" 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
     “v@:@@” 意思是,两个参数的没有返回值。
     自己定义一个方法,我这里是myNewMethod,因为runtime里没有这个方法,则创建这个方法
     [self class]可以替换为其他的类
     */
    class_addMethod([self class], @selector(myNewMethod), (IMP)myNewMethod, "v@:");
    if ([self respondsToSelector:@selector(myNewMethod)]) {
        
        [self performSelector:@selector(myNewMethod)];
        
    } else{
        NSLog(@"Sorry,I don't know");
    }
}

// 这里是增加的方法
void myNewMethod(id self,SEL _cmd){
    NSLog(@"I am from GuangZhou");
}

其次是交换方法和跟踪方法,我们需要在+(void)load方法里声明,viewcontroller都会优先走load这个方法

这里我在BeeChangeMethodController里定义了一个类方法(beeChange_method),然后我们自己在viewcontroler写了一个(view_method)来交换他

+ (void)load
{
    // **************************** 动态交换两个方法的实现 ***************************************
    
    BeeChangeMethodViewController *beeChangeVC = [[BeeChangeMethodViewController alloc]init];
    Class PersionClass = object_getClass([beeChangeVC class]);
    Class toolClass = object_getClass([self class]);
    
    //源方法的SEL和Method   注意不要把selector里面的方法名写错
    SEL oriSEL = @selector(beeChange_method);
    Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
    
    //交换方法的SEL和Method
    SEL cusSEL = @selector(view_method);
    Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
    
    //先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况
    BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
    if (addSucc) {
        // 添加成功:将源方法的实现替换到交换方法的实现
        class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        
    }else {
        //添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
        method_exchangeImplementations(oriMethod, cusMethod);
    }
    
    // *******************************************************************
    
    // ************* 跟踪记录APP中按钮的点击次数和频率等数据 ********************
    /**
     *  因为可能别人不一定会去实例化你的子类,或者其他类实现了点击方法不确定是哪一个,则可以通过runtime这个方法来解决
     */
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class selfClass = [self class];
        
        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
        
        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
        
        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }
        
    });
    
    
    // ******************************************************************* 
}

交换方法只要有了定义,你调用的时候就会变成你交换了的方法
这里我们就定义了一个view_method来替换
注意:如果是在别的类中定义方法来代替,则必须是类方法;如果是在同一个类中,则可以有私有方法

接下来是跟踪方法

// 执行跟踪方法
    [self mySendAction:@selector(followAction:) to:self forEvent:UIEventTypeTouches];

这样子我就跟踪了followAction的方法,followAction一触发,跟踪方法也会跟着走

#pragma mark - 跟踪方法
/**
 *  跟交换方法一样,跟踪方法也需要在load方法里定义;
    当我们想要对一个方法在其原有的功能上增加功能,如果不想去实例化,或者说实现了很多点击方法不确定是哪一个,那我们可以通过runtime,在不碰源代码的基础上进行代码添加
 */
// 跟踪方法
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    BeeFollowViewController *followVC = [[BeeFollowViewController alloc]init];
    followVC.beeBlock = ^(NSInteger num){
        _Ji = num;
        NSLog(@"num = %ld",_Ji);
        if (!_beeTitleLabel) {
            _beeTitleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 350, ScreenWidth, 30)];
            _beeTitleLabel.textAlignment = 1;
            [self.view addSubview:_beeTitleLabel];
        }
        _beeTitleLabel.text = [NSString stringWithFormat:@"你已经跳转过%ld次",_Ji];

    };
    [followVC followAction:_Ji];
}

这过程中我也写了个小demo,希望也能够帮助大家理解
https://github.com/iOSJYF/Garlic_runtime/tree/master/Garlic_WithRuntime

谢谢~

相关文章

网友评论

    本文标题:蜜蜂浅谈Runtime Part2(菜鸟级别)

    本文链接:https://www.haomeiwen.com/subject/tloksttx.html