Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Objective-C运行时特性:Method Swizzling魔法

$
0
0

OC运行时特性,为我们提供了一个叫做Method Swizzling的方法魔法利器,利用它我们可以更加随心所欲的在运行时期间对编译器已经的方法再次动手脚,主要包括:交换类中某两个方法的实现、重新添加或替换某个方法的具体实现。

运行时的几种特殊类型

  • Class: 类名,通过类的class类方法获得,例如:[UIViewController class];
  • SEL:选择器,也就是方法名,通过@selector(方法名:)获得,例如:@selector(buttonClicked:);
  • Method:方法,即运行时类中定义的方法,包括方法名(SEL)和方法实现(IMP)两部分,通过运行时方法class_getInstanceMethod或class_getClassMethod获得;
  • IMP:方法实现类型,指的是方法的实现部分,通过运行时方法class_getMethodImplementation或method_getImplementation获得;

替换类中某两个类方法或实例方法的实现

关键运行时函数:method_exchangeImplementations(method1, method2)

这里随便定义一个Test类,类中定义两个实例方法和类方法并在.m文件中实现,在运行时将两个实例方法的实现对调,以及将两个类方法的实现对调。注意运行时代码写在类的load方法内,该方法只会在该类第一次加载时调用一次,且写运行时代码的地方需要引入运行时头文件#import <objc/runtime.h>

Test类定义:

#import <Foundation/Foundation.h>

@interface Test : NSObject

/**
 * 定义两个公有实例方法
 */
- (void)instanceMethod1;
- (void)instanceMethod2;

/**
 * 定义两个公有类方法
 */
+ (void)classMethod1;
+ (void)classMethod2;

@end
#import "Test.h"
#import <objc/runtime.h>

@implementation Test

/**
 * runtime代码写在类第一次调加载的时候(load方法有且只有依次会被调用)
 */
+ (void)load {
    // 1. 或许当前类名
    Class class = [self class];

    // 2. 获取方法名(选择器)
    SEL selInsMethod1 = @selector(instanceMethod1);
    SEL selInsMethod2 = @selector(instanceMethod2);

    SEL selClassMethod1 = @selector(classMethod1);
    SEL selClassMethod2 = @selector(classMethod2);

    // 3. 根据方法名获取方法对象
    Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
    Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);

    Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
    Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);

    // 4. 交换实例方法的实现和类方法的实现
    if (!InsMethod1 || !InsMethod2) {
        NSLog(@"实例方法实现运行时交换失败!");
        return;
    }
    // 交换实例方法的实现
    method_exchangeImplementations(InsMethod1, InsMethod2);
    if (!ClassMethod1 || !ClassMethod2) {
        NSLog(@"类方法实现运行时交换失败!");
        return;
    }
    // 交换类方法的实现
    method_exchangeImplementations(ClassMethod1, ClassMethod2);
}

/**
 * 实例方法的原实现
 */
- (void)instanceMethod1 {
    NSLog(@"instanceMethod1...");
}
- (void)instanceMethod2 {
    NSLog(@"instanceMethod2...");
}

/**
 * 类方法的原实现
 */
+ (void)classMethod1 {
    NSLog(@"classMethod1...");
}
+ (void)classMethod2 {
    NSLog(@"classMethod2...");
}

@end

测试代码:

#import "ViewController.h"
#import "Test.h"

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 测试类方法调用
    [Test classMethod1];
    [Test classMethod2];

    Test *test = [[Test alloc] init];
    // 测试实例方法调用
    [test instanceMethod1];
    [test instanceMethod2];
}

@end

通过下面的输出结果可知,两个实例方法和类方法的实现都被互换了:

2017-03-06 17:47:13.684 SingleView[41495:1196960] classMethod2...
2017-03-06 17:47:13.684 SingleView[41495:1196960] classMethod1...
2017-03-06 17:47:13.685 SingleView[41495:1196960] instanceMethod2...
2017-03-06 17:47:13.685 SingleView[41495:1196960] instanceMethod1...

重新设置类中某个方法的实现

关键运行时函数:method_setImplementation(method, IMP)

理解了上面的例子,我们现在略微修改其中运行时代码,通过重新设置方法的实现实现上面同样的效果

修改后的运行时代码为:

/**
 * runtime代码写在类第一次调加载的时候(load方法有且只有依次会被调用)
 */
+ (void)load {
    // 1. 或许当前类名
    Class class = [self class];

    // 2. 获取方法名(选择器)
    SEL selInsMethod1 = @selector(instanceMethod1);
    SEL selInsMethod2 = @selector(instanceMethod2);

    SEL selClassMethod1 = @selector(classMethod1);
    SEL selClassMethod2 = @selector(classMethod2);

    // 3. 根据方法名获取方法对象
    Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
    Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);

    Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
    Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);

    // 下面代码为修改部分... ...
    // 4. 获取方法的实现
    IMP impInsMethod1 = method_getImplementation(InsMethod1);
    IMP impInsMethod2 = method_getImplementation(InsMethod2);

    IMP impClassMethod1 = method_getImplementation(ClassMethod1);
    IMP impClassMethod2 = method_getImplementation(ClassMethod2);

    // 5. 重新设置方法的实现
    // 重新设置instanceMethod1的实现为instanceMethod2的实现
    method_setImplementation(InsMethod1, impInsMethod2);
    // 重新设置instanceMethod2的实现为instanceMethod1的实现
    method_setImplementation(InsMethod2, impInsMethod1);

    // 重新设置classMethod1的实现为classMethod2的实现
    method_setImplementation(ClassMethod1, impClassMethod2);
    // 重新设置classMethod2的实现为classMethod1的实现
    method_setImplementation(ClassMethod2, impClassMethod1);
}

运行后打印结果和上面方法实现交换的例子结果相同:

2017-03-06 18:27:53.032 SingleView[41879:1212691] classMethod2...
2017-03-06 18:27:53.032 SingleView[41879:1212691] classMethod1...
2017-03-06 18:27:53.033 SingleView[41879:1212691] instanceMethod2...
2017-03-06 18:27:53.033 SingleView[41879:1212691] instanceMethod1...

替换类中某个方法的实现

关键运行时函数:class_replaceMethod

这种方法只能替换实例方法的实现,而不能替换类方法的实现,修改的代码如下:

/**
 * runtime代码写在类第一次调加载的时候(load方法有且只有依次会被调用)
 */
+ (void)load {
    // 1. 或许当前类名
    Class class = [self class];

    // 2. 获取方法名(选择器)
    SEL selInsMethod1 = @selector(instanceMethod1);
    SEL selInsMethod2 = @selector(instanceMethod2);

    SEL selClassMethod1 = @selector(classMethod1);
    SEL selClassMethod2 = @selector(classMethod2);

    // 3. 根据方法名获取方法对象
    Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
    Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);

    Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
    Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);

    // 4. 获取方法的实现
    IMP impInsMethod1 = method_getImplementation(InsMethod1);
    IMP impInsMethod2 = method_getImplementation(InsMethod2);

    IMP impClassMethod1 = method_getImplementation(ClassMethod1);
    IMP impClassMethod2 = method_getImplementation(ClassMethod2);

    // 下面代码为修改部分... ...
    // 5. 获取方法编码类型
    const char* typeInsMethod1 = method_getTypeEncoding(InsMethod1);
    const char* typeInsMethod2 = method_getTypeEncoding(InsMethod2);

    const char* typeClassMethod1 = method_getTypeEncoding(ClassMethod1);
    const char* typeClassMethod2 = method_getTypeEncoding(ClassMethod2);

    // 替换InsMethod1的实现为InsMethod2的实现
    class_replaceMethod(class, selInsMethod1, impInsMethod2, typeInsMethod2);
    // 替换InsMethod2的实现为InsMethod1的实现
    class_replaceMethod(class, selInsMethod2, impInsMethod1, typeInsMethod1);

    class_replaceMethod(class, selClassMethod1, impClassMethod2, typeClassMethod2);
    class_replaceMethod(class, selClassMethod2, impClassMethod1, typeClassMethod1);
}

通过结果可见实例方法的实现成功被替换,而类方法的实现没有被替换:

2017-03-06 18:47:03.598 SingleView[42106:1221468] classMethod1...
2017-03-06 18:47:03.599 SingleView[42106:1221468] classMethod2...
2017-03-06 18:47:03.600 SingleView[42106:1221468] instanceMethod2...
2017-03-06 18:47:03.600 SingleView[42106:1221468] instanceMethod1...

以上介绍的是同一个类中方法实现的再改动,实际上也可以修改或交换不同类之间方法的实现。

在运行时为类补加新的方法

关键运行时函数:class_addMethod()

除了在编译期显式的定义方法,还可以在运行时补加新的实例方法,但不可以添加新的类方法,这里接上面的例子为Test在运行时添加一个新的名为newInsMethod的方法,方法的实现设置为InsMethod1的实现,修改后的运行时代码为:

/**
 * runtime代码写在类第一次调加载的时候(load方法有且只有依次会被调用)
 */
+ (void)load {
    // 1. 或许当前类名
    Class class = [self class];

    // 2. 获取方法名(选择器)
    SEL selInsMethod1 = @selector(instanceMethod1);
    SEL selInsMethod2 = @selector(instanceMethod2);

    SEL selClassMethod1 = @selector(classMethod1);
    SEL selClassMethod2 = @selector(classMethod2);

    // 3. 根据方法名获取方法对象
    Method InsMethod1 = class_getInstanceMethod(class, selInsMethod1);
    Method InsMethod2 = class_getInstanceMethod(class, selInsMethod2);

    Method ClassMethod1 = class_getClassMethod(class, selClassMethod1);
    Method ClassMethod2 = class_getClassMethod(class, selClassMethod2);

    // 4. 获取方法的实现
    IMP impInsMethod1 = method_getImplementation(InsMethod1);
    IMP impInsMethod2 = method_getImplementation(InsMethod2);

    IMP impClassMethod1 = method_getImplementation(ClassMethod1);
    IMP impClassMethod2 = method_getImplementation(ClassMethod2);

    // 5. 获取方法编码类型
    const char* typeInsMethod1 = method_getTypeEncoding(InsMethod1);
    const char* typeInsMethod2 = method_getTypeEncoding(InsMethod2);

    const char* typeClassMethod1 = method_getTypeEncoding(ClassMethod1);
    const char* typeClassMethod2 = method_getTypeEncoding(ClassMethod2);

    // 下面代码为修改部分... ...
    // 6. 为类添加新的实例方法和类方法
    SEL selNewInsMethod = @selector(newInsMethod);
    BOOL isInsAdded = class_addMethod(class, selNewInsMethod, impInsMethod1, typeInsMethod1);
    if (!isInsAdded) {
        NSLog(@"新实例方法添加失败!");
    }
}

测试新函数代码:

// 测试运行时新添加实例方法调用
    Test *test = [[Test alloc] init];
    [test newInsMethod];

运行结果打印出“instanceMethod1…”证明新实例方法动态添加成功:

2017-03-06 19:07:15.447 SingleView[42354:1230571] instanceMethod1...

除了在运行时为类添加新的方法,还可以通过其他运行时函数class_addIvar、class_addProperty、class_addProtocol等动态地为类添加新的变量、属性和协议等等。

作者:cordova 发表于2017/3/6 19:22:42 原文链接
阅读:59 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>