RunLoop
RunLoop初识
RunLoop是什么?
- 字面意思:运行循环
- 程序运行过程中循环的处理事情
- 实际是一个对象, 这个对象提供一个 入口函数,执行这个入口函数后,程序会进入一个do..while循环,循环的处理一些事情。
RunLoop有什么用?
-
如果没有Runloop?
int main(int argc, char * argv[]){ @autoreleasepool { NSLog(@“%s”, __func__); } return 0; } //结果:程序执行完就会退出。
-
如果有RunLoop?
int main(int argc, char * argv[]){ @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])) } } //结果:程序一直执行没有退出
- RunLoop有什么用?
- 保持程序的持续运行
- 处理App中的各种事件(触摸、定时器、PerformSelector)
- 节省CPU资源、提高程序性能: 该做事的时候做事,该休息的时候休息。
- RunLoop怎么使用?
-
iOS提供了2套API来访问和使用RunLoop:
//NSRunLoop基于CFRunLoopRef封装 Foundation:NSRunLoop Core Foundation:CFRunLoopRef
-
RunLoop与线程
-
线程与RunLoop是一一对应的
-
线程创建的时候,并没有创建RunLoop对象,RunLoop会在第一次获取的时候自动创建
-
主线程默认开启了RunLoop, 子线程默认没有开启子线程
static CFMutableDictionaryRef __CFRunLoops = NULL; static CFLock_t loopsLock = CFLockInit; // should only be called by Foundation // t==0 is a synonym for "main thread" that always works CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //__CFRunLoops全局字典 if (!__CFRunLoops) { __CFUnlock(&loopsLock); //创建字典并且创建主runloop CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); //线程为key runloop为值存进去 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); } //去runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { //没有取到就创建 CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } return loop; }
RunLoop相关类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopRef
- 一个RunLoop对应着一条线程
- 一个RunLoop包含多个Mode,每个 Mode 又包含若干个 Source/Timer/Observer
- Source/Timer/Observer又叫mode item。不同mode下的mode item互不影响
- RunLoop运行过程中,只选择一种模式运行
- 切换Mode,程序退出当前RunLoop mode,再重新指定Mode执行
CFRunLoopModeRef
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
//获取当前模式
CFRunLoopRef rl = CFRunLoopGetCurrent();
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(rl);
NSLog(@"mode ---> %@", mode);
//获取全部模式
CFArrayRef array = CFRunLoopCopyAllModes(rl);
NSLog(@"array ---> %@", array);
/*
UITrackingRunLoopMode,//界面跟着,主要用于scrollview滑动,会自动切换
GSEventReceiveRunLoopMode,//接受系统事件
kCFRunLoopDefaultMode,
kCFRunLoopCommonModes//公用属性items
*/
CFRunLoopSourceRef
//源分为source0和source1
//source0需要我们标志为待处理的状态,并且唤醒runloop
//source1系统标志为待处理的状态,并且已经唤醒runloop
//能自定义的也就是source0
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;//标志
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
//联合体,节省内存
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
//示例
- (void) sourceTest {
CFRunLoopSourceContext context = {
0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
schedule,
cancel,
perform
};
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
//触发schedule
CFRunLoopAddSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
//触发perform,标识为待处理
CFRunLoopSourceSignal(source0);
CFRunLoopWakeUp(CFRunLoopGetCurrent());
//触发cancel
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source0, kCFRunLoopDefaultMode);
CFRelease(source0);
}
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
NSLog(@"%s", __func__);
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
NSLog(@"%s", __func__);
}
void perform(void *info) {
NSLog(@"%s", __func__);
}
source0
触摸事件
自定义输入源
performSelector:onThread:
source1
端口(Port)
计时源
NSTimer
performSelector:withObject:afterDelay
NSPort
- NSPort是什么?
- 通信通道的抽象类。
- 能干什么?
- 我们能够使用端口进行线程间的一个通信。
-
要接收传入消息,必须将NSPort对象添加到NSRunLoop对象中作为输入源
-
端口用完之后,如果不用, 要释放, 不然产生的端口对象可能会逗留并创建内 存泄漏。要使端口对象无效,请调用它的invalidate方法。
-
Foundation定义了NSPort的三个具体子类。NSMachPort和NSMessagePort只允许本地(在同一台机器上)通信。NSSocketPort支持本地和远程通信,但是对于本地情 况,可能比其他的要昂贵。
-
在使用allocWithZone:或port创建NSPort对象时,将创建 一个NSMachPort对象。
@interface ViewController () @property (nonatomic, strong) NSPort* subThreadPort; @property (nonatomic, strong) NSPort* mainThreadPort; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.mainThreadPort = [NSPort port]; self.mainThreadPort.delegate = self; [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode]; [self task]; } - (void) task { NSThread* thread = [[NSThread alloc] initWithBlock:^{ self.subThreadPort = [NSPort port]; self.subThreadPort.delegate = self; [[NSRunLoop currentRunLoop] addPort:self.subThreadPort forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; }]; [thread setName:@"子线程"]; [thread start]; } - (void)handlePortMessage:(id)message { NSLog(@"%@", [NSThread currentThread]); NSMutableArray* components = [message valueForKey:@"components"]; if ([components count] > 0) { NSData* data = [components objectAtIndex:0]; NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@", str); } sleep(2); if (![[NSThread currentThread] isMainThread]) { NSMutableArray* sendComponents = [NSMutableArray array]; NSData* data = [@"world" dataUsingEncoding:NSUTF8StringEncoding]; [sendComponents addObject:data]; [self.mainThreadPort sendBeforeDate:[NSDate date] components:sendComponents from:self.subThreadPort reserved:0]; } } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSMutableArray* components = [NSMutableArray array]; NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding]; [components addObject:data]; //端口传递的都是NSData [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0]; } @end
CFRunLoopTimerRef
- (void) timerTest {
CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0, 1, 0, 0, ^(CFRunLoopTimerRef timer) {
NSLog(@"%s", __func__);
});
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
}
CFRunLoopObserverRef
观察者(observer): 监听RunLoop的状态
- (void) observerTest {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"%lu", activity);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}
RunLoop内部执行过程
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
/// 核心函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
/// 通知 Observers: 即将处理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 通知 Observers: 即将处理Source事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
/// 处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 处理sources0返回为YES
if (sourceHandledThisLoop) {
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
/// 判断有无端口消息(Source1)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
/// 处理消息
goto handle_msg;
}
/// 通知 Observers: 即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/// 等待被唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
/// 通知 Observers: 被唤醒,结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被Timer唤醒) {
/// 处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
} else if (被GCD唤醒) {
/// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else if (被Source1唤醒) {
/// 被Source1唤醒,处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 处理block
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
// main dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
休眠的原理
-
内核:进程、线程
-
RunLoop实现休眠的原理, 真正的原因是:
- 调用了内核的API(mach_msg), 进入内核态,由内核来将线程置于休眠
- 有消息,就唤醒线程,回到用户态,来处理消息.
应用与总结
-
定时器
-
scrollView冲突问题
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello"); }]; NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello %@", [[NSRunLoop currentRunLoop] currentMode]); }]; //加入运行循环 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //放入子线程 NSThread* th = [[NSThread alloc] initWithBlock:^{ NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello"); }]; //线程保活 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; }]; [th start];
定时器
- NSTimer不准时的原因,主要有2个:
- RunLoop循环处理的这个时间
- 受RunLoop模式的影响
- gcd timer与NSTimer是不同的:
- 都是源,一个是RunLoop的源,一个Dispatch的源
- GCD timer不需要加入mode
@interface ViewController () @property (nonatomic, strong) dispatch_source_t timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self gcdTimerTest]; } - (void) gcdTimerTest { // 主队列。交由主线程处理。如果runloop任务繁重。受影响 dispatch_queue_t queue = dispatch_get_main_queue(); //子线程。跟runloop没有关系 // dispatch_queue_t queue = dispatch_queue_create("timer_serial_label", DISPATCH_QUEUE_SERIAL); // 创建定时器 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // 设置定时的开始时间、间隔时间 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1*NSEC_PER_SEC, 0); // 设置定时器回调 dispatch_source_set_event_handler(timer, ^{ NSLog(@"你好"); // [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { // NSLog(@"hello"); // }]; }); // 启动定时器,默认是关闭的 dispatch_resume(timer); //生命周期不受runloop影响。延长生命周期 self.timer = timer; } @end
- GCDTimer总结:
- GCDTimer精度高
- GCDTimer主线程执行会RunLoop影响,子线程不受影响
- GCDTimer不受模式切换的影响
- NSTimer不准时的原因,主要有2个: