RunLoop(总结详细)

RunLoop资料



问题: 什么是RunLoop?

  • 从字面意思看

运行循环
跑圈

  • 基本作用

保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
.....

没有runLoop

有runLoop

main函数中的RunLoop

]




RunLoop与线程

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

获得RunLoop对象

  • Foundation

[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

  • CoreFoundation

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop相关类

  • CoreFoundation中关于RunLoop的5个类

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef
  • **CFRunLoopModeRef **

    • CFRunLoopModeRef代表RunLoop的运行模式
    • 一个 RunLoop包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
    • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
    • 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
    • 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
  • 系统默认注册了5个Mode:

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

RunLoop处理逻辑

  • RunLoop处理逻辑-网友整理版版
    RunLoop处理逻辑-网友整理版版
  • ** RunLoop处理逻辑-官方版 **



    RunLoop处理逻辑-官方版

代码展示区

01-掌握-NSRunLoop基本概念
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    /*
    // Do any additional setup after loading the view, typically from a nib.
    NSRunLoop *currentRunloop =  [NSRunLoop currentRunLoop];
    NSLog(@"currentRunloop = %@", currentRunloop);
    NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
    NSLog(@"mainRunloop = %@", mainRunloop);
     */
    /*
    CFRunLoopRef currentRunloop =  CFRunLoopGetCurrent();
    NSLog(@"currentRunloop = %@", currentRunloop);
    CFRunLoopRef mainRunloop = CFRunLoopGetMain();
    NSLog(@"mainRunloop = %@", mainRunloop);
     */
    
    /*
     1.一条线程对应一个RunLoop
     2.主线程的RunLoop默认已经创建好了, 而子线程的需要我们自己手动创建
     3.一个NSRunLoop/CFRunLoopRef, 就代表一个RunLoop对象
     4.如何获取当前线程对应的RunLoop对象,currentRunLoop/CFRunLoopGetCurrent
     5.如何获取主线程对应的RunLoop对象,mainRunLoop/CFRunLoopGetMain
     6.只要线程结束了, 那么与之对应的RunLoop对象也会被释放
     */
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    [thread start];

}

- (void)show
{
//    [[NSRunLoop alloc] init]; // 注意, 如果想给子线程添加RunLoop, 不能直接alloc init
    
    [NSRunLoop currentRunLoop]; // 只要调用currentRunLoop方法, 系统就会自动创建一个RunLoop, 添加到当前线程中
}

@end


02-NSRunLoopMode中的类

#import "ViewController.h"

@interface ViewController ()

/** 时间 */
@property (nonatomic, strong) dispatch_source_t timer;
- (IBAction)btnClick:(id)sender;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
    [self gcdTimer];
}
- (IBAction)btnClick:(id)sender {
    NSLog(@"%s", __func__);
}


- (void)gcdTimer
{
    /*
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
     NSLog(@"-----------");
     });
     */
    
    NSLog(@"%s", __func__);
    // 0.获取一个全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 1.创建一个定时器
    // 第四个参数: 传递一个队列, 该队列对顶了将来的回调方法在哪个线程中执行
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //    NSLog(@"%@", timer);
    
    // 2.指定定时器开始的时间和间隔的时间, 以及精准度
    /*
     第1个参数: 需要给哪个定时器设置
     第2个参数: 定时器开始的时间 / DISPATCH_TIME_NOW立即执行
     第3个参数: 定时器开始之后的间隔时间
     第4个参数: 定时器间隔执行的精准度, 传入0代表最精准(尽量的让定时器精准), 传入一个大于0的值, 代表多少秒的范围是可以接受的
     第四个参数存在的意义: 主要是为了提高程序的性能
     注意点: Dispatch的定时器接收的时间是纳秒
     */
    
    // 开始时间
    //    dispatch_time_t startTime = DISPATCH_TIME_NOW;
    dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
    
    // 间隔时间
    uint64_t interval = 1.0 * NSEC_PER_SEC;
    dispatch_source_set_timer(timer, startTime, interval, 0 * NSEC_PER_SEC);
    
    // 3.指定定时器的回调方法
    /*
     第1个参数: 需要给哪个定时器设置
     第2个参数: 需要回调的block
     */
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"++++++++++++++ %@", [NSThread currentThread]);
        
    });
    
    // 4.开启定时器
    dispatch_resume(timer);
}
- (void)timer
{
    /*
     // 1.创建一个NSTimer
     NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
     // 2.将NSTimer添加到RunLoop中, 并且告诉系统, 当前Tiemr只有在RunLoop的默认模式下才有效
     //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
     
     // 2.将NSTimer添加到RunLoop中, 并且告诉系统, 当前Tiemr只有在Tracking的默认模式下才有效
     //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
     
     // 2.将NSTimer添加到RunLoop中, 并且告诉系统, 在所有被"标记"common的模式都可以运行
     */
    /*
     common modes = <CFBasicHash 0x7fc0b8615250 [0x104be7180]>{type = mutable set, count = 2,
     entries =>
     0 : <CFString 0x1058bae50 [0x104be7180]>{contents = "UITrackingRunLoopMode"}
     2 : <CFString 0x104bc3080 [0x104be7180]>{contents = "kCFRunLoopDefaultMode"}
     }
     UITrackingRunLoopMode和kCFRunLoopDefaultMode都被标记为了common模式, 所以只需要将timer的模式设置为forMode:NSRunLoopCommonModes, 就可以在默认模式和追踪模式都能够运行
     */
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    // 注意: 如果是通过scheduledTimerWithTimeInterval创建的NSTimer, 默认就会添加到RunLoop得DefaultMode中 , 所以它会自动运行
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 虽然默认已经添加到DefaultMode中, 但是我们也可以自己修改它的模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}


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

@end


03-CFRunLoopObserverRef

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建Observer
    /*
     第1个参数: 指定如何给observer分配存储空间
     第2个参数: 需要监听的状态类型/ kCFRunLoopAllActivities监听所有状态
     第3个参数: 是否每次都需要监听
     第4个参数: 优先级
     第5个参数: 监听到状态改变之后的回调
     */
    CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从睡眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出");
                break;
            default:
                break;
        }
    });
    
    
    // 给主线程的RunLoop添加一个观察者
    /*
     第1个参数: 需要给哪个RunLoop添加观察者
     第2个参数: 需要添加的Observer对象
     第3个参数: 在哪种模式下可以可以监听
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
    
    // 释放对象
    CFRelease(observer);
    
    [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
}

- (void)show{
     NSLog(@"show");
}
@end


04-掌握-RunLoop应用场景

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

/** 子线程 */
@property (nonatomic, strong) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad
{
    /*
     自动释放池什么时候创建和释放
     1.第一次创建, 是在runloop进入的时候创建  对应的状态 = kCFRunLoopEntry
     2.最后一次释放, 是在runloop退出的时候  对应的装 = kCFRunLoopExit
     3.其它创建和释放
        * 每次睡觉的时候都会释放前自动释放池, 然后再创建一个新的
     
     _wrapRunLoopWithAutoreleasePoolHandler activities = 0x1,   
     1  = kCFRunLoopEntry  进入loop  创建自动释放池
     
     _wrapRunLoopWithAutoreleasePoolHandler activities = 0xa0,  
     160 = kCFRunLoopBeforeWaiting  即将进入睡眠 ,先释放上一次创建的自动释放池, 然后再创建一个新的释放池
     +
     kCFRunLoopExit 即将退出loop  释放自动释放池
     
     */
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
//    NSLog(@"%d %d", 0x1, 0xa0);
    
    
    NSThread *thread = [NSThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    self.thread = thread;
    [thread start];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
    /*
    // 1.在指定模式下进行特定的操作
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 inModes:@[UITrackingRunLoopMode]];
     */
    
    // 默认清空下一个线程只能使用一次, 也就是说只能执行一个操作, 执行完毕之后就不能使用了
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)show{
    NSLog(@"%s", __func__);
//    while(1);
    
    // 1.子线程的NSRunLoop需要手动创建
    // 2.子线程的NSRunLoop需要手动开启
    // 3.如果子线程的NSRunLoop没有设置source or timer, 那么子线程的NSRunLoop会立刻关闭
//    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    
   
//    NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [[NSRunLoop currentRunLoop] run];
    
    
    // 注意点: NSRunLoop只会检查有没有source和timer, 没有就关闭, 不会检查observer
    CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    });

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 释放对象
    CFRelease(observer);
    
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"end -----");
}

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

@end



如何开启一个常驻线程

  • Listing 3-1 shows the main routine for a thread that attaches a run loop observer to its run loop.
    The purpose of the example is to show you how to create a run loop observer, so the code simply sets up a run loop observer to monitor all run loop activities.
    The basic handler routine (not shown) simply logs the run loop activity as it processes the timer requests.
    • 翻译: 显示了主程序的线程高度运行循环观察其运行循环。
      这个例子的目的是向您展示如何创建一个运行循环观察者,因此代码简单地设置一个运行循环观察员监督所有运行循环活动。
      最基本的处理程序例程(图中未显示)简单的日志处理计时器请求运行循环活动。
**Listing 3-1**  Creating a run loop observer
- (void)threadMain

{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];

    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};

    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);

    if (observer) {
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];

        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }

    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
    NSInteger loopCount = 10;
    do {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
   } while (loopCount);
}

// 官方文档介绍
When configuring the run loop for a long-lived thread, it is better to add at least one input source to receive messages. 
Although you can enter the run loop with only a timer attached, once the timer fires, it is typically invalidated,
which would then cause the run loop to exit. Attaching a repeating timer could keep the run loop running over a longer period of time, 
but would involve firing the timer periodically to wake your thread, which is effectively another form of polling. 
By contrast, an input source waits for an event to happen, keeping your thread asleep until it does.

/*
    当你配置一个线程为常驻线程时,最好的方式是添加至少一个输入源去接收消息。
    尽管你可以只用一个定时器连接进入RunLoop,一旦定时器被销毁,RunLoop也通常是无效的,这会导致退出当前运行循环。
    附加一个相关的定时器可以让保持长时间的运行,但需要触发计时器定期唤醒你的线程,这实际上是另一种形式的循环。
    相比之下,一个输入源等待一个事件发生,保持你的线程运行,直到它睡着了。
*/

基于端口的常驻线程

  • shows the primary thread code for launching a secondary worker thread. Because the Cocoa framework performs many of the intervening steps for configuring the port and run loop, the launchThread method is noticeably shorter than its Core Foundation equivalent; however, the behavior of the two is nearly identical. One difference is that instead of sending the name of the local port to the worker thread, this method sends the NSPort object directly.
    • 显示了启动一个主要线程代码辅助工作线程。因为Cocoa框架执行许多干预措施的配置端口和运行循环,launchThread方法明显短于其核心基础等效;然而,两个的行为几乎是相同的。一个区别是,而不是发送本地端口的名称工作线程,该方法直接发送NSPort对象。
NSPort* myPort = [NSMachPort port];

if (myPort) {
    // This class handles incoming port messages.
    [myPort setDelegate:self];

    // Install the port as an input source on the current run loop.
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];

    // Detach the thread. Let the worker release the port.
    [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:) toTarget:[MyWorkerClass class] withObject:myPort];
}

// 官方文档介绍
In order to set up a two-way communications channel between your threads, 
you might want to have the worker thread send its own local port to your main thread in a check-in message. 
Receiving the check-in message lets your main thread know that all went well in launching the second thread and also gives you a way to send further messages to that thread.

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

推荐阅读更多精彩内容