美文网首页iOS
iOS runloop和线程有什么关系?

iOS runloop和线程有什么关系?

作者: 进击的阿牛哥 | 来源:发表于2019-06-12 12:39 被阅读9次

    Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。

    runloop 和线程的关系:

    1 一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。

    2 我们只能在当前线程中操作当前线程的RunLoop,而不能去操作其他线程的RunLoop。

    3 RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候。

    4 主线程的RunLoop对象系统自动帮助我们创建好了(原理如下),而子线程的RunLoop对象需要我们主动创建。

    1. 主线程的run loop默认是启动的。

    iOS的应用程序里面,程序启动后会有一个如下的main()函数

    上边的代码中开启RunLoop的过程可以简单的理解为如下代码:

    从上边可看出,程序一直在do-while循环中执行,所以UIApplicationMain函数一直没有返回,我们在运行程序之后程序不会马上退出,会保持持续运行状态。

    重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。

    2. 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。

    3. 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 

    NSRunLoop *runloop = [NSRunLoop currentRunLoop];

    RunLoop原理

    RunLoop运行逻辑图

    这张图对于我们理解RunLoop来说太有帮助了,下边我们可以来说下官方文档给我们的RunLoop逻辑。

    在每次运行开启RunLoop的时候,所在线程的RunLoop会自动处理之前未处理的事件,并且通知相关的观察者。

    具体的顺序如下:

    1 通知观察者RunLoop已经启动

    2 通知观察者即将要开始的定时器

    3 通知观察者任何即将启动的非基于端口的源

    4 启动任何准备好的非基于端口的源

    5 如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9

    6 通知观察者线程进入休眠状态

    7 将线程置于休眠知道任一下面的事件发生:

    * 某一事件到达基于端口的源

    * 定时器启动

    * RunLoop设置的时间已经超时

    * RunLoop被显示唤醒

    8 通知观察者线程将被唤醒

    9 处理未处理的事件

    * 如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2

    * 如果输入源启动,传递相应的消息

    * 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2

    10 通知观察者RunLoop结束。

    RunLoop实战应用

    哈哈,讲了这么多云里雾里的原理知识,下边终于到了实战应用环节。

    光弄懂是没啥用的,能够实战应用才是硬道理。下面讲解一下RunLoop的几种应用。

    1 NSTimer的使用

    NSTimer的使用方法在讲解CFRunLoopTimerRef类的时候详细讲解过,具体参考上边2.3 CFRunLoopTimerRef

    2 ImageView推迟显示

    有时候,我们会遇到这种情况:

    当界面中含有UITableView,而且每个UITableViewCell里边都有图片。这时候当我们滚动UITableView的时候,如果有一堆的图片需要显示,那么可能会出现卡顿的现象。

    怎么解决这个问题呢?

    这时候,我们应该推迟图片的显示,也就是ImageView推迟显示图片。有两种方法:

    1. 监听UIScrollView的滚动

    因为UITableView继承自UIScrollView,所以我们可以通过监听UIScrollView的滚动,实现UIScrollView相关delegate即可。

    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;

    -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; 

    这两方法一个是停止拖拽时调用   一个是当滚动视图嘎然而止 时调用    在这两方法里面写给ImageView加载图片的方法   就能避免因为加载图片导致UITableView滚动时卡顿的问题

    ***  -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

    {

        //如果tableview停止滚动,开始加载图像

        if(!decelerate){

            [self loadImagesForOnscreenRows];

        }

    }

    *** -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

    {

        //如果tableview停止滚动,开始加载图像

        [self loadImagesForOnscreenRows];

    }

    2. 利用PerformSelector设置当前线程的RunLoop的运行模式

    kCFRunLoopDefaultMode:App的默认运行模式,通常主线程是在这个运行模式下运行

    UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)

    然后 我们滑动UITableView时候 RunLoop的运行模式就会变为UITrackingRunLoopMode   

    所以我们把给ImageView加载图片的方法用PerformSelector设置当前线程的RunLoop的运行模式kCFRunLoopDefaultMode  这样滑动时候就不会执行加载图片的方法了

    也就能避免因为加载图片导致UITableView滚动时卡顿的问题

    利用performSelector方法为UIImageView调用setImage:方法,并利用inModes将其设置为RunLoop下NSDefaultRunLoopMode运行模式。代码如下:

    [cell performSelector:@selector(setImage)withObject:nil afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

    下边利用Demo演示一下该方法。

    在项目中的Main.storyboard中添加一个UIImageView,并添加属性,并简单添加一下约束(不然无法显示)如下图所示。

    添加UIImageView

    在项目中拖入一张图片,比如下图。

    1 然后我们在touchesBegan方法中添加下面的代码,在Demo中请在touchesBegan中调用[self showDemo3];方法。

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 

    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"tupian"] afterDelay:4.0 inModes:@[NSDefaultRunLoopMode];

    }

    1 运行程序,点击一下屏幕,然后拖动UIText View,拖动4秒以上,发现过了4秒之后,UIImageView还没有显示图片,当我们松开的时候,则显示图片,效果如下:

    这样我们就实现了在拖动完之后,在延迟显示UIImageView。

    3 后台常驻线程(很常用)

    我们在开发应用程序的过程中,如果后台操作特别频繁,经常会在子线程做一些耗时操作(下载文件、后台播放音乐等),我们最好能让这条线程永远常驻内存。

    那么怎么做呢?

    添加一条用于常驻内存的强引用的子线程,在该线程的RunLoop下添加一个Sources,开启RunLoop。

    具体实现过程如下:

    在项目的ViewController.m中添加一条强引用的thread线程属性,如下图:

    在viewDidLoad中创建线程self.thread,使线程启动并执行run1方法,代码如下。在Demo中,请在viewDidLoad调用[self showDemo4];方法。

    运行之后发现打印了----run1-----,而未开启RunLoop则未打印。

    这时,我们就开启了一条常驻线程,下边我们来试着添加其他任务,除了之前创建的时候调用了run1方法,我们另外在点击的时候调用run2方法。

    那么,我们在touchesBegan中调用PerformSelector,从而实现在点击屏幕的时候调用run2方法。具体代码如下:

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

    {   

        //利用performSelector,在self.thread的线程中调用run2方法执行任务

        [self performSelector:@selector(run2)onThread:self.thread withObject:nil waitUntilDone:NO];

    }

    -(void)run2

    {

        NSLog(@"----run2------");

    }

    经过运行测试,除了之前打印的----run1-----,每当我们点击屏幕,都能调用----run2------

    这样我们就实现了常驻线程的需求。

    相关文章

      网友评论

        本文标题:iOS runloop和线程有什么关系?

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