mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 12:27:15 +00:00
170 lines
6.5 KiB
Markdown
170 lines
6.5 KiB
Markdown
# RunLoop 的应用
|
||
|
||
> 了解了 RunLoop 的底层原理以及特点后我们有必要想一想它可以应用在什么地方?现在归纳下常见的应用场景
|
||
|
||
## NSTimer
|
||
|
||
我们常常写的 **NSTimer** 都是在的默认的运行状态下执行的(**NSDefaultRunLoopMode**)。所以我们会经常遇到 NSTimer 执行不准去的问题。因为 RunLoop 是有不同的运行状态的,当我们 UI 滚动的时候从 **NSDefaultRunLoopMode** 切换到 **UITrackingRunLoopMode**,所以添加到 **NSDefaultRunLoopMode** 状态下的事件是不会执行的,为了达到定时器准确的目的有2种方法。方法一:必须根据具体需求给 NSTimer 指定具体的 **CFRunLoopModeRef**。方法二:利用 GCD 的 timer 不会受 **NSDefaultRunLoopMode** 影响的特点。
|
||
|
||
```objective-c
|
||
//默认状态下的 NSTimer
|
||
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
|
||
NSLog(@"我在执行了");
|
||
}];
|
||
```
|
||
|
||
```objective-c
|
||
//方法1:给 NSTimer 指定运行状态
|
||
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
|
||
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||
```
|
||
|
||
```objective-c
|
||
//方法2:GCD 的单位是纳秒。使用 GCD 创建的 timer 正常创建后不会执行,因为创建后设置了指定的时间后触发,所以当代码运行到最后一行的时候,Timer 还没执行,就被销毁了。所以我们必须设置一个属性去保存它。
|
||
|
||
//1、创建队列
|
||
dispatch_queue_t queue = dispatch_get_main_queue();
|
||
//2、创建 timer
|
||
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||
// self.timer = timer;
|
||
//3、设置 timer 的参数:精准度、时间间隔
|
||
//第三个参数为 GCD timer 的精准度
|
||
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
|
||
//4、为 Timer 设置任务
|
||
dispatch_source_set_event_handler(timer, ^{
|
||
NSLog(@"%@",[NSRunLoop currentRunLoop]);
|
||
});
|
||
//5、执行任务
|
||
dispatch_resume(timer);
|
||
```
|
||
|
||
## ImageView显示\(PerformSelector\)
|
||
|
||
UITableView 在滚动的时候一个优化点之一就是 UIImageView 的显示,通常需要根据网络去下载图片。所以如果用户快速滚动列表的时候,如果立马下载并显示图片的话,势必会对 UI 的刷新产生影响,直观的表现就是会卡顿,**FPS** 达不到60。
|
||
|
||
利用 RunLoop 可以实现这个效果,就是给下载并显示图片的方法指定 **NSRunLoopMode**。
|
||
|
||
```objective-c
|
||
- (IBAction)clickLoadIMage:(id)sender {
|
||
//[self.imageview performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"test"] afterDelay:2];
|
||
[self performSelector:@selector(downloadAndShowImage) withObject:nil afterDelay:2 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
|
||
}
|
||
|
||
- (void)downloadAndShowImage{
|
||
self.imageview.image = [UIImage imageNamed:@"test"];
|
||
}
|
||
```
|
||
|
||
## 常驻线程
|
||
|
||
我们需要常驻线程,那么就需要线程不要销毁,那么一些做法比如设置任务死循环,那么线程就不会销毁;将当前线程强引用不要销毁等都存在问题。最好的方法是为当前线程设置合理的 RunLoop
|
||
|
||
```objective-c
|
||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
||
LBPThread *thred = [[LBPThread alloc] initWithTarget:self selector:@selector(showThreadLife) object:nil];
|
||
self.lbpThread = thred;
|
||
[thred start];
|
||
}
|
||
|
||
- (void)showThreadLife{
|
||
NSLog(@"---show----");
|
||
}
|
||
|
||
- (IBAction)clickLoadIMage:(id)sender {
|
||
NSLog(@"%s",__func__);
|
||
[self performSelector:@selector(contactWithTwoThread) onThread:self.lbpThread withObject:nil waitUntilDone:YES];
|
||
}
|
||
|
||
- (void)contactWithTwoThread{
|
||
NSLog(@"----contactWithTwoThread----");
|
||
}
|
||
```
|
||
|
||

|
||
|
||
我们都知道 RunLoop 存在必须要有一个 Timer 或者 Source。
|
||
|
||
```objective-c
|
||
//方法1:添加 Port 的 Source。Sourcr1
|
||
- (void)showThreadLife{
|
||
NSLog(@"---show----");
|
||
//子线程的 RunLoop 是需要自己手动创建并添加;RunLoop 如果不要销毁那么至少存在一个 Timer 或 Source
|
||
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
|
||
[[NSRunLoop currentRunLoop] run];
|
||
}
|
||
//方法2
|
||
- (void)showThreadLife{
|
||
NSLog(@"---show----");
|
||
//子线程的 RunLoop 是需要自己手动创建并添加;RunLoop 如果不要销毁那么至少存在一个 Timer 或 Source
|
||
|
||
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(test) userInfo:nil repeats:YES];
|
||
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
|
||
[[NSRunLoop currentRunLoop] run];
|
||
}
|
||
```
|
||
|
||
注意:添加 Observer 是没有效果的。
|
||
|
||
注意:直接添加的 Mach Port 后线程确实是常驻线程,但是如果需要让线程停止,`[runLoop run]` 方法不能满足。
|
||
`[runLoop runMode:NSDefualtRunLoopMode beforeDate:[NSDate distantFuture]];`
|
||
设计可以停止的常驻线程
|
||
|
||
```Objective-C
|
||
@property (assign, nonmatioc) BOOL shouldStopRun;
|
||
|
||
self.shouldStopRun = NO;
|
||
|
||
- (void) start {
|
||
while(!shouldStopRun && [runLoop runMode:NSDefualtRunLoopMode beforeDate:[NSDate distantFuture]]);
|
||
}
|
||
|
||
- (void)removeSourceOrTimer {
|
||
self.shouldStopRun = YES;
|
||
CFRunLoopStop(CFRunLoopGetCurrent()); // RunLoop 源代码里面一直循环的条件,得来的停止条件
|
||
}
|
||
|
||
```
|
||
|
||
## 自动释放池
|
||
|
||
自动释放池什么时候创建和释放
|
||
|
||
创建时间:第一次进入 RunLoop 的时候
|
||
|
||
释放时间:RunLoop 退出的时候
|
||
|
||
其他情况:当 RunLoop 将要休眠的时候释放,然后创建一个新的
|
||
|
||
**\_wrapRunLoopWithAutoreleasePoolHandler** **0x1**
|
||
|
||
**\_wrapRunLoopWithAutoreleasePoolHandler** **0xa0**
|
||
|
||
0x1 和 0xa0 是十六进制的数,对应十进制为1和160。
|
||
|
||
|
||
## RunLoop 空闲时做一些任务
|
||
```
|
||
- (void)print
|
||
{
|
||
NSLog(@"test");
|
||
}
|
||
|
||
- (void)viewDidLoad
|
||
{
|
||
CFRunLoopActivity flags = kCFRunLoopBeforeWaiting;
|
||
CFRunLoopObserverRef runloopObserver = CFRunLoopObserverCreateWithHandler(
|
||
kCFAllocatorDefault, flags, YES, 0,
|
||
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
||
[self performSelector:@selector(print)
|
||
onThread:[NSThread mainThread]
|
||
withObject:nil
|
||
waitUntilDone:NO
|
||
modes:@[ NSDefaultRunLoopMode ]];
|
||
});
|
||
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopDefaultMode);
|
||
}
|
||
```
|
||
|
||
|
||
|