一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
iShot2022-02-08 22.39.00.png
CFRunLoopRef
-
代表 RunLoop 的对象
1. CFRunLoopGetCurrent();- [NSRunLoop currentRunLoop];
-
获得主线程的 RunLoop 对象
- CFRunLoopGetMain();
- [NSRunLoop mainRunLoop];
CFRunLoopModeRef
- 代表 RunLoop 的运行模式
- kCFRunLoopDefaultMode
- App的默认运行模式,通常主线程是在这个运行模式下运行
- UITrackingRunLoopMode
- 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
- UIInitializationRunLoopMode
- 在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode
- 接受系统内部事件,通常用不到
- kCFRunLoopCommonModes
- 是个模式集合,而不是一个具体的模式.
- kCFRunLoopDefaultMode
CFRunLoopSourceRef
- 就是 RunLoop 模型图中提到的输入源 / 事件源
1. Source0 :非基于Port
- (void)testDemo4
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"starting thread.......");
_runLoopRef = CFRunLoopGetCurrent();
//初始化_source_context。
bzero(&_source_context, sizeof(_source_context));
//这里创建了一个基于事件的源,绑定了一个函数
_source_context.perform = fire;
//参数
_source_context.info = "hello";
//创建一个source
_source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
//将source添加到当前RunLoop中去
CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);
//开启runloop 第三个参数设置为YES,执行完一次事件后返回
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);
NSLog(@"end thread.......");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (CFRunLoopIsWaiting(_runLoopRef)) {
NSLog(@"RunLoop 正在等待事件输入");
//添加输入事件
CFRunLoopSourceSignal(_source);
//唤醒线程,线程唤醒后发现由事件需要处理,于是立即处理事件
CFRunLoopWakeUp(_runLoopRef);
}else {
NSLog(@"RunLoop 正在处理事件");
//添加输入事件,当前正在处理一个事件,当前事件处理完成后,立即处理当前新输入的事件
CFRunLoopSourceSignal(_source);
}
});
}
//此输入源需要处理的后台事件
static void fire(void* info){
NSLog(@"我现在正在处理后台任务");
printf("%s",info);
}
// - 2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
// - 2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件输入
// - 2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我现在正在处理后台任务
hello
// - 2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......
- Source1:基于Port,通过内核和其他线程通信,接收、分发系统事件
- (void)testDemo3{
//声明两个端口 随便怎么写创建方法,返回的总是一个NSMachPort实例
NSMachPort *mainPort = [[NSMachPort alloc]init];
NSPort *threadPort = [NSMachPort port];
//设置线程的端口的代理回调为自己
threadPort.delegate = self;
//给主线程runloop加一个端口
[[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//添加一个Port
[[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
NSString *s1 = @"hello";
NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// - 这个传参数组里面只能装两种类型的数据,一种是NSPort的子类,一种是NSData的子类
NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
//过2秒向threadPort发送一条消息,第一个参数:发送时间。msgid 消息标识。
//components,发送消息附带参数。reserved:为头部预留的字节数(从官方文档上看到的,猜测可能是类似请求头的东西...)
[threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];
});
}
//这个NSMachPort收到消息的回调,注意这个参数,可以先给一个id。如果用文档里的NSPortMessage会发现无法取值
- (void)handlePortMessage:(id)message{
NSLog(@"收到消息了,线程为:%@",[NSThread currentThread]);
//只能用KVC的方式取值
NSArray *array = [message valueForKeyPath:@"components"];
NSData *data = array[1];
NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",s1);
// - NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
// - NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];
// - [NSRunloop currentRunloop] removePort: addPort:threadPort forMode: runMode:NSDefaultRunLoopMode];
// - 2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了,线程为:<NSThread: 0x60800026d700>{number = 3, name = (null)}
// - 2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello
}
CFRunLoopTimerRef
- 就是 RunLoop 模型图中提到的定时源
-
基于时间的触发器, 当timer入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调
-
创建timer
-
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
// - CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerContext context;
bzero(&context, sizeof(context));
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, &myCFTimeCallback, &context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode);
CFRunLoopObserverRef
- 观察者,能够监听 RunLoop 的状态改变
- 可监听的状态
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒:64
kCFRunLoopExit = (1UL << 7), // 即将从Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
- 创建observer
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 释放Observer
CFRelease(observer);
上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环, 所以如果希望runloop保活, 需要至少包含一个 modeitem(Source/Timer/Observer).
RunLoop 的核心就是一个 mach_msg() 这个函数去接收消息,如果没有别人发送 port 消息(基于port的source事件/timer时间到了/runloop自身超时/被其他调用者唤醒)过来,内核会将线程置于等待状态.
RunLoop 的 Mode
struct __CFRunLoop {
/**
一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到
RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时
,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer
同步到具有 “Common” 标记的所有Mode里。
*/
CFMutableSetRef _commonModes; // - 标记为Common的mode集合
CFMutableSetRef _commonModeItems; //- <Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // - 当前的mode
CFMutableSetRef _modes;
...
};
// - kCFRunLoopDefaultMode : 默认的mode
// - kCFRunLoopCommonModes : 标记为 common的mode, 可以用这个字符串操作 Common Items,或标记一个 Mode 为 “Common”
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
App 启动后 RunLoop 的状态
CFRunLoop {
current mode = kCFRunLoopDefaultMode
common modes = {
UITrackingRunLoopMode
kCFRunLoopDefaultMode
}
common mode items = {
// source0 (manual)
CFRunLoopSource {order =-1, {
callout = _UIApplicationHandleEventQueue}}
CFRunLoopSource {order =-1, {
callout = PurpleEventSignalCallback }}
CFRunLoopSource {order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}}
// source1 (mach port)
CFRunLoopSource {order = 0, {port = 17923}}
CFRunLoopSource {order = 0, {port = 12039}}
CFRunLoopSource {order = 0, {port = 16647}}
CFRunLoopSource {order =-1, {
callout = PurpleEventCallback}}
CFRunLoopSource {order = 0, {port = 2407,
callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
CFRunLoopSource {order = 0, {port = 1c03,
callout = __IOHIDEventSystemClientAvailabilityCallback}}
CFRunLoopSource {order = 0, {port = 1b03,
callout = __IOHIDEventSystemClientQueueCallback}}
CFRunLoopSource {order = 1, {port = 1903,
callout = __IOMIGMachPortPortCallback}}
// Ovserver
CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
callout = _wrapRunLoopWithAutoreleasePoolHandler}
CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting
callout = _UIGestureRecognizerUpdateObserver}
CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit
callout = _afterCACommitHandler}
CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
callout = _wrapRunLoopWithAutoreleasePoolHandler}
// Timer
CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,
next fire date = 453098071 (-4421.76019 @ 96223387169499),
callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}
},
modes = {
CFRunLoopMode {
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode {
sources0 = { /* same as 'common mode items' */ },
sources1 = { /* same as 'common mode items' */ },
observers = { /* same as 'common mode items' */ },
timers = { /* same as 'common mode items' */ },
},
CFRunLoopMode {
sources0 = {
CFRunLoopSource {order = 0, {
callout = FBSSerialQueueRunLoopSourceHandler}}
},
sources1 = (null),
observers = {
CFRunLoopObserver >{activities = 0xa0, order = 2000000,
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
)},
timers = (null),
},
CFRunLoopMode {
sources0 = {
CFRunLoopSource {order = -1, {
callout = PurpleEventSignalCallback}}
},
sources1 = {
CFRunLoopSource {order = -1, {
callout = PurpleEventCallback}}
},
observers = (null),
timers = (null),
},
CFRunLoopMode {
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
}
}
}
- AutoreleasePool (_wrapRunLoopWithAutoreleasePoolHandler())
- Entry中(即将进入loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池
- BeforeWaiting(准备进入休眠)时候调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池, Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。
- 在主线程执行的代码,通常是写在事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏.
- 事件响应(__IOHIDEventSystemClientQueueCallback())
- 当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用_UIApplicationHandleEventQueue()包装成UIEvent进行应用的处理或分发。
- 手势识别(_UIGestureRecognizerUpdateObserver())
- 当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。当监听到 BeforeWaiting (Loop即将进入休眠)时候, 其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
- 界面更新(_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv())
- 当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去, 当监听到BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件时, 会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
- 计时器
- 一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件, 如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。
- CADisplayLink也是一样的, 如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉
- PerformSelecter
- 当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法不会执行.
NSRunLoop
- -(void)run;
- 永久运行在NSDefaultRunLoopMode, 这种方式开启的runloop无法停止(除非移除这个runloop上的所有事件源),
- 无法使用CFRunLoopStop(runloopRef),停止Run Loop的运行。
- -(void)runUntilDate:(NSDate *)limitDate;
- 可以控制每次Run Loop的运行时间,也是运行在NSDefaultRunLoopMode模式。
- 这个方法运行Run Loop一段时间会退出给你检查运行条件的机会,如果需要可以再次运行Run Loop。
3.无法使用CFRunLoopStop(runloopRef),停止Run Loop的运行。
- -(BOOL)runMode:(NSString *)mode beforeDate:(NSDtate *)limitDate;
- 可以使用CFRunLoopStop(runloopRef),停止Run Loop的运行
CFRunLoopRef
CFRunLoop能直接停止掉所有的CFRunLoop运行起来的runloop
网友评论