(iOS干货)关于RunLoop的详细整理

RunLoop

1.RunLoop的基本作用

  • 1.保持程序的持续运行
  • 2.处理app中的各种事件(比如触摸事件、定时器事件、selector事件
  • 3.节省CPU资源,提高程序性能,有事情就做事情,没事情就休息

2.RunLoop与线程

  • 1.关系:一个Runloop对应着一条唯一的线程
  • 2.创建:主线程Runloop已经创建好了,子线程的runloop需要手动创建 创建方式:
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

子线程创建的runloop还需要手动的开启:

[currentRunloop run];
  • 3.生命周期:Runloop在第一次获取时创建,在线程结束时销毁

2.RunLoop运行模式对NSTimer定时器的影响

  • 1.首先创建一个定时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

这里的task为一个普通的打印操作,这时候往控制器的view里拖一个Text View,

  • 2.把定时器对象添加到runloop中,并且制定runloop的运行模式为默认:只有当runloop的运行模式为NSDefaultRunLoopMode的时候定时器才工作,也就是说这时候如果滑动Text View,定时器就不工作了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  • 3.如果想在滚动Text View的时候,定时器也工作,可以:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

但是如果这样做的话,当我们停止滚动的时候定时器又不工作了

  • 4.有时候我们需要在默认情况下以及在滚动的时候都让定时器工作,这时候我们就可以:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  • 由此可见:NSRunLoopCommonModes = NSDefaultRunLoopMode & UITrackingRunLoopMode

  • 拓展:①scheduledTimerWithTimeInterval方法:创建定时器并默认添加到当前线程的Runloop中指定默认运行模式

  • ②timerWithTimeInterval:创建定时器,如果该定时器要工作还需要添加到runloop中并指定相应的运行模式

  • 通过以上代码我们不难看出,NSTimer的定时器是受运行模式影响的,,而开发中我们有时候彻底去除这种影响,很显然,NSTimer定时器不能做到这点,这时,我们可以使用GCD的定时器。

GCD定时器

GCD定时器是不受运行模式的影响的,因此,以后尽量使用此定时器,,该定时器的具体参数如下所示:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //01 创建定时器对象
    /*
     第一个参数:是一个宏,表示要创建的是一个定时器
     第二个参数和第三个参数:默认总是传0 描述信息
     第四个参数:队列  决定代码块(dispatch_source_set_event_handler)在哪个线程中调用(主队列-主线程)
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

    //02 设置定时器
    /*
     第一个参数:定时器对象
     第二个参数:定时器开始计时的时间(开始时间)
     第三个参数:设置间隔时间 GCD的时间单位:纳秒
     第四个参数:精准度
     */
    //这句代码是设置开始两秒之后再调用
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(timer, t, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //03 GCD定时器时间到了之后要执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD----%@",[NSThread currentThread]);
    });

    //04 默认是在挂起状态,需要手动恢复执行
    dispatch_resume(timer);

    //如果没有一个强指针指向,一创建就回被释放。
    self.timer = timer;

}

CFRunLoopModeRef

  • a.基本说明:
  • CFRunloopModeRef代表着Runloop的运行模式
  • 一个Runloop中可以有多个mode,一个mode里面又可以有多个source\observer\timer等等
  • 每次runloop启动的时候,只能指定一个mode,这个mode被称为该Runloop的当前mode
  • 如果需要切换mode,只能先退出当前Runloop,再重新指定一个mode进入
  • 这样做主要是为了分割不同组的定时器等,让他们相互之间不受影响
  • b.Model的分类:
  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunloopSourceRef

分类(根据函数调用栈来区分):

  • 1.Source0:非基于Port的 :凡是用户主动触发事件都是Source0事件
  • 2.Source1:基于Port的:凡是系统事件都是Source1事件

CFRunLoopObserverRef

作用:监听运行循环的状态

selecter事件与RunLoop之间的关系

  • 默认情况下,selecter事件是被添加到当前的runloop中执行的,并且指定了运行模式为默认,由此可见,performSelecter事件是受运行模式的影响的,仔细查看以下代码,看看有什么问题:
    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

一个显而易见的问题就是我们在子线程中来设置显示图片,然而抛开这个问题不管,图片依旧不会被设置上去,因为在task中缺少一个运行循环,我们需要手动开启一个子运行循环才可以。

继续查看一下代码,图片会被设置到imageView上面吗?

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];

    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
    NSLog(@"+++++");
}

答案是否定的,因为我们虽然开启了子运行循环,但是当我们开启这个循环的时候,当前循环里既没有source事件(包括timer事件),也没有selecter事件,于是循环立刻就退出了。正确的书写方式如下:

    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];

    -(void)task{
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
      NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
    NSLog(@"+++++");
}

注意:runLoop是一个死循环,因此++++是不会打印的

使用RunLoop实现常驻线程

众所周知,我们手动开启的子线程在执行完任务之后就会销毁,而有时候我们需要一个子线程在执行完当前任务后,不要销毁,等我们需要的时候再来执行其它任务,这就用到了常驻线程。

  • 假设我们又这样一个需求,但我们点击按钮1的时候会开启一条子线程来执行run1任务,当我们点击按钮2的时候,再让刚才的线程来执行run2任务,具体实现代码如下:
- (IBAction)btn1:(id)sender {
    //01 创建线程,执行任务
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run1) object:nil];

    //02 执行任务
    [thread start];

    self.thread = thread;
}

为了不让刚开启的线程销毁,我们需要给它添加一个运行循环,保证它不释放:

-(void)run1
{
    NSLog(@"run1---%@",[NSThread currentThread]);
    //001 获得当前线程对应的runloop对象
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];

    //002 为runloop添加input soucre或者是timer souce或selecter事件(最好就是一个基于端口的事件,这样就不会去执行不必要的方法)
    [currentRunloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

//以下为其它保证程序运行的方案,不推荐使用。
    //[self performSelector:@selector(run3) withObject:nil afterDelay:3.0];
    //[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];

    //003 启动runloop
    //runUntilDate |run 内部都指定了运行模式为默认
    [currentRunloop run];

}

按钮2:

- (IBAction)goOnBtnClick:(id)sender {
    //让之前创建的线程继续执行
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
}

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

RunLoop的自动释放池

runloop的自动释放池什么时候创建释放?

    1. 当runloop进入的时候会创建一个自动释放池。
  • 2)当runloop退出的时候会把之前的自动释放池销毁。
  • 3)当runloop即将进入休眠的时候会把之前的自动释放池先销毁,然后创建一个新的自动释放池。
720984-20151203120852377-618169617.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容