欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

RunLoop 总结:RunLoop的应用场景(二)

程序员文章站 2022-05-27 08:17:46
runloop 总结:runloop的应用场景(二) 上一篇讲了使用runloop保证子线程的长时间存活,而不是执行完任务后就立刻销毁的应用场景。这一篇就讲述一下runloop如何保证nstimer...

runloop 总结:runloop的应用场景(二)

上一篇讲了使用runloop保证子线程的长时间存活,而不是执行完任务后就立刻销毁的应用场景。这一篇就讲述一下runloop如何保证nstimer在视图滑动时,依然能正常运转。

参考资料

好的书籍都是值得反复看的,那好的文章,好的资料也值得我们反复看。我们在不同的阶段来相同的文章或资料或书籍都能有不同的收获,那它就是好文章,好书籍,好资料。
关于ios 中的runloop资料非常的少,以下资料都是非常好的。

cf框架(这是一份很重要的源码,可以看到cf框架的每一次迭代,我们可以下载最新的版本来分析,或与以下文章对比学习。目前最新的是cf-1153.18.tar.gz)runloop官方文档(学习ios的任何技术,官方文档都是入门或深入的极好手册;我们也可以在xcode—>help—>docementation and api reference —>搜索runloop—> guides(59)—>《threading programming guide:run loops》这篇即是)深入理解runloop(不要看到右边滚动条很长,其实文章占篇幅2/5左右,下面有很多的评论,可见这篇文章的火热)runloop个人小结 (这是一篇总结的很通俗容易理解的文章)sunnyxx线下分享runloop(这是一份关于线下分享与讨论runloop的视频,备用地址:https://pan.baidu.com/s/1plm4vf9)iphonedevwiki中的cfrunloop(commonmodes中其实包含了三种mode,我们通常知道两种,还有一种是啥,你知道么?)*中的event loop(可以看看这篇文章了解一下事件循环)

使用场景

1.我们经常会在应用中看到tableview 的header 上是一个横向scrollview,一般我们使用nstimer,每隔几秒切换一张图片。可是当我们滑动tableview的时候,顶部的scollview并不会切换图片,这可怎么办呢?
2.界面上除了有tableview,还有显示倒计时的label,当我们在滑动tableview时,倒计时就停止了,这又该怎么办呢?

场景中的代码实现

我们的定时器timer是怎么写的呢?
一般的做法是,在主线程(可能是某控制器的viewdidload方法)中,创建timer。
可能会有两种写法,但是都有上面的问题,下面先看下timer的两种写法:

// 第一种写法
nstimer *timer = [nstimer timerwithtimeinterval:1.0 target:self selector:@selector(timerupdate) userinfo:nil repeats:yes];
[[nsrunloop currentrunloop] addtimer:timer formode:nsdefaultrunloopmode];
[timer fire];
// 第二种写法
[nstimer scheduledtimerwithtimeinterval:1.0 target:self selector:@selector(timerupdate) userinfo:nil repeats:yes];

上面的两种写法其实是等价的。第二种写法,默认也是将timer添加到nsdefaultrunloopmode下的,并且会自动fire。。
要验证这一结论,我们只需要在timerupdate方法中,将当前runloop的currentmode打印出来即可。

- (void)timerupdate
{
    nslog(@"当前线程:%@",[nsthread currentthread]);
    nslog(@"启动runloop后--%@",[nsrunloop currentrunloop].currentmode);
//    nslog(@"currentrunloop:%@",[nsrunloop currentrunloop]);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.count ++;
        nsstring *timertext = [nsstring stringwithformat:@"计时器:%ld",self.count];
        self.timerlabel.text = timertext;
    });
}
// 控制台输出结果:
2016-12-02 15:33:57.829 runloopdemo02[6698:541533] 当前线程:{number = 1, name = main}
2016-12-02 15:33:57.829 runloopdemo02[6698:541533] 启动runloop后--kcfrunloopdefaultmode

然后,我们在滑动tableview的时候timerupdate方法,并不会调用。
* 原因是啥呢?*
原因是当我们滑动scrollview时,主线程的runloop 会切换到uitrackingrunloopmode这个mode,执行的也是uitrackingrunloopmode下的任务(mode中的item),而timer 是添加在nsdefaultrunloopmode下的,所以timer任务并不会执行,只有当uitrackingrunloopmode的任务执行完毕,runloop切换到nsdefaultrunloopmode后,才会继续执行timer。

* 要如何解决这一问题呢?*
解决方法很简单,我们只需要在添加timer 时,将mode 设置为nsrunloopcommonmodes即可。

- (void)timertest
{
    // 第一种写法
    nstimer *timer = [nstimer timerwithtimeinterval:1.0 target:self selector:@selector(timerupdate) userinfo:nil repeats:yes];
    [[nsrunloop currentrunloop] addtimer:timer formode:nsrunloopcommonmodes];
    [timer fire];
    // 第二种写法,因为是固定添加到defaultmode中,就不要用了
}

从runloop官方文档和 iphonedevwiki中的cfrunloop可以看出,nsrunloopcommonmodes并不是一种mode,而是一种特殊的标记,关联的有一个set ,官方文档说:for cocoa applications, this set includes the default, modal, and event tracking modes by default.(默认包含nsdefaultrunloopmode、nsmodalpanelrunloopmode、nseventtrackingrunloopmode)
添加到nsrunloopcommonmodes中的还没有执行的任务,会在mode切换时,再次添加到当前的mode中,这样就能保证不管当前runloop切换到哪一个mode,任务都能正常执行。并且被添加到nsrunloopcommonmodes中的任务会存储在runloop 的commonmodeitems中。

其他一些关于timer的坑

我们在子线程中使用timer,也可以解决上面的问题,但是需要注意的是把timer加入到当前runloop后,必须让runloop 运行起来,否则timer仅执行一次。

示例代码:

//首先是创建一个子线程
- (void)createthread
{
    nsthread *subthread = [[nsthread alloc] initwithtarget:self selector:@selector(timertest) object:nil];
    [subthread start];
    self.subthread = subthread;
}

// 创建timer,并添加到runloop的mode中
- (void)timertest
{
    @autoreleasepool {
        nsrunloop *runloop = [nsrunloop currentrunloop];
        nslog(@"启动runloop前--%@",runloop.currentmode);
        nslog(@"currentrunloop:%@",[nsrunloop currentrunloop]);
        // 第一种写法,改正前
    //    nstimer *timer = [nstimer timerwithtimeinterval:1.0 target:self selector:@selector(timerupdate) userinfo:nil repeats:yes];
    //    [[nsrunloop currentrunloop] addtimer:timer formode:nsdefaultrunloopmode];
    //    [timer fire];
        // 第二种写法
      [nstimer scheduledtimerwithtimeinterval:1.0 target:self selector:@selector(timerupdate) userinfo:nil repeats:yes];

        [[nsrunloop currentrunloop] run];
    }
}

//更新label
- (void)timerupdate
{
    nslog(@"当前线程:%@",[nsthread currentthread]);
    nslog(@"启动runloop后--%@",[nsrunloop currentrunloop].currentmode);
    nslog(@"currentrunloop:%@",[nsrunloop currentrunloop]);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.count ++;
        nsstring *timertext = [nsstring stringwithformat:@"计时器:%ld",self.count];
        self.timerlabel.text = timertext;
    });
}

添加timer 前的控制台输出:

RunLoop 总结:RunLoop的应用场景(二)

添加timer后的控制台输出:

RunLoop 总结:RunLoop的应用场景(二)

从控制台输出可以看出,timer确实被添加到nsdefaultrunloopmode中了。可是添加到子线程中的nsdefaultrunloz喎?/kf/ware/vc/" target="_blank" class="keylink">vce1vzgxa76oszt7c28jnus659ravo6x0aw1lcra8xny5u7rc1f2zo7xe1mvxqqgj1elt1srhzqrjtstyo788l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9jb2rlpjwvcd4ncjxwpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlpjxjb2rlptxivs3kx7bgz9+zznprcnvubg9vclxeudjptchlo6zdv9k7upbp37pmtrzt0nk7upbt69auudjbqrxeunvutg9vckostvjdv9k7upzsdw5mb29wv8ne3lvh09c24lj2tw9kzagjq1bvu+hu2rbgupbp37pmvoth0lu7wltwtndqym7o8aoss8rp1rp2tuc49s/fs8znrmqx1rtq0lxe0ke5+6gj1rtq0lxeym7o8cbkyrw+zcrhunvutg9vcmilupe49k1vzgxa79a00nc497j2axrlbagj0vloqlj1bkxvb3dkx7bawak1xmg9upajrm/gu6wyu7vh07dp7kosy/ns1nta19pp37pmzo2803rpbwvyo6y7rlavytpnvmqxo6x0aw1lcstc1f2zo9tl0nchozwvy29kzt48l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9jb2rlpjwvy29kzt48l2nvzgu+pc9wpg0kpggyiglkpq=="总结">总结

1、如果是在主线程中运行timer,想要timer在某界面有视图滚动时,依然能正常运转,那么将timer添加到runloop中时,就需要设置mode 为nsrunloopcommonmodes。
2、如果是在子线程中运行timer,那么将timer添加到runloop中后,mode设置为nsdefaultrunloopmode或nsrunloopcommonmodes均可,但是需要保证runloop在运行,且其中有任务。