Método Objective-C 的Swizzling

El uso de la confusión de métodos en Objective-Cchino es relativamente común, para entender su esencia, primero debemos entender dos conceptos.

1. Tiempo de ejecución

Objective-CEs un lenguaje dinámico con características de tiempo de ejecución muy flexibles. runtimeEs una suite escrita en lenguaje C, y C++ensamblador que proporciona características orientadas a objetos y de tiempo de ejecución.APIObjective-C

La esencia del método.

Objective-C, el método se compone de SELy IMP, el primero se denomina número de método, el último se denomina implementación del método.
OCEl método de llamada se denomina envío de un mensaje. Antes de enviar un mensaje, se buscará el mensaje. El proceso de búsqueda es a través del proceso de SELbúsqueda IMP. Además, a menudo usamos @selector(someMethod)una sintaxis de este tipo en el código, @selector()llamada selector de método, que devuelve el SELvalor real . ejecución Esta SELbúsqueda corresponde a IMP.

3. MétodoSwizzling

3.1 Principio

La ofuscación de métodos se logra explotando runtimelas propiedades y la naturaleza compositiva de los métodos. Por ejemplo, hay métodos sel1correspondientes imp1, sel2correspondientes imp2y después de la ofuscación del método, de modo que el resultado de la búsqueda runtimecuando se busca el método sel1se convierte en imp2:

3.2 Combate real

Cree un nuevo iOSproyecto, cree una nueva clase Persony Personagregue dos métodos -walky -run:

// Person.h
@interface Person : NSObject

- (void)walk;

- (void)run;

@end

// Person.m
@implementation Person

- (void)walk {
    NSLog(@"walk");
}

- (void)run {
    NSLog(@"run");
}

@end
复制代码

Cree una nueva NSObjectcategoría MethodSwizzlingde la siguiente manera:

// NSObject+MethodSwizzling.h
@interface NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL;

@end

// NSObject+MethodSwizzling.m
#import <objc/runtime.h>

@implementation NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL {
    Method orgMethod = class_getInstanceMethod(cls, orgSEL);
    Method tgtMethod = class_getInstanceMethod(cls, targetSEL);
    method_exchangeImplementations(orgMethod, tgtMethod);
}

@end
复制代码

Person.mAnule el +loadmétodo en :

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodswizzlingWithClass:self
                                orgSEL:@selector(walk)
                             targetSEL:@selector(run)];
    });
}
复制代码

ViewController-viewDidLoad中调用-walk

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    [person walk];
}
复制代码

运行程序,控制台输出如下:

2020-02-09 21:26:07.613230+0800 TestObjC[1716:74628] run
复制代码

调用的是-walk,实际执行的是-run,完成了方法混淆。

3.3 方法混淆的坑点

如果只声明了-run,但是没有实现,运行后会执行-walk方法,也就是不会交换成功,但如果没实现-walk,实现了-run,程序并不会执行-run,而是直接会崩掉,因为我们是显示的调用了-walk方法。因此在做方法混淆时,需要考虑方法是否实现的场景。
如果有人把子类的方法和父类的方法进行交换,那么父类在调用该方法时就会出现问题,所以还得考虑交换的是否是本类的方法。
基于以上的坑点,为了程序的健壮性,需要把方法混淆的函数修改一下:

@implementation NSObject (MethodSwizzling)

+ (void)methodswizzlingWithClass:(Class)cls orgSEL:(SEL)orgSEL targetSEL:(SEL)targetSEL {
    if (nil == cls) {
        NSLog(@"methodswizzlingWithClass: nil class");
        return;
    }
    
    Method orgMethod = class_getInstanceMethod(cls, orgSEL);
    Method tgtMethod = class_getInstanceMethod(cls, targetSEL);
    
    // 1.当原方法未实现,添加方法,并设置IMP为空实现
    if (nil == orgMethod) {
        class_addMethod(cls, orgSEL, method_getImplementation(tgtMethod), method_getTypeEncoding(tgtMethod));
        method_setImplementation(tgtMethod,
                                 imp_implementationWithBlock(^(id self, SEL _cmd) {
            NSLog(@"methodswizzlingWithClass: %@, orgMethod is nil", cls);
        }));
        return;
    }
    
    // 2.在当前类添加原方法,添加失败,说明当前类实现了该方法
    BOOL didAddMethod = class_addMethod(cls,
                                        orgSEL,
                                        method_getImplementation(orgMethod),
                                        method_getTypeEncoding(orgMethod));
    if (didAddMethod) {
        // 添加成功,当前类未实现,父类实现了orgSEL
        // 此时不必做方法交换,直接将tgtMethod的IMP替换为父类orgMethod的IMP即可
        class_replaceMethod(cls,
                            targetSEL,
                            method_getImplementation(orgMethod),
                            method_getTypeEncoding(orgMethod));
    } else {
        // 正常情况,直接交换
        method_exchangeImplementations(orgMethod, tgtMethod);
    }
}

@end
复制代码

验证一下效果,(Person-walk-run都实现),并添加一个-jump方法:

// Person.m
- (void)jump {
    NSLog(@"jump");
}
复制代码

新建一个类Student继承自Person,并添加一个方法-study

// Student.h
@interface Student : Person

- (void)study;

@end

// Student.m
@implementation Student

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodswizzlingWithClass:self
                                orgSEL:@selector(jump)
                             targetSEL:@selector(study)];
    });
}

- (void)study {
    NSLog(@"study");
}

@end
复制代码

然后viewDidLoad中修改如下:

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    [person walk];
    Student *std = [[Student alloc] init];
    [std study];
    [person jump];
}
复制代码

运行以后,输出结果如下:

2020-02-09 22:38:55.180903+0800 TestObjC[2551:122647] run
2020-02-09 22:38:55.181070+0800 TestObjC[2551:122647] jump
2020-02-09 22:38:55.181185+0800 TestObjC[2551:122647] jump
复制代码

Student中,并没有实现-jump,但它把-jump-study做了交换(实际实现是在Student中添加了-jump,然后将其IMP置为Person-jumpIMP),这样以来,Student调用-study就执行了-jumpIMP,效果上相当于做了交换,而父类Person调用-jump并没有受到影响。
把父类Person-jump的实现注释掉,同时注释掉viewDidLoad中第8行[person jump],运行程序,输出如下:

2020-02-09 22:49:39.611851+0800 TestObjC[2667:129133] run
2020-02-09 22:49:39.612015+0800 TestObjC[2667:129133] methodswizzlingWithClass: Student, orgMethod is nil
复制代码

Supongo que te gusta

Origin juejin.im/post/7084160200176828446
Recomendado
Clasificación