Files
knowledge-kit/Chapter1 - iOS/1.40.md
杭城小刘 e89fe0ca1c docs: 内容
2020-11-08 15:51:47 +08:00

6.1 KiB
Raw Blame History

RunLoop 的应用

了解了 RunLoop 的底层原理以及特点后我们有必要想一想它可以应用在什么地方?现在归纳下常见的应用场景

NSTimer

我们常常写的 NSTimer 都是在的默认的运行状态下执行的(NSDefaultRunLoopMode)。所以我们会经常遇到 NSTimer 执行不准去的问题。因为 RunLoop 是有不同的运行状态的,当我们 UI 滚动的时候从 NSDefaultRunLoopMode 切换到 UITrackingRunLoopMode,所以添加到 NSDefaultRunLoopMode 状态下的事件是不会执行的为了达到定时器准确的目的有2种方法。方法一必须根据具体需求给 NSTimer 指定具体的 CFRunLoopModeRef。方法二:利用 GCD 的 timer 不会受 NSDefaultRunLoopMode 影响的特点。

//默认状态下的 NSTimer
[NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
    NSLog(@"我在执行了");
}];
//方法1:给 NSTimer 指定运行状态
NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(show) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
 //方法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

- (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

- (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。

//方法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]]; 设计可以停止的常驻线程

@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。

参考文章:

http://www.cocoachina.com/ios/20180515/23380.html

http://www.cocoachina.com/ios/20170417/19075.html

https://www.jianshu.com/p/4c38d16a29f1

https://www.cnblogs.com/kenshincui/p/6823841.html

https://juejin.im/entry/599c13bc6fb9a0248926a77d

https://blog.ibireme.com/2015/05/18/runloop/