Traps in Method Swizzling

Keywords: iOS

Author: Dai Pei
Address: http://blog.csdn.net/dp948080952/article/details/54882811
Reprinted please indicate the source

This article is not about method exchange. There are many such articles. If you don't know what method exchange is, you can read this article. Method Swizzling

trap

Method swapping is a dangerous thing, which can cause some unknown errors. Recently, when I used method swapping, I encountered such a problem. After I exchanged a system method, when I called this method, the loop could not jump out of the method I exchanged.

In the end, I found the key to the problem, which is that this method may not exist!

Every iOS version update brings up new API s that did not exist in previous versions, and if you swap a method without considering its existence, it can lead to serious errors.

For example, I once exchanged a method:

- (void)openURL:(NSURL*)url options:(NSDictionary<NSString *, id> *)options completionHandler:(void (^ __nullable)(BOOL success))completion

This method only exists after iOS 10. Others will judge whether this method will be responded to when they use it. But we will see that the exchange code below will add this method first. If this method does not exist, then the original response will become a response. Then the system before iOS 10 will enter this method, resulting in a dead cycle.

    Class class = [self class];

    Method method1 = class_getInstanceMethod(class, sel1);
    Method method2 = class_getInstanceMethod(class, sel2);

    BOOL didAddMethod =
    class_addMethod(class,
                    sel1,
                    method_getImplementation(method2),
                    method_getTypeEncoding(method2));

    if (didAddMethod) {
        class_replaceMethod(class,
                            sel2,
                            method_getImplementation(method1),
                            method_getTypeEncoding(method1));
    } else {
        method_exchangeImplementations(method1, method2);
    }

Solution

It's also easy to solve this problem by making a judgment before exchanging:

    if (![class instancesRespondToSelector:sel1] || ![class instancesRespondToSelector:sel2]) {
        return ;
    }

If you do not respond to this method, return directly.

encapsulation

In order not to repeat the code exchanged by methods and reduce errors, we can encapsulate it and place it in the Category of NSObject.

NSObject+DPExtension.h

#import <Foundation/Foundation.h>

@interface NSObject (DPExtension)

+ (void)intanceMethodExchangeWithOriginSelector:(SEL)sel1 swizzledSelector:(SEL)sel2;

@end

NSObject+DPExtension.m

#import "NSObject+DPExtension.h"
#import <objc/runtime.h>

@implementation NSObject (DPExtension)

+ (void)intanceMethodExchangeWithOriginSelector:(SEL)sel1 swizzledSelector:(SEL)sel2 {

    Class class = [self class];

    if (![class instancesRespondToSelector:sel1] || ![class instancesRespondToSelector:sel2]) {
        return ;
    }

    Method method1 = class_getInstanceMethod(class, sel1);
    Method method2 = class_getInstanceMethod(class, sel2);

    BOOL didAddMethod =
    class_addMethod(class,
                    sel1,
                    method_getImplementation(method2),
                    method_getTypeEncoding(method2));

    if (didAddMethod) {
        class_replaceMethod(class,
                            sel2,
                            method_getImplementation(method1),
                            method_getTypeEncoding(method1));
    } else {
        method_exchangeImplementations(method1, method2);
    }
}

@end

That's all.^^

Posted by php_user13 on Fri, 22 Mar 2019 03:06:52 -0700