什么是RunLoop?
- 顾名思义:运行循环,在程序运行过程中循环做一些事情;
- 每条线程都有唯一的与之对应的RunLoop对象;
- RunLoop保存在一个全局的Dictionary中,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建; - RunLoop会在线程结束时销毁;
- 主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop;
RunLoop的基本作用:
- 保持程序的持续运行
- 处理App中各种事件
- 节省CPU资源,提高程序性能(该做事时做事,该休息时休息)
iOS中有2套API来访问和使用RunLoop
Foundtation: NSRunLoop
Core Foundation: CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC封装
NSRunLoop *r1 = [NSRunLoop currentRunLoop];
CFRunLoopRef r2 = CFRunLoopGetCurrent();
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
Source0: 触摸事件、performSelector:onThread
Source1: 基于Port的线程间通信、系统事件捕捉
Timers:NSTimer、performSelector:withObject:afterDelay:
Observers: 用于监听RunLoop的状态、UI刷新(BeforeWaiting)、Autorelease pool(BeforeWaiting)
CFRunLoopMode
- CFRunLoopMode代表RunLoop的运行模式
- RunLopp包含若干个mode,每个mode又包含若干个Source0/ Source1/ Timer/ Observer
- RunLoop启动时只能选择其中一个mode,作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
- 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种mode
kCFRunLoopDefaultMode: App的默认mode,通常主线程就是在这个Mode下运行
UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他mode影响
注意:NSRunLoopCommonModes并不是真的模式,它只是一个标记,设置为后,source会在_commonModes中存放的mode中运行;
_commonModes中默认存放的是kCFRunLoopDefaultMode和UITrackingRunLoopMode;
当source设置在NSRunLoopCommonModes模式后,那么该source会被存放到_commonModeItems中
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
};
RunLoop休眠的原理:
通过mach_msg 从用户态进入内核态,从而可以使线程进入睡眠;
RunLoop如何响应用户操作?
CFRunLoop的currentMode中的Source1捕捉到用户事件,包装成事件队列后传递给source0处理;
通知observers结束休眠,处理source;
RunLoop运行逻辑.png
应用范畴:
定时器、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
RunLoop在项目中的应用有
控制线程的生命周期
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL isStopThread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.thread = [[NSThread alloc] initWithBlock:^{
/*添加NSPort,让RunLoop立马销毁,
因为当RunLoop中没有source0 source1 observer timer时,会立马销毁 */
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
//通过isStopThread标记位控制Loop
while (weakSelf && !weakSelf.isStopThread) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
//NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的NSRunLoop
//[[NSRunLoop currentRunLoop] run];
}];
[self.thread start];
}
- (void)test {
NSLog(@" __ %s __ %@", __func__, [NSThread currentThread]);
}
- (void)stop {
NSLog(@" __ %s __ %@", __func__, [NSThread currentThread]);
//修改结束Loop的标记位
_isStopThread = YES;
//停止本次Loop
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (IBAction)onTestOnThread:(id)sender {
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (IBAction)onStopThread:(id)sender {
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
@end
解决NSTimer在滑动时停止工作的问题
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并不是真的模式,它只是一个标记,设置为后,source会在_commonModes中存放的mode中运行;
_commonModes中默认存放的是kCFRunLoopDefaultMode和UITrackingRunLoopMode;
当source设置在NSRunLoopCommonModes模式后,那么该source会被存放到_commonModeItems中
网友评论