performSelector的原理及应用场景分析

简介: performSelector的原理及应用场景分析

1、performSelector实现原理

performSelector依托于RunLoop执行,如果当前线程没有RunLoop的话,该方法就会失效。


用例证明:


- (void)viewDidLoad {
     [super viewDidLoad];
     NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(nslogHi) object:nil];
     [thread start];
     [self performSelector:@selector(nslogHello) onThread:thread withObject:nil waitUntilDone:NO];
    NSLog(@"_end");
}
- (void)nslogHi {
    NSLog(@"hi....");
}
- (void)nslogHello {
    NSLog(@"hello.....");
}
/// 输出结果
2015-09-28 14:09:15.650 PCRunLoopThread[74414:5556013] hi....
2015-09-28 14:09:15.650 PCRunLoopThread[74414:5555961] _end


结论:


1、线程在执行后会退出当前的RunLoop,也就是RunLoop会在一个线程结束时一同销毁。

2、如果当前线程没有RunLoop的话,performSelector:onThread的方法也就失效。

延伸:如果想要把hello…打印出来,那么可以对这个线程设置成一直运行或者暂时阻塞一下,直至 nslogHello 方法运行结束。


2、performSelector触发时机

@property (nonatomic, strong) UIButton *testBtn;
- (UIButton *)testBtn {
    if (!_testBtn) {
        _testBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        _testBtn.frame = CGRectMake(100, 100, 100, 100);
        _testBtn.backgroundColor = [UIColor redColor];
        [_testBtn addTarget:self action:@selector(testAction:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:_testBtn];
    }
    return _testBtn;
}
- (void)testAction:(UIButton *)sender {
    NSLog(@"testAction >>>> before");
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self performSelector:@selector(delayAction:) withObject:@"delay" afterDelay:1.f];
    NSLog(@"testAction >>>> after");
}
- (void)delayAction:(id)sender {
    NSLog(@"delayAction >>>> sender:%@",sender);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    [self testBtn];
}


1、点击一次:


2021-02-20 11:37:38.537893+0800 TestDemo[17102:8687749] testAction >>>> before
2021-02-20 11:37:38.538475+0800 TestDemo[17102:8687749] testAction >>>> after
2021-02-20 11:37:39.539686+0800 TestDemo[17102:8687749] delayAction >>>> sender:delay

2、连续多次点击


2021-02-20 11:48:12.665228+0800 TestDemo[17102:8687749] testAction >>>> before
2021-02-20 11:48:12.665806+0800 TestDemo[17102:8687749] testAction >>>> after
2021-02-20 11:48:12.836208+0800 TestDemo[17102:8687749] testAction >>>> before
2021-02-20 11:48:12.836669+0800 TestDemo[17102:8687749] testAction >>>> after
2021-02-20 11:48:12.993410+0800 TestDemo[17102:8687749] testAction >>>> before
2021-02-20 11:48:12.993900+0800 TestDemo[17102:8687749] testAction >>>> after
2021-02-20 11:48:13.153061+0800 TestDemo[17102:8687749] testAction >>>> before
2021-02-20 11:48:13.153571+0800 TestDemo[17102:8687749] testAction >>>> after
2021-02-20 11:48:14.154847+0800 TestDemo[17102:8687749] delayAction >>>> sender:delay


总结:


1、performSelector: withObject: afterDelay: 这个方法是单线程的,也就是说只有当前调用此方法的函数执行完毕后,selector方法才会被调用。

2、cancelPreviousPerformRequestsWithTarget 没有后续参数表示取消全部。

拓展:

1、当第一次触发时,performSelector函数不作延迟;之后,每次触发延迟处理的情况时。可以设置延迟时间 delay 参数为可变参数。


3、performSelector 与 dispatch_source_t(事件联结)的区别

原理:

1、dispatch_source不依赖于Runloop,直接和底层内核交互,准确性更高。

2、performSelector依赖于Runloop运行,会因为主线被占用而产生延迟。

执行顺序:

1、dispatch_source在selector执行之前,需要触发事件,才产生事件联结。等selector中的方法执行完之后,才会出发下一次事件。

2、performSelector:只有当前调用此方法的函数执行完毕后,selector方法才会被调用。

应用场景:

1、dispatch_source:事件联结、高精度计时器

2、performSelector:时间延迟处理(按钮防抖处理)、即时搜索数据列表处理。


dispatch_source_merge_data的机制原理见:

Dispatch Source 应用


4、注意点:Object传参


@interface NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end


注意:以上方法中一个重要参数:anArgument,发现如果参数不为空,那取消时的参数也要一致,否则不能取消成功。


[self performSelector:@selector(didRuninCurrModel:) withObject:[NSNumber numberWithBool:YES] afterDelay:3.0f];
[self performSelector:@selector(didRuninCurrModelNoArgument) withObject:nil afterDelay:3.0f];

假如在三秒内执行以下取消方法,则结果分别为:


//可以取消成功。
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(didRuninCurrModel:) object:[NSNumber numberWithBool:YES]];
//不能取消成功。参数不匹配
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(didRuninCurrModel:) object:[NSNumber numberWithBool:NO]];
//不能取消成功。参数不匹配
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(didRuninCurrModel:) object:nil];
//可以成功取消
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(didRuninCurrModelNoArgument) object:nil];
//可以成功取消全部。
[NSObject cancelPreviousPerformRequestsWithTarget:self];
//可以成功取消全部。
[[self class] cancelPreviousPerformRequestsWithTarget:self];


5、应用:按钮防抖处理


#import <UIKit/UIKit.h>
#define kDefaultInterval 0.2  //默认时间间隔
NS_ASSUME_NONNULL_BEGIN
@interface UIButton (AntiShake)
/// 设置防抖时间间隔
@property (nonatomic, assign) NSTimeInterval antiShakeTimeInterval;
@end
NS_ASSUME_NONNULL_END



#import "UIButton+AntiShake.h"
#import <objc/runtime.h>
@interface UIButton()
/// 是否忽略点击事件
@property (nonatomic, assign) BOOL isIgnoreEvent;
@end
@implementation UIButton (AntiShake)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL selA = @selector(sendAction:to:forEvent:);
        SEL selB = @selector(mySendAction:to:forEvent:); // B是自己定义的方法
        Method methodA =   class_getInstanceMethod(self,selA);
        Method methodB = class_getInstanceMethod(self, selB);
        BOOL isAdd = class_addMethod(self, selA, method_getImplementation(methodB), method_getTypeEncoding(methodB));
        if (isAdd) {
            class_replaceMethod(self, selB, method_getImplementation(methodA), method_getTypeEncoding(methodA));
        }else{
            method_exchangeImplementations(methodA, methodB);
        }
    });
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSStringFromClass(self.class) isEqualToString:@"UIButton"]) {
        if (self.isIgnoreEvent) {
            return;
        }else {
            self.antiShakeTimeInterval = self.antiShakeTimeInterval <= 0 ?kDefaultInterval:self.antiShakeTimeInterval;
        }
        self.isIgnoreEvent = YES;
        [self performSelector:@selector(setIsIgnoreEvent:) withObject:[NSNumber numberWithBool:NO] afterDelay:self.antiShakeTimeInterval];
    }
    [self mySendAction:action to:target forEvent:event];
}
#pragma mark - Setting & Getting
- (NSTimeInterval)antiShakeTimeInterval {
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}
- (void)setAntiShakeTimeInterval:(NSTimeInterval)antiShakeTimeInterval {
    objc_setAssociatedObject(self, @selector(antiShakeTimeInterval), @(antiShakeTimeInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)setIsIgnoreEvent:(BOOL)isIgnoreEvent {
    objc_setAssociatedObject(self, @selector(isIgnoreEvent), @(isIgnoreEvent), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isIgnoreEvent {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
@end


6、应用:延迟搜索设置

#pragma mark -
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    // 获取真实数据
     NSString * str = [textField.text stringByReplacingCharactersInRange:range withString:string];
    //延迟加载处理
    [self delaySearch:str];
    return YES;
}
/// 延迟搜索设置
- (void)delaySearch:(NSString *)str {
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self performSelector:@selector(goSearch:) withObject:str afterDelay:1.f];
}
/// 发起检索
static NSString *_citySearchHistory = @"";
- (void)goSearch:(NSString *)str {
    if (!kIsEmptyStr(str)) {
        // 避免同词多次请求
        if ([str isEqualToString:_citySearchHistory]) {
            return;
        } else {
            _citySearchHistory = str;
        }
        NSArray *arr = [AreaModel getSearchList:str];
        self.searchTableView.areaSearchArr = arr;
    } else {
        self.searchTableView.areaSearchArr = @[];
    }
}


7、拓展:RunLoop和线程


7.1、RunLoop和线程的关系

  每条线程都有唯一的一个与之对应的RunLoop对象,一个线程可以开启多个RunLoop,只不过都是嵌套在最大的RunLoop中,其关系是保存在一个全局的 Dictionary 里。

1

7.2、线程中RunLoop的生命周期

创建:

1、主线程:run loop默认是启动的,用于接收各种输入sources

2、子线程:线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。

在当前子线程中调用[NSRunLoop currentRunLoop]的时候,如果有就获取,没有就创建


启动:

1、主线程:默认是启动的

2、子线程:要手动添加


获取:

1、主线程:全局获取其RunLoop;[NSRunLoop mainRunLoop]或者 CFRunLoopGetMain();

2、子线程:只能在线程的内部获取其RunLoop;[NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent();


销毁:

1、主线程:app结束时

2、子线程:子线程结束


链接拓展:

1、performSelector的常用方法

2、RunLoop 处理事件源-- performSelector方法


相关文章
|
1月前
|
安全 Java 编译器
深入探讨Java反射:解析机制与应用场景
反射是Java的一种强大而灵活的特性,它允许程序在运行时获取类的信息、构造对象、调用方法和访问字段。在Java中,每个类都有一个对应的Class对象,通过这个对象,我们可以了解类的结构和行为。
109 1
 深入探讨Java反射:解析机制与应用场景
|
1月前
|
存储 安全 Java
【深度挖掘Java并发编程底层源码】「底层技术原理体系」带你零基础认识和分析学习相关的异步任务提交机制FutureTask的底层原理
【深度挖掘Java并发编程底层源码】「底层技术原理体系」带你零基础认识和分析学习相关的异步任务提交机制FutureTask的底层原理
23 0
|
1月前
|
消息中间件 缓存 Java
【多线程学习】深入探究定时器的重点和应用场景
【多线程学习】深入探究定时器的重点和应用场景
|
1月前
|
存储 Java 数据库连接
ThreadLocal的原理解析以及应用场景分析
ThreadLocal的原理解析以及应用场景分析 什么是ThreadLocal? ThreadLocal是Java中的一个线程管理工具,用于保证线程间数据的独立性。每个ThreadLocal对象可以存储一个线程局部变量,即对于同一个ThreadLocal对象,每个线程都有自己独立的变量。
|
8月前
|
人工智能 程序员 C#
通过简单原理增强软件可靠性
通过简单原理增强软件可靠性
|
9月前
|
缓存 Java
Java线程池创建方式和应用场景
Java线程池创建方式和应用场景
67 0
|
9月前
|
缓存 C++
12-objc_msgSend底层调用流程探究
12-objc_msgSend底层调用流程探究
37 0
|
11月前
|
数据采集 Java
Java线程池使用场景和方法分析
Java线程池使用场景和方法分析
72 0
|
11月前
|
安全 算法 生物认证
全面分析生物技术的优缺点以及应用场景
全面分析生物技术的优缺点以及应用场景
116 0
优化对象变高效
没有优化过的对象,不足以看出C++的优势