RunLoop基础知识

作用
  1. 保持程序的持续运行
  2. 处理APP中的各种事件(比如触摸事件、定时器事件、Selector事件)
  3. 节省CPU资源,提高程序性能:该做事时做事,该休息时休息 (用户态 切换到 内核态)
RunLoop与多线程的关系
  • 线程与RunLoop是一一对应的关系;RunLoop保存在一个全局的NSDictionary字典里面,线程为key,RunLoop为Value
  • 主线程的RunLoop在Main函数中自动开启,保证了程序的持续运行。
    子线程的RunLoop需要主动创建;RunLoop在第一次获取时由系统内部创建,在线程结束时销毁。(苹果不允许直接创建 RunLoop)
  • 只能在一个线程的内部获取其 RunLoop(主线程除外),它是寄生于线程的
    参考

RunLoop 有五种运行模式,其中常用的有1、2两种

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
  4. UIInitializationRunLoopMode: 启动Mode,在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  5. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

kCFRunLoopCommonModes模式

一种模式组合,在主线程默认包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。子线程只包NSDefaultRunLoopMode。
注意:
①在添加事件源的时候填写这个模式就相当于向组合中所有包含的Mode中注册了这个事件源。
②在选择RunLoop的runMode时不可以填这种模式否则会导致RunLoop运行不成功。
③你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到 kCFRunLoopCommonModes组合。

参考

每次RunLoop启动时,只能指定其中一个 Mode;如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入;这样做主要是为了分隔开不同组的 事件源,让其互不影响

RunLoop对象.png

source就是输入源事件,Timer即为定时源事件,Observer相当于消息循环中的一个监听器

Observer的创建

  • NSRunLoop没有相关方法,只能通过CFRunLoop相关方法创建
// 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
                
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);

上面的 Source/Timer/Observer 被统称为 mode item,一个item可以被同时加入多个mode。
但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item 都没有(只有Observer也不行),则 RunLoop 会直接退出,不进入循环。

RunLoop正常运行的条件是:

  1. 有Mode。
  2. Mode有事件源。
  3. 运行在有事件源的Mode下

经过NSRunLoop封装后,只可以往mode中添加两类事件源:NSPort(对应的是source1)和NSTimer(Timer源放在后面讲)。

启动RunLoop有那些方法及区别

NSRunLoop总共包装了3个方法供我们使用

- (void) run

不建议使用。 除非希望子线程永远存在,因为这个会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用 CFRunLoopStop(runloopRef);也无法停止RunLoop的运行,那么这个子线程也就无法销毁,只能永久运行下去

- (void)runUntilDate:(NSDate *)limitDate

//举例代码
while (!Stop){

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}

比上面的接口好点,有个超时时间,可以控制每次RunLoop的运行时间,也是运行在NSDefaultRunLoopMode模式。这个方法运行RunLoop一段时间会退出给你检查运行条件的机会,如果需要可以再次运行RunLoop。注意CFRunLoopStop(runloopRef);仍然无法停止RunLoop的运行,因此最好自己设置一个合理的RunLoop运行时间。

- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate

有一个超时时间限制,而且可以设置运行模式
这个接口在非Timer事件触发、显式的用CFRunLoopStop停止RunLoop或者到达limitDate后会退出返回。如果仅是Timer事件触发并不会让RunLoop退出返回,但是如果是PerfromSelector事件或者其他Input Source事件触发处理后,RunLoop会退出返回YES。同样可以像上面那样用while包起来使用

关于GCD

RunLoop 底层也会用到 GCD 的东西,但同时 GCD 提供的某些接口也用到了 RunLoop, 例如 dispatch_async()。

PerformSelecter

performSelector相关的知识.png
runLoop调用run方法的内部实现.png

子线程保活

子线程保活.png
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为YES
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    // 清空线程
    self.thread = nil;
}

源码地址

自动释放池

Timer和Source也是一些变量,需要占用一部分存储空间,所以要释放掉,如果不释放掉,就会一直积累,占用的内存也就越来越大。

那么什么时候释放,怎么释放呢?

主线程的RunLoop默认启动,并会自动创建自动释放池。当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始跑圈时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放。
子线程需要在线程中手动添加自动释放池
NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool。GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool
参考

RunLoop的应用

  1. NSTimer 用于轮播图
  2. TableView滚动时不显示图片
  3. TableView停止滚动时计算行高或者预加载
    sunnyxx的UITableView+FDTemplateLayoutCell利用Observer在界面空闲状态下计算出UITableViewCell的高度并进行缓存。
  4. 怎样保证子线程数据回来更新UI的时候,不打断用户的滑动操作。
  • tableView在滑动,处于UITrackingRunloopMode模式下。
  • 子线程请求的数据,那么在和主线程处理的时候,我们将更新的逻辑加载defaultMode下。那么defaultMode下的操作是不会执行的。
  • 滑动结束了,runloop由UITrackingRunloopMode又回到defaultMode下了,那么defaultMode下的更新操作就能执行了

RunLoop高级1
RunLoop高级2

参考2
参考3
参考4 很详细
参考5

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

推荐阅读更多精彩内容

  • RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。 RunLoop是一种事件运...
    汴城码农阅读 204评论 0 0
  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,325评论 0 5
  • 转自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飘金阅读 975评论 0 4
  • https://blog.ibireme.com/2015/05/18/runloop/ RunLoop 是 iO...
    SmallDe阅读 692评论 0 51
  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 614评论 0 2