中国語でのメソッドの混乱の使用Objective-C
は比較的一般的です。その本質を理解するには、最初に2つの概念を理解する必要があります。
1.ランタイム
Objective-C
これは、非常に柔軟なランタイム機能を備えた動的言語です。これruntime
はC
、、、C++
およびアセンブリ言語で記述されたスイートであり、オブジェクト指向およびランタイム機能を提供します。API
Objective-C
メソッドの本質
Objective-C
、メソッドはとで構成されSEL
、IMP
前者はメソッド番号と呼ばれ、後者はメソッド実装と呼ばれます。
OC
呼び出し元のメソッドはメッセージの送信と呼ばれます。メッセージを送信する前に、メッセージが検索されます。検索プロセスは検索プロセスを介しSEL
て行われIMP
ます。さらに@selector(someMethod)
、コードでこのような構文を使用することがよくあります。これは、実際のメッセージ@selector()
を返すメソッドセレクターと呼ばれます。SEL
このSEL
ルックアップはに対応しIMP
ます。
3. MethodSwizzling
3.1原則
メソッドの難読化は、メソッドruntime
の特性と構成上の性質を利用することによって実現されます。たとえば、対応するメソッド、sel1
対応するメソッド、およびメソッドの難読化後のメソッドがあるため、メソッドを検索したときの検索結果は次のようになります。imp1
sel2
imp2
runtime
sel1
imp2

3.2実際の戦闘
新しいiOS
プロジェクトを作成し、新しいクラスを作成し、2つのメソッドを追加Person
して:Person
-walk
-run
// Person.h
@interface Person : NSObject
- (void)walk;
- (void)run;
@end
// Person.m
@implementation Person
- (void)walk {
NSLog(@"walk");
}
- (void)run {
NSLog(@"run");
}
@end
复制代码
次のように新しいNSObject
カテゴリを作成します。MethodSwizzling
// 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
オーバーライドします:+load
+ (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
复制代码