iOS多线程-RunLoop简介

  • 什么是RunLoop?

    • 从字面上来看是运行循环的意思.

    • 内部就是一个do{}while循环,在这个循环里内部不断的处理各种任务(比如:source/timer/Observer)

    • RunLoop的存在其实就是为线程而存在的.线程的作用就是执行一个特定的任务,但是默认情况下线程执行完任务后就不能再次执行任务,这是因为默认情况下线程是没有开启RunLoop的.如果开启RunLoop之后,线程执行完任务之后,会一直等待,直到再次接受到任务,接续执行任务.线程销毁前,会先释放这个线程所对应的RunLoop.

  • RunLoop基本作用

    • 保持程序的持续运行,保持线程的持续运行.

    • 处理App中的各种事件(比如触摸事件,定时器事件,Selector事件)

    • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

  • RunLoop对象

    • ios中有2套API来访问和使用RunLoop

    • 一套是Fundation(纯OC的)框架中的

      • NSRunLoop
      // 获得当前线程的RunLoop对象
      [NSRunLoop currentRunLoop];
      // 获得主线程的RunLoop对象
      [NSRunLoop mainRunLoop];
      
    • 一套是Core Fundation(纯C语言的)框架中的

      • CFRunLoopRef
      // 获得当前线程的RunLoop对象
      CFRunLoopGetCurrent();
      // 获得主线程的RunLoop对象
      CFRunLoopGetMain();
      
    • NSRunLoo和CFRunLoopRef都代表着RunLoop对象.NSRunLoop是基于CFRunLoopRef的一层OC包装

  • RunLoop与线程

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

    • 主线程的Runloop系统已经自动创建好了,子线程的RunLoop需要手动创建

    • RunLoop在第一次获取时由系统自动创建,在线程结束时销毁

    • 如果想给子线程创建RunLoop,不能直接alloc&init,只要调用获取当前线程RunLoop方法即可,系统会自动放回当前线程的RunLoop,如果当前线程没有RunLoop,系统会自动创建.

  • RunLoop相关类

  • Core Fundation中关于RunLoop的5个类

    • CFRunLoopRef: RunLoop对象
    • CFRunLoopModeRef: RunLoop运行模式.
    • CFRunLoopSoruceRef: 事件源(输入源)
    • CFRunLoopTimerRef:基于时间的触发器.
    • CFRunLoopObserverRef: 观察者,能够监听RunLoop的状态改变
  • CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop运行模式

  • 一个RunLoop对象包含若干个Mode(模式),每个Mode又包含若干个 source/Timer/Observer

  • RunLoop运行时,只能指定一个Mode, 这个Mode又称之为CurrentMode,然后RunLoo就执行CurrentMode中的source/Timer/Observer

  • 如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做是为了分隔开不同组的Source/Timer/Observer,让其不受影响

  • 系统默认注册了5个Mode:

    • NSDefaultRunLoopMode: App的默认Mode,通常主线程实在这个模式下运行
    • UITrackingRunLoopMode:界面跟踪Mode,用于界面控件(ScrollView,tableView等等)追踪触摸滑动,保证界面滑动时不受其他Mode影响
    • UIInitializationRunLoopMode:在刚启动App是进入的第一个Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode:接收系统事件的内部Mode,通常用不到
    • NSRunLoopCommonMode:这是一个占位的Mode,不是一种真正的Mode,(可以看成模式组,默认情况下包括了NSDefaultRunLoopMode,UITrackingRunLoopMode)两种模式.
  • CFRunLoopTimerRef

    • CFRunLoopTimerRef是基于时间的触发器

    • CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //创建一个NSTimer定时器,默认情况下NSTimer是不会执行的,只有把NSTimer添加到RunLoop中,由RunLoop管理执行
    NSTimer * timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(show) userInfo:nil repeats:YES];
    
    // 在当前线程中RunLoop添加一个timer, 并告诉runLoop, 这个timer只能在NSDefaultRunLoopMode模式下才能触发
    // runLoop会找到NSDefaultRunLoopMode,然后把timer添加NSDefaultRunLoopMode中的Timer数组中
     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    //在当前线程中RunLoop添加一个timer, 并告诉runLoop, 这个timer只能在NSRunLoopCommonModes模式下才能触发
    //runLoop会找到NSDefaultRunLoopMode和UITrackingRunLoopMode
    //然后把timer添加NSDefaultRunLoopMode和UITrackingRunLoopMode中的Timer数组中
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    //利用此方法创建的NSTimer, 系统会自动放入当前线程中的currentRunLoop中,并且只能在NSDefaultRunLoop模式下才能触发
    NSTimer * timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    //虽然通过类方法scheduledTimerWithTimeInterval创建NSTimer,会自动添加到NSDefaultRunLoopMode模式中
    //但我们还是可以修改它的模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

}
```

  • CFRunLoopSoruceRef

    • 按照官方文档,source的分类:

      • Port-Based Sources:基于端口的事件源:监听程序响应的端口,基于端口事件是由系统内核自动发送的.
      • Custom Input Sources: 自定义输入源:监听自定义事件源,而自定义的输入源是需要人工从其他线程发送
      • Cocoa Perfrom Selector Source: selector事件源
    • 按照源码函数调用栈,source的分类:

      • Source0:非基于Prot(端口)的,是用户主动触发的事件
      • Source1:基于Prot(端口)的,通过内核和其他线程相互发送消息
  • CFRunLoopObserverRef

    • CFRunLoopObserverRef:观察者对象,可以监听RunLoop的状态

    • RunLoop状态:

      • kCFRunLoopEntry 即将进入runLoop
      • kCFRunLoopBeforeTimers 即将处理Timer
      • kCFRunLoopBeforeSources 即将处理source(事件源)
      • kCFRunLoopBeforeWaiting 即将进入休眠
      • kCFRunLoopAfterWaiting 即将从休眠中醒来
      • kCFRunLoopExit 即将退出runLoop
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //创建一个CFRunLoopObserverRef
    /*
     第一个参数: CFRunLoopObserverRef(观察者)分配内存空间方式
     第二个参数: 监听那些状态 kCFRunLoopAllActivities(监听所有状态)
     第三个参数: 是否每次都要监听
     第四个参数: 优先级
     第五个参数: 回调函数
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES,0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            // observer监听对象
            //activity Runloop当前状态
    });
    /*
     第一个参数: 为那个线程下的RunLoop添加CFRunLoopObserverRef(观察者)
     第二个参数: 需要添加的CFRunLoopObserverRef(观察者)
     第三个参数: 把监听添加到RunLoop那个模式中
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    //记得内存管理,因为Core Foundation不在ARC管理范围内
    //带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
    //销毁对象函数:CFRelease对象
    CFRelease(observer);
    }
    
  • RunLoop处理逻辑

  • 如果RunLoop中没有Timer或source,RunLoop就会立刻退出

  • 每次运行RunLoop,RunLoop会自动处理之前未处理的消息,并通知相关观察者.具体顺序如下:

    • 1.通知观察者RunLoop已经启动

    • 2.通知观察者即将开始启动定时器

    • 3.通知观察者即将启动非基于端口的事件源

    • 4.启动任何准备好的非基于端口的事件源

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

    • 6.通知观察者线程进入休眠

    • 7.将线程置于休眠直到任意下面的事件发生:

      • 某一事件到达基于端口的源
      • 定时器启动
      • RunLoop设置的时间已经超时.(系统底层会给RunLoop设置一个超时时间,源码中设置的是:9999999999.0)
      • RunLoop被手动唤醒
    • 8.通知观察者线程将被唤醒.

    • 9.处理未处理的事件

      • 如果用户定义的定时器启动,处理定时器事件并重启RunLoop.进入步骤2
      • 如果事件源启动,传递相应的消息
      • 如果RunLooop被显示唤醒而且时间还没超时,重启RunLoop.进入步骤2
    • 10.通知观察者RunLoop结束.

  • 如何让子线程成为常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)

//
//  ViewControllerRunLoop.m
//
//  Created by yuxuan on 16/2/17.
//  Copyright © 2016年 apple. All rights reserved.
//

#import "ViewControllerRunLoop.h"


@interface ViewControllerRunLoop ()



@property(nonatomic,strong)NSThread * thread;

@end
@implementation ViewControllerRunLoop



-(void)viewDidLoad{

    //创建子线程执行任务
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];

    [self.thread start];

}

-(void)run{
    NSLog(@"跑起来");
    //默认情况下,子线程是不会常驻的
    //只有子线程中runloop启动,并且runloop中有source或timer,才会常驻
    //只有常驻线程才能再次执行任务,因为线程中有runloop来处理事件了
    //子线程的runloop是需要手动创建的, 并且需要手动启动
    NSRunLoop * rl =  [NSRunLoop currentRunLoop];

    //如果子线程的runloop没有 source / timer 的话, 哪么子线程的runloop会立即关闭
    //在runLoop中添加一个timer
    [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];

    //启动runloop
    [rl run];

    //如果线程成为了常驻线程,你会发现,不会执行到这行代码
    //也就是说这个方法不会执行完,
    NSLog(@"end");
}

-(void)timerRun{
    NSLog(@"%s",__func__);
}

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

    // 让子线程再次执行任务
    [self performSelector:@selector(againRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}

-(void)againRun{

    NSLog(@"再次跑起来");
}


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

推荐阅读更多精彩内容