运行时之方法交换

Posted LiLM

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了运行时之方法交换相关的知识,希望对你有一定的参考价值。

在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有就是方法交换

方法交换的原理:在OC中调用一个方法其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector方法的实现,达到和方法挂钩的目的。

每一个类都有一个方法列表,存放在selector的名字和方法实现的映射关系,imp有点像函数指针,指向具体的方法实现。

可以利用method_exchanggeimplementations来交换两个方法的imp

可以利用class_replaceMethod来修改类

可以利用method_setimplementation来直接设置某个方法的imp

归根结底方法交换就是偷换了selector的imp

Method Swizzling 实践

举个例子好了,我想钩一下NSArray的lastObject 方法,只需两个步骤。

第一步:给NSArray加一个我自己的lastObject
[cpp] view plaincopyprint?
#import "NSArray+Swizzle.h"

@implementation NSArray (Swizzle)

- (id)myLastObject
{
id ret = [self myLastObject];
NSLog(@"********** myLastObject *********** ");
return ret;
}
@end

乍一看,这不递归了么?别忘记这是我们准备调换IMP的selector,[self myLastObject] 将会执行真的 [self lastObject] 。

第二步:调换IMP

[cpp] view plaincopyprint?
#import <objc/runtime.h>
#import "NSArray+Swizzle.h"

int main(int argc, char *argv[])
{
@autoreleasepool {

Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject));
Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject));
method_exchangeImplementations(ori_Method, my_Method);

NSArray *array = @[@"0",@"1",@"2",@"3"];
NSString *string = [array lastObject];
NSLog(@"TEST RESULT : %@",string);

return 0;
}
}
Method Swizzling 的封装

[cpp] view plaincopyprint?
//
// RNSwizzle.m
// MethodSwizzle

#import "RNSwizzle.h"
#import <objc/runtime.h>
@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector
withIMP:(IMP)newIMP {
Class class = [self class];
Method origMethod = class_getInstanceMethod(class,
origSelector);
IMP origIMP = method_getImplementation(origMethod);

if(!class_addMethod(self, origSelector, newIMP,
method_getTypeEncoding(origMethod)))
{
method_setImplementation(origMethod, newIMP);
}

return origIMP;
}
@end
定义完毕新方法后,需要弄清楚什么时候实现与系统的方法交互?
 答 : 既然是给系统的方法添加额外的功能,换句话说,我们以后在开发中都是使用自己定义的方法,取代系统的方法,所以,当程序一启动,就要求能使用自己定义的功能方法.说道这里:我们必须要弄明白一下两个方法 :
 +(void)initialize(当类第一次被调用的时候就会调用该方法,整个程序运行中只会调用一次)
 + (void)load(当程序启动的时候就会调用该方法,换句话说,只要程序一启动就会调用load方法,整个程序运行中只会调用一次)


+ (void)load {
/*
     self:UIImage
     谁的事情,谁开头 1.发送消息(对象:objc) 2.注册方法(方法编号:sel) 3.交互方法(方法:method) 4.获取方法(类:class)
     Method:方法名

     获取方法,方法保存到类
     Class:获取哪个类方法
     SEL:获取哪个方法
     imageName
*/
    // 获取imageName:方法的地址
    Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));

    // 获取wg_imageWithName:方法的地址
    Method wg_imageWithNameMethod = class_getClassMethod(self, @selector(wg_imageWithName:));

    // 交换方法地址,相当于交换实现方式
    method_exchangeImplementations(imageNameMethod, wg_imageWithNameMethod);

}


以上是关于运行时之方法交换的主要内容,如果未能解决你的问题,请参考以下文章

[ObjectC]Runtime运行时之三:方法与消息

Objective-C Runtime 运行时之三:方法与消息(转载)

Flink运行时之客户端提交作业图-下

Objective-C Runtime 运行时之四:Method Swizzling

Objective-C Runtime 运行时之四:Method Swizzling(转载)

Flink运行时之基于Netty的网络通信(下)