The original link

I don’t know when swizzling will be interpreted as AOP development, developers are talking about runtime, call it black magic; Proud of all the method_swizzling in the project, not realizing that it breaks the integrity of the code and splinters key logic. This article is based on the cancer in the iOS world, from another perspective to talk about why we should be vigilant

Call sequentiality

Call sequentiality is at the heart of the linking article, and it can disrupt the original execution order of methods, leading to unexpected errors. Let’s start with a simple piece of code:

@interface SLTestObject: NSObject @end @implementation SLTestObject - (instancetype)init { self = [super init]; return self; } @end void testIsSelectorSame() { Method allocate1 = class_getClassMethod([NSObject class], @selector(alloc)); Method allocate2 = class_getClassMethod([SLTestObject class], @selector(alloc)); Method initialize1 = class_getInstanceMethod([NSObject class], @selector(init)); Method initialize2 = class_getInstanceMethod([SLTestObject class], @selector(init)); assert(allocate1 == allocate2 && initialize1 ! = initialize2); }Copy the code

The purpose of this code is to prove a conclusion:

If the subclass does not override the method declared by the parent class, the code implemented by the parent class is executed when the subclass object calls the method

Based on this theory, assume a scenario: now count the number of times the user enters and leaves the Controller using the no-buried scheme:

@implementation UIViewController (SLCount)

+ (void)load {
    sl_swizzle([self class], @selector(viewWillAppear:), @selector(sl_viewWillAppearI:));
    sl_swizzle([self class], @selector(viewDidDisappear:), @selector(sl_viewDidDisappearI:));
}

- (void)sl_viewWillAppearI: (BOOL)animated {
    [SLControllerCounter countControllerEnter: [self class]];
    [self sl_viewWillAppearI: animated];
}

- (void)sl_viewDidDisappearI: (BOOL)animated {
    [SLControllerCounter countControllerLeave: [self class]];
    [self sl_viewDidDisappearI: animated];
}

@end
Copy the code

Since UIViewController is the parent of all controllers, theoretically swizzle should be able to count all controllers. There is also a custom base controller in the project and the SLBaseViewController has this code:

@implementation SLBaseViewController (SLCount)

+ (void)load {
    sl_swizzle([self class], @selector(viewWillAppear:), @selector(sl_viewWillAppearII:));
    sl_swizzle([self class], @selector(viewDidDisappear:), @selector(sl_viewDidDisappearII:));
}

- (void)sl_viewWillAppearII: (BOOL)animated {
    [self prepareRequest];
    [self sl_viewWillAppearII: animated];
}

- (void)sl_viewDidDisappearII: (BOOL)animated {
    [self sl_viewDidDisappearII: animated];
    [self cancelAllRequests];
}

@end
Copy the code

However, the two codes crash in specific scenarios. The reason for the exception is that the subclass performs swizzle operations before the parent class without rewriting methods. The iOS usage Method name SEL and Method implementation IMP are stored separately, using the structure Method to associate the two together:

typedef struct Method {
    SEL name;
    IMP imp;
} Method;
Copy the code

Swap methods swap imPs between the two methods. Ideally, the parent class completes swizzle before the child class, and the original method saves the IMP after Swizzle. In this case, the child class will call swizzle correctly. The following figure shows the association between SEL and IMP, with the arrow indicating the order of IMP calls:

But if the subclass swizzle occurs earlier, then the IMP corresponding to viewWillAppear has been modified, and the parent class swizzle is called in the wrong order:

The solutions are not complicated, including:

  1. inswizzleBefore youaddMethodTo ensure that subclasses do not follow the default implementation of their parent class
  2. Each call passesselTo obtainimpperform

Specific implementation code can refer to the iOS world cancer solution

Behavior conflict

In OOP design, description objects are abstracted into classes and object behavior is abstracted into interfaces. From an engineer’s perspective, an interface with a single responsibility facilitates iterative maintenance. Once a class is designed, it should have little or no interface changes. For well-designed interfaces, Swizzle may directly break the behavior of the entire interface:

For example, Crash protection is a popular tool at the moment, but KVO protection may be a bad one. In terms of implementation, in order to avoid cyclic reference caused by KVO, it is necessary to insert a weakProxy in the middle of the reference relationship for protection, so the listening code can actually be converted into:

/ / surface code [observedObj addObserver: self forKeyPath: keyPath options: NSKeyValueObservingOptionNew context: nil]; WeakProxy *proxy = [WeakProxy new]; proxy.client = self; [observedObj addObserver: proxy forKeyPath: keyPath options: NSKeyValueObservingOptionNew context: nil];Copy the code

Why is this design bad? Once the client appears with code like this:

- (void)dealloc {
    ......
    [observedObj removeObserver: self forKeyPath: keyPath];
}
Copy the code

In general, most current implementations of protection tools crash. To users outside the Swizzle code, it may not be at all clear that the Observer has already been moved, causing the original correct invocation to fail. One solution is to swizzle the remove interface as well, so that the listeners of the two calls match:

- (void)sl_removeObserver: (id)observer forKeyPath: (NSString *)keyPath {
    [self sl_removeObserver: observer.proxy forKeyPath: keyPath];
}
Copy the code

However, after doing so, first of all, KVO’s behavior has been modified, and breaking the interface may lead to potential hazards. Secondly, if there are multiple protection tools and weakProxy implementation is followed, the KVO function will become invalid once there are two or more protection tools:

OneWeakProxy *proxy = [OneWeakProxy new];
proxy.client = self;    
[observedObj addObserver: proxy forKeyPath: keyPath options: NSKeyValueObservingOptionNew context: nil];

TwoWeakProxy *proxy = [TwoWeakProxy new];
proxy.client = self;    /// self is OneWeakProxy
[observedObj addObserver: proxy forKeyPath: keyPath options: NSKeyValueObservingOptionNew context: nil];
Copy the code

After a second WeakProxy generation and method invocation, the object created by OneWeakProxy is released. Additional work needs to be done if multiple protection tools are not to interfere with the process. And if one of them is not perfectly implemented, the whole system may simply break down, so KVO protection is not necessarily a good solution

Code integrity

In the above example, KVO is a capability provided by the base class NSObject. Since subclasses implement this principle by default, the swizzle of this method affects virtually all objects. For example, the following code has exactly the same effect:

/// swizzle 1
void swizzleTableView() {
    Method ori = class_getClassMethod([UITableView class], @selector(addObserver:forKeyPath:options:context:));
    Method cus = class_getClassMethod([UITableView class], @selector(sl_addObserver:forKeyPath:options:context:));
    method_exchange(ori, cus);
}

/// swizzle 2
void swizzleObj() {
    Method ori = class_getClassMethod([NSObject class], @selector(addObserver:forKeyPath:options:context:));
    Method cus = class_getClassMethod([NSObject class], @selector(sl_addObserver:forKeyPath:options:context:));
    method_exchange(ori, cus);
}
Copy the code

The default implementation of the first method is NSObject, so once swizzle occurs all objects will be in effect. There are two problems with this:

  1. nonUITableViewThe object is still receivedKVOInterception impact of
  2. There is nosl_addObserver:forKeyPath:options:context:Object will crash

On the other hand, the interface design of a class is always in favor of dress-up mode thinking, with class objects at different levels doing their own work when their methods are called. This design makes inheritance flexible enough, as the implementation code for viewDidLoad shows:

- (void)viewDidLoad {
    [super viewDidLoad];
    /// setup work
}
Copy the code

In other words, code built with this dress-up mindset, if one of the methods in the middle is affected or even broken, the class in the middle starts to collapse down, and you can imagine if UIView goes wrong, the application almost loses its ability to display controls. But if you do need an intermediary of Swizzle, you must ensure that swizzle has little or no impact on subclass objects