Swift5 Method Swizzling 方法交换

最近看了一下关于Swift5的特性,发现了一个叫 @_dynamicReplacement(for:) 的新特性比较有趣,所以研究了一下。
新建一个Swift 5.0的工程,然后输入以下代码,这里有一个key word叫dynamic,这个关键字在Swift3中就出现了,代表动态派发,只有加了dynamic修饰的方法才能被@_dynamicReplacement(for:) :

import Foundation

class Test {
    dynamic func testA() {
        print("testA")
    }
}

extension Test {
    @_dynamicReplacement(for: testA())
    func testB() {
        print("testB")
    }
}

Test().testA()

运行一下发现控制台输出了:

testB
Program ended with exit code: 0

与OC的Method Swizzling做一个比较:
先写一个OC的类:

//Test.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Test : NSObject
- (void)testA;
- (void)testB;
- (void)testC;
@end

NS_ASSUME_NONNULL_END

//Test.m
#import "Test.h"
#import <objc/runtime.h>

@implementation Test

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL testASelector = @selector(testA);
        SEL testBSelector = @selector(testB);
        Method testAMethod = class_getInstanceMethod(class, testASelector);
        Method testBMethod = class_getInstanceMethod(class, testBSelector);
        method_exchangeImplementations(testAMethod, testBMethod);

        SEL testCSelector = @selector(testC);
        Method testCMethod = class_getInstanceMethod(class, testCSelector);
        method_exchangeImplementations(testAMethod, testCMethod);
    });
}

- (void)testA {
    NSLog(@"A");
}

- (void)testB {
    NSLog(@"B");
    [self testB];
}

- (void)testC {
    NSLog(@"C");
    [self testC];
}
@end

//main.m
#import <Foundation/Foundation.h>
#import "Test.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test * t = [[Test alloc] init];
        [t testA];
    }
    return 0;
}

上面的程序很明显,运行结果是:

2019-09-26 23:12:39.513796+0800 OC[87404:218117] C
2019-09-26 23:12:39.514014+0800 OC[87404:218117] B
2019-09-26 23:12:39.514036+0800 OC[87404:218117] A
Program ended with exit code: 0

OC中方法交换是一个一个来的,所以结果会输出 CBA,因为 testA 方法首先被替换成了 testB ,其替换后的结果又被替换成了 testC 。进而由于 Swizze 的方法都调用了原方法,所以会输出 CBA。

Method Swizzling 在 Runtime 中的原理就是方法指针的交换。
由于 OC 对于实例方法的存储方式是以方法实例表,那么我们只要能够访问到其指定的方法实例,修改 imp 指针对应的指向,再对引用计数和内存开辟等于 Class 相关的信息做一次更新就实现了 Method Swizzling。

对于这个例子,我们用Swift的@_dynamicReplacement(for:) 写一遍:

import Foundation

class Test {
    dynamic func testA() {
        print("A")
    }
}

extension Test {
    @_dynamicReplacement(for: testA())
    func testB() {
        print("B")
        testA()
    }
}

extension Test {
    @_dynamicReplacement(for: testA())
    func testC() {
        print("C")
        testA()
    }
}

Test().testA()

跑一下代码,发现运行结果却是:

C
A
Program ended with exit code: 0

交换一下两个 extension 的顺序继续尝试,其打印结果又变成了 BA 。

所以可以断定在执行顺序上,后声明的将会生效。

那么应该如何实现这种连环 Hook 的场景呢?

经过多次的尝试,从代码层面应该是不可能办到了,总会以最后一次的方法为准。

因此在Build Settings 中 的Other Swift Flags 里增加 -Xfrontend -enable-dynamic-replacement-chaining :
在这里插入图片描述
重新运行一遍,奇迹发生了:
在这里插入图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页