一、runLoop是什么
字面意思:运行循环,程序运行过程中循环的处理事件
它的实际:实际是一个对象,这个对象提供一个入口函数,执行这个入口函数后,程序会进入一个do..while
循环,循环的处理一些事情。
二、runLoop有什么用?
2.1 如果没有runLoop
?
int main(int argc, char * argv[]){
@autoreleasepool {
NSLog(@“%s”, __func__);
}
return 0;
}
结果:程序执行完就会退出。
2.2 如果有runLoop
?
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
结果:程序一直执行没有退出。
三、runLoop在程序中的例子
3.1 触摸
3.2 定时器
3.3
PerformSelector
3.4
GCD
异步操作回到主线程中
总结
runLoop
基本作用:
1、保持程序的持续运行
2、处理App
中的各种事件(触摸、定时器、PerformSelector
)
3、节省CPU
资源、提高程序性能:该做事的时候做事,该休息的时候休息。
四、拓展关于PerformSelector
- (void)viewDidLoad {
[super viewDidLoad];
BOOL isNoFire = [self respondsToSelector:@selector(fire)];
if (isNoFire) {
[self performSelector:@selector(fire)];
}
}
- (void)fire {
NSLog(@"%s", __func__);
}
总结:下面讲讲performSelector
调用方法和直接调用方法的区别
4.1、performSelector
是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验;
如果fire
不存在,那么直接调用:在编译时候就能够发现(借助Xcode
可以写完就发现),但是使用performSelector
的话一定是在运行时候才能发现(此时程序崩溃);
Cocoa
支持在运行时向某个类添加方法,即方法编译时不存在,但是运行时候存在,这时候必然需要使用performSelector
去调用。所以有时候如果使用了performSelector
,为了程序的健壮性,会使用检查方法respondsToSelector
。
4.2、直接调用方法时候,一定要在头文件中声明该方法的使用,也要将头文件import
进来。而使用performSelector
时候, 可以不用import
头文件包含方法的对象,直接用performSelector
调用即可。
4.3、performSelector
是在iOS
中的一种方法调用方式。他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime
的一种应用方式。
所以performSelector
和直接调用方法的区别就在与runtime
。
直接调用编译是会自动校验。如果方法不存在,那么直接调用 在编译时候就能够发现,编译器会直接报错。
但是使用performSelector
的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector
的话他就会有个最佳伴侣respondsToSelector:
来在运行时判断对象是否响应此方法。
五、runLoop与线程的关系?
总结:
5.1 线程与runLoop
是一一对应的
5.2 线程创建的时候,并没有创建runLoop
对象,runLoop
会在第一次获取的时候自动创建。
5.3 主线程默认开启了runLoop
,子线程默认没有开启。
源码如下:
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 访问 loopsDic 时的锁
static CFLock_t loopsLock = CFLockInit;
// 获取一个 pthread 对应的 RunLoop。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
}
// 直接从 Dictionary 里获取。
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
之间是一一对应的,其关系是保存在一个全局的 Dictionary
里。线程刚创建时并没有 RunLoop
,如果你不主动获取,那它一直都不会有。RunLoop
的创建是发生在第一次获取时,RunLoop
的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop
(主线程除外)。
六、runLoop执行过程?
//
// RunLoop内部执行过程.c
// TZRunloop001-初识
//
// Created by hzg on 2018/8/25.
// Copyright © 2018年 tz. All rights reserved.
//
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
), 进入内核态,由内核来将线程置于休眠 -
有消息,就唤醒线程,回到用户态,来处理消息.
八、GCD Timer与NSTimer区别?
8.1、NSTimer不准时的原因?
1.
runLoop
循环处理的这个时间
2.受runLoop
模式的影响
8.2、GCD Timer与NSTimer不同的原因?
- 都是源,一个是
runLoop
的源,一个Dispatch
的源GCD timer
不需要加入mode
代码如下
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self gcdTimerTest];
}
- (void) gcdTimerTest {
// 队列
dispatch_queue_t queue = dispatch_get_main_queue(); // 主线程队列,会受到runLoop影响
// dispatch_queue_t queue = dispatch_queue_create("timer_serial_label", DISPATCH_QUEUE_SERIAL); // 子线程队列,不会受到runLoop影响
// 创建定时器
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(@"你好");
});
// 启动定时器,默认是关闭的
dispatch_resume(timer);
self.timer = timer;
}
总结:GCD Timer总结?
1.
GCD Timer
精度高
2.GCD Timer
主线程执行会受runLoop
影响,子线程不受影响
3.GCD Timer
不受模式切换的影响
参考
iOS performSelector(参考)
推荐好文
深入理解RunLoop