iOS锦囊

RunLoop-基础与应用

2018-02-06  本文已影响17人  mtry

RunLoop的概念

一种可以控制线程存在时间,有事的时候干活,没事的时候休息的机制。

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。

  1. CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API。
  2. NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API。

注意:NSRunLoop和CFRunLoopRef可以混用

image_1.jpg

RunLoop与线程的关系

RunLoop的获取

RunLoop不能显示的创建,只能在当前线程下通过[NSRunLoop currentRunLoop]CFRunLoopGetCurrent() 进行获取。如果当前线程的RunLoop存在就直接返回,不存在就自动创建。

运行和终止

NSRunLoop的开启

//从[NSDate date]到limitDate一直运行,run默认时间为[NSDate distantFuture]
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;

//从[NSDate date]到limitDate一直运行,不过只要期间收到响应就结束
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

直接调用发现RunLoop是没有运行的,解决方案如下

方式一:调用之前添加一些port或者timer

- (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode;
- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode;

方式二:使用循环不断添加,保证RunLoop一直运行着

while(!finished)
{
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}

原因:一个RunLoop包含多个mode,每个mode包含一个source集合、一个timer数组、一个observer数组;RunLoop一次只能运行一个mode,当mode中的source、timer、observer没有数据时,会在运行完马上结束,port属于source,当然往mode中添加一些observer也是可以的。方式二虽然是可以使RunLoop一直运行,不过他一直在空转。

注意:关于NSRunLoop的终止只能制定时间结束才能终止

CFRunLoopRef的开启

因为NSRunLoop是对CFRunLoopRef的封装,因此他们自然都需要遵守同样的约束。

//开启RunLoop
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
//往CFRunLoopRef中的mode添加source,observer,timer
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)

//结束当前runloop
CFRunLoopStop(CFRunLoopRef runloop)

关于Mode

主要列举我们在iOS中常用的几种mode

Default(默认模式,在没有指定mode下,就会使用这种模式)

Event tracking(追踪 ScrollView 滑动时的状态)

Common modes(集合暂时可以理解为Default和Event tracking这两种模式)

理解common mode和普通mode的区别需要从CFRunLoopMode和CFRunLoop分析

struct __CFRunLoopMode {
    CFStringRef _name;             // Mode Name
    CFMutableSetRef _sources0;     // Set
    CFMutableSetRef _sources1;     // Set
    CFMutableArrayRef _observers;  // Array
    CFMutableArrayRef _timers;     // Array
    ...
};

struct __CFRunLoop {
    CFMutableSetRef _commonModes;         // Set
    CFMutableSetRef _commonModeItems;     // Set<Source/Observer/Timer>    
    CFRunLoopModeRef _currentMode;        // Current Runloop Mode
    CFMutableSetRef _modes;               // Set
    ...
};

当我们把一些source/observer/timer添加到commonMode时,其实是添加到了_commonModeItems中,然后把当前全部的source/observer/timer同步到_commonModes中的普通mode上。把一个普通的mode添加到_commonModes上通过使用CFRunLoopAddCommonMode方法。

注意:主线程的RunLoop中的_commonModes默认下有UITrackingRunLoopMode和kCFRunLoopDefaultMode,在子线程中默认只有kCFRunLoopDefaultMode。

关于Source/Observer/Timer

Source

Source分两种Source0和Source1

Observer

主要是用来观察RunLoop的状态,RunLoop的状态有

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

使用

- (NSThread *)thread
{
    if(!_thread)
    {
        _thread = [[NSThread alloc] initWithTarget:self selector:@selector(onThread) object:nil];
        _thread.name = @"customThread";
    }
    return _thread;
}

- (void)onThread
{
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"runloop进入");
                break;
            
            case kCFRunLoopBeforeTimers:
                NSLog(@"runloop要去处理timer");
                break;

            case kCFRunLoopBeforeSources:
                NSLog(@"runloop要去处理Sources");
                break;

            case kCFRunLoopBeforeWaiting:
                NSLog(@"runloop要睡觉了");
                break;

            case kCFRunLoopAfterWaiting:
                NSLog(@"runloop醒来啦");
                break;

            case kCFRunLoopExit:
                NSLog(@"runloop退出");
                break;
            default:
                break;
         }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);

    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

Timer

RunLoop的应用

让Timer在tableView滚动时继续计时

方案一:把计时器同时添加NSDefaultRunLoopMode和UITrackingRunLoopMode

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

方案二:把计时器添加到NSRunLoopCommonModes模式上

方案三:开一个子线程进行计时,更新UI时切换至主线程

控制一些线程存在时间

线程永久存在

- (NSThread *)thread
{
    if(!_thread)
    {
        _thread = [[NSThread alloc] initWithTarget:self selector:@selector(onThread) object:nil];
        _thread.name = @"customThread";
    }
    return _thread;
}

- (void)onThread
{
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

控制线程在合适的时候退出

- (NSThread *)thread
{
    if(!_thread)
    {
        _thread = [[NSThread alloc] initWithTarget:self selector:@selector(onThread) object:nil];
        _thread.name = @"customThread";
    }
    return _thread;
}

- (void)onThread
{
    while(!isFinished)
    {
        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }
}

总结

一个线程对应一个RunLoop,一个RunLoop只能运行一个mode,一个mode里面包含source/observer/timer。mode主要描述的是线程的状态,比如tableView滚动时,主线程把当前RunLoop的切换到UITrackingRunLoopMode下,当滚动停止时又换回至NSDefaultRunLoopMode。当RunLoop在休眠状态时,可以被source/timer这些方式激活。激活的方式其实就是在RunLoop内部实现一个do-while循环不断进行轮询是否满足激活的条件。

参考资料

深入理解RunLoop
iOS线下分享《RunLoop》by 孙源@sunnyxx
RunLoop系列之源码分析

上一篇下一篇

猜你喜欢

热点阅读