RunLoop

来源:互联网 发布:淘宝搬家到微店 编辑:程序博客网 时间:2024/06/05 08:52

1.1 run loops 是一个关联线程的基础设施。run loop是一个事件处理循环,主要用于安排工作和接受事件的。它可以让线程在有工作的时候繁忙,没有工作的时候休眠,如果没有run loop的话,你需要创建while后者for循环来不断监听事件,但这样性能低下。

1.2 每个线程都有一个run loop,你不必手动创建run loop,可以通过CFRunLoopGetCurrent()或者[NSRunLoop currentRunLoop]获取当前线程的run loop。主线程默认开启run loop并接受用户事件。当你点击按钮时,此时对应的点击事件会触发并把事件分配到run loop并处理事件对应的handler。

1.3 run loop事件分为两种,一种是输入源,一种是定时器源。输入源又分为Custom输入源和Port源,Custom是客户端自己发送信号,而Port源是系统自己发送信号的。定时器源是周期性发送信号的源,发送同步的事件。输入源发送的是异步的事件,通常来自其它线程和其它应用。


1.4 run Loop Mode是一个输入源和定时器源的集合,可以创建自己的model(只需要传递一个字符串)或者使用系统提供的mode,只有run Loop运行在特定的mode下,该mode下的源才会被监视并处理事件。

以下是系统提供的mode.



大多数情况下使用的是Default,也可以是Common modes,这个包括了default,model, eventTracking modes。


1.5 主线程是默认开启run loop的,其他线程需要手动开启runloop,所以在使用NSTimer在其他线程时,需要添加到当前线程的runloop

- (void)myThread2 {    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];    NSRunLoop *runloop = [NSRunLoop currentRunLoop];    [runloop addTimer:timer forMode:NSDefaultRunLoopMode];    [runloop run];}

1.6 run loop会自动创建自动释放池,在每次run loop休眠时释放自动释放池的对象。

1.7 cacoa中的Selector源会自动加入到当前runloop上,当Selector执行完毕后从runloop中移除。在其他线程中需要手动开启runloop否则Selector不会执行。

- (void)myThread3 {    [self performSelector:@selector(myMethod) withObject:nil afterDelay:0];    NSRunLoop *runloop = [NSRunLoop currentRunLoop];    [runloop run];}

1.8 run loop的简单应用。

- (void)myThread {    NSLog(@"线成开始");    _runloop = CFRunLoopGetCurrent();    [NSRunLoop currentRunLoop];    CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, schedule, cancel, perform};    _source = CFRunLoopSourceCreate(NULL, 0, &context);    CFRunLoopAddSource(_runloop, _source, kCFRunLoopDefaultMode);    CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};    _observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);    CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);    BOOL done = NO;    do {        CFRunLoopRunResult result =  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);        switch (result) {            case kCFRunLoopRunFinished:                NSLog(@"kCFRunLoopRunFinished");            case kCFRunLoopRunStopped:                NSLog(@"kCFRunLoopRunStopped");                done = YES;                break;            case kCFRunLoopRunTimedOut:                NSLog(@"kCFRunLoopRunTimedOut");                break;            case kCFRunLoopRunHandledSource:                NSLog(@"kCFRunLoopRunHandledSource");                break;        }    } while (!done);    NSLog(@"线程结束");}

这里CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false)的10 代表runloop运行的时间,单位为秒。当明确的停止runloop后者没有源在runloop的时候退出,线程结束。

CFRunLoopSourceContext的最后一个参数是一个c函数指针,用于处理事件的。info指针是自定义的参数,用于传递给注册函数的info参数。CFRunLoopSourceContext的结构如下。

typedef struct {    CFIndexversion;    void *info;    const void *(*retain)(const void *info);    void(*release)(const void *info);    CFStringRef(*copyDescription)(const void *info);    Boolean(*equal)(const void *info1, const void *info2);    CFHashCode(*hash)(const void *info);    void(*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);    void(*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);    void(*perform)(void *info);} CFRunLoopSourceContext;

以下是注册函数的实现。

void perform(void *info) {    ViewController *vc = (__bridge ViewController *)info;    NSLog(@"执行开始-info=%@", vc);    sleep(1);    NSLog(@"执行结束-info=%@", vc);}void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {    ViewController *vc = (__bridge ViewController *)info;    NSLog(@"schedule info=%@,mode=%@", vc, mode);}void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {    ViewController *vc = (__bridge ViewController *)info;    NSLog(@"cancel info=%@,mode=%@", vc, mode);}

schedule函数是添加源的时候执行,perform函数是事件到来时执行,cancel函数是源失效的时候执行。

向源发送信号并唤醒runloop,触发perform函数。

- (IBAction)clickButton:(id)sender {    if (CFRunLoopIsWaiting(_runloop)) {        if (CFRunLoopSourceIsValid(_source)) {            NSLog(@"发送信号");            CFRunLoopSourceSignal(_source);            CFRunLoopWakeUp(_runloop);        } else {            NSLog(@"CFRunLoopSourceInvalidate");        }    } else {        NSLog(@"CFRunLoopIsWaiting");    }}
让源失效,触发cancel函数。

- (IBAction)clickCancelBtn:(id)sender {    if (CFRunLoopIsWaiting(_runloop)) {        NSLog(@"取消源");        CFRunLoopSourceInvalidate(_source);    }}

手动停止runloop。

- (IBAction)clickStopButton:(id)sender {    if (CFRunLoopIsWaiting(_runloop)) {        NSLog(@"停止RunLoop");        CFRunLoopStop(_runloop);    }}

移除源。

- (IBAction)clickRemoveButton:(id)sender {    if (CFRunLoopIsWaiting(_runloop)) {        NSLog(@"移除源");        CFRunLoopRemoveSource(_runloop, _source, kCFRunLoopDefaultMode);    }}
添加runloop观察者。

CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};    _observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);    CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);
observeCallBack是c函数指针,用于处理观察事件。info指针是自定义的参数,用于传递给注册函数的info参数。
void observeCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {    switch (activity) {        case kCFRunLoopEntry:            NSLog(@"kCFRunLoopEntry");            break;        case kCFRunLoopBeforeTimers:            NSLog(@"kCFRunLoopBeforeTimers");            break;        case kCFRunLoopBeforeSources:            NSLog(@"kCFRunLoopBeforeSources");            break;        case kCFRunLoopBeforeWaiting:            NSLog(@"kCFRunLoopBeforeWaiting");            break;        case kCFRunLoopAfterWaiting:            NSLog(@"kCFRunLoopAfterWaiting");            break;        case kCFRunLoopExit:            NSLog(@"kCFRunLoopExit");            break;        default:            break;    }}

每次运行run loop,你线程的run loop对会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:

  1. 通知观察者run loop已经启动
  2. 通知观察者任何即将要开始的定时器
  3. 通知观察者任何即将启动的非基于端口的源
  4. 启动任何准备好的非基于端口的源
  5. 如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9。
  6. 通知观察者线程进入休眠
  7. 将线程置于休眠直到任一下面的事件发生:
    • 某一事件到达基于端口的源
    • 定时器启动
    • Run loop设置的时间已经超时
    • run loop被显式唤醒
  8. 通知观察者线程将被唤醒。
  9. 处理未处理的事件
    • 如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
    • 如果输入源启动,传递相应的消息
    • 如果run loop被显式唤醒而且时间还没超时,重启run loop。进入步骤2
  10. 通知观察者run loop结束。
当runloop有源但事件没有到来时,runloop进入休眠等待状态。以下是打印的log。

2017-10-25 16:03:20.434434+0800 aa[5027:3503969] 发送信号2017-10-25 16:03:20.434853+0800 aa[5027:3504031] kCFRunLoopAfterWaiting2017-10-25 16:03:20.434914+0800 aa[5027:3504031] kCFRunLoopBeforeTimers2017-10-25 16:03:20.434948+0800 aa[5027:3504031] kCFRunLoopBeforeSources2017-10-25 16:03:20.435070+0800 aa[5027:3504031] 执行开始-info=<ViewController: 0x102b0eb90>2017-10-25 16:03:21.436837+0800 aa[5027:3504031] 执行结束-info=<ViewController: 0x102b0eb90>2017-10-25 16:03:21.436988+0800 aa[5027:3504031] kCFRunLoopBeforeTimers2017-10-25 16:03:21.437047+0800 aa[5027:3504031] kCFRunLoopBeforeSources2017-10-25 16:03:21.437096+0800 aa[5027:3504031] kCFRunLoopBeforeWaiting2017-10-25 16:03:22.467261+0800 aa[5027:3503969] 发送信号2017-10-25 16:03:22.467683+0800 aa[5027:3504031] kCFRunLoopAfterWaiting2017-10-25 16:03:22.467715+0800 aa[5027:3504031] kCFRunLoopBeforeTimers2017-10-25 16:03:22.467733+0800 aa[5027:3504031] kCFRunLoopBeforeSources

runLoop的使用环境。

1.需要一个辅助线程来监听事件并处理,但需要消耗较低的性能时。

2.使用自定义源或者端口来和其他线程通讯。

3.使用定时器在线程中。

4.使用任何performSelector...方法。

5.保持线程周期性执行任务。

还有尽量避免手动的停止runloop,应该让runloop在一定的时间内退出或者把所有的源移除后自动退出。