Objective-CのMethodSwizzling

中国語でのメソッドの混乱の使用Objective-Cは比較的一般的です。その本質を理解するには、最初に2つの概念を理解する必要があります。

1.ランタイム

Objective-Cこれは、非常に柔軟なランタイム機能を備えた動的言語です。これruntimeC、、、C++およびアセンブリ言語で記述されたスイートであり、オブジェクト指向およびランタイム機能を提供します。APIObjective-C

メソッドの本質

Objective-C、メソッドはとで構成されSELIMP前者はメソッド番号と呼ばれ、後者はメソッド実装と呼ばれます。
OC呼び出し元のメソッドはメッセージの送信と呼ばれます。メッセージを送信する前に、メッセージが検索されます。検索プロセスは検索プロセスを介しSELて行われIMPます。さらに@selector(someMethod)、コードでこのような構文を使用することがよくあります。これは、実際のメッセージ@selector()を返すメソッドセレクターと呼ばれます。SELこのSELルックアップはに対応しIMPます。

3. MethodSwizzling

3.1原則

メソッドの難読化は、メソッドruntimeの特性と構成上の性質を利用することによって実現されます。たとえば、対応するメソッドsel1対応するメソッド、およびメソッドの難読化後のメソッドがあるため、メソッドを検索したときの検索結果は次のようになります。imp1sel2imp2runtimesel1imp2

3.2実際の戦闘

新しいiOSプロジェクトを作成し、新しいクラスを作成し、2つのメソッドを追加PersonPerson-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-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
复制代码

おすすめ

転載: juejin.im/post/7084160200176828446