CFRunLoop的概念及使用
2020/3/25 23:01:47
本文主要是介绍CFRunLoop的概念及使用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
CFRunLoop
的概念
简单来说,
CFRunLoop
对象负责监控事件输入源以及对其进行分发管理。CFRunLoop
管理的类型通常分为sources(CFRunLoopSource
)、timers(CFRunLoopTimer
)和observers(CFRunLoopObserver
)三种类型。
CFRunLoop
的使用
1. CFRunLoopSource
CFRunLoopSourceRef
是产生事件的地方。Source
包括Source0
和Source1
两个版本。
Source0
:主要由应用程序管理,它并不能主动触发事件。使用时,你需要先调用CFRunLoopSourceSignal(source)
,将这个Source
标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)
来唤醒RunLoop
,让其处理这个事件。通常我们使用的也是Source0
事件。
Source1
:主要由于RunLoop
和kernel
进行管理。包含了一个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
的状态变化,以便在不同状态时做一些操作。可以通过CFRunLoopObserverCreateWithHandler
和CFRunLoopObserverCreate
方法创建观察对象,前者是通过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 }; 复制代码
- 通过监听
RunLoop
的kCFRunLoopBeforeWaiting
和kCFRunLoopExit
状态,回调处理相关操作,以实现利用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
调用方法主要集中在kCFRunLoopBeforeSources
和kCFRunLoopAfterWaiting
状态之间,可以通过开辟一个子线程来实时计算两个状态之间的耗时,看是否超过某个阈值,从而来判断主线程的卡顿情况。
@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系列文章
参考资料
这篇关于CFRunLoop的概念及使用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值