概述
- 什么是RunLoop
- 运行循环
- 基本的作用
- 保持程序的持续运行
- 处理App中的各种事件(触摸事件、定时器事件、Selector事件)
- 节省CPU资源,提高程序的性能
iOS项目中RunLoop
- iOS程序的入口
main
函数
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain
函数内部开启了一个RunLoop
。这个RunLoop
是系统启动。跟我们的主线程
相关联。也就是这个RunLoop处理主线程的各种事件。
RunLoop对象
- iOS提供了两套API来访问RunLoop
-
Foundation
-
Core Foundation
- RunLoop
-
NSRunLoop是基于CFRunLoop的一层OC包装。Core Foundation是纯C语言的。苹果开源RunLoop源码 官方文档
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- 主线程RunLoop已经自动创建好并开启了,子线程的RunLoop需要主动创建
- 子线程的RunLoop在第一次获取时创建,在线程结束时销毁。
子线程的RunLoop是懒加载的方式,默认没有创建。在子线程第一次获取时创建。
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
}
- (void)run
{
NSRunLoop *rp = [NSRunLoop currentRunLoop];
NSLog(@"--------------------");
}
获取RunLoop函数的源码
// t==0 is a synonym for "main thread" that always works
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);
}
// 1.线程作为Key去__CFRunLoops取对应的loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 1.1 如果没有取到
if (!loop) {
// 1.1.1创建RunLoop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 1.1.2 key作为当前线程 Value作为RunLoop
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相关的类
- Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopMode
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
如果一个RunLoop
想要运行起来,里面必须有一个Mode
并且Model里面必须有一个Source
或者Timer
。否则该RunLoop
直接退出。
CFRunLoopMode
- 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
- 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
- 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
- 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
- 系统默认注册了5个Mode:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode 是一种标记 带此标记的模式
UITrackingRunLoopMode kCFRunLoopDefaultMode
CFRunLoopTimerRef
- CFRunLoopSourceRef是事件源(输入源)可以简单理解为
NSTimer
。 - NSTimer定时器
-
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES]; - (void)run { NSLog(@"--------------------"); }
上面通过NSTimer的
scheduledTimerWithTimeInterval
方式创建其内部做了很多事情。它等价于下面timerWithTimeInterval
的方式。 -
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; - (void)run { NSLog(@"--------------------"); }
默认我们的Timer被添加到RunLoop的
NSDefaultRunLoopMode
模式下。
scheduledTimerWithTimeInterval
默认将Timer
添加进RunLoop
中并且对timer
有强引用, 控制器引用timer
使用weak
。其实在Timer没有调用invalidate
方法之前timer
会强引用这控制器。控制器销毁了timer
不一定销毁 。只有调用invalidate
后Timer
才会销毁。
当我们拖动UIScrollView的时候,主线程关联的RunLoop进入`UITrackingRunLoopMode`模式。开发为了在满足两种模式下定时器都能够工作。做如下设置。
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(run) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; - (void)run { NSLog(@"--------------------"); }
- 上面将Timer添加到标记为
NSRunLoopCommonModes
的模式中。其中有NSRunLoopCommonModes
标记的模式为UITrackingRunLoopMode
和NSDefaultRunLoopMode
。
CFRunLoopSourceRef
-
苹果分类
- Port-Based Sources
- Custom Input Sources
- Cocoa Perform Selector Sources
-
基于调用栈分类
- Source0:非基于Port的(用户的点击事件...)
- Source1:基于Port的(通过内核和其他线程通信、接受、分发系统事件)
CFRunLoopObserverRef
- 是观察者,能够监听RunLoop的状态改变
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
- 使用Core Foundation API创建观察者并监听RunLoop的状态
// @param activities 需要监听RunLoop的哪些状态 kCFRunLoopAllActivities监听RunLoop的所有状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出RunLoop");
break;
default:
break;
}
});
// 2.监听当前线程RunLoop的默认模式的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
RunLoop处理逻辑
RunLoop实践
Timer
ImageView的显示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 如果渲染很多图片 需要将显示图片放在default模式下显示 当拖拽ScrollView是不显示
[self.imageview performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20180316_64"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
}
常驻线程
- 默认一个线程的任务处理完毕就会死亡。通过RunLoop可以让一个线程不死(常驻)。
#import "ViewController.h"
#import <CoreFoundation/CoreFoundation.h>
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
_thread = thread;
[thread start];
}
// 常驻线程
- (void)run
{
NSLog(@"--------------------run");
// 创建子线程的runloop
NSRunLoop *rp = [NSRunLoop currentRunLoop];
// 添加Source
[rp addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 运行RunLoop
[rp run]; // 等价于 [rp runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
- (void)test
{
NSLog(@"--------------------test");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
网友评论