RunLoop
1.RunLoop概述
- 顾名思义: 运行循环,在程序运行过程中循环做一些事情
- 应用范畴:
- 定时器(
Time
)、PerformSelector
- GCD Async Mian Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool(自动释放池)
- 上述这些技术都是基于RunLoop实现的
- 定时器(
- 如果没有RunLoop,程序执行完代码就会退出程序,如:
int main(int argc, const char * argv[]) {
@autoreleasepool {
//命令行程序默认没有RunLoop 执行完代码就退出
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
- 如果有了RunLoop,程序并不会马上退出,而是保持运行状态
- RunLoop的基本作用:
- 保持程序持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
// UIApplicationMain()中会自动创建一个RunLoop,来保持程序的持续运行
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
2. RunLoop对象
2.1 获取RunLoop对象
- IOS中有两套API来访问和使用RunLoop
- Foundation: NSRunLoop
- Core Foundation : CFRunLoopRef
- NSRunLoop和CFRunLoop都代表这RunLoopd对象,NSRunLoop是基于CFRunLoopRef的一层OC包裹,并且CFRunLoopRef是开源的
- 开源地址: CFRunLoopRef开源代码地址
//Foundation获取RunLoop对象
[NSRunLoop currentRunLoop];//获取当前线程的RUnLoop
[NSRunLoop mainRunLoop];//获取主线程的RunLoop
//Core Foundation获取RunLoop对象
CFRunLoopGetCurrent();//获取当前线程的RunLoop对象
CFRunLoopGetMain();//获取主线程的RunLoop
2.2 RunLoop与线程
-
每条线程都有唯一的一个与之对应的RunLoop对象
-
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
-
线程刚开始创建的时候并没有RunLoop对象,RunLoop会在第一次获取它时创建
-
RunLoop会在线程结束时销毁
-
主线程RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
-
下面我们查看获取RunLoop对象的源码:
- 根据上面地址下载CFRunLoopRef的源码
- 根据获取方法查看源码
//获取主线程RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
// 获取当前线程RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
//获取RunLoop的核心方法
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//首先根据key(当前线程)从CFDictionaryGetValue(全局字典)中获取RunLoop对象
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
//判断RunLoop是否存在, 如果不存在就创建一个新的RunLoop对象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
//再次从字典中取出RunLoop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//如果loop对象不存在,则把创建的新的RunLoop对象设置到字典中, key为当前线程, 并且把newloop对象赋值给loop对象,最后返回
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
- 由上述获取RunLoop源码可以知道,每一个RunLoop对象都有一个唯一一个与之对应的线程,底层采用字典来缓存,线程作为key,RunLoop对象作为value, 并且Runloop对象的创建是在第一个获取RunLoop的时候创建的, 并不会自动先创建好
2.3 RunLoop相关的类
-
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef : RunLoop类型
- CFRunLoopModelRef : RunLoop中的Model(模式)对象
- CFRunLoopSourceRef: RunLoop中的Source(事件)对象
- CFRunLoopTimerRef: RunLoop中的Timer(定时器)对象
- CFRunLoopObserverRef: RunLoop中的observer(观察者)对象
-
CFRunLoopRef的代码结构
//OC中头文件可以找到
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
//CFRunLoopRef源码中找到,删除部分源码之后
struct __CFRunLoop {
pthread_t _pthread; //对应的当前线程对象
CFMutableSetRef _commonModes;//储存标记为common(通用)的模式
CFMutableSetRef _commonModeItems;//储存能够在common(通用)的模式下工作item
CFRunLoopModeRef _currentMode;//RunLoop正在执行的model
CFMutableSetRef _modes;//储存所有Model的数组
};
- CFRunLoopModeRef的源码:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
//删除部分源码之后
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
-
CFRunLoopModeRef
代表RunLoop
的运行模式 -
一个RunLoop包含若干个Model,每个
Model
又包含若干个Source0/Source1/Timer/Observer
-
RunLoop
启动只能选择其中一个Model
,作为currentModel
-
如果需要切换
Model
,只能退出当前Model
,在重新选择一个Model
进入,不同组的Source0/Source1/Timer/Observer
能分隔开,互不影响 -
如果
Model
里没有任何Source0/Source1/Timer/Observer
,RunLoop
会立马退出 -
大概结构图如下:
-
CFRunLoopModeRef
的常见两种ModelkCFRunLoopDefaultMode
(NSDefaultRunLoopMode
):App的默认Model,通常主线程是在这个Model下运行UITrackingRunLoopMode
; 界面跟踪Model,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Model影响- 而系统还提供
kCFRunLoopCommonModes
(NSRunLoopCommonModes
)这种模式,但是这并不是真正的模式,它只是一个标记,主要你传入这个标记,那么标记这种模式的对象,就可以在所有标记为common
的模式下运行,上面两种才是真正存在的模式,而且这两种模式都是标记为common
的
2.4 Model内事件分类及RunLoop的状态
-
Model里面事件的分类
Source0
- 触摸事件处理(会被包装成为Source0对象给RunLoop处理)
performSelector: onThread:
Source1
- 基于Port的线程间通信
- 系统的事件捕捉
- Timers
- NSTimer
performSelector: withObject: afterDelay:inModes:
- Observers
- 用于监听Runloop的状态
- UI刷新(BeforeWating–即将休眠)
-
RunLoop运行流程:
-
RunLoop
的状态:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即将进入休眠
kCFRunLoopBeforeTimers = (1UL << 1),//即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),//即将推出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
2.5 示例
2.5.1 触摸屏幕事件
- 下面我们用触摸屏幕的例子,查看是不是会把触摸事件封装成
Soucre0
对象,然后再给Runloop来处理 - LLDB指令 bt 可以打印函数调用栈
- 由此可知,系统确实会将触摸事件,先封装成为
Source0
对象,然后再交给Runloop处理
2.5.2 监听RunLoop的状态
- 下面我来监听下RunLoop的状态
- 第一种方式:
void observerStateChanged(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry---即将进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers---即将处理Timers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources---即将处理Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting---即将休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting---刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit---即将推出RunLoop");
break;
default:
break;
}
}
//监听RunLoop的状态
//创建一个observer的对象
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerStateChanged, nil);
//添加observer对象到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
//释放observer对象
CFRelease(observer);
- 第二种方式:
//使用block来监听
CFRunLoopObserverRef observer1 = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry---即将进入");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit---即将推出RunLoop");
break;
default:
break;
}
});
//添加observer对象到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer1, kCFRunLoopCommonModes);
//释放observer对象
CFRelease(observer1);
- 上述两种方式都可以监听RunLoop的状态,当RunLoop切换Model(模式)时,首先会退出当前Model,然后进入另外一个Model
2.6 RunLoop的运行逻辑
- 通知Observers: 进入RunLoop
- 通知Observers:即将处理Timers
- 通知Observers:即将处理Sources
- 处理Blocks
- 处理Source0(可能会再次处理Blocks)
- 如果存在Source1,就跳转到第8步
- 通知Observers:开始休眠(等待消息回复)
- 通知Observers:结束休眠(被某个消息唤醒)
- 处理Timer
- 处理GCD Async To Main Queue
- 处理Source1
- 处理Blocks
- 根据当前的执行结果,决定如何操作
- 回到第2步
- 退出RunLoop
- 通知Observers:退出RunLoop
-
流程图:
-
源码执行逻辑(删除部分源码,只留下逻辑源码):
//RunLoop的入口
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
int32_t result = kCFRunLoopRunFinished;
//通知observers:进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情,核心函数
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知observers:退出loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//通知observers: 即将处理Timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知observers: 即将处理Sources
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
//处理Source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//判断有误Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有Source1 就跳转到 handle_msg
goto handle_msg;
}
}
//通知observers: 即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//开始睡觉
__CFRunLoopSetSleeping(rl);
do {
// 等待别的消息来唤醒线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
//通知observers: 结束休眠
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
}
}
#endif
// 是被 GCD唤醒
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
} else {
//是被Source1唤醒
//处理Source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
//处理blocks
__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;
}
return retVal;
}
2.7 RunLoop休眠细节
// 等待别的消息来唤醒线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
- RunLoop一旦进入休眠状态,就意味着当前线程就不作任何事情了,那CPU就不给当前线程分配任何资源,就会阻塞当前线程,不会继续往下执行了
- RunLoop的休眠最终是调用内核层面的API 达到线层的真正休眠(达到节省CPU资源的目的),而不是代码阻塞试的“假”休眠(阻塞)
- RunLoop休眠的实现原理:
2.8 RunLoop的应用
2.8.1 控制线程生命周期(线程保活)
- AFNetworking: 通过RunLoop的方式让子线程一直不死,保存在内存中 ,在频繁使用子线程的过程中,有助于提高性能
- 线程的任务一旦执行完毕,生命周期就结束,无法在使用,保住线程的命为什么要用RunLoop,用强指针不就好了吗?准确的来讲,使用RunLoop是为了让线程保持激活状态。
- 实现线程保活
- 保持线程活下来,我们可以启动当前线程的RunLoop,通过RunLoop的特性来保持线程不死
- 如果只调用
[[NSRunLoop currentRunLoop] run];
,达不到线程不死的目的,因为当RunLoop中当前执行的Model中没有任何Source0/Source1/Timer/Observer
的时候,RunLoop
会立马退出‘,代用该方法底层是调用runMode: beforeDate:
方法,传入一个默认Modle,但是该Model下没有任何的Source0/Source1/Timer/Observer
,所以会立刻退出 - 保持Runloop不退出,我们需要添加
Source0/Source1/Timer/Observer
,可以执行[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
@interface GYThread : NSThread
@end
@implementation GYThread
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
@interface ViewController ()
@property (nonatomic, strong)GYThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[GYThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//waitUntilDone: 如果是YES: 表示需要等到子线程执行完成,在往下执行 NO: 表示不等待 继续往下执行
[self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
}
//线程需要执行的事情
- (void)run {
NSLog(@"methodName====%s currentThread=====%@",__func__,[NSThread currentThread]);
}
/// 该方法目的:线程保活
- (void)threadTest {
NSLog(@"methodName====%s currentThread=====%@",__func__,[NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
//上述两句代码保证了线程一直处在运行中
NSLog(@"END--------");
}
@end
- 缺点:
- 无法控制
Thread
(线程)的生命周期,线程一直处在运行中,无法销毁 控制器
和Thread
对象形成循环引用,都无法进行释放
- 无法控制
- 无法销毁线程的原因:
- 首先因为改线程对应的RunLoop一直没有停止,表示该线程一直出在运行中, 只有当线程中的事情做完了,线程才回销毁,所以要想线程销毁,那么必须控制当前RunLoop对象停止,所以可以执行以下代码:
- (void)stop {
//停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
}
//停止runLoop
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
- 但是调用停止当前RunLoop的方法之后, 最后我们发现还是无法停止RunLoop 。原因如下:
[[NSRunLoop currentRunLoop] run]
,该方法官方描述it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
,表示run
的是开启一个无限循环,底层在不断的循环调用runMode: beforeDate:
方法,底层代码结构如下:
while(1) {
[[NSRunLoop currentRunLoop] runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate];
}
-
所以你停止的只是其中一次的Runloop,然后又会进入RunLoop,所以这个
run
方法是不会停止的,它专门用于开启一个永不停止的线程(RunLoop),所以需要停止RunLoop,我们需要模仿这个结构自己编译一个可以控制的循环,来控制RunLoop -
解决上述两个问题,最终代码
@interface ViewController ()
@property (nonatomic, strong)GYThread *thread;
@property (nonatomic, getter=isStop) BOOL stop;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.stop = NO;
__weak typeof(self) weakSelf = self;
self.thread = [[GYThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStop) {
//执行这句代码之后,当前线程就卡在这里休眠,等待被事件唤醒,如果没有事件唤醒,也没有结束RunLoop,那么当前线程会一直卡在这里、睡眠,保证当前线程一直有一个runLoop存在
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"END--------%@",[NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)run {
NSLog(@"methodName====%s currentThread=====%@",__func__,[NSThread currentThread]);
}
//停止RunLoop执行方法
- (IBAction)stopBtnClick:(UIButton *)sender {
if (!self.thread) return;
//停止runLoop
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)stop {
//设置标记
self.stop = YES;
//停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"---%s----",__func__);
//当子线程停掉之后,最后把线程对象清空
self.thread = nil;
}
- (void)dealloc {
//停止runLoop
if (self.thread) {
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
NSLog(@"%s",__func__);
}
@end
-
有人可能有疑问,开启Runloop那里为什么需要添加循环, 在
NSDefaultRunLoopMode
模式下,如果没有外层循环开启RunLoop的话,只开启一次,那么当你唤醒线程,执行一次任务之后,RunLoop就会退出,好似开启一次RunLoop,执行一次任务之后,RunLoop就结束了,所以为了保证RunLoop的不退出,我们需要开启一个外循环,来不断的进入runloop,根据需要来进入和结束runloop的循环 -
当我们在viewController界面没有点击停止按钮,而是直接退出界面时, 我们在
delloc
方法中调用[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
方法,如果waitUntilDone参数为NO
可能会引起程序崩溃,抛出坏内存访问异常,因为如果设置为NO,意味着我们不需要等待子线程任务执行完成,就接着执行主线程,delloac
被调用完成时,意味着控制器销毁了,但是与此同时,子线程还在访问控制器,所以可能会发生坏内存访问,是因为控制器已经销毁了。解决方法:参数设置为YES -
当我们直接返回时,返现控制器销毁了,但是线程还没有销毁,当你返回时,控制会调用
delloac
方法, 而发现控制器要销毁时,weakSelf(弱指针)
会被清空,设置成nil,所以你停止掉当前RunLoop时,当再次判断!weakSelf.isStop
该条件,由于weakSelf=nil
,那么值是NO
,取反就是YES
,所以会再次进入循环,重新进入RunLoop,导致线程不会死亡
while (!weakSelf.isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
- 解决方案:
//判断当前对象是否存在,如果不存在就直接不执行循环
while (weakSelf && !weakSelf.isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
2.8.2 解决NSTimer在滑动时停止工作问题
* 创建的NSTime对象,不管是你手动启动,还是自动启动,默认都是添加到RunLoop的`NSDefaultRunLoopModel`模式下,所以当滑动时,RunLoop进入`UITrackingRunLoopMode`下时,就没办法执行默认模式下的Timer任务了
* 解决方法就是把Timer手动添加到NSRunLoopCommonModes下,这两可以在滑动时也可以解决定时器停止工作的问题
//该方法创建定时器,系统默认直接添加到NSDefaultRunLoopMode 模式下 ,所以在滑动状态下是无法执行定时器的任务的
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"123");
}];
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"123");
}];
//把timer对象添加到NSRunLoopCommonModes下
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
2.8.3对线程保活封装成
@interface GYPermanentThread : NSObject
/// 开始一个可控制生命周期的线程
- (void)start;
/// 停止线程
- (void)stop;
/// 执行任务
/// @param block <#block description#>
- (void)executeTask:(void(^)(void))block;
@end
@interface GYThread : NSThread
@end
@implementation GYThread
- (void)dealloc
{
NSLog(@"--%s",__func__);
}
@end
@interface GYPermanentThread ()
// 这里声明GYThread是为了打印线程声明周期, 如果不需要刻意换乘NSThread
@property (nonatomic, strong) GYThread *innerThread;
/// OC下使用,在使用C语言时 可能用不到
@property (nonatomic, getter=isStop) BOOL stop;
@end
@implementation GYPermanentThread
#pragma mark -- public method
//OC版实现
//- (instancetype)init
//{
// self = [super init];
// if (self) {
//
// __weak typeof(self) weakSelf = self;
// self.innerThread = [[GYThread alloc] initWithBlock:^{
// NSLog(@"begin-----------");
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//
// while (weakSelf && !weakSelf.isStop) {
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// }
//
// NSLog(@"end-----------");
// }];
//
// //可以直接启动
// [self.innerThread start];
// }
// return self;
//}
//C语言实现 : 使用C语言我们更加灵活的控制RunLoop
- (instancetype)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.innerThread = [[GYThread alloc] initWithBlock:^{
NSLog(@"begin-----------");
//创建一个context对象 因为这个对象是一个结构体,而且放在栈上, 所以最好初始化,如果不初始划, 当前这个内存可能存在一些垃圾直,导致后面出现问题
CFRunLoopSourceContext context = {0};
//创建一个source对象
CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
//添加一个Source对象到RunLoop中
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
//运行RunLoop returnAfterSourceHandled: true 代表处理一次Source事件之后就返回 false: 表示处理完Soucre事件之后不返回
//如果设置为true, 就需要添加外循环
// while (weakSelf && !weakSelf.isStop) {
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
// }
//如果参数设置为false
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
NSLog(@"end-----------");
}];
//可以直接启动
[self.innerThread start];
}
return self;
}
- (void)start {
if (!self.innerThread) return;
[self.innerThread start];
}
- (void)executeTask:(void (^)(void))block {
if (!self.innerThread || !block) return;
[self performSelector:@selector(__exectueTask:) onThread:self.innerThread withObject:block waitUntilDone:NO];
}
- (void)stop {
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc {
NSLog(@"---%s---", __func__);
[self stop];
}
#pragma mark -- private method
- (void)__stop {
self.stop = YES;
//停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__exectueTask:(void (^)(void))task {
task();
}
@end
3. 面试题
3.1 讲讲RunLoop,项目中有用到吗?
- 控制线程声明周期(线程保活)->参考文章2.8.1
- 解决NSTimer在滑动时停止工作的问题->参考文章2.8.2
- 监控应用卡顿(后续待学习)
- 性能优化(后续待学习)
3.2 RunLoop内部实现逻辑?
- 参考本文2.6 RunLoop的运行逻辑
3.3 RunLoop和线程的关系?
- 一对一的关系,一条线程对应一个RunLoop对象,默认线程的RunLoop对象是没有创建的,在你第一此获取RunLoop的时候创建
3.4 timer与RunLoop的关系?
- 结构上:RunLoop下面有许多Model(模式),而Model中又有
_timers
数组,存放着许多Timer - RunLoop会通知Observers,即将处理Timers,而且Time也可以唤醒RunLoop,让RunLoop结束休眠
- RunLoop控制timer什么时候执行
3.5 程序中添加每3秒一次的NSTimer,当拖动tableview时timer可能无法响应怎么解决?
- 创建一个NSTimer对象,然后把该对象添加到当前RunLoop的kCFRunLoopCommonModes(通用)模式下
3.6 RunLoop是怎么响应用户操作的,具体流程是什么样的?
- 首先是由Source1把系统事件捕捉,一旦用户操作,事件会在Source1中处理,相当于一个Source1事件,而Source1又会将事件包装成事件队列,而事件队列是在Source0中处理的。(先由Source1捕捉,然后放在Source0中处理)
3.7 说说RunLoop的几种状态?
kCFRunLoopEntry
—即将进入kCFRunLoopBeforeTimers
—即将处理TimerskCFRunLoopBeforeSources
—即将处理SourcekCFRunLoopBeforeWaiting
—即将休眠kCFRunLoopAfterWaiting
—刚从休眠中唤醒kCFRunLoopExit
—即将推出RunLoop
3.8 RunLoop的Model作用是什么?
- 能分隔开不同组的
Source0/Source1/Timer/Observer
,互不影响