目录
简介
为了避免主线程阻塞导致界面卡顿,会创建子线程(任务执行完毕后则销毁)
NSThread *thread=[[NSThread alloc]initWithBlock:^{
// 任务...
}];
[thread start];
如果该任务需要频繁执行,频繁创建会消耗资源。
线程任务执行完毕后会进入死亡状态,不能再次开启。在线程内部死循环可以让线程保留,但又会疯狂地执行。
我们需要的是有任务再去执行,没任务则休眠。
解决机制:
RunLoop是一个消息循环机制。它保证了线程不会退出,在没有消息时让线程休眠节约资源,在收到消息时唤醒线程处理任务。
RunLoop
iOS有两种RunLoop:
1、NSRunLoop
对CFRunLoopRef进行封装,面向对象API,非线程安全
2、CFRunLoopRef
在CoreFoundation框架(开源)内,纯C函数API,线程安全。
4种创建方式(不允许直接创建):
1、[NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop
2、[NSRunLoop mainRunLoop];
3、CFRunLoopGetMain();
4、CFRunLoopGetCurrent();
3种运行方式:
1、[runLoop run];
不建议使用,除非希望子线程永远存在。
Run Loop会永久性的运行NSDefaultRunLoopMode模式,CFRunLoopStop(runloopRef);无法停止RunLoop的运行
2、[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
设置超时时间,运行NSDefaultRunLoopMode模式,CFRunLoopStop(runloopRef);无法停止RunLoop的运行
3、[runLoop runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:60]];
在非Timer事件触发:CFRunLoopStop或limitDate可停止。
Timer事件触发:无法停止
能正常运行的条件:
1、至少包含一个Mode(RunLoop默认包含了DefaultMode)
2、该Mode下需要有至少一个的事件源(Timer/Source)。
3、运行在该Mode下。
NSRunLoop只可以往mode中添加两类事件源:NSPort(对应的是source1)和NSTimer。
RunLoop可包含多个Mode。Mode由Source、Observer、Timer组成。
Mode有3种:
1、
kCFRunLoopDefaultMode(CFRunLoop)
NSDefaultRunLoopMode(NSRunLoop)
默认Mode(滚动时失效)
2、
UITrackingRunLoopMode(CFRunLoop)
NSEventTrackingRunLoopMode(NSRunLoop)
仅在滚动时有效
3、
kCFRunLoopCommonModes(CFRunLoop)
NSRunLoopCommonModes(NSRunLoop)
组合模式:1+2
添加Mode:
CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), (CFStringRef)UITrackingRunLoopMode);
Source由source0和source1组成:
1.source0
例:UIEvent(触摸事件、滑动事件等),performSelector这种需要手动触发的操作。
2.source1:
系统内核的mach_msg事件(系统内部的端口事件)。了解即可。
例:唤醒RunLoop或者让RunLoop进入休眠节省资源等。
Timer为定时源事件。
NSTimer定时器的触发基于RunLoop。如果一个任务执行时间较长,那么当错过一个时间点后只能等到下一个时间点执行,并不会延后执行。
Observer
相当于消息循环中的一个监听器,随时通知外部当前RunLoop的运行状态。只能通过CFRunLoop相关方法创建(NSRunLoop没有相关方法创建)。
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
例
self.thread=[[NSThread alloc]initWithBlock:^{
// 获取当前子线程的RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 下面这一行必须加,否则RunLoop无法正常启用。
// 给RunLoop添加了一个占位事件源,告诉RunLoop有事可做,让RunLoop运行起来。暂时这个事件源不会有具体的动作,而是要等RunLoop跑起来过后等有消息传递了才会有具体动作。
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
NSLog(@"RunLoop:%@",runLoop);
// 让RunLoop跑起来
[runLoop run];
}];
[self.thread start];
//
[self performSelector:@selector(taskToDo) onThread:self.thread withObject:nil waitUntilDone:NO];
以下并非源码(对源码进行了可读性优化,便于理解)
/// 全局的Dictionary,key 是 线程, 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());
}
可以看出
1、线程默认不开启RunLoop(主线程除外)。RunLoop仅在第一次获取时创建,仅能在线程内部获取RunLoop。
2、线程和RunLoop之间是一一对应的关系,对应关系保存在全局Dic中。
autoreleasepool
NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool。
GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool。
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
//[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
解决UITableView有大量图片滚动时卡顿
当tableView的cell上有大量从网络获取的图片时,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,有可能会造成卡顿。
将设置图片的任务在CFRunLoopDefaultMode下进行,滚动时不会执行
[self.myImageView performSelector:@selector(setImage:)
withObject:[UIImage imageNamed:@""]
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
网友评论