从字面上理解就是一个运行循环,我们一般程序就是执行一个线程,是一条直线.有起点终点.而runloop就是一直在线程上面画圆圈,一直在跑圈,在不断跑圈中,一直在检测一些点击事件、定时器等等,一旦检测到就开始执行,执行结束后再睡眠,睡眠中再检测,除非切断否则一直在运行,否则就一直在循环。其内部的结构是一个do-while循环,在这个循环内部不断处理各种任务(比如timer、source、Observer)
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC的包装
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
//获取当前线程的RunLoop
[NSRunLoop currentRunLoop];
//获取主线程的RunLoop
[NSRunLoop mainRunLoop];
//获取当前线程的RunLoop
CFRunLoopGetMain();
//获取主线程的RunLoop
CFRunLoopGetCurrent();
//runloop运行,
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
注:NSRunLoop初始化不需要alloc,只需要在该线程里调用[NSRunLoop currentRunLoop],
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
CFRunLoopModeRef代表RunLoop的运行模式,正是因为runloop里有source、timer、observer,runloop才能一直保持运行,如果没有这些,runloop会直接退出循环
一个RunLoop包含了若干个Mode,每个Mode又包含了若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入循环
每个Mode可以设置自己的 Source/Timer/Observer,让其互不影响
系统默认注册了5个Mode(苹果只开放了前俩个)
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollerView追踪触摸滑动,保证界面滑动时不受其他Mode影响
UIInitalizaationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode:接受系统事件内部的Mode(绘图渲染等等),通常用不到
kCFRunLoopCommonModes:这是占位的Mode,不是真正的Mode
//在UI上添加一个TextView,滑动TextView,观察timer是否执行
- (void)viewDidLoad {
[super viewDidLoad];
//调用scheduledTimer返回定时器,系统已自动添加RunLoop中,而且默认在NSDefaultRunLoopMode模式,这样每隔1s都会执行run
//但是当你滑动textView时,定时器将失效,run方法停止执行,当你停止滑动时,run方法继续执行
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//修改模式(当你把这个timer添加到当前的runloop中时,不管你滑不滑动textView,run方法都会执行)
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
-----------------------------------------------
//调用timerWith返回定时器,需要手动加入Runloop
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
-----------------------------------------------
//1、只运行在NSDefaultRunLoopMode模式,一旦RunLoop进入其他模式(当你滑动textView时),定时器就不会工作了(run方法不执行)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
-----------------------------------------------
//2、只运行在UITrackingRunLoopMode模式,一旦RunLoop进入其他模式(当不滑动textView时),定时器就不会工作了(run方法不执行)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
-----------------------------------------------
//3、定时器会在标记Common modes的模式下会工作,标记为Common modes的模式(不管你滑不滑动textView,run方法都执行):UITrackingRunLoopMode和NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
-(void)run{
NSLog(@"___");
}
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),//即将进入Runloop
kCFRunLoopBeforeTimers = (1UL << 1),//即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理Source事件
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//从刚才休眠中唤醒
kCFRunLoopExit = (1UL << 7),//即将退出runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU//监听所有状态
};
如果在runloop中添加Observer,只能用CF的函数,一般用于拦截系统事件等
/*
添加Observer
参数1:默认值
参数2:监听活动(这里监听了kCFRunLoopAllActivities)
参数3:重复
参数4:0
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"---监听Runloop状态改变--%lu",activity);
});
/*
添加观察者,监听Runloop的在kCFRunLoopDefaultMode模式下的状态
参数1:当前runloop
参数2:监听者
参数3:模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//释放Observer
CFRelease(observer);
关于runloop的内存,在runloop中,一个runloop会对应一条线程,而自动释放池是针对当前线程的一些对象,它会在kCFRunLoopBeforeWaiting之前会被释放一次,在下一次启动的时候会重新创建,
当有一个耗时任务需要长期留在子线程里工作,这时需要将这个线程处理为一个常驻线程,所谓常驻线程只需在线程的mode中添加(source、timer、observer任意一个即可),那么这个线程中的runloop就可以跑起来,当你想runloop停掉的时候,直接调用类似- (void)removePort:(NSPort *)aPort forMode:(NSRunLoopMode)mode方法移除即可。
@interface ThreadViewController ()
@property (strong, nonatomic) NSThread *thread;
@end
@implementation ThreadViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(play1) object:nil];
[self.thread start];
}
-(void)play1{
@autoreleasepool{
// NSLog(@"runloop---start--");
// NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test1) userInfo:nil repeats:YES];
// //在当前子线程的runloop中的mode中添加定时器(有了Timer,runloop就可以跑起来)
// [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
//
//timer 的scheduledTimerWithTimeInterval方法已经默认添加到了runloop中
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test1) userInfo:nil repeats:YES];
//启动runloop
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop---end--");
}
}
-(void)test1{
NSLog(@"test1****%@",[NSThread currentThread]);
}
-(void)play{
@autoreleasepool{
NSLog(@"runloop---start--");
//在当前子线程的runloop中的mode中添加端口(其实就是加了source,有了source,runloop就可以跑起来)
[[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//启动runloop
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop---end--");
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//在self.thread线程中调用test
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)test{
NSLog(@"test---%@",[NSThread currentThread]);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
网络下载的图片,如果显示在UIScrollView、UITableView上,当滑动屏幕的时候,UI有时会卡顿,解决这个问题可以用runloop
//假如后台下载图片已经完成,此刻仍在滑动屏幕,那么可以延迟显示,当你停止滑动屏幕的时候再显示,此刻只需将runloop模式改为NSDefaultRunLoopMode。
- (void)viewDidLoad {
[super viewDidLoad];
[self showImage];
}
#pragma mark-指定runloop执行
-(void)showImage{
//只在NSDefaultRunLoopMode模式下显示
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"aa"] afterDelay:3 inModes:@[NSDefaultRunLoopMode]];
}
最后,附上以上的demo,git:(https://github.com/hejiasu/RunLoop)