iOS runloop 简述及实例

2020-05-10  本文已影响0人  依米米一

一、概念

runloop
1、字面理解:“跑圈”“循环执行”
2、实义:通过内部维护的事件循环来对事件/消息进行管理的一个对象
** 寄生于线程的消息循环机制,保证线程的存活,而不是线程执行完任务后就消亡
** 没有消息处理->休眠以避免资源占用(用户态->内核态)
** 有消息处理->立即唤醒(内核态->用户态)

二、整体结构

1.一个runloop对应了多种mode,每个mode下又有多种source,timer,Observer
2.每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
3.如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响


整体图

三、Runloop运行逻辑

事件队列

事件循环的实现机制是
当你被问到,runloop具体操作流程的时候,你该怎么回答。
当我们手动点击屏幕,UI操作,实际runloop是这样来的


事件循环的实现机制

四、主线程默认开启runloop,子线程是不开启RunLoop的!!!子线程的代码执行完毕后就会被回收!!!上代码

@interface ViewController ()
@property (nonatomic,assign,getter=isfinish) BOOL finish;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    _finish = YES;
    //子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //初始化NSTimer
        NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeup) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        
        while (self.finish) {
            //开启运行循环
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0.1]];
        }
        NSLog(@"当RunLoop停止的时候线程会被销毁");
        NSLog(@"%@",[NSThread currentThread]);
    });
}
- (void)timeup {
    NSLog(@"timer%@",[NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    _finish = NO;
}

运行结果 可正常执行子线程

2020-05-09 17:36:58.740731+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:36:59.744646+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:00.743766+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:01.743925+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:02.740088+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}

点击屏幕任何地方后运行结果:线程被销毁,即不再执行运行循环

 //开启运行循环
 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0.1]];
2020-05-09 17:36:59.744646+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:00.743766+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:01.743925+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:02.740088+0800 demoTest[86561:2515635] timer<NSThread: 0x600002137400>{number = 3, name = (null)}
2020-05-09 17:37:03.189652+0800 demoTest[86561:2515635] 当RunLoop停止的时候线程会被销毁
2020-05-09 17:37:03.189834+0800 demoTest[86561:2515635] <NSThread: 0x600002137400>{number = 3, name = (null)}
小结:主线程默认开启runloop,子线程不开启RunLoop,需要代码添加执行循环

五、实现一个常驻线程

如何实现一个常驻线程,又怎么销毁一个常驻线程。
说明:为什么要实现常驻线程,因为有些操作,需要多次在子线程中执行,但是我们不想每次都开启执行一次,结束了。。多处使用场景,让子线程runloop一直存在,节省内存开销。

三种方法实现常驻线程
创建子线程

@interface ViewController ()
@property(nonatomic,strong)NSThread * testThread;
@property(nonatomic,assign)BOOL runAlways;
@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.testThread = [[NSThread alloc] initWithTarget:self selector:@selector(runTask) object:nil];
    self.testThread.name = @"测试线程";
    [self.testThread start];
}

实现常驻线程

1、addPort
2、addTimer
3、CFRunLoopAddSource

-(void)runTask{
    self.runAlways = YES;
    NSLog(@"执行线程:%@",[NSThread currentThread]);

    //--------第一种 addPort
    //获取当前线程的runLoop
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    //为当前的runLoop添加model
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    //运行 runLoop
    [runLoop run];


    //----------第二种 addTimer
    /*
    //获取当前线程的runLoop
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(runTask2) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
    [runLoop run];
    */


  //----------第三种source
    /*
    //创建source
    CFRunLoopSourceContext context = {0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    //创建runloop同时在DefaultMode模式下添加source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    while (self.runAlways) {
        NSLog(@"source--当前线程:%@",[NSThread currentThread]);
        CFRunLoopRun();
    }
    //某一时机 runAlways为no时,保证跳出runloop,线程退出
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
*/

}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelector:@selector(runTask2) onThread:self.testThread withObject:nil waitUntilDone:NO];
}

-(void)runTask2{
    NSLog(@"当前线程:%@",[NSThread currentThread]);
    NSLog(@"%s 完成",__PRETTY_FUNCTION__);
}

1、addPort 运行结果 实现常驻,触发页面时持有线程

2020-05-10 11:18:04.837590+0800 demoOne[33638:1500106] 执行线程:<NSThread: 0x600001e8b540>{number = 5, name = 测试线程}
2020-05-10 11:18:06.616884+0800 demoOne[33638:1500106] 当前线程:<NSThread: 0x600001e8b540>{number = 5, name = 测试线程}
2020-05-10 11:18:06.617043+0800 demoOne[33638:1500106] -[ViewController runTask2] 完成

2、addTimer 运行结果 实现常驻,每隔1.0秒走一次方法

2020-05-10 11:28:00.349596+0800 demoOne[33668:1505508] 执行线程:<NSThread: 0x60000089ec00>{number = 6, name = 测试线程}
2020-05-10 11:28:01.352583+0800 demoOne[33668:1505508] 当前线程:<NSThread: 0x60000089ec00>{number = 6, name = 测试线程}
2020-05-10 11:28:01.352864+0800 demoOne[33668:1505508] -[ViewController runTask2] 完成
2020-05-10 11:28:01.436690+0800 demoOne[33668:1505508] 当前线程:<NSThread: 0x60000089ec00>{number = 6, name = 测试线程}
2020-05-10 11:28:01.436875+0800 demoOne[33668:1505508] -[ViewController runTask2] 完成
2020-05-10 11:28:02.352970+0800 demoOne[33668:1505508] 当前线程:<NSThread: 0x60000089ec00>{number = 6, name = 测试线程}
2020-05-10 11:28:02.353353+0800 demoOne[33668:1505508] -[ViewController runTask2] 完成
2020-05-10 11:28:03.352095+0800 demoOne[33668:1505508] 当前线程:<NSThread: 0x60000089ec00>{number = 6, name = 测试线程}
2020-05-10 11:28:03.352318+0800 demoOne[33668:1505508] -[ViewController runTask2] 完成

3、CFRunLoopAddSource 运行结果 实现常驻,触发页面时持有线程

2020-05-10 11:30:03.492318+0800 demoOne[33706:1507948] 执行线程:<NSThread: 0x60000043a840>{number = 7, name = 测试线程}
2020-05-10 11:30:03.492603+0800 demoOne[33706:1507948] source--当前线程:<NSThread: 0x60000043a840>{number = 7, name = 测试线程}
2020-05-10 11:30:05.379058+0800 demoOne[33706:1507948] 当前线程:<NSThread: 0x60000043a840>{number = 7, name = 测试线程}
2020-05-10 11:30:05.379283+0800 demoOne[33706:1507948] -[ViewController runTask2] 完成
source 退出子线程操作 添加限制条件

- (void)createButton{
   UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.frame = CGRectMake(100, 100, 100, 100);
    btn.backgroundColor = [UIColor lightGrayColor];
    [btn setTitle:@"按钮" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];

}


- (void)click{
    self.runAlways = NO;
    NSLog(@"点击按钮source移除----当前线程:%@",[NSThread currentThread]);
}
运行结果:当前线程已在主线程,点击屏幕又会持有子线程
2020-05-10 11:53:41.415894+0800 demoOne[33924:1525077] 执行线程:<NSThread: 0x600001b57740>{number = 7, name = 测试线程}
2020-05-10 11:53:43.843947+0800 demoOne[33924:1525077] source--当前线程:<NSThread: 0x600001b57740>{number = 7, name = 测试线程}
2020-05-10 11:53:46.977248+0800 demoOne[33924:1525077] 当前线程:<NSThread: 0x600001b57740>{number = 7, name = 测试线程}
2020-05-10 11:53:46.977501+0800 demoOne[33924:1525077] -[ViewController runTask2] 完成
2020-05-10 11:53:50.492606+0800 demoOne[33924:1524988] 点击按钮source移除----当前线程:<NSThread: 0x600001b18ec0>{number = 1, name = main}

六、具体实例

CFRunloopSourceRef

1、Source0:非基于Port的 ,用于用户主动触发事件
2、Source1:基于Port的,通过内核和其它线程相互发送消息
可以通过打断点的方式查看一个方法的函数调用栈


函数调用栈
AFNetworking 中运用 Runloop

AFURLConnectionOperation 基于 NSURLConnection 构建,其希望能在后台线程接收 Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}

此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息
当需要这个后台线程执行任务时,AFNetworking 通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中实现消息发送

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

timer

三种创建方式

1、第一种创建方式timer(系统会默认添加到Runloop的NSDefaultRunLoopMode中)

1),scheduledTimerWithTimeInterval: invocation: repeats:
2),scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:
//初始化NSTimer---1主线程直接
 [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeup) userInfo:nil repeats:YES];
- (void)timeup {
   NSLog(@"时间到%@",[NSThread currentThread]);
}

运行结果主线程直接启动不需要代码添加到runloop

2020-05-09 16:50:27.279293+0800 demoTest[85568:2475995] 时间到<NSThread: 0x600002a06880>{number = 1, name = main}
2020-05-09 16:50:28.278690+0800 demoTest[85568:2475995] 时间到<NSThread: 0x600002a06880>{number = 1, name = main}
2020-05-09 16:50:29.279113+0800 demoTest[85568:2475995] 时间到<NSThread: 0x600002a06880>{number = 1, name = main}
2020-05-09 16:50:30.278079+0800 demoTest[85568:2475995] 时间到<NSThread: 0x600002a06880>{number = 1, name = main}
2020-05-09 16:50:31.279187+0800 demoTest[85568:2475995] 时间到<NSThread: 0x600002a06880>{number = 1, name = main}

2、第二种创建方式timer(代码添加到runloop)

1),timerWithTimeInterval: target: selector: userInfo: repeats:
2),timerWithTimeInterval: invocation: repeats:
   //初始化NSTimer---2
  NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timeup) userInfo:nil repeats:YES];
  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

- (void)timeup {
    NSLog(@"NSTimer---2 %@",[NSThread currentThread]);
}

运行结果

2020-05-09 16:55:57.607139+0800 demoTest[85709:2481598] NSTimer---2 <NSThread: 0x6000036e2900>{number = 1, name = main}
2020-05-09 16:55:58.606587+0800 demoTest[85709:2481598] NSTimer---2 <NSThread: 0x6000036e2900>{number = 1, name = main}
2020-05-09 16:55:59.606793+0800 demoTest[85709:2481598] NSTimer---2 <NSThread: 0x6000036e2900>{number = 1, name = main}
2020-05-09 16:56:00.606252+0800 demoTest[85709:2481598] NSTimer---2 <NSThread: 0x6000036e2900>{number = 1, name = main}
2020-05-09 16:56:01.606854+0800 demoTest[85709:2481598] NSTimer---2 <NSThread: 0x6000036e2900>{number = 1, name = main}

3、第三种创建方式timer(代码添加到runloop)

initWithFireDate: interval: target: selector: userInfo: repeats:
 //初始化NSTimer---3
 NSTimer *timer= [[NSTimer alloc]initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:5]interval:1 target:self selector:@selector(timeup) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- (void)timeup {
    NSLog(@"NSTimer---3 %@",[NSThread currentThread]);
}

运行结果

2020-05-09 16:58:47.493477+0800 demoTest[85782:2484896] NSTimer---3 <NSThread: 0x6000012bc240>{number = 1, name = main}
2020-05-09 16:58:48.493312+0800 demoTest[85782:2484896] NSTimer---3 <NSThread: 0x6000012bc240>{number = 1, name = main}
2020-05-09 16:58:49.493144+0800 demoTest[85782:2484896] NSTimer---3 <NSThread: 0x6000012bc240>{number = 1, name = main}

CFRunLoopObserverRef 观察者,能够监听RunLoop的状态改变

NSLog(@"当前线程:%@",[NSThread currentThread]);
    //创建一个runloop监听者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        NSLog(@"监听runloop状态改变---%zd",activity);
    });

    //为runloop添加一个监听者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);

运行结果:能runloop状态

2020-05-10 17:33:08.727344+0800 demoOne[35093:1679140] 当前线程:<NSThread: 0x600002ae8240>{number = 1, name = main}
2020-05-10 17:33:39.321042+0800 demoOne[35093:1679140] 监听runloop状态改变---2
2020-05-10 17:33:39.321231+0800 demoOne[35093:1679140] 监听runloop状态改变---4
2020-05-10 17:33:39.322211+0800 demoOne[35093:1679140] 监听runloop状态改变---2
2020-05-10 17:33:39.322354+0800 demoOne[35093:1679140] 监听runloop状态改变---4
2020-05-10 17:33:39.324036+0800 demoOne[35093:1679140] 监听runloop状态改变---2
2020-05-10 17:33:39.324189+0800 demoOne[35093:1679140] 监听runloop状态改变---4
2020-05-10 17:33:39.324511+0800 demoOne[35093:1679140] 监听runloop状态改变---2
2020-05-10 17:33:39.324640+0800 demoOne[35093:1679140] 监听runloop状态改变---4
2020-05-10 17:33:39.337857+0800 demoOne[35093:1679140] 监听runloop状态改变---2
2020-05-10 17:33:39.338037+0800 demoOne[35093:1679140] 监听runloop状态改变---4
2020-05-10 17:33:39.338532+0800 demoOne[35093:1679140] 监听runloop状态改变---2

具体常用事例:tableview中渲染多张图片出现卡顿问题,可用runloop循环使用观察者每次只渲染几张大图解决卡顿

OB实例(加载1024x1024大图)
typedef void(^runloopBlock) (void);

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic,strong) UITableView * tableView;
@property (nonatomic,strong) NSMutableArray * taskes;
@property (nonatomic,assign) NSInteger maxQueueLength;
@end

static NSString * ID = @"IDCELL";
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
   
    
    //让runloop不休眠(一直存活,一直走"唤醒")runloop添加了timer 瞬间观察到runloop
    [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(timeMethond) userInfo:nil repeats:YES];
    _taskes = [[NSMutableArray alloc]init];
    _maxQueueLength = 9 ;
    //添加观察者
    [self addRunloopObserver];
    
     [self.view addSubview:self.tableView];
}

- (void)timeMethond{
    //不做任何事只为runloop唤醒
}


- (UITableView *)tableView{
    _tableView=[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
    _tableView.delegate=self;
    _tableView.dataSource=self;
    return _tableView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 140;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    
    return 100;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    
    return 1;
}

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    for (NSInteger i = 1; i <= 4; i++) {
        [[cell.contentView viewWithTag:i] removeFromSuperview];
        //优化卡顿
        [self addTaskes:^{
              [ViewController addImageWith:cell index:i];
        }];
//        //卡顿
//        [ViewController addImageWith:cell index:i];
    }

    return cell;
}


#pragma mark 关于Runloop C语言
- (void)addRunloopObserver{
    //获取runloop
    CFRunLoopRef runloop= CFRunLoopGetCurrent();
    //初始化runloop观察者上下文结构体
    CFRunLoopObserverContext context = {0,(__bridge void *)(self),&CFRetain,&CFRelease,NULL};
    //定义观察者
    static CFRunLoopObserverRef defaultObserver;
    defaultObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, NSIntegerMax-999, &callBack, &context);
    //观察添加到当前runloop
    CFRunLoopAddObserver(runloop, defaultObserver, kCFRunLoopCommonModes);
    CFRelease(defaultObserver);
}

//runloop中事件的执行体
static void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity,void * info){
    NSLog(@"唤醒");
    ViewController * VC = (__bridge ViewController *)info;
    if (VC.taskes.count==0) {
        return;
    }
    //取出任务
    runloopBlock block = VC.taskes.firstObject;
    block();
    [VC.taskes removeObjectAtIndex:0];
}

//添加block 任务
- (void)addTaskes:(runloopBlock)task{
    [self.taskes addObject:task];
    if (self.taskes.count > _maxQueueLength) {
        [self.taskes removeObjectAtIndex:0];
    }
}

+ (void)addImageWith:(UITableViewCell *)cell index:(NSInteger)index{
    UIImageView * imageV = [[UIImageView alloc]initWithFrame:CGRectMake(20*index+80*(index-1),5 , 80, 90)];
    imageV.tag = index;
    NSString * path = [[NSBundle mainBundle] pathForResource:@"imagetest" ofType:@"png"];
    UIImage * image = [UIImage imageWithContentsOfFile:path];
    imageV.image =  image;
    [UIView transitionWithView:cell.contentView duration:0.3 options:(UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionCrossDissolve) animations:^{
        [cell.contentView addSubview:imageV];
    } completion:nil];
}
1024X1024大图

七、补充

定时器:NSTimer 与GCD

NSTimer创建
@property(nonatomic,strong)NSTimer* timer;
 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeup) userInfo:nil repeats:YES];
    - (void)timeup {
       NSLog(@"时间到%@",[NSThread currentThread]);
    }

运行结果

2020-05-10 16:46:28.031652+0800 demoOne[34828:1650155] 时间到<NSThread: 0x600002fec380>{number = 1, name = main}
2020-05-10 16:46:29.032614+0800 demoOne[34828:1650155] 时间到<NSThread: 0x600002fec380>{number = 1, name = main}

取消timer

 [self.timer invalidate];
 self.timer = nil;
GCD timer创建
@property(nonatomic,strong)dispatch_source_t gcdTimer;
//0.创建一个队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    //1.创建一个GCD的定时器
    /*
     第一个参数:说明这是一个定时器
     第四个参数:GCD的回调任务添加到那个队列中执行,如果是主队列则在主线程执行
     */
    dispatch_source_t gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    //2.设置定时器的开始时间,间隔时间以及精准度
    //设置开始时间,三秒钟之后调用,注:GCD中的时间为纳秒NSEC_PER_SEC,3.0 *NSEC_PER_SEC即为3秒
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW,3.0 *NSEC_PER_SEC);
    //设置定时器工作的间隔时间
    uint64_t intevel = 1.0 * NSEC_PER_SEC;

    /*
     第一个参数:要给哪个定时器设置
     第二个参数:定时器的开始时间DISPATCH_TIME_NOW表示从当前开始
     第三个参数:定时器调用方法的间隔时间
     第四个参数:定时器的精准度,如果传0则表示采用最精准的方式计算,如果传大于0的数值,则表示该定时切换可以接收该值范围内的误差,通常传0
     该参数的意义:可以适当的提高程序的性能
     注意点:GCD定时器中的时间以纳秒为单位(面试)
     */
    dispatch_source_set_timer(gcdTimer, start, intevel, 0 * NSEC_PER_SEC);
    //3.设置定时器开启后回调的方法
    /*
     第一个参数:要给哪个定时器设置
     第二个参数:回调block
     */
    dispatch_source_set_event_handler(gcdTimer, ^{
        NSLog(@"------%@",[NSThread currentThread]);
    });

    //4.执行定时器
    dispatch_resume(gcdTimer);
 //注意:dispatch_source_t本质上是OC类,在这里是个局部变量,需要强引用
    self.gcdTimer = gcdTimer;

运行结果:在子线程

2020-05-10 16:49:08.220418+0800 demoOne[34883:1653302] ------<NSThread: 0x600003492ec0>{number = 7, name = (null)}
2020-05-10 16:49:09.219542+0800 demoOne[34883:1653302] ------<NSThread: 0x600003492ec0>{number = 7, name = (null)}
2020-05-10 16:49:10.219824+0800 demoOne[34883:1653302] ------<NSThread: 0x600003492ec0>{number = 7, name = (null)}
2020-05-10 16:49:11.220617+0800 demoOne[34883:1653309] ------<NSThread: 0x6000034fd940>{number = 5, name = (null)}

取消定时器

// 演示如何取消定时器
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 取消定时器
        dispatch_cancel(self.gcdTimer);
    });

异同点
NSTimer:
1、NSTimer是RunLoop的源
2、自定义的需要加入mode
3、 必须调用 invalidate 来停止其定时任务,并且NSTimer 对其Target是强引用,要注意Target 与 - NSTimer间造成的循环引用造成的内存泄漏
4、不准时(原因:1.RunLoop循环处理的时间,可能某个时刻runloop需要处理很多任务;2.受RunLoop模式的影响,如果NSTimer没有加入到NSRunLoopCommonModes的话,就会受到UITrackingRunLoopMode和NSDefaultRunLoopMode的切换影响)

GCD的timer
1、gcd的timer是dispatch的源
2、不需要加入mode,GCD内部已经将runloop进行封装
3、精度很高

持续更新~

上一篇下一篇

猜你喜欢

热点阅读