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、子线程:子线程结束
链接拓展:
2、RunLoop 处理事件源-- performSelector方法