# iOS开发之RunLoop

iOS开发之RunLoop

什么是RunLoop

  • 运行循环,跑圈
  • 其实内部就是do-while循环,在这个循环n内部不断的处理各种任务(比如Source/Timer/Observer)
  • 一个线程对应一个RunLoop,主线程的RunLoop默认启动了,子线程需要手动调用(run方法)
  • RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source/Timer/Observer,那就直接退出RunLoop

RunLoop的作用

  • 让程序一直运行并接受用户输入
  • 决定程序在何时应该处理哪些Event
  • 调用结构(Message Queue)
  • 节省CPU时间

RunLoop对象

  • Foundation : NSRunLoop(OC对C的RunLoop的简单的封装)
[NSRunLoop currentRunLoop]; //获取当前线程的Runloop
[NSRunLoop mainRunLoop]; //获取主线程的RunLoop
  • Core Foundation CFRunLoop(C语言 开源 跨平台的)
CFRunLoopGetCurrent();//获取当前线程的Runloop
CFRunLoopGetMain(); //获取主线程的RunLoop

RunLoop的机制

  • 每条线程都有唯一一个与之对应的Runloop对象
  • 主线程的RunLoop已经创建好了,子线程的Runloop需要自动创建
  • RunLoop在第一次获取时创建,在线程结束时销毁

CFRunLoopModelRef

代表的是Runloop的运行模式

  • 一个Runloop包含若干个Mode,每个Model又包含了多个Source/Timer/Observer
  • Runloop在同一段时间只能且必须在一种特定的Mode下Run
  • 更换Mode时,需要停止当前的Loop,然后重启新的Loop
  • Mode是iOS App滑动顺利的关键
  • 可以自己定制Mode(基本不会发生的)

NSDefaultRunLoopMode 默认状态,空闲状态,通常主线程是在这个Mode下运行
UITrackingRunLoopMode 界面跟踪 Mode, 滑动ScrollView时
UIInitializationRunLoopMode 私有,App启动时进入的第一个Mode 启动完后不在使用
NSRunLoopCommonModes   Mode集合 默认包括上面第一和第二
GSEventReceiveRunLoopMode 接受系统内部时间的Mode

CFRunLoopSource

  • Source是Runloop的数据源的抽象类(类似IOS中的protocol)
  • 定义了2个版本的Source
    1. Source0 :处理app的内部时间,App自己负责管理 如:UIEvent CFSocket
    2. Source1 :用Runloop和内核管理,Mach port驱动,如 CFMachPort CFMessagePort (Port可以用于进程间的端口通讯)

CFRunLoopTimerRef

  • CFRunLoopTimerRef是基于时间的触发器
  • 基本上说的就是NSTimer

CFRunLoopObserver

向外部报告Runloop当前状态的更改,能够监听Runloop的状态的改变

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7), //退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });

    // 添加观察者:监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    // 释放Observer
    CFRelease(observer);
    

注意这里面的observer是需要释放的: 在ARC中自动内存管理的是OC的对象

CF的内存管理(Core Foundation)

  • 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release 比如CFRunLoopObserverCreate.
  • release函数:CFRelease(对象);

RunLoop的处理逻辑

RunLoopObserver与Autorelease Pool

在RunLoop睡觉之前释放(kCFRunLoopBeforeWaiting)

UITrackingRunLoopMode 与 Timer

Timer默认是被添加在NSDefaultRunLoopMode中的,当ScrollerView滑动的时候就会影响到
Timer,若不希望Timer被影响,需要添加到NSRunLoopCommonModes

    
    [[NSRunLoop currentRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeaction) userInfo:nil repeats:NO] forMode:NSRunLoopCommonModes];
    

Runloop与dispath_get_main_queue()

GCD到dispath到main queque的block被分发到main Runloop执行

GCD中的定时器和Runloop没有关系的,GCD的定时器是不受RunLoop的Mode的影响的

    // 获得队列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 创建一个定时器(dispatch_source_t本质还是个OC对象)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 设置定时器的各种属性(什么时候开始任务,每隔多长时间执行一次)
    // GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
    // 何时开始执行第一个任务
    // dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);
    
    // 设置回调
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);
        count++;
        
//        if (count == 4) {
//            // 取消定时器
//            dispatch_cancel(self.timer);
//            self.timer = nil;
//        }
    });
    
    // 启动定时器
    dispatch_resume(self.timer);

RunLoop的挂起与唤醒

  • 指定用于唤醒的mach_port端口
  • 调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap状态
  • 由另一个线程(或者另一个进程中的线程) 向内核发送这个端口的msg后,trap状态被唤醒.
     //在子线程中默认是没有RunLoop的 
    //获取Runloop,当当前线程没有runloop的时候,该方法就会启动runloop
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    
    //给Runloop一个端口,这样就可以保持Runloop处于唤醒的状态
    [loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

    //run
    [loop run];

RunLoop创建一个常驻服务线程的方法

 [[NSThread currentThread] setName:@"thread1"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefalutRunLoopMode]//一直活着
[runLoop run];

Topic: 一个TableView延迟加载图片的思路

    UIImageView * iconImageView = [UIImageView new];

    UIImage *image = nil;
    
    [iconImageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
    
    //NSDefaultRunLoopMode 设置为这个模式的时候 当TableView在滑动到时候Runloop在UITrackingRunLoopMode模式,这样setimage方法就不会被调用.只有不滑动的时候,runloop切换到NSDefaultRunLoopMode模式,这时候设置图片
    

应用场景

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

推荐阅读更多精彩内容

  • runLoop,正如其名,表示一直运行着的循环。 一般来说,一个线程只能执行一个任务,执行完就会推出,如果我们需要...
    li大鹏阅读 1,627评论 3 11
  • 前言 最近离职了,可以尽情熬夜写点总结,不用担心第二天上班爽并蛋疼着,这篇的主角 RunLoop 一座大山,涵盖的...
    zerocc2014阅读 12,340评论 13 67
  • RunLoop 是 iOS 开发中一个非常基础而又重要的一个概念 为什么说它非常重要呢? 它不仅是检验一个程序员水...
    小小小阿博er阅读 810评论 2 19
  • Runloop是iOS和OSX开发中非常基础的一个概念,从概念开始学习。 RunLoop的概念 -般说,一个线程一...
    小猫仔阅读 948评论 0 1
  • Run loop 剖析:Runloop 接收的输入事件来自两种不同的源:输入源(intput source)和定时...
    Mitchell阅读 12,360评论 17 111