RunLoop由浅到深,个人小结

文艺求关注.png
  • <h5>初识RunLoop</h5>
    • 从字面意义看:运行循环
  • <b>基本作用</b>
    • 保持程序的运行循环
    • 处理app中的各种时间(比如触摸事件、定时器事件、selector事件)
    • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
    • . . . . . . .
  • <b>main函数中的runLoop</b>
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • UIApplicationMain函数内部就启动了一个RunLoop
  • 所以UIApplication函数一直没有返回,保持了程序的持续运行
  • 这个默认启动的RunLoop是跟主线程相关联的

  • <h5>RunLoop对象</h5>
    • iOS中有2套API来访问和使用RunLoop
      • Foundation
        - NSRunLoop
      • Core Foundation
        - CFRunLoopRef
    • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    • NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

  • <h5>RunLoop与线程间关系</h5>
    • 每条线程都有唯一的一个与之对应的RunLoop对象
    • 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
    • RunLoop在第一次获取时创建,在线程结束时销毁
  • <h5>获得RunLoop对象的方法</h5>
    • Foundation
      [NSRunLoop currentRunLoop]; //获得当前线程的RunLoop对象
      [NSRunLoop mainRunLoop]; //获得主线程的RunLoop对象
    • Core Foundation
      CFRunLoopGetCurrent(); //获得当前线程的RunLoop对象
      CFRunLoopGetMain(); //获得主线程的RunLoop对象
    • 如何转化如上得到的两个RunLoop函数,使其地址一样
      mainRunLoop.getCFRunLoop

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}

- (void) run {
    // 如何创建子线程对应的RunLoop,比较奇葩
    // currentRunLoop本身是懒加载,会在第一次调用的时候判断是否存在,如不存在,自动创建
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
    NSLog(@"run ---------- %@", [NSThread currentThread]);
}

  • <h5>RunLoop的相关类</h5>
    • Core Foundation中关于RunLoop的5个类
      CFRunLoopRef
      CFRunLoopModeRef //RunLoop的运行模式
      CFRunLoopSourceRef // 数据源、事件源
      CFRunLoopTimerRef //NSTimer
      CFRunLoopObserverRef // 观察、监听RunLoop
RunLoop5各类对应关系.png

<b>* 注意点:</b>

  • 在RunLoop中有多个运行模式,但是RunLoop只能选择一种模式运行
  • mode里面至少要有一个timer或者source,observer可有可无(只是用来观察/监听RunLoop)

  • <b>CFRunLoopModeRef</b>
    • <b>CFRunLoopModeRef代表RunLoop的运行模式</b>
      • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
      • 每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
      • 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
    • <b>系统默认注册了5个Mode</b>
      • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行(常用)
      • UITrackingRunLoopMode: 界面追踪Mode,用于tableView、ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响(常用)
      • UIInitializationRunLoopMode:在刚启动App时,进入的第一个mode,启动后就不再使用
      • GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
      • kCFRunLoopCommonModes:这是一个占位用的Mode,不是真正的Mode
  • <h5> CFRunLoopModeRef使用</h5>
- (void)timer {
    // 1.创建定时器
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    /**
     2.添加定时器到RunLoop中,指定runLoop的运行模式
     @param NSTimer 定时器
     @param Mode runLoop的运行模式
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //UITrackingRunLoopMode: 界面追踪
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    //NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
    //占用,标签,凡是添加到NSRunLoopCommonModes中的事件都会被同时添加到打上common的运行模式上
    /**
     0 : <CFString 0x10af41270 [0x10a0457b0]>{contents =
         "UITrackingRunLoopMode"}
     2 : <CFString 0x10a065b60 [0x10a0457b0]>{contents = "kCFRunLoopDefaultMode"}
     */
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];  
}
- (void) run {
    NSLog(@"run -----------%@------%@", [NSThread currentThread], [NSRunLoop currentRunLoop]);
}
  • <b>子线程中使用RunLoop的注意点</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
}
- (void)timer {
    
    // 如果是在子线程中使用此timer创建方式,则需要手动创建runLoop
    NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
    
    // 该方法内部自动添加到runLoop中,并且设置运行模式为默认
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    // 子线程中创建runLoop之后不会执行,需要手动开启runLoop
    [currentRunLoop run];
}

  • <h5>定时器补充—不受RunLoop影响的定时器(GCD定时器)</h5>
@property (nonatomic, strong) dispatch_source_t timer;

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

    /**
     01.创建GCD中的定时器
     @param DISPATCH_SOURCE_TYPE_TIMER source的类型,表示是定时器
     @param 0 描述信息,线程ID
     @param 0 更详细的描述信息
     @param dispatchQueue 队列,确定GCD定时器中的任务在哪个线程中执行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    /**
     02.设置定时器(起始时间|间隔时间|精准度)
     @param timer 定时器对象
     @param DISPATCH_TIME_NOW 起始时间,DISPATCH_TIME_NOW 从现在开始计时
     @param intervalInSeconds * NSEC_PER_SEC 间隔时间
     @param leewayInSeconds * NSEC_PER_SEC 精准度,绝对精准0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    // 03.设置定时器执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);
    });
    
    // 04.启动执行
    dispatch_resume(timer);
    
    self.timer = timer;
}
  • <b>注意点:</b>
    • 1.GCD的定时器与NSTimer的定时器相比,GCD的定时器时间更为精准
    • 2.GCD的定时器完全不受RunLoop Mode的影响

  • <h5>CFRunLoopSourceRef</h5>
    • CFRunLoopSourceRef是事件源(输入源)

    • 以前的分法

      • Port-Based Sources //基于端口的事件
      • Custom Input Sources //自定义事件
      • Cocoa Perform Selector Sources //perform Selector
    • 现在的分发:(通过函数调用站的方式来分)

      • Source0:非基于Port的 //用户触发的事件
      • Source1:基于Port的 //系统内部的消息事件

  • <h5>CFRunLoopObserverRef</h5>
- (void) observer {
    /**
     // 01.创建监听者
     @param allocator 制定如何分配存储空间
     @param activities 要监听的状态 kCFRunLoopAllActivities 所有状态
     @param repeats 是否持续监听
     @param order 优先级,总是传0
     @param observer activity 当状态改变的时候回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        /**
         kCFRunLoopEntry = (1UL << 0),          //即将进入runLoop
         kCFRunLoopBeforeTimers = (1UL << 1),   //即将处理timer事件
         kCFRunLoopBeforeSources = (1UL << 2),  //即将处理sources事件
         kCFRunLoopBeforeWaiting = (1UL << 5),  //即将进入睡眠
         kCFRunLoopAfterWaiting = (1UL << 6),   //被唤醒
         kCFRunLoopExit = (1UL << 7),           //runloop推出
         kCFRunLoopAllActivities = 0x0FFFFFFFU  //所有状态
         */
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入runLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理sources事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入睡眠");
                break;
            case kCFRunLoopExit:
                NSLog(@"runloop推出" );
                break;
            case kCFRunLoopAllActivities:
                NSLog(@"所有状态");
                break;
            default:
                break;
        }
    });
}

  • <h5>RunLoop处理逻辑—官方版</h5>


    RunLoop处理逻辑-官方版.png
  • <b>RunLoop的事件队列</b>
    每次运行run loop,你的线程的run loop会自动处理之前未处理的消息,并通知相关的观察者。具体顺序如下:
    • 1.通知观察者run loop已经启动
    • 2.通知观察者任何即将要开始的定时器
    • 3.通知贯彻着任何即将启动的非基于端口的源
    • 4.启动任何准备好的非基于端口的源
    • 5.如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9
    • 6.通知观察者线程进入休眠
    • 7.将线程置于休眠直到任一下面的事件发生:
      • 某一事件到达基本端口的源
      • 定时器启动
      • RunLoop设置的事件已经超时
      • RunLoop被显示唤醒
    • 8.通知观察者线程被唤醒
    • 9.处理为处理的事件
      • 如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
      • 如果输入源启动,传递相应的消息
      • 如果run loop被显示唤醒而且时间还未超时,重启run loop。进入步骤2
    • 10.通知观察者run loop结束

  • <h5>RunLoop处理逻辑—网友整理版</h5>


    RunLoop处理逻辑-网友整理版.png

  • <h5>RunLoop应用—常驻线程</h5>
    RunLoop在开发中有很多应用场景,比如说:处理NSTimer不工作,ImageView的显示,PerformSelector事件,常驻线程,以及自动释放池等
  • <b>常驻线程</b>
@property (nonatomic, strong) NSThread *thread;
// 在storyboard中有两个按钮,创建线程、让线程继续执行任务
//需求:如何保证子线程不挂掉,同时执行其他任务
- (IBAction)creatThreadBtnClick:(id)sender {
    // 01.创建线程
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
    [self.thread start];
}
- (void)task1 {
    NSLog(@"task1-----------%@", [NSThread currentThread]);
// 解决方法(保证子线程不挂掉,同时执行其他任务):RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //保证RunLoop不退出
    // 方法一:添加Timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runOperation) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    // 方法二:添加Source
    [runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];    // 给runLoop设置时间,10秒钟后runLoop自动退出,(该设置只针对于子线程中的runLoop有用,对主线程中的runLoop没有任何作用)
    NSLog(@"--------end---------"); // 10秒钟之后打印该内容
}
- (void)runOperation {

    NSLog(@"%s", __func__);
}
- (IBAction)otherOperationBtnClick:(id)sender {
    [self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)task2 {
    NSLog(@"task2-----------%@", [NSThread currentThread]);
}

  • <h5>RunLoop常见面试题</h5>
    • 什么是RunLoop?
      • 从字面意思看:运行循环、跑圈
      • 其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)
      • 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法)
      • RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
    • 自动释放吃什么时候创建?什么时候释放?
      • 第一次创建在:启动runLoop的时候
      • 最后一次销毁在:runLoop退出的时候
      • 其他时候的创建和销毁:当runLoop即将睡眠的时候销毁之前的释放池,并重新创建一个新的
    • 在开发中如何使用RunLoop?什么应用场景?
      • 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
        • 在子线程中开启一个定时器
        • 在子线程中进行一些长期监控
      • 可以控制定时器在特定模式下执行
      • 可以让某些事件(行为、任务)在特定模式下执行
      • 可以添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情)
关注一下又不会怀孕.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容