RunLoop的简单使用

run loop简介

run loop是一个关联线程的基础设施。run loop是一个事件处理循环,主要用于安排工作和接收事件。它可以让线程在有工作的时

候繁忙,没有工作的时候休眠。如果没有run loop的话,你需要创建while或者for循环来不断监听事件,但这样性能低下。

每个线程都有一个run loop,你不必手动创建run loop,可以通过CFRunLoopGetCurrent()或者[NSRunLoop currentRunLoop]获

取当前线程的run loop。主线程默认开启run loop并接收用户事件。当你点击按钮时,此时对应的点击事件会触发并把事件分配到

run loop并处理事件对应的Handler。

run loop事件分为两种,一种是输入源,一种是定时器源。输入源又分为Custom输入源和Port输入源。Custom输入源是客服端发

送信号,Port输入源是系统发送信号。定时器源是周期性发送信号的源,它发送同步事件。输入源发送异步事件,通常来自其它线

程或者其它应用。

run loop mode是输入源和定时器源的集合。你可以创建自己的mode(只需要传递一个字符串)或者使用系统提供的mode。只有

run loop运行在特定的mode下,该mode下的源才会被监视并处理事件。

一下是系统提供的mode。


大多数情况下使用的是Default mode,也可以是Common mode,它包含Default mode、Model mode、Event tracking mode。

主线程是默认开启run loop,其它线程需要手动开启run loop。所以在其它线程使用NSTimer,需要把NSTimer添加到线程的run loop。

- (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];
}

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

cocoa的selector源会自动加入到当前的run loop上,selector执行完毕后会自动从run loop移除。在其它线程中,你需要手动开启

run loop,否则selector不会执行。

- (void)myThread3 {
    [self performSelector:@selector(myMethod) withObject:nil afterDelay:0];
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
}
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代表run loop运行的时间(单位为秒)。当明确地停止run 

loop或者没有源在run loop时,run loop会退出而且线程结束。

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

info参数。CFRunLoopSourceContext的结构如下:

typedef struct {
    CFIndex	version;
    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函数会在源失效时执行。

向源发送信号并唤醒run loop,触发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);
    }
}

手动停止run loop。

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

移除源。

- (IBAction)clickRemoveButton:(id)sender {
    if (CFRunLoopIsWaiting(_runloop)) {
        NSLog(@"移除源");
        CFRunLoopRemoveSource(_runloop, _source, kCFRunLoopDefaultMode);
    }
}

添加run loop观察者。

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结束。

当run loop还有源但事件灭有到来时,run loop进入休眠等待状态。一下是打印的log。

2017-10-25 16:03:20.434434+0800 aa[5027:3503969] 发送信号
2017-10-25 16:03:20.434853+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:20.434914+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:20.434948+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-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] kCFRunLoopBeforeTimers
2017-10-25 16:03:21.437047+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-10-25 16:03:21.437096+0800 aa[5027:3504031] kCFRunLoopBeforeWaiting
2017-10-25 16:03:22.467261+0800 aa[5027:3503969] 发送信号
2017-10-25 16:03:22.467683+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:22.467715+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:22.467733+0800 aa[5027:3504031] kCFRunLoopBeforeSources
run loop的使用环境

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

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

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

4. 在子线程使用任何performSelector方法。

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

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值