runloop

runloop概念

  • RunLoop 相关类

    CFRunLoopRef:代表 RunLoop 的对象
    CFRunLoopModeRef:代表 RunLoop 的运行模式
    CFRunLoopSourceRef:就是 RunLoop 模型图中提到的输入源 / 事件源
    CFRunLoopTimerRef:就是 RunLoop 模型图中提到的定时源
    CFRunLoopObserverRef:观察者,能够监听 RunLoop 的状态改变

    一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
    RunLoop启动时只能选择其中一个Mode,作为currentMode
    如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
    如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

  • Mode
    mode图解

    mode种类
    kCFRunLoopDefaultMode:App的默认运行模式,通常主线程是在这个运行模式下运行
    UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动)
    kCFRunLoopCommonModes:伪模式,(kCFRunLoopDefaultMode + UITrackingRunLoopMode)
    UIInitializationRunLoopMode(不常用):在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
    GSEventReceiveRunLoopMode(不常用):接受系统内部事件,通常用不到

  • source
    Source0:触摸事件处理、performSelector:onThread:
    Source1:基于Port的线程间通信、系统事件捕捉

  • timer
    NSTimer
    performSelector:withObject:afterDelay:

  • observes
    UI刷新(BeforeWaiting)
    Autorelease pool(BeforeWaiting)
    用于监听RunLoop的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), //即Souce
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠唤醒
    kCFRunLoopExit = (1UL << 7), //即将退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
  • runloop与线程关系

    每条线程都有唯一的一个与之对应的RunLoop对象
    RunLoop保存在一个全局的Dictionary里,线程作为keyRunLoop作为value
    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    RunLoop会在线程结束时销毁
    主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

  • 获取runloop对象

    1
    2
    3
    4
    5
    6
    //Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    //Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
  • runloop对象主要内容

    1
    2
    3
    4
    5
    6
    7
    struct __CFRunLoop {
    pthread_t _pthread; //RunLoop对应的线程
    CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode
    CFMutableSetRef _commonModeItems;//存储所有commonMode的 item(source、timer、observer)
    CFRunLoopModeRef _currentMode; //当前运行的mode
    CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
    };

runloop原理

  • runLoop确实是do while通过判断result的值实现的
    1
    2
    3
    4
    5
    6
    7
    8
    // 用DefaultMode启动
    void CFRunLoopRun(void) { /* DOES CALLOUT */
    int32_t result;
    do {
    result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),kCFRunLoopDefaultMode, 1.0e10, false);
    CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
  • 原理图

runloop实用

  • 滚动Scrollview导致定时器失效

    1
    2
    3
    //用runloop解决 或者直接使用GCD创建定时器具体参考
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  • 常驻线程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    - (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
    }

    - (void)run {
    NSLog(@"----------run----%@", [NSThread currentThread]);
    @autoreleasepool{
    /*如果不加这句,会发现runloop创建出来就挂了,因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉*/
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // 方法1 ,2,3实现的效果相同,让runloop无限期运行下去
    [[NSRunLoop currentRunLoop] run];
    // 方法2
    //[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // 方法3
    //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
    }
    NSLog(@"---------");
    }

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

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    }

  • 滑动时不渲染图片,停止滑动再渲染

    1
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
  • 监听RunLoop的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    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;
    }
    });
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);

参考文档

Runloop源码地址
Runloop官方文档介绍
iOS RunLoop详解
iOS 多线程:『RunLoop』详尽总结