• This post was originally posted on my personal blog:”Unruly Pavilion”
  • Article links:portal
  • This article was updated at 13:21:26 on July 12, 2019

This article introduces the dark magic Method Swizzling in iOS development “Runtime”. Through this article, you will learn:

  1. Introduction to Method Swizzling
  2. Method Swizzling using Method (four schemes)
  3. Method Swizzling use caution
  4. Application scenario 4.1 Global page statistics 4.2 Font adaptation based on screen size 4.3 Handling button repeated clicking 4.4 TableView and CollectionView Abnormal loading placeholder 4.5 APM (Application performance management) and Preventing crashes

Bujige/YSC-Runtime-MethodSwizzling


In the basics of iOS development: Runtime, we explained how the iOS Runtime mechanism (Runtime system) works. Includes the principle and flow of message sending and forwarding mechanism.

Starting with this article, let’s take a look at how the Runtime is used in real development.

In this article we will take a look at one of the most controversial dark arts of the Runtime Runtime system: Method Swizzling


1. Introduction to Method Swizzling

Method Swizzling is used to change an existing selector implementation. We can change the method call at runtime by changing the mapping of the Method List of the selector Class. This is essentially an IMP (method implementation) that swaps the two methods.

In the last article we saw that Method corresponds to the objc_method structure; The objC_method structure contains SEL method_name (method name) and IMP method_IMP (method implementation).

// typedeft struct objc_method *Method; struct objc_method { SEL _Nonnull method_name; Char * _Nullable method_types; // Method type IMP _Nonnull method_IMP; // method implementation};Copy the code

Method (Method), SEL (Method name), IMP (Method implementation)

At runtime, the Class maintains a method list to determine the correct delivery of messages. The method list holds the element method. Method maps a pair of key-value pairs: SEL (Method name) : IMP (Method implementation).

Method Swizzling modifies the Method list so that key-value pairs in different methods are swapped. For example, the first two key pairs are SEL A: IMP A, SEL B: IMP B, and then become SEL A: IMP B, SEL B: IMP A. As shown in the figure:


2. Method Swizzling

Suppose there are two methods in the current class: – (void)originalFunction; And – (void) swizzledFunction; . If we want to swap the implementations of the two methods to implement the call – (void)originalFunction; The method actually calls – (void)swizzledFunction; – (void)swizzledFunction; The method actually calls – (void)originalFunction; The effect of the method. So we need to do the same as the following code.


2.1 Method Swizzling simple to use

+ (void)load in the current class; Method Swizzling – (void)originalFunction; And – (void) swizzledFunction; Method implementation.

#import "ViewController.h"
#import <objc/runtime.h>@interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self SwizzlingMethod]; [self originalFunction]; [self swizzledFunction]; Void SwizzlingMethod = [self Class]; void SwizzlingMethod = [self Class]; SEL originalSelector = @selector(originalFunction); SEL swizzledSelector = @selector(swizzledFunction); OriginalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); Method_exchangeImplementations (originalMethod, swizzledMethod); } // Original method - (void)originalFunction {NSLog(@)"originalFunction"); } // replace method - (void)swizzledFunction {NSLog(@)"swizzledFunction");
}

@end
Copy the code

Print result: [91009:30112833] swizzledFunction [91009:30112833 09:59:20. 414930 + 0800 Runtime – MethodSwizzling originalFunction [91009-30112833]

You can see that the two methods swapped successfully.


We just briefly demonstrated how to do Method Swizzling in the current class. However, in general daily development, Method Swizzling is not performed directly in the original class. It’s more about adding a category for the current class, and then doing Method Swizzling within the category. In addition, the real use will be more than the above to consider things, more complex.

In the daily use of Method Swizzling, there are several commonly used schemes. The details are as follows.

2.2 Method Swizzling scheme A

Add Method Swizzling to the classification of this class in the normal way

This approach is most commonly used in development. However, we still need to pay attention to some matters, which WILL be explained in detail in the following 3. Method Swizzling.

@implementation UIViewController (Swizzling)

// A method implementation that swaps the original method and the replacement method
+ (void)load {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        / / the current class
        Class class = [self class];
        
        // The old method name and the replacement method name
        SEL originalSelector = @selector(originalFunction);
        SEL swizzledSelector = @selector(swizzledFunction);
        
        // Old method structure and replacement method structure
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        /* If the current class does not have the IMP of the original method, the method implementation inherited from the parent class * needs to add an originalSelector method to the current class, * but implements it with the replacement method swizzledMethod */
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            // After the IMP of the original method is added successfully, modify the IMP of the replacement method to the original method
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            // Add failed (indicating that the IMP of the original method was included), call the implementation of the swap two methodsmethod_exchangeImplementations(originalMethod, swizzledMethod); }}); }// The original method
- (void)originalFunction {
    NSLog(@"originalFunction");
}

// Replace the method
- (void)swizzledFunction {
    NSLog(@"swizzledFunction");
}

@end
Copy the code

2.3 Method Swizzling Plan B

Add the Method Swizzling swap Method to the class classification, but using function Pointers.

The biggest difference between scheme B and Scheme A is the way that function Pointers are used. The biggest advantage of using function Pointers is that naming errors can be effectively avoided.

#import "UIViewController+PointerSwizzling.h"
#import <objc/runtime.h>

typedef IMP *IMPPointer;

// Swap method functions
static void MethodSwizzle(id self, SEL _cmd, id arg1);
// The original method function pointer
static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

// Swap method functions
static void MethodSwizzle(id self, SEL _cmd, id arg1) {
    
    // Add the code for the exchange method here
    NSLog(@"swizzledFunc");
    
    MethodOriginal(self, _cmd, arg1);
}

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return(imp ! =NULL);
}

@implementation UIViewController (PointerSwizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzle:@selector(originalFunc) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
    });
}

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}

// The original method
- (void)originalFunc {
    NSLog(@"originalFunc");
}

@end
Copy the code

2.4 Method Swizzling scheme C

Add the Method Swizzling swap Method to the other classes

This kind of situation usually use much, one of the most famous is the _AFURLSessionTaskSwizzling AFNetworking private class. _AFURLSessionTaskSwizzling mainly solved the iOS7 and iOS8 NSURLSession difference on the system. Keep the NSURLSession versions of different system versions basically the same.

static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}

@interface _AFURLSessionTaskSwizzling : NSObject

@end

@implementation _AFURLSessionTaskSwizzling

+ (void)load {
    if (NSClassFromString(@"NSURLSessionTask")) {
        
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class].@selector(af_resume)));
        Class currentClass = [localDataTask class];
        
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            Class superClass = [currentClass superclass];
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
            if(classResumeIMP ! = superclassResumeIMP && originalAFResumeIMP ! = classResumeIMP) { [selfswizzleResumeAndSuspendMethodForClass:currentClass]; } currentClass = [currentClass superclass]; } [localDataTask cancel]; [session finishTasksAndInvalidate]; }} + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self.@selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self.@selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend)); }} - (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    
    if(state ! =NSURLSessionTaskStateRunning) {[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; }} - (void)af_suspend {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_suspend];
    
    if(state ! =NSURLSessionTaskStateSuspended) {[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; }}Copy the code

2.5 Method Swizzling plan D

Excellent third-party frameworks: JRSwizzle and RSSwizzle

JRSwizzle and RSSwizzle are excellent third-party frameworks for encapsulating Method Swizzling.

  1. JRSwizzle attempts to resolve conflicts between Method Swizzling and class inheritance on different platforms and system versions. It has strong compatibility with earlier versions of various platforms. The JRSwizzle core is using the method_exchangeImplementations method. For robustness, the class_addMethod operation is done first.

  2. RSSwizzle mainly uses the class_replaceMethod method to avoid the substitution of subclasses affecting the parent class. Moreover, the process of exchange method is locked to enhance thread safety. What are the dangers of Method Swizzling in Objective-C? The problem mentioned in. Is a safer and more elegant Method Swizzling solution.


Conclusion:

In development, we usually use scheme A or the third-party framework RSSwizzle in Scheme D to implement Method Swizzling. In the following 3. Method Swizzling usage notes, we will also see a lot of matters needing attention. These tips aren’t meant to scare off beginners, but rather to get the most out of Method Swizzling. As for the program selection, no matter what kind of program I choose, I think only the most suitable program for the project is the best program.


3. Method Swizzling

Method Swizzling is called dark magic because swapping methods using Method Swizzling is dangerous. Someone on Stack Overflow pointed out the dangers and pitfalls of using Method Swizzling. He compared Method Swizzling to a sharp knife in the kitchen. Some people give up the knife for fear that it will be too sharp and hurt themselves, or use a dull knife. But the truth is: a sharp knife is safer than a dull one, if you have enough experience.

Method Swizzling can be used to write better, more efficient, and more maintainable code. But it can also be misused and go horribly wrong. Therefore, when using Method Swizzling, we still need to pay attention to some matters to avoid possible risks.

  • What are the dangers of Method Swizzling in Objective-C?

Here’s a summary of what to pay attention to when using Method Swizzling, in combination with other bloggers’ posts on Method Swizzling, the dangers and pitfalls mentioned on Stack Overflow, and my own personal insights.

  1. Should be only in the+loadExecute Method Swizzling in.

When the program starts, all classes are loaded first, and the +load method for each class is called. And it will only be called once during the entire program lifecycle (excluding external display calls). So Method Swizzling in the +load Method is the best way to do it.

Why not use the +initialize method instead?

The +initialize method is called only when the first message is sent to the class. If the class is only referenced and not called, the +initialize method is not executed. Method Swizzling affects the global state, and the +load Method ensures that the exchange occurs when the class is loaded, ensuring that the exchange results. Using the +initialize method does not guarantee this and may not function as an exchange method when used.

  1. Method Swizzling in+loadDo not call when executed in[super load];.

As we said, when the program starts, it loads all the classes first. If you call the [super Load] Method from the + (void)load Method, the superclass’s Method Swizzling will be executed twice, and the Method swap will be performed twice. This invalidates the Method Swizzling of the parent class.

  1. Method Swizzling should always be theredispatch_onceIn the execution.

Method Swizzling is not an atomic operation and dispatch_once ensures that code is executed only once, even in different threads. Therefore, we should always perform Method Swizzling on dispatch_once to ensure that Method substitution is performed only once.

  1. After using Method Swizzling, remember to call the implementation of the native Method.

Remember to call the native method implementation after swapping (unless you’re absolutely sure you don’t need to call the native method implementation) : APIs provide rules for input and output, and the method implementation in the middle is an invisible black box. Method implementations are swapped and some callback methods do not call the implementation of the native method, which can cause the underlying implementation to crash.

  1. Avoid naming conflicts and parameters_cmdBeen tampered with.
  1. A good way to avoid naming conflicts is to prefix the replacement method to distinguish it from the native method. It is important to make sure that any place that calls a native method does not have unexpected results because it has swapped method implementations. Remember to call the implementation of the native Method in the Method Swizzling swap after using the Method. An implementation that swaps methods and does not call native methods can cause the underlying implementation to crash.

  2. A better way to avoid method naming conflicts is to use function Pointers, also known as scheme B, which can effectively avoid method naming conflicts and tampering with the _cmd parameter.

  1. Be cautious with Method Swizzling.

Using Method Swizzling, you change code that you don’t own. When we use Method Swizzling, we usually change some of the system framework’s object methods, or class methods. We changed not just one object instance, but all the object instances of that class in the project, as well as all the object instances of subclasses. Therefore, you should be careful when using Method Swizzling.

For example, if you override a method in a class without calling the super method, you might have problems. In most cases, the super method is expected to be called (unless otherwise specified). If you use the same idea for Method Swizzling, it can cause a lot of problems. If you don’t call the original Method implementation, the more you change your Method Swizzling the less secure your code will be.

  1. For Method Swizzling, the order of calls is important.

The + load method is called as follows:

  1. First call the main class, according to the compile order, according to the inheritance relationship from the parent class to the child class call;
  2. Then call classification, according to the compilation order, call;
  3. + loadMethods are called only once unless actively called.

This invocation rule results in an uncertain order of + load method calls. One order may be: Parent -> subclass -> parent category -> subclass category, or parent -> Subclass -> subclass category -> parent category. Therefore, the order of Method Swizzling cannot be guaranteed, so there is no guarantee that the order of Method calls after Method Swizzling is correct.

So the Method Swizzling is used for must be a Method of the current class. There may be problems with copying imPs from its parent class onto its own. If the + load method is called in the order parent -> subclass -> parent class -> subclass class, then the effect is that calling the subclass’s replacement method does not call the parent class’s replacement method correctly. Nan Zhi Cold: Cancer in iOS -MethodSwizzling

A more detailed look at the order of calls can be found in this blog post: Objective-C Method Swizzling


4. Method Swizzling application scenario

Method Swizzling can swap the implementation of the two methods, and is more applied to the system class library in development, as well as the Method replacement of third-party frameworks. In the case of officially closed source code, we can use the Method Swizzling Runtime to add additional functionality to the existing methods, which allows us to do a lot of interesting things.


4.1 Global Page Statistics function

Requirement: add statistics function in all pages, every time the user enters the page statistics once.

If one day the company’s products need us to fulfill this requirement. How do we do that?

Let’s think about a couple of ways to do this:

First: Manually add

Add statistics code directly to all pages once. All you need to do is write a statistical code and copy and paste it in viewWillAppear: for all your pages.

Second: use inheritance

Create a base class from which all pages inherit. In this case, you only need to add statistics once in viewDidAppear: of the base class. This modification code is still a lot, if all pages are not the beginning of the custom base class, then you need to modify the inheritance relationship of all pages, will also cause a lot of repeated code, and a great deal of work.

Third: using classification + Method Swizzling

We can use the features of categories to do this. If a class’s classification overrides a method of that class, the method of that class is invalidated, and the overridden method of the class takes effect.

So, we can create a Category for UIViewController, override viewWillAppear: in the Category, add statistics code to it, and then introduce that Category in all of our controllers. But then, all viewWillAppear: that inherits from UIViewController itself is invalid and will not be called.

This is done using Method Swizzling. The steps are as follows:

  1. Implement a custom in the classificationxxx_viewWillAppear:Methods;
  2. Using Method Swizzling willviewDidAppear:And customxxx_viewWillAppear:Do method swaps.
  3. Then, inxxx_viewWillAppear:Add statistical code and calls toxxx_viewWillAppear:Implementation; Because the two methods are swapped, they are essentially calledviewWillAppear:Methods.
  • Code implementation:
#import "UIViewController+Swizzling.h"
#import <objc/runtime.h>

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
    
    if(! [self isKindOfClass:[UIViewController class]]) {// remove system UIViewController // add statistics code NSLog(@)"Enter page: %@", [self class]);
    }
    
    [self xxx_viewWillAppear:animated];
}

@end
Copy the code

4.2 Fonts fit according to screen size

Requirement: All control fonts must be scaled equally to the screen size.

As usual, let’s think about a couple of ways to do this.

The first is manual modification

All used in the UIFont place, manual judgment, add adaptation code. I can’t bear to look at that workload.

Second: use macro definitions

In the PCH file, define a method to evaluate a scaled font. When using a set font, first call the macro – defined zoom font method. But this also requires modification of all the UIFont used. The workload is still heavy.

/ / macro definition#define UISCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)/** * Static inline CGFloat FontSize(CGFloat FontSize){return fontSize * UISCREEN_WIDTH / XXX_UISCREEN_WIDTH;
}
Copy the code

Third: using classification + Method Swizzling

  1. Create a Category for UIFont.
  2. Implement a custom in the classificationxxx_systemFontOfSize:Method to add a method to scale the font.
  3. Using Method Swizzling willsystemFontOfSize:Methods andxxx_systemFontOfSize:Do method swaps.
  • Code implementation:
#import "UIFont+AdjustSwizzling.h"
#import <objc/runtime.h>

#define XXX_UISCREEN_WIDTH 375

@implementation UIFont (AdjustSwizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(systemFontOfSize:);
        SEL swizzledSelector = @selector(xxx_systemFontOfSize:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); } + (UIFont *)xxx_systemFontOfSize:(CGFloat)fontSize {
    UIFont *newFont = nil;
    newFont = [UIFont xxx_systemFontOfSize:fontSize * [UIScreen mainScreen].bounds.size.width / XXX_UISCREEN_WIDTH];
    
    return newFont;
}

@end
Copy the code

Note: This method is only suitable for pure code. For details on adapting XIB fonts to screen size, see this blog post: Small Life: iOS XIB files adapt to screen size


4.3 Handling repeated button clicking

Requirement: Avoid clicking a button multiple times quickly.

So let’s think about a couple of ways to do this.

First: use Delay Delay, and non-clickable methods.

It’s very intuitive and very simple. But it’s a lot of work, and you need to add code everywhere you have buttons. I hate to admit it: I used this approach on previous projects.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(100.100.100.100)];
    button.backgroundColor = [UIColor redColor];
    [button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonClick:(UIButton *)sender {
    sender.enabled = NO;
    [self performSelector:@selector(changeButtonStatus:) withObject:sender afterDelay:0.8f];
    
    NSLog("Clicked the button");
}

- (void)changeButtonStatus:(UIButton *)sender {
    sender.enabled = YES;
}
Copy the code

Second: using classification + Method Swizzling

  1. forUIControlUIButtonCreate a Category.
  2. Add one to the categoryNSTimeInterval xxx_acceptEventInterval;Property to set the repeat click interval
  3. Implement a custom in the classificationxxx_sendAction:to:forEvent:Method to which to add the corresponding method for the limited time.
  4. Using Method Swizzling willsendAction:to:forEvent:Methods andxxx_sendAction:to:forEvent:Do method swaps.
  • Code implementation:
#import "UIButton+DelaySwizzling.h"
#import <objc/runtime.h>

@interface UIButton(a)

// Repeat the click interval
@property (nonatomic.assign) NSTimeInterval xxx_acceptEventInterval;

@end


@implementation UIButton (DelaySwizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(sendAction:to:forEvent:);
        SEL swizzledSelector = @selector(xxx_sendAction:to:forEvent:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); } - (void)xxx_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    
    // If you want to set a uniform interval, you can add the following sentences here
    if (self.xxx_acceptEventInterval <= 0) {
        // If there is no custom interval, the default is 0.4 seconds
        self.xxx_acceptEventInterval = 0.4;
    }
    
    // Whether the interval is shorter than the set interval
    BOOL needSendAction = (NSDate.date.timeIntervalSince1970 - self.xxx_acceptEventTime >= self.xxx_acceptEventInterval);
    
    // Update the timestamp of the last click
    if (self.xxx_acceptEventInterval > 0) {
        self.xxx_acceptEventTime = NSDate.date.timeIntervalSince1970;
    }
    
    // The response event is executed only when the interval between two clicks is less than the specified interval
    if (needSendAction) {
        [selfxxx_sendAction:action to:target forEvent:event]; }} - (NSTimeInterval )xxx_acceptEventInterval{
    return [objc_getAssociatedObject(self."UIControl_acceptEventInterval") doubleValue];
}

- (void)setXxx_acceptEventInterval:(NSTimeInterval)xxx_acceptEventInterval{
    objc_setAssociatedObject(self."UIControl_acceptEventInterval", @(xxx_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSTimeInterval )xxx_acceptEventTime{
    return [objc_getAssociatedObject(self."UIControl_acceptEventTime") doubleValue];
}

- (void)setXxx_acceptEventTime:(NSTimeInterval)xxx_acceptEventTime{
    objc_setAssociatedObject(self."UIControl_acceptEventTime", @(xxx_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
Copy the code

Small zebra: IOS prevents UIButton from being clicked repeatedly


4.4 Placeholder map is incorrectly loaded in TableView and CollectionView

When a network exception occurs in a project, or when the TableView or CollectionView data is empty for various reasons, it is usually necessary to load the placeholder map to display. Is there a good way or technique to load a placeholder map?

The first is to judge after refreshing the data

This should be the usual practice. When the data is returned, refresh TableView, CollectionView, judge, if the data is empty, load the placeholder map. If the data is not empty, the placeholder map is removed and the data is displayed.

The second way is to rewrite the reloadData Method using classification + Method Swizzling.

Take TableView as an example:

  1. Create a Category for TableView and add a refresh callback block property and a placeholder View property to the Category.
  2. Implement a custom in the classificationxxx_reloadDataMethod, in which to add to determine whether empty, and load the placeholder, hide the placeholder related code.
  3. Using Method Swizzling willreloadDataMethods andxxx_reloadDataDo method swaps.
  • Code implementation:
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UITableView (ReloadDataSwizzling)

@property (nonatomic.assign) BOOL firstReload;
@property (nonatomic.strong) UIView *placeholderView;
@property (nonatomic.copy) void(^reloadBlock)(void);

@end

/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /

#import "UITableView+ReloadDataSwizzling.h"
#import "XXXPlaceholderView.h"
#import <objc/runtime.h>

@implementation UITableView (ReloadDataSwizzling)


+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(reloadData);
        SEL swizzledSelector = @selector(xxx_reloadData);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else{ method_exchangeImplementations(originalMethod, swizzledMethod); }}); } - (void)xxx_reloadData {
    if (!self.firstReload) {
        [self checkEmpty];
    }
    self.firstReload = NO;
    
    [self xxx_reloadData];
}


- (void)checkEmpty {
    BOOL isEmpty = YES; // Void flag
    
    id <UITableViewDataSource> dataSource = self.dataSource;
    NSInteger sections = 1; // There is only one set of tableViews by default
    if ([dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
        sections = [dataSource numberOfSectionsInTableView:self] - 1; // Get the current TableView group number
    }
    
    for (NSInteger i = 0; i <= sections; i++) {
        NSInteger rows = [dataSource tableView:self numberOfRowsInSection:i]; // Get the number of rows in the current TableView group
        if (rows) {
            isEmpty = NO; // If the number of rows exists, it is not null}}if (isEmpty) { // If empty, load the placeholder map
        if (!self.placeholderView) { // If not customized, load the default placeholder map
            [self makeDefaultPlaceholderView];
        }
        self.placeholderView.hidden = NO;
        [self addSubview:self.placeholderView];
    } else { // Not empty, hide the placeholder map
        self.placeholderView.hidden = YES; }} - (void)makeDefaultPlaceholderView {
    self.bounds = CGRectMake(0.0.self.frame.size.width, self.frame.size.height);
    XXXPlaceholderView *placeholderView = [[XXXPlaceholderView alloc] initWithFrame:self.bounds];
    __weak typeof(self) weakSelf = self;
    [placeholderView setReloadClickBlock:^{
        if(weakSelf.reloadBlock) { weakSelf.reloadBlock(); }}];self.placeholderView = placeholderView;
}

- (BOOL)firstReload {
    return [objc_getAssociatedObject(self.@selector(firstReload)) boolValue];
}

- (void)setFirstReload:(BOOL)firstReload {
    objc_setAssociatedObject(self.@selector(firstReload), @(firstReload), OBJC_ASSOCIATION_ASSIGN);
}

- (UIView *)placeholderView {
    return objc_getAssociatedObject(self.@selector(placeholderView));
}

- (void)setPlaceholderView:(UIView *)placeholderView {
    objc_setAssociatedObject(self.@selector(placeholderView), placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void(^) (void))reloadBlock {
    return objc_getAssociatedObject(self.@selector(reloadBlock));
}

- (void)setReloadBlock:(void(^) (void))reloadBlock {
    objc_setAssociatedObject(self.@selector(reloadBlock), reloadBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end
Copy the code

Sure: Zero lines of code to add exception loading placeholders to your App


4.5 APM (Application Performance Management) to prevent program crashes

  1. Replace NSURLConnection with Method Swizzling, the original implementation of nSURlSession-related (such as the constructor of NSURLConnection and the start Method), and add network performance burying behavior to the implementation. The original implementation is then called. To monitor the network.
  2. To prevent a program from crashing, use Method Swizzling to intercept system methods that are prone to crashing, and then use the replacement Method to catch the exception type NSException before handling the exception. The most common example is interceptionarrayWithObjects:count:Method to keep arrays out of bounds, and there are lots of examples on the web, so I won’t show you the code.
  • Some examples of using Method Swizzling features for APM (Application Performance Management) :
    • New to Relic:https://newrelic.com
    • Listen to cloud APM: https://www.tingyun.com
    • NetEaseAPM:http://apm.netease.com/
    • ONE APM:https://www.oneapm.com/
  • Open source projects to prevent crashes:
    • GitHub: Chenfanfang/AvoidCrash
    • GitHub: ValiantCat/XXShield

The resources

  • Stack Overflow: What are the dangers of Method Swizzling in Objective-C?
  • NSHipster: Method Swizzling
  • Lei Chunfeng’s technology blog: Objective-C Method Best Practices Swizzling
  • Learn bottomless: Method Swizzling’s correct posture
  • Yuling Tianyu’s blog: Objective-C Method Swizzling
  • Liu Xiaozhuang: iOS Black magic – Method Swizzling
  • Sure: Runtime Method Swizzling

The last

It took a whole two weeks to write Method Swizzling, during which I consulted a large number of Method Swizzling related materials, but the harvest was very worthwhile. At the same time, I hope to bring you some help.

In the next article, I’ll look at the underlying principles of categories in the Runtime.

If the article is wrong, please correct, thank you.


IOS Development: Runtime

  • IOS development: “Runtime” details (1) Basic knowledge
  • IOS development: “Runtime” details (ii) Method Swizzling
  • IOS development: “Runtime” details (3) Category underlying principles
  • IOS development: “Runtime” detailed explanation (4) obtain the class detailed attributes, methods

Not yet completed:

  • IOS development: “Runtime” details (5) Crash protection system
  • IOS development: “Runtime” (6) Objective-C 2.0
  • IOS development: “Runtime” details (seven) KVO low-level implementation