Runtime – prevent repeated clicks – uibutton, uitableview

Time:2022-1-4

Gesture has a system to process single-click, and the time interval will not be customized for the time being. Only uibutton and uitableview (uicollectionview) are processed

1. Idea:

UIButton hook sendAction
Uitableview hook setdelegate (this method is not available in swift, use OC instead)
Gesture hook initWithTarget:action:

be careful:
1. The sendaction method of hook button is actually the sendaction of uicontrol. Clicking the uicontrol subclass (return button on the navigation bar) will crash. Therefore, replace uibutton hook with uicontrol hook. Meanwhile, in order to avoid and minimize the impact on other uicontrol subclasses (uislider / uiswitch), the function of preventing repeated clicks is turned off by default
[_UIButtonBarButton xx_sendActionWithAction:to:forEvent:]: unrecognized selector sent to instance 0x7f8e2a41b380'
2. The method in OC is nsdate date. Timeintervalsince1970, not [[nsdate date] timeintervalsince1970]
3. Since the setdelegate method may be called many times, it is necessary to judge whether it has been swizzling to prevent repeated execution. After the tableview is hook in base class A, the method will be called twice if subclasses B and C setdelegate respectively_ exchange… (didSelectRowAtIndexPath)。 Specifically, when the base class is uitableviewcontroller, it has no impact. When the base class is a customized VC with uitableview, subclass a is normal and subclass B is abnormal. Solution 1: didselectrow is not written in the base class, but implemented in the subclass, or written in the base class but overridden by the subclass. Solution 2: safer runtime method exchange library aspects.

2. Code

+(void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalAppearSelector = @selector(setDelegate:);
        SEL swizzingAppearSelector = @selector(my_setDelegate:);
        method_exchangeImplementations(class_getInstanceMethod([self class], originalAppearSelector), class_getInstanceMethod([self class], swizzingAppearSelector));
    });
}
-(void)my_setDelegate:(id<UITableViewDelegate>)delegate{
    [self my_setDelegate:delegate];
    
    SEL sel = @selector(tableView:didSelectRowAtIndexPath:);
    SEL sel_t = @selector(my_tableView:didSelectRowAtIndexPath:);

    //If tableview: didselectrowatindexpath: is not implemented, hook is not required
    if (![delegate respondsToSelector:sel]){
        return;
    }
    BOOL addsuccess = class_addMethod([delegate class],
                                      sel_t,
                                      method_getImplementation(class_getInstanceMethod([self class], sel_t)),
                                      nil);

    //If the addition is successful, the implementation will be exchanged directly. If the addition is not successful, it means that the implementation has been added and exchanged before
    if (addsuccess) {
        Method selMethod = class_getInstanceMethod([delegate class], sel);
        Method sel_Method = class_getInstanceMethod([delegate class], sel_t);
        method_exchangeImplementations(selMethod, sel_Method);
    }
}

//Because we exchanged methods, when the didselected of tableview is called, the following methods are actually called:
-(void)my_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    if(NSDate.date.timeIntervalSince1970 - tableView.acceptEventTime < tableView.accpetEventInterval) {
        Nslog (@ "click too fast");
        return;
    }
    if (tableView.accpetEventInterval > 0) {
        tableView.acceptEventTime = NSDate.date.timeIntervalSince1970;
    }
    [self my_tableView:tableView didSelectRowAtIndexPath:indexPath];
}

3. Precautions and risk points

1. Avoid swapping parent methods
If the current class does not implement the exchanged method but the parent class implements it, the implementation of the parent class will be exchanged. If multiple inheritors of this parent class are exchanged, the method will be exchanged many times and confused. At the same time, when calling the method of the parent class, it will crash because it cannot be found. Therefore, before the exchange, you should try to add a new implementation imp of the exchanged function for the current class. If the addition is successful, it indicates that the class does not implement the exchanged method, so you only need to replace the implementation of the classification exchange method as the implementation of the original method. If the addition fails, the exchanged method is implemented in the original class, and the exchange can be carried out directly.
2. Loading sequence and interaction of load method
A class B may have an inherited super class A and its own class C. if the load method is also implemented in the class, what is their calling order? The system will first call the load method of super, then call the load method of class B itself, and then call the load method of class B’s classification C. that is to say, the load method in the real inheritance chain, including the classification extension, will be executed, but the execution order needs to be paid attention to. The load method is different from the embodiment of other coverage methods in classification. If other methods in class B are overridden in classification C, the methods in classification C will be executed first. However, different from load, it will be executed, because this is the method of class loading settings.
3. It is difficult to troubleshoot problems
Text long press edit copy or cut click events need to be filtered
Slide right to delete the case: the sendaction method will be called twice quickly

IOS uses runtime to solve the problem of repeated jump caused by clicking the same button multiple times
IOS slice programming hook
Statistical practice of IOS no buried point data
OC exchanges the same method multiple times
Method swizzling points you should pay attention to
Runtime usage scenarios – buried points, repeated clicks, array cross-border crash

Recommended Today

(Do not request a bean from a BeanFactory in a destroy method implementation!)

Exception: Singleton bean creation not allowed while singletons of this factory are in destruction (do not request a bean from a beanfactory in a destruction method implementation!) If your project references spring cloud starter Zipkin jar and spring boot starter data redis jar at the same time, redis will not be connected and this exception […]