早期技术文章搬家, 原文链接
RunLoop 应用:NSTimer、 PerformSelector、常驻线程
iOS 中有两套API访问 Foundation(NSRunLoop), CoreFoundation CFRunLoopRef
一、RunLoop 引入
- 在程序的启动入口Main 函数中,UIApplicationMain 函数内部就启动了一个与**
主线程
**相关联的RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- RunLoop 字面意义上:跑圈(那我可不可以看成是死循环呢),那么来验证一下吧
int main(int argc, char * argv[]) {
@autoreleasepool {
// 查看了UIApplicationMain 的返回值是int类型
// UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
NSLog(@"welcome to Runloop");
int jjTest = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"快到这里来");
return jjTest;
}
}
- 从上可以看出main 函数里面启动了个RunLoop,而且是保持运行的状态
二、RunLoop里面到底有啥呢?
- 上面说过RunLoop 和 主线程的关系,那线程和RunLoop 是什么关系呢?
- 每条线程都有
一个唯一的
与之对应的RunLoop对象 - Foundation:
- 1.获取当前线程RunLoop:[NSRunLoop currentRunLoop]
- 2.获取主线程RunLoop:[NSRunLoop mainRunLoop]
- CoreFoundation:
- 1.获取当前线程RunLoop:CFRunLoopGetCurrent()
- 2.获取主线程RunLoop:CFRunLoopGetMain()
- 每条线程都有
- 了解一个对象的最好方式就是打印出来看看:
-
我的天该怎么看:JJ看到的貌似是一些CFRunLoopObserver CFRunLoopSource, 还有timers 等对象,打印出来发现还是有些复杂了,what should i do~
-
那就只能看看官方文档有关RunLoop的描述了,如果英文不好的就继续往下
-
根据文档的描述:RunLoop相关联的类有五个(再看看打印出来的内容)
- CFRunLoopRef
- CFRunLoopSourceRef
- CFRunLoopObserverRef
- CFRunLoopTimerRef
- CFRunLoopModeRef
-
RunLoop 中对象
三、RunLoop相关类的理解
-
1.从上图可以看出
- 一个RunLoop 可以有多个Mode(模式),每个模式又包含若干个
Source
/Observer
/Timer
- RunLoop 一次只能运行一种Mode,切换Mode需要退出当前Mode
- 一个RunLoop 可以有多个Mode(模式),每个模式又包含若干个
-
CFRunLoopModeRef 一共有五种模式
- kCFRunLoopDefaultMode 默认模式,通常主线程在这个模式下运行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview的触摸滑动
- UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
- GSEventReceiveRunLoop:内部Mode,接收系事件。
-
CFRunLoopSourceRef(函数调用栈)
- Source0:非基于Port(处理事件)
- Source1:基于Port (接收分发系统事件)
- 触摸事件:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"-------");
}
- 触摸事件的函数调用栈调用顺序入下图:
-
CFRunLoopTimerRef:基于时间的触发器(和NSTimer差不多)
-
CFRunLoopObserverRef:监听RunLoop状态的观察者
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), // 64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
- RunLoop 处理逻辑
四、RunLoop 使用
-
监听RunLoop:监听RunLoop的状态 用途:在RunLoop唤醒前做些操作
- 创建Observer:CFRunLoopObserverCreateWithHandler
- 添加观察者 :CFRunLoopAddObserver
- 释放Observer:CFRelease
- 本例ARC状态,ARC只能对OC对象进行内存管理,C中的对象需要自行管理
// 创建observer CFRunLoopObserverRef obser = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"-----Runloop状态------%zd", activity); }); // 添加观察者:监听RunLoop的状态 CFRunLoopAddObserver(CFRunLoopGetMain(), obser, kCFRunLoopDefaultMode); // 释放Observer CFRelease(obser);
-
在RunLoop中创建定时器:
- 定时器NSTimer的创建方式一:timerWithTimeInterval:
- 创建timer
- 将timer 添加到RunLoop中
NSTimer *timer =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES]; // 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 定时器只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 标记为common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 定时器NSTimer的创建方式一:timerWithTimeInterval:
}
- 定时器NSTimer的创建方式二:scheduledTimerWithTimeInterval
- 创建timer:此方法是自动将timer 添加到线程中
- 注意NSTimer 在`子线程创建timer` 需要手动开启当前线程的RunLoop
```objc
- (void)scheduledTimer {
// 调用了
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
// 改变Timer的模式调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void) JYTest {
NSLog(@"----0 0 ");
}
-
需求一 开辟一个不死线程来处理自定义的一些事件(常驻线程)
- 如何让一个线程处理一个任务之后不死呢?
- 答:主线程是通过创建RunLoop 使得主线程不死,一直处理事件。所以创建一个子线程,并创建子线程与之对应的RunLoop
-
创建一个不死线程:
- 创建一个子线程
- 为了监听线程是否被销毁,本例自定了一个
JJThread
,并重写dealloc 方法
// 将线程定义成属性
@property (nonatomic, strong) JJThread *thread;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.thread = [[JJThread alloc] initWithTarget:self selector:@selector(jjTest) object:nil];
[self.thread start];
}
- 创建RunLoop,方式一
- 1.前面说过RunLoop中必须有source,Observer,Timer 其中的任一一个,若为空则执行一次RunLoop,就退出Runloop
- 2.stackoverflow解释上面说的这个问题
- (void)jjTest {
// NSRunloop 中必须要有timer, source, observer 一个或者多个,不然会导致
NSLog(@"-----jjTest-------%@", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"-----jjTest-------%@", [NSThread currentThread]);
// http://stackoverflow.com/questions/31199802/what-does-nsrunloop-currentrunloop-runmodensdefaultrunloopmode-beforedate
}
-
创建RunLoop,方式二
- 上面说RunLoop 必须要有中必须要有timer, source, observer 一个或者多个,这里为什么没有源还行呢。
- 答:因为RunLoop一直在执行退出,进入的状态,这个中间若是接收到performSelector 发放不就有source,可以尝试一下
- 注意点:while 条件中会定义一个BOOL 的属性,做标记管理RunLoop 是否退出死循环。
- (void)jjTest {
NSLog(@"----------run----%@", [NSThread currentThread]);
while (1) {
[[NSRunLoop currentRunLoop] run];
}
}
- (void)jjRun {
NSLog(@"------jjRun------%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(jjRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- PS:有需要源码github/JeversonJee下载