文章目录
前言
这应该是面对招聘来说最重要的一个问题了
这篇要认真写和认真学 还是一个很抽象的概念
好好学习吧
游戏不如意你可以骂队友 那生活呢
祝你好运
什么是RunLoop
顾名思义
运行循环
在程序运行过程中循环做一些事情
应用范围
- 定时器 (Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应 手势识别 界面刷新
- 网络请求
- AutoreleasePool
也就是没有RunLoop 上述事件是无法实现的
RunLoop的基本作用
在iOS程序int main
代码中
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
这里就是创建了一个RunLoop
- 有了RunLoop 程序不会马上退出 而是保持运行状态
- 处理App中的各种事件 比如触摸事件 定时器事件等
- 节省CPU资源 提高程序性能 该做事时做事 该休息时休息
RunLoop对象
iOS的两套API来访问和使用Runloop
- Foundation : NSRunLoop
- Core Foundation : CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
获取RunLoop对象
Foundation
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//获得当前线程的Runloop对象
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
//获得主线程的Runloop对象
Core Foundation
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
//获得当前线程的Runloop对象
CFRunLoopRef runLoop = CFRunLoopGetMain();
//获得主线程的Runloop对象
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里 线程作为Key RunLoop作为value
- 线程刚创建时并没有RunLoop对象 RunLoop会在第一次获取它的时候创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建) 子线程默认没有开启RunLoop
RunLoop相关的类
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
底层结构是这样子 源码里面的
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
我们把重要的拿出来 简化一下这个结构体
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
CFMutableSetRef _modes;
里面装着好多CFRunLoopModeRef
对象
但是其中只有一个可以称为_currentMode
也就是当前的mode
那么我们看看CFRunLoopModeRef
类结构体 直接简化贴重要的了
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
可以看出来
CFRunLoopModeRef
中装着CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopMode的item
Mode中的Source0/Source1/Timer/Observer被统称为mode item 一个item可以被同时加入多个mode 但一个item被重复加入到同一个mode时不会有效果的 如果一个mode中一个item都没有 那么RunLoop会直接退出 不进入循环
Mode管理mode item的接口有以下几个
CFRunLoopAddTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFRunLoopMode mode#>)
CFRunLoopRemoveTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFRunLoopMode mode#>)
CFRunLoopAddSource(<#CFRunLoopRef rl#>, <#CFRunLoopSourceRef source#>, <#CFRunLoopMode mode#>)
CFRunLoopRemoveSource(<#CFRunLoopRef rl#>, <#CFRunLoopSourceRef source#>, <#CFRunLoopMode mode#>)
CFRunLoopAddObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFRunLoopMode mode#>)
CFRunLoopRemoveObserver(<#CFRunLoopRef rl#>, <#CFRunLoopObserverRef observer#>, <#CFRunLoopMode mode#>)
CFRunLoopModeRef
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含若干个Mode 每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动的时候只能选择其中一个Mode 作为currentMode
- 如果需要切换Mode 只能退出当前RunLoop 再重新选择一个Mode进入 (不会导致程序退出)
-
- 这样做主要是为了分割开不同组的Source0/Source1/Timer/Observer
常见的两种Mode
- kCFRunLoopDefaultMode (NSDefaultRunLoopMode) : APP的默认Mode 默认主线程是在这个Mode下运行
- UITrackingRunLoopMode : 界面跟踪Mode 用于ScrollView追踪触摸滚动 保证界面滑动时不受其他Mode影响
不太常见的几种Mode
- UIInitializationRunLoopMode : 在刚启动App时进入的第一个Mode 启动完就不再使用
- GSEventReceiveRunLoopMode : 接受系统事件内部的Mode 通常用不到
特殊Mode
- kCFRunLoopCommonModes : 这是一个用于占位的Mode 作为标记UITrackingRunLoopMode和kCFRunLoopDefaultMode用 并不是一种真正的Mode
CFRunLoopModeRef的成员变量
Source0
- 负责触摸事件处理
- performSelector:onThread:
Source1
- 基于Port的线程间通信
- 系统事件捕捉 (之后分发给Source0处理)
Timers
- NSTimer
- performSelector:withObject:afterDelay:
Observers
- 用于监听RunLoop的状态
- UI刷新 (BeforeWaiting) 也就是在线程睡眠之前
- autorelease pool
CFRunLoopObserverRef
RunLoop一共有这么几种状态 源码里的
上面说了Observers用于监听RunLoop的状态
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
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
添加Observer监听RunLoop的所有状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
RunLoop的运行逻辑以及分析
- 通知Observers : 进入Loop
- 通知Observers : 即将处理Timers
- 通知Observers : 即将处理Sources
- 处理Blocks
- 处理Sources (可能会再次遇到Blocks)
- 如果存在Source1 就跳转到第8步
- 通知Observers : 开始休眠 (等待消息唤醒)
- 通知Observers : 结束休眠 (被某个消息唤醒)
- 处理Timer
- 处理GCD Async To main Queue
- 处理Source1
- 处理Blocks
- 根据前面的执行结果 决定如何操作
- 回到第2步
- 退出Loop
- 通知Observers : 退出Loop
循环的就是第2步到第10步的1.
源码流程分析
我们知道点击事件是由RunLoop处理的 那我们只需要处理一个点击事件 看看它的调用栈就可以了
在这里打一个断点
昨天就会出来这些东西
可以看到1和14中间省略了好多 那么中间这些应该就是RunLoop的调用过程
在下面输入一个 bt
就可以看到完整的调用栈
找到第一个带有RunLoop的函数 还看到了Run的字样 那么RunLoop的入口应该就是这个函数 我们寻找这个函数就好了
在源码中找到了这个函数 很长很复杂
我们只需要关注一些重要的 另外太细节的东西没必要盯着
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
//通知Observers : 进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知Observers : 进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
剩下一些重要的大概就是这样子 一些锁啊 栈进出啊我们没有必要关心 只需要知道这个流程是什么样子的就好了
然后我们在看看具体要做的事情的 __CFRunLoopRun函数里面的流程
也是一样 我删除了需要没有必要的 留下了重要的 在代码附近都写了注释
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知Observers : 即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers : 即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 判断有无Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 如果有Source1 就跳转到handle_msg
goto handle_msg;
}
}
//通知Observers : 即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
//等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
__CFRunLoopUnsetSleeping(rl);
//通知Observers : 结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
// if被timer唤醒
if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
// if被GCD唤醒
else if (livePort == dispatchPort) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { //被Source1唤醒
// 处理Source1
__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;
}
} while (0 == retVal);
return retVal;
}
RunLoop的源码还是很抽象的 从源码看下来基本上和这个标题开始的流程是差不多的
最终是通过这个很长的函数 去调用UIKit处理事件的
小总结一下这些很长的函数
通知Observers : 进入Loop
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
处理Blocks
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
处理Source0
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
处理Timer
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
处理GCD
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
处理Source1
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
休眠的细节
//等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
这一句代码是真正阻塞了线程 因为RunLoop休眠了 是真的什么都不干了 所以是真正阻塞了线程 跟别的线程阻塞是任务卡在这里不太一样 调用了内核的API 底层是调用了mach_msg()函数
从用户态调用mach_mag()函数之后 会自动转到内核态 调用了内核态的mach_msg()函数
随后等待消息 没有消息就让线程休眠 有消息就唤醒线程 转换到用户态处理消息
真正休眠了线程就会节省cpu资源
RunLoop在实际开发中的应用
控制线程生命周期 (线程保活)
让一个线程不要销毁的那么快
我们经常需要在子线程进行一些任务 如果任务结束 线程就销毁
那么在后面的任务里可能又需要创建子线程 这样比较麻烦
所以我们需要控制线程的生命周期 也就是线程保活
正常情况
先看看正常情况下的子线程
创建一个NSThread类
重写dealloc方法
- (void)dealloc {
NSLog(@"dealloc");
}
在viewController里
WXThread *thread = [[WXThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
- (void)run {
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
}
可以看到 几乎是在同一时间子线程就已经销毁了
无用方法
第一时间可以想到的办法可能是这样的
- (void)run {
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
while(1);
}
可是这样有什么意义呢 这个线程虽然没有被销毁 但是已经做不了其他任务了 因为它一直卡在while(1)
的循环里 就算安排了别的任务 也不能进行
这种保命的方法没有什么意义
RunLoop方法
那是不是我们让RunLoop在这个线程里跑起来 然后休眠就可以了呢
- (void)run {
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] run];
NSLog(@"end");
}
也是失败了 这里的原因是什么呢
如果Mode里面没有任何Source0/Source1/Timer/Observer RunLoop会直接退出
所以我们就需要往RunLoop里面添加Source/Timer/Observer
- (void)run {
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"end");
}
这里加什么都可以 这样RunLoop就会休眠 因为它没有什么事情可以做 但是也不会导致线程销毁 因为里面有RunLoop阻塞了线程
而在实际开发中
这个方法只是为了线程保活
子线程实际上要执行的任务是另外的任务
@property (nonatomic, strong) WXThread *thread;
self.thread = [[WXThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
//子线程要执行的任务
- (void)test {
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
}
self.thread = [[WXThread alloc] initWithTarget:self selector:@selector(run) object:nil];
这一句代码对当前的控制器A进行了强引用 在我们返回另一个控制器B的时候
控制器A是不会dealloc的 同样的 self.thread
这个子线程 也不会dealloc
可以这样写
self.thread = [[WXThread alloc] initWithBlock:^{
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"end");
}];
[self.thread start];
这样写也是保了线程的活
而且我们返回另一个控制器B的时候 控制器A就会dealloc
但是 self.thread
这个子线程 还是不会dealloc
为什么呢
[[NSRunLoop currentRunLoop] run];
因为这个线程一直卡在这个地方 线程一直不会dealloc
这样这个线程就会一直不销毁 如果我们希望的就是它一直不销毁那还可以 这样就相当于是一个全局的线程 一直不会销毁 谁都可以调用它
但是我们的目标是 控制线程的生命周期
要能做到想让这个线程什么时候销毁就什么时候销毁
首先 我们不能在控制器A的dealloc方法中停止RunLoop CFRunLoopStop(CFRunLoopGetCurrent());
因为控制器A的dealloc方法默认是在主线程调用的 而子线程的RunLoop是子线程的
这样子做就是停止了主线程的RunLoop
那是不是可以在控制器A的dealloc方法里调用子线程的停止RunLoop的方法呢
- (void)dealloc {
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)stopSelfThread {
CFRunLoopStop(CFRunLoopGetCurrent());
}
还是不行
这时候就涉及到[[NSRunLoop currentRunLoop] run];
的底层
在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的底层一直在调用
而且是无限循环 相当于
while(1) {
[NSRunLoop currentRunLoop] runMode:<#(nonnull NSRunLoopMode)#> beforeDate:<#(nonnull NSDate *)#>
}
所以CFRunLoopStop(CFRunLoopGetCurrent());
这一句代码 相当于只是停止了其中的一次循环 很快就会开启之后的循环
所以NSRunLoop的run方法是无法停止的 它专门用于开启一个永不销毁的线程
@property (nonatomic, assign, getter=isStoped) BOOL stopped;
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[WXThread alloc] initWithBlock:^{
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.thread start];
- (void)dealloc {
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)stopSelfThread {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}
开始我认为这样是可以的 在控制器dealloc的时候调用stopSelfThread
以为可以结束这个线程 结果发现没有
正确的方法应该是 有一个点击事件 点击了之后调用stopSelfThread
方法 然后就可以结束线程的生命
这个我的停止按钮 里面添加了销毁线程的点击事件 调用stopSelfThread
方法
- (void)stopp {
if (!self.thread) return;
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
至于为什么在控制器调用stopSelfThread
方法程序会崩溃
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:NO];
单独看看这个方法 使用self
进行调用stopSelfThread
方法 而这个方法如果在控制器的dealloc
方法调用 能走到dealloc
方法 说明控制器已经销毁了 自然无法调用
首先[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:NO];
这句代码的最后一个参数要设置为YES
设置为NO的意思就是相当于异步async 可以先执行后面的
所以我们需要先执行这一句代码 保证控制器在存活的状态下执行
随后在前面要多加一个判断条件weakSelf
有值
我们知道一个弱指针指向的对象如果要销毁了 那么弱指针会置nil
既然置nil了 那么即使执行了self.stopped = YES
在原先的判断条件!weakSelf.stopped
中 这里也会是nil
那么就会接着循环RunLoop 所以无法停止RunLoop
那么我们只需要加上要判断weakSelf
也有值就可以停止RunLoop了
while (weakSelf && !weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
最后一件事 真最后一件了
上面这样做我们解决了当控制器销毁时也同时销毁线程
但是试试就知道 如果我们先点击了停止按钮 也就是先销毁线程
然后我们再点击back 退出当前的控制器A 程序就会崩溃
因为子线程已经被销毁了 你还要再控制器销毁之前再进入子线程一次
肯定是不可以的🙅
所以加上几个判断条件就好了嘛
首先在stopSelfThread
方法的最后将子线程置空
self.thread = nil;
然后在方法
- (void)stopp {
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
中的前面添加判断条件 if (!self.thread) return;
完整代码 :
省略了一些UI部分
@property (nonatomic, strong) WXThread *thread;
@property (nonatomic, assign, getter=isStoped) BOOL stopped;
- (void)viewDidLoad {
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[WXThread alloc] initWithBlock:^{
NSLog(@"%s -- %@", __func__, [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@ -- end", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self stopp];
}
- (void)stopp {
if (!self.thread) return;
[self performSelector:@selector(stopSelfThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)stopSelfThread {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
解决NSTimer在滑动时停止工作的问题
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStyleGrouped];
self.tableView.backgroundColor = [UIColor yellowColor];
static int count = 0;
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", count++);
}];
[self.view addSubview:self.tableView];
我们在tableView里设置一个定时器 每过一秒钟count++
正常来说就是一秒钟加1
但是我们在滚动tableView的时候 定时器就会停止
可以看到本来时40 - 41 下一个变成了46 - 47 中间停了五秒钟
这就是NSTimer在滑动时停止工作的问题
RunLoop在同一时间只能运行一种Mode
那我们需要解决的问题就是让这个定时器在两种模式下都能工作
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", count++);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSRunLoopCommonModes不是一种真的模式 而是一种标记
提醒一下这个timer 将来可以运行在什么模式下
默认模式和滑动模式都属于NSRunLoopCommonModes
这样就解决了问题