Runloop:应用篇
目录
一,解决performSelector:withObject:afterDelay:方法不执行的问题
二,解决界面滑动时NSTimer会停止的问题
三,启动方式
四,线程保活(一)
五,线程保活(二)
一,解决performSelector:withObject:afterDelay:方法不执行的问题
1,解决前
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1---%@", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"3---%@", [NSThread currentThread]);
[self performSelector:@selector(perform)
withObject:nil
afterDelay:3.0];
NSLog(@"4---%@", [NSThread currentThread]);
});
NSLog(@"2---%@", [NSThread currentThread]);
}
- (void)perform {
NSLog(@"5---%@", [NSThread currentThread]);
}
// 打印
1---<NSThread: 0x600003cebb00>{number = 1, name = main}
2---<NSThread: 0x600003cebb00>{number = 1, name = main}
3---<NSThread: 0x600003cb1ec0>{number = 3, name = (null)}
4---<NSThread: 0x600003cb1ec0>{number = 3, name = (null)}
2,解决后
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1---%@", [NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"3---%@", [NSThread currentThread]);
[self performSelector:@selector(perform)
withObject:nil
afterDelay:3.0];
// 启动当前线程的runloop
[[NSRunLoop currentRunLoop] run];
NSLog(@"4---%@", [NSThread currentThread]);
});
NSLog(@"2---%@", [NSThread currentThread]);
}
// 打印
1---<NSThread: 0x60000154d940>{number = 1, name = main}
2---<NSThread: 0x60000154d940>{number = 1, name = main}
3---<NSThread: 0x600001522580>{number = 6, name = (null)}
5---<NSThread: 0x600001522580>{number = 6, name = (null)}
4---<NSThread: 0x600001522580>{number = 6, name = (null)}
3,说明
-
此方法内部会创建一个
NSTimer
添加到当前线程的runloop
中 -
子线程的
runloop
默认是不开启的 -
必须手动开启
runloop
,NSTimer
才会被处理,此方法才能正常执行
4,注意点
-
不能在此方法前启动
runloop
,此时NSTimer
还没添加进来,由于没有事件需要处理,runloop
一启动就会退出 -
runloop
启动后先进入内部循环处理NSTimer
,处理完才会退出内部循环,代码才能往下执行,所以先打印5再打印4
二,解决界面滑动时NSTimer会停止的问题
1,解决前
- (void)viewDidLoad {
[super viewDidLoad];
__block int count = 1;
[NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", count++);
}];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSLog(@"%s", __func__);
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
NSLog(@"%s", __func__);
}
// 打印
17:45:58.607770+0800 Demo[98743:17866588] 1
17:45:59.608341+0800 Demo[98743:17866588] 2
17:46:00.607569+0800 Demo[98743:17866588] 3
17:46:01.607681+0800 Demo[98743:17866588] 4
17:46:02.608093+0800 Demo[98743:17866588] 5
17:46:02.965690+0800 Demo[98743:17866588] -[ViewController scrollViewWillBeginDragging:]
17:46:12.026709+0800 Demo[98743:17866588] -[ViewController scrollViewDidEndDragging:willDecelerate:]
17:46:12.028420+0800 Demo[98743:17866588] 6
17:46:12.608795+0800 Demo[98743:17866588] 7
17:46:13.608181+0800 Demo[98743:17866588] 8
17:46:14.608112+0800 Demo[98743:17866588] 9
17:46:15.609020+0800 Demo[98743:17866588] 10
2,解决后
- (void)viewDidLoad {
[super viewDidLoad];
__block int count = 1;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
repeats:YES
block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", count++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
// 打印
17:54:52.672257+0800 Demo[98815:17903655] 1
17:54:53.672473+0800 Demo[98815:17903655] 2
17:54:54.673453+0800 Demo[98815:17903655] 3
17:54:54.948438+0800 Demo[98815:17903655] -[ViewController scrollViewWillBeginDragging:]
17:54:55.673715+0800 Demo[98815:17903655] 4
17:54:56.675426+0800 Demo[98815:17903655] 5
17:54:57.675849+0800 Demo[98815:17903655] 6
17:54:58.676955+0800 Demo[98815:17903655] 7
17:54:59.214448+0800 Demo[98815:17903655] -[ViewController scrollViewDidEndDragging:willDecelerate:]
17:54:59.676629+0800 Demo[98815:17903655] 8
17:55:00.677990+0800 Demo[98815:17903655] 9
17:55:01.677777+0800 Demo[98815:17903655] 10
3,说明
-
scheduledTimerWithTimeInterval:
方法会自动将NSTimer
添加到NSDefaultRunLoopMode
中 -
当界面滑动时,
runloop
会切换到UITrackingRunLoopMode
,所以NSTimer
会停止 -
NSRunLoopCommonModes
代表NSDefaultRunLoopMode
和UITrackingRunLoopMode
,所以界面滑动时NSTimer
不会停止
三,启动方式
1,run
:永不退出
[runloop run];
// 相当于
while (1) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
}
2,runUntilDate:
:在指定时间退出
NSDate *limitDate = [NSDate dateWithTimeIntervalSinceNow:10];
[runloop runUntilDate:limitDate];
// 相当于
while ([NSDate.date compare:limitDate] == NSOrderedAscending) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:limitDate];
}
3,runMode:beforeDate:
:在指定时间或者完成一次循环后退出
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
四,线程保活(一)
1,作用
-
子线程执行一次操作后生命周期就结束了
-
如果经常在子线程中执行操作,就需要频繁的创建和销毁线程,这样比较消耗性能
-
保活就是让子线程的生命周期不要那么快结束,可以持续的执行操作
2,无法持续的执行操作
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"initWithBlock---%@", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
// 该操作无效
[self performSelector:@selector(work)
onThread:self.thread
withObject:nil
waitUntilDone:NO];
}
- (void)work {
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
}
@end
// 打印
initWithBlock---<NSThread: 0x600000b75a80>{number = 6, name = (null)}
-[ViewController touchesBegan:withEvent:]
// block执行完,子线程的生命周期就结束了
3,启动runloop
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"begin---%@", [NSThread currentThread]);
// 创建runloop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// 防止mode为空runloop退出
[runloop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
// 启动runloop,runloop会让代码阻塞在此
[runloop run];
NSLog(@"end---%@", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"%s", __func__);
// 该操作有效
[self performSelector:@selector(work)
onThread:self.thread
withObject:nil
waitUntilDone:NO];
}
// 打印
begin---<NSThread: 0x600001225ac0>{number = 7, name = (null)}
-[ViewController touchesBegan:withEvent:]
-[ViewController work]---<NSThread: 0x600001225ac0>{number = 7, name = (null)}
// 没有打印end,说明block没有执行完,子线程的生命周期还没有结束
4,点击按钮退出runloop
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign, getter=isStopped) BOOL stopped;
@end
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"begin---%@", [NSThread currentThread]);
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
/*
1,run方法无法退出,所以改用此方法
2,由于此方法完成一次循环后就退出了,所以需要加上while循环重新进入
*/
while (!weakSelf.isStopped) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
}
NSLog(@"end---%@", [NSThread currentThread]);
}];
[self.thread start];
}
- (IBAction)buttonClick {
NSLog(@"%s", __func__);
[self performSelector:@selector(stop)
onThread:self.thread
withObject:nil
waitUntilDone:NO];
}
- (void)stop {
NSLog(@"%s---%@", __func__, [NSThread currentThread]);
self.stopped = YES;
// 退出当前线程的runloop
CFRunLoopStop(CFRunLoopGetCurrent());
}
// 打印
begin---<NSThread: 0x60000207bec0>{number = 7, name = (null)}
-[ViewController touchesBegan:withEvent:]
-[ViewController work]---<NSThread: 0x60000207bec0>{number = 7, name = (null)}
-[ViewController buttonClick]
-[ViewController stop]---<NSThread: 0x60000207bec0>{number = 7, name = (null)}
end---<NSThread: 0x60000207bec0>{number = 7, name = (null)}
// 有打印end,说明block执行完了,子线程的生命周期也就结束了
5,控制器销毁时退出runloop
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"begin---%@", [NSThread currentThread]);
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
// 加上weakSelf非空判断,表示如果控制器销毁了,就不用再进入runloop了
while (weakSelf && !weakSelf.isStopped) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
}
NSLog(@"end---%@", [NSThread currentThread]);
}];
[self.thread start];
}
- (IBAction)buttonClick {
NSLog(@"%s", __func__);
[self performSelector:@selector(stop)
onThread:self.thread
withObject:nil
/*
1,YES表示等待stop方法执行完再往下执行,NO表示不等待
2,如果这里传NO的话,那么在执行stop方法时控制器已经销毁了,再使用self就会有问题
*/
waitUntilDone:YES];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self buttonClick];
}
// 打印
begin---<NSThread: 0x6000032f6680>{number = 7, name = (null)}
-[ViewController touchesBegan:withEvent:]
-[ViewController work]---<NSThread: 0x6000032f6680>{number = 7, name = (null)}
-[ViewController dealloc]
-[ViewController buttonClick]
-[ViewController stop]---<NSThread: 0x6000032f6680>{number = 7, name = (null)}
end---<NSThread: 0x6000032f6680>{number = 7, name = (null)}
// 有打印end,说明block执行完了,子线程的生命周期也就结束了
五,线程保活(二)
1,封装
- 外部接口
typedef void(^YJCustomThreadTask)(void);
@interface YJCustomThread : NSObject
- (void)run;
- (void)executeTask:(YJCustomThreadTask)task;
- (void)stop;
@end
- 内部实现
@interface YJCustomThread ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation YJCustomThread
- (instancetype)init {
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"begin---%@", [NSThread currentThread]);
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
}
NSLog(@"end---%@", [NSThread currentThread]);
}];
}
return self;
}
- (void)run {
[self.thread start];
}
- (void)executeTask:(YJCustomThreadTask)task {
if (!self.thread) return;
[self performSelector:@selector(_execute:)
onThread:self.thread
withObject:task
waitUntilDone:NO];
}
- (void)_execute:(YJCustomThreadTask)task {
if (task) {
task();
}
}
- (void)stop {
if (!self.thread) return;
[self performSelector:@selector(_stop)
onThread:self.thread
withObject:nil
waitUntilDone:YES];
}
- (void)_stop {
NSLog(@"stop---%@", [NSThread currentThread]);
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
@end
- 使用
@interface ViewController ()
@property (nonatomic, strong) YJCustomThread *customThread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.customThread = [YJCustomThread new];
[self.customThread run];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.customThread executeTask:^{
NSLog(@"execute---%@", [NSThread currentThread]);
}];
}
- (IBAction)buttonClick {
[self.customThread stop];
}
@end
// 打印
begin---<NSThread: 0x600000502300>{number = 7, name = (null)}
execute---<NSThread: 0x600000502300>{number = 7, name = (null)}
stop---<NSThread: 0x600000502300>{number = 7, name = (null)}
end---<NSThread: 0x600000502300>{number = 7, name = (null)}
2,用CFRunLoopRef
实现
- (instancetype)init {
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"begin---%@", [NSThread currentThread]);
// 创建runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
// 防止mode为空runloop退出
CFRunLoopSourceContext context = {};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode);
CFRelease(source);
/*
1,启动runloop
2,第二个参数传无穷大,表示永不超时
3,第三个参数传false,表示完成一次循环后不退出
*/
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
// 如果第三个参数传true,表示完成一次循环后退出,那么就需要加上while循环重新进入
// while (weakSelf && !weakSelf.isStopped) {
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
// }
NSLog(@"end---%@", [NSThread currentThread]);
}];
}
return self;
}