这一篇,我们了解一下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
谢谢~