Runloop:应用篇

作者: 码小菜 | 来源:发表于2020-03-15 18:32 被阅读0次
    夜景

    目录
    一,解决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默认是不开启的

    • 必须手动开启runloopNSTimer才会被处理,此方法才能正常执行

    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代表NSDefaultRunLoopModeUITrackingRunLoopMode,所以界面滑动时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;
    }
    

    相关文章

      网友评论

        本文标题:Runloop:应用篇

        本文链接:https://www.haomeiwen.com/subject/bsaxshtx.html