iOS Runloop 补充

主要讲解Runloop的底层结构;

文中使用的 CF源码是CF-1153.18版本;

Runloop 简析
Runloop 补充


1. Runloop 的应用范畴

  • 定时器(timer)和 PerformSelector;
  • GCD 的 Asyn Main Queue;
  • 事件相应, 手势识别, 界面刷新 ;
  • 网络请求;
  • AutoreleasePool

2. Runloop 的底层结构

我的都知道RunloopCFRunloopRef和基于它封装的CFRunloop,所以我们只需要探究下CFRunloopRef的结构即可得知它的底层结构;

///源码中CFRunLoopRef的定义如下
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
===>
///__CFRunLoop的定义如下, 有很多成员, 不过我们只需要确定它的主要组成是各个 Mod 即可;
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
     ///每一个 runloop 中都有一个线程, 确保线程和 runloop 是一一对应的;
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
     ///Mod的集合
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
===>
///Mod的底层结构如下
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    ///集合sources0
    CFMutableSetRef _sources0;
    ///集合souces1
    CFMutableSetRef _sources1;
    ///数组observers
    CFMutableArrayRef _observers;
    ///数组timers
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
...
}

关于Runloop中常用的几个类如下:
CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, CFRunLoopObserverRef, CFRunLoopTimerRef;
CFRunLoopModeRef中的几个主要变量的含义:

  • source0 : 处理触摸事件,或者 performSelector:OnThread:方法;
  • source1 : 基于port的线程通讯(addPort: forMode:), 系统事件捕捉(例如点击屏幕后首先是source1捕捉然后再封装转发给source0处理);
  • Timer: 就是NSTimer, 调用performSelector: withObject: afterDelay:也是类似操作;
  • Observer :监听Runloop的状态; 主线程UI刷新(例如设置一个颜色, 代码执行完后并不会立即改变, 而是在Runloop状态BeforeWaiting之前刷新的);另外AutoreleasePool也是一样具体什么时候releaseRunloop的监听者决定;

如果Mode中没有任何的Source0, Source, Timer, ObserverRunloop会立即退出;

3. Runloop的几种状态:

我们都知道Runloop是一个事件循环, 整个循环有多少种状态呢, 源码种定义如下:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    ///即将进入 Runloop
    kCFRunLoopEntry = (1UL << 0),
    ///即将处理 Timer
    kCFRunLoopBeforeTimers = (1UL << 1),
    ///即将处理 Source
    kCFRunLoopBeforeSources = (1UL << 2),
    ///即将进入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    ///即将唤醒 Runloop
    kCFRunLoopAfterWaiting = (1UL << 6),
    ///即将退出 Runloop
    kCFRunLoopExit = (1UL << 7),
    ///所有状态集合
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • kCFRunLoopEntry : 即将进入Runloop;
  • kCFRunLoopBeforeTimers : 即将处理Timer;
  • kCFRunLoopBeforeSources: 即将处理Sources;
  • kCFRunLoopBeforeWaiting : 即将进入休眠(事情已经处理完毕);
  • kCFRunLoopAfterWaiting : 从休眠中唤醒;
  • kCFRunLoopExit: 即将退出Runloop;
  • kCFRunLoopAllActivities: 所有状态集合;
    关于这些状态的测试, 具体详见补充1;

4. Runloop在日常开发种的使用场景

具体测试代码相见下方补充阶段;

  • 控制线程的生命周期(线程保活);补充2
  • 解决NSTimer在滑动时失效的问题;补充3
  • 监控应用卡顿;补充4
  • 性能优化;补充5

补充

补充1:Runloop的各个状态测试

测试代码如下:

    ///创建一个Observer
    CFRunLoopObserverRef CFObserver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                NSLog(@"Runloop即将进入Runloop: kCFRunLoopEntry");
                break;
            }
               case kCFRunLoopBeforeTimers: {
                   NSLog(@"Runloop即将处理Timer: kCFRunLoopBeforeTimers");
                   break;
               }
                case kCFRunLoopBeforeSources: {
                    NSLog(@"Runloop即将处理Source: kCFRunLoopBeforeSources");
                    break;
                }
                case kCFRunLoopBeforeWaiting: {
                    NSLog(@"Runloop即将进入休眠: kCFRunLoopBeforeWaiting");
                    break;
                }
                case kCFRunLoopAfterWaiting: {
                    NSLog(@"Runloop从休眠中唤醒: kCFRunLoopAfterWaiting");
                    break;
                }
                case kCFRunLoopExit: {
                    NSLog(@"Runloop即将退出: kCFRunLoopExit");
                }
            default:
                break;
        }
      });
    ///为Runloop添加Observer
    CFRunLoopAddObserver(CFRunLoopGetMain(), CFObserver, kCFRunLoopCommonModes);
    ///CF框架下Create或者Copy的对象要进行释放
    CFRelease(CFObserver);

具体打印结果由于篇幅太大, 不再贴出, 请自行下载测试代码查看打印结果;

补充2:控制线程的生命周期

通过启动子线程的Runloop并添加source1达到线程不会 kill的效果; 详解请看这篇文章

    /*
     case3:
     子线程中开启runloop, 为其添加任意的souce0/souce1/timer/observer, 并让其run, 则此线程由于runloop的关系就不会被销毁;
     */
    self.thread3 = [[XThread alloc] initWithBlock:^{
            /*
         我们知道runloop中如果没有任何source0/souce1/timer/observer 则runloop会立即退出;
         所以为runloop添加source1, 然后让runloop执行run;
         */
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
        /*
         注意run的释义:If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
         大致翻译:  如果没有souces或者timers添加到runloop中则方法理解退出; 如果有,将在NSDefaultRunLoopMode模式下无限次执行runMode:beforeDate:来处理添加的souces和timers;
         注意: 调用 runloop 的 run 方法则不再能取消 runloop 即使是调用了CFRunLoopStop(CFRunLoopGetCurrent()); 也只是停了其中一次循环;

         */
        [[NSRunLoop currentRunLoop] run];
    }];
    [self.thread3 start];

另一种方式

 ///创建一个source --  往 runloop 中添加 source1 source0 timer  observer 均可打到效果
    ///初始化 context
    CFRunLoopSourceContext context = {0};
    ///当一个runloop中没有事件源处理时, 运行完就会退出;
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    ///1. 2. 创建runloop 同时向runloop中的defaultMode下面添加source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    ///3. 启动runloop
    while (shouldRun) {
        @autoreleasepool {
            ///令当前的runloop运行在defaultMode下
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true);
        }
    }
    ///某个时机, 将静态变量shouldRun = NO时, 退出runloop, 进而退出线程;
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
补充3: 解决NSTimer在界面拖动时暂时失效的问题

正常情况下, NSTimer都是添加在DefaultMode模式下; 一旦界面拖动时Runloop就会切换模式到TrackingMode模式下, NSTimer就会暂时失效;
解决方案:将NStimer添加到RunloopCommonMode模式下;这样Runloop就会同时处理DefaultModeTrackingMode中的事件;

    static NSInteger i = 1;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"执行次数:  %ld", i ++);
    }];
    [timer fire];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
补充4:详见性能优化部分
补充5:相见性能优化部分

文中测试代码


参考文章和下载链接
Apple 一些源码的下载地址

你可能感兴趣的:(iOS Runloop 补充)