El uso de la confusión de métodos en Objective-C
chino es relativamente común, para entender su esencia, primero debemos entender dos conceptos.
1. Tiempo de ejecución
Objective-C
Es un lenguaje dinámico con características de tiempo de ejecución muy flexibles. runtime
Es una suite escrita en lenguaje C
, y C++
ensamblador que proporciona características orientadas a objetos y de tiempo de ejecución.API
Objective-C
La esencia del método.
Objective-C
, el método se compone de SEL
y IMP
, el primero se denomina número de método, el último se denomina implementación del método.
OC
El 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 SEL
bú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 SEL
valor real . ejecución Esta SEL
búsqueda corresponde a IMP
.
3. MétodoSwizzling
3.1 Principio
La ofuscación de métodos se logra explotando runtime
las propiedades y la naturaleza compositiva de los métodos. Por ejemplo, hay métodos sel1
correspondientes imp1
, sel2
correspondientes imp2
y después de la ofuscación del método, de modo que el resultado de la búsqueda runtime
cuando se busca el método sel1
se convierte en imp2
:

3.2 Combate real
Cree un nuevo iOS
proyecto, cree una nueva clase Person
y Person
agregue dos métodos -walk
y -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 NSObject
categoría MethodSwizzling
de 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.m
Anule el +load
mé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
中-jump
的IMP
),这样以来,Student
调用-study
就执行了-jump
的IMP
,效果上相当于做了交换
,而父类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
复制代码