阅读 334

CFRunLoop的概念及使用

CFRunLoop的概念

简单来说,CFRunLoop对象负责监控事件输入源以及对其进行分发管理。CFRunLoop管理的类型通常分为sources(CFRunLoopSource)、timers(CFRunLoopTimer)和observers(CFRunLoopObserver)三种类型。

CFRunLoop的使用

1. CFRunLoopSource

CFRunLoopSourceRef是产生事件的地方。Source包括Source0Source1两个版本。

Source0:主要由应用程序管理,它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。通常我们使用的也是Source0事件

Source1:主要由于RunLoopkernel进行管理。包含了一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种Source能主动唤醒RunLoop的线程。

- (void)cfSource {
    //创建上下文
    CFRunLoopSourceContext context = {};
    context.perform = runLoopSourceCallback;
    context.info = (__bridge void *)self;
    
    //创建source
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    
    //添加source
    CFRunLoopAddSource(runLoop, source, kCFRunLoopCommonModes);
    
    NSLog(@"create source in %@", [NSDate date]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //执行source相关事件
        CFRunLoopSourceSignal(source);
        CFRunLoopWakeUp(runLoop);
        CFRelease(source);
    });
}

// source回调
static void runLoopSourceCallback(void *info) {
    NSLog(@"reiceive source in %@", [NSDate date]);
}

//执行结果
create source in Wed Mar 25 16:22:08 2020
reiceive source in Wed Mar 25 16:22:11 2020
复制代码

根据上面的执行结果,可见,对于Source0事件,我们必须调用CFRunLoopSourceSignal方法去标记为“待处理”事件,对于CFRunLoopWakeUp可以根据具体情况调用,如果当前RunLoop是处于运行状态,不调用也是OK的,但为了避免当前RunLoop可能处于休眠状态,最好加上。

2. CFRunLoopTimerRef

CFRunLoopTimerRef是基于时间的触发器。和NSTimer类似,可以执行一些定时任务。

CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, // 用于分配内存,通常使用kCFAllocatorDefault即可
                                       CFAbsoluteTime fireDate,  // 第一次触发调用的时间
                                       CFTimeInterval interval,  // 回调间隔
                                       CFOptionFlags flags,      // 苹果备用参数,传0即可
                                       CFIndex order,            // RunLoop执行事件的优先级,对于Timer是无用的,传0即可
                                       CFRunLoopTimerCallBack callout, // 回调callback
                                       CFRunLoopTimerContext *context); // 用于与callback联系的上下文context
复制代码
- (void)cfTimer {
    self.timerCount = 0;
    //创建上下文
    CFRunLoopTimerContext context = {};
    context.info = (__bridge void*)self; //将当前对象作为参数传入
    
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
                                                   CFAbsoluteTimeGetCurrent() + 1, //第一次回调的时间,则设置为1s以后
                                                   3, //回调时间间隔
                                                   0, 0, &timerFiredCallback, &context);
    // 设置运行时间误差范围
    CFRunLoopTimerSetTolerance(timer, 0.1);
    CFRunLoopAddTimer(runloop, timer, kCFRunLoopCommonModes);
    CFRelease(timer);
    NSLog(@"start timer in %@", [NSDate date]);
}

static void timerFiredCallback(CFRunLoopTimerRef timer, void *info) {
    ViewController *controller = (__bridge ViewController *)info;
    NSLog(@"recieve timer event with count: %@, in %@", @(controller.timerCount), [NSDate date]);
    if (++controller.timerCount == 5) {
        CFRunLoopTimerInvalidate(timer); //关闭定时器
    }
}
复制代码
//执行结果
start timer in Wed Mar 25 16:42:59 2020
recieve timer event with count: 0, in Wed Mar 25 16:43:00 2020
recieve timer event with count: 1, in Wed Mar 25 16:43:03 2020
recieve timer event with count: 2, in Wed Mar 25 16:43:06 2020
recieve timer event with count: 3, in Wed Mar 25 16:43:09 2020
recieve timer event with count: 4, in Wed Mar 25 16:43:12 2020
复制代码

根据结果,可以看到第一次回调是在1s之后,剩余的回调都是每隔3s回调一次。

3. CFRunLoopObserverRef

CFRunLoopObserverRef:观察者,主要用于观察RunLoop的状态变化,以便在不同状态时做一些操作。可以通过CFRunLoopObserverCreateWithHandlerCFRunLoopObserverCreate方法创建观察对象,前者是通过block方式回调,后者是通过C函数callback方式。

  • 状态集
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
};
复制代码
  • 通过监听RunLoopkCFRunLoopBeforeWaitingkCFRunLoopExit状态,回调处理相关操作,以实现利用RunLoop空闲状态时做一些额外的操作。
- (void)runBlockWhenMainThreadIdle {
    __weak typeof(self) wSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"start submit block in %@", [NSDate date]);
        [wSelf runWithBlock:^{
            NSLog(@"finish block when main thread is idle in %@", [NSDate date]);
        }];
    });
}

- (void)runWithBlock:(void(^)(void))block {
    CFRunLoopActivity flag = kCFRunLoopBeforeWaiting | kCFRunLoopExit; //监听RunLoop即将休眠和退出的状态
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, flag, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        //回调操作
        if (block) {
            block();
        }
        //移除相关监听
        CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        CFRelease(observer);
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}
复制代码

第三方库相关应用场景

1. YYTransaction

通过监听RunLoop进入休眠和结束前的状态,来执行布局更新操作。

/// Update layout and selection before runloop sleep/end.
- (void)_commitUpdate {
#if !TARGET_INTERFACE_BUILDER
    _state.needUpdate = YES;
    [[YYTransaction transactionWithTarget:self selector:@selector(_updateIfNeeded)] commit];
#else
    [self _update];
#endif
}
复制代码
@interface YYTransaction()
@property (nonatomic, strong) id target;
@property (nonatomic, assign) SEL selector;
@end

static NSMutableSet *transactionSet = nil;

static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (transactionSet.count == 0) return;
    NSSet *currentSet = transactionSet;
    transactionSet = [NSMutableSet new];
    [currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [transaction.target performSelector:transaction.selector];
#pragma clang diagnostic pop
    }];
}

static void YYTransactionSetup() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        transactionSet = [NSMutableSet new];
        CFRunLoopRef runloop = CFRunLoopGetMain();
        CFRunLoopObserverRef observer;
        
        observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
                                           kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                           true,      // repeat
                                           0xFFFFFF,  // after CATransaction(2000000)
                                           YYRunLoopObserverCallBack, NULL);
        CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    });
}


@implementation YYTransaction

+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
    if (!target || !selector) return nil;
    YYTransaction *t = [YYTransaction new];
    t.target = target;
    t.selector = selector;
    return t;
}

- (void)commit {
    if (!_target || !_selector) return;
    YYTransactionSetup();
    [transactionSet addObject:self];
}

- (NSUInteger)hash {
    long v1 = (long)((void *)_selector);
    long v2 = (long)_target;
    return v1 ^ v2;
}

- (BOOL)isEqual:(id)object {
    if (self == object) return YES;
    if (![object isMemberOfClass:self.class]) return NO;
    YYTransaction *other = object;
    return other.selector == _selector && other.target == _target;
}
@end
复制代码

2. NSThread+Add

为子线程手动添加自动释放池。通过监听RunLoop中的kCFRunLoopEntry状态,保证执行前插入NSAutoreleasePool,然后通过监听kCFRunLoopBeforeWaiting | kCFRunLoopExit状态,保证RunLoop进入睡眠或结束时,释放相关对象。

static NSString *const YYNSThreadAutoleasePoolKey = @"YYNSThreadAutoleasePoolKey";
static NSString *const YYNSThreadAutoleasePoolStackKey = @"YYNSThreadAutoleasePoolStackKey";

static const void *PoolStackRetainCallBack(CFAllocatorRef allocator, const void *value) {
    return value;
}

static void PoolStackReleaseCallBack(CFAllocatorRef allocator, const void *value) {
    CFRelease((CFTypeRef)value);
}


static inline void YYAutoreleasePoolPush() {
    NSMutableDictionary *dic =  [NSThread currentThread].threadDictionary;
    NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
    
    if (!poolStack) {
        /*
         do not retain pool on push,
         but release on pop to avoid memory analyze warning
         */
        CFArrayCallBacks callbacks = {0};
        callbacks.retain = PoolStackRetainCallBack;
        callbacks.release = PoolStackReleaseCallBack;
        poolStack = (id)CFArrayCreateMutable(CFAllocatorGetDefault(), 0, &callbacks);
        dic[YYNSThreadAutoleasePoolStackKey] = poolStack;
        CFRelease(poolStack);
    }
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // create
    [poolStack addObject:pool]; // push
}

static inline void YYAutoreleasePoolPop() {
    NSMutableDictionary *dic =  [NSThread currentThread].threadDictionary;
    NSMutableArray *poolStack = dic[YYNSThreadAutoleasePoolStackKey];
    [poolStack removeLastObject]; // pop
}

static void YYRunLoopAutoreleasePoolObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
        case kCFRunLoopEntry: {
            YYAutoreleasePoolPush();
        } break;
        case kCFRunLoopBeforeWaiting: {
            YYAutoreleasePoolPop();
            YYAutoreleasePoolPush();
        } break;
        case kCFRunLoopExit: {
            YYAutoreleasePoolPop();
        } break;
        default: break;
    }
}

static void YYRunloopAutoreleasePoolSetup() {
    CFRunLoopRef runloop = CFRunLoopGetCurrent();

    CFRunLoopObserverRef pushObserver;
    pushObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopEntry,
                                           true,         // repeat
                                           -0x7FFFFFFF,  // before other observers
                                           YYRunLoopAutoreleasePoolObserverCallBack, NULL);
    CFRunLoopAddObserver(runloop, pushObserver, kCFRunLoopCommonModes);
    CFRelease(pushObserver);
    
    CFRunLoopObserverRef popObserver;
    popObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit,
                                          true,        // repeat
                                          0x7FFFFFFF,  // after other observers
                                          YYRunLoopAutoreleasePoolObserverCallBack, NULL);
    CFRunLoopAddObserver(runloop, popObserver, kCFRunLoopCommonModes);
    CFRelease(popObserver);
}

@implementation NSThread (YYAdd)

+ (void)addAutoreleasePoolToCurrentRunloop {
    if ([NSThread isMainThread]) return; // The main thread already has autorelease pool.
    NSThread *thread = [self currentThread];
    if (!thread) return;
    if (thread.threadDictionary[YYNSThreadAutoleasePoolKey]) return; // already added
    YYRunloopAutoreleasePoolSetup();
    thread.threadDictionary[YYNSThreadAutoleasePoolKey] = YYNSThreadAutoleasePoolKey; // mark the state
}

@end
复制代码

这里还有一点值得参考的是,releasepool的存储位置是放在了线程的私有空间threadDictionary中。另外关于子线程中操作是否需要手动进行释放,可以参考iOS 各个线程 Autorelease 对象的内存管理。个人觉得加上是比较好的,毕竟官方文档并没有明确说明子线程中是不需要加的。

3. SMLagMonitor

RunLoop调用方法主要集中在kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting状态之间,可以通过开辟一个子线程来实时计算两个状态之间的耗时,看是否超过某个阈值,从而来判断主线程的卡顿情况。

@interface SMLagMonitor() {
    int timeoutCount;
    CFRunLoopObserverRef runLoopObserver;
    @public
    dispatch_semaphore_t dispatchSemaphore;
    CFRunLoopActivity runLoopActivity;
}
@end

@implementation SMLagMonitor

#pragma mark - Interface
+ (instancetype)shareInstance {
    static id instance = nil;
    static dispatch_once_t dispatchOnce;
    dispatch_once(&dispatchOnce, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (void)beginMonitor {
    self.isMonitoring = YES;
    //监测卡顿
    if (runLoopObserver) {
        return;
    }
    dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步
    //创建一个观察者
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                              kCFRunLoopAllActivities,
                                              YES,
                                              0,
                                              &runLoopObserverCallBack,
                                              &context);
    //将观察者添加到主线程runloop的common模式下的观察中
    CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    
    //创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的loop用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, STUCKMONITORRATE * NSEC_PER_MSEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //出现三次出结果
                    if (++timeoutCount < 3) {
                        continue;
                    }
//                    NSLog(@"monitor trigger");
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                        NSString *stackStr = [SMCallStack callStackWithType:SMCallStackTypeMain];
                        SMCallStackModel *model = [[SMCallStackModel alloc] init];
                        model.stackStr = stackStr;
                        model.isStuck = YES;
                        [[[SMLagDB shareInstance] increaseWithStackModel:model] subscribeNext:^(id x) {}];
                    });
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
    
}

- (void)endMonitor {
    self.isMonitoring = NO;
    [self.cpuMonitorTimer invalidate];
    if (!runLoopObserver) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    CFRelease(runLoopObserver);
    runLoopObserver = NULL;
}

#pragma mark - Private

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}

@end
复制代码

RunLoop系列文章

参考资料