【iOS】—— RunLoop thread resident and thread keep alive

What happens if no threads are resident?

We generally write a sub-thread, which will be automatically destroyed after executing the assigned tasks, such as the following situation:
we first rewrite the dealloc method in NSThread, and when the print will call the dealloc method.

#import "NSThread+NewDealloc.h"

@implementation NSThread (NewDealloc)
- (void)dealloc {
    
    
    NSLog(@"%s", __func__);
}
@end

Call the method in ViewController:

@implementation FirstViewController

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomeThing) object:nil];
    [thread start];
}
- (void)doSomeThing {
    
    
    NSLog(@"%s", __func__);
}

insert image description here
According to the printed results, we can see that the thread is automatically destroyed after the child thread finishes executing the task.
And we sometimes need to perform tasks in a sub-thread frequently. Frequent creation and destruction of threads will cause waste of resources. At this time, RunLoop is used to keep the thread alive for a long time.

thread resident

In the process of developing an application, if the background operation is very frequent, such as playing music in the background, downloading files, etc., we hope that this thread will always reside in memory. We can add a strong reference sub-
thread for permanent memory, in this thread Add a Sources under the RunLoop, open RunLoop

@interface FirstViewController ()
@property (nonatomic, strong) NSThread *thread;
@end
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
    [self.thread start];
- (void)run1 {
    
    
    NSLog(@"----run1-----");

    /*如果不加这句,会发现runloop创建出来就挂了,因为runloop如果没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。
          下面的方法给runloop添加一个NSport,就是添加一个事件源,也可以添加一个定时器,或者observer,让runloop不会挂掉*/

    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // 方法1 ,2,3实现的效果相同,让runloop无限期运行下去
    // 方法2
//    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // 方法3
//    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
        
    [[NSRunLoop currentRunLoop] run];
        // 测试是否开启了RunLoop,如果开启RunLoop,则来不了这里,因为RunLoop开启了循环。
        NSLog(@"未开启RunLoop");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    // 利用performSelector,在self.thread的线程中调用run2方法执行任务
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)run2 {
    
    
    NSLog(@"----run2------");
}

insert image description here
The thread will not be dealloc no matter how many times the screen is tapped.

We must ensure that the thread does not die before it can receive time processing in the background, so if you do not add NSPort or NSTimer, you will find that the thread will die after executing the run method, and then executing the touchbegan method will be invalid.

After implementing one of the above three methods, you can find that the run method has been executed, and then click the screen at this time to continuously execute the test method, because the thread self.thread is always in the background, waiting for events to be added to it, and then execute.

thread keepalive

The method of starting and closing RunLoop was mentioned at the end of the previous blog [iOS] - Beginners with RunLoop
Let's go straight to the topic:

Through the above analysis of the method of starting and closing RunLoop, we probably have such an idea:

  • If we want to control RunLoop, we need to use the runMode:beforeDate: method, because one of the other two methods cannot stop and the other can only rely on the timeout mechanism
  • The CFRunLoopStop() method will only end the current runMode:beforeDate: method call, we must do something else

For the above questions, here are the answers:

  • First of all, because the runMode:beforeDate: method is a single call, we need to add a loop to it, otherwise it will be over after calling it once, which is similar to the effect of not using RunLoop
  • The condition of this loop can be set to YES by default. When the stop method is called, execute the CFRunLoopStop() method and change the loop condition to NO to stop the loop and RunLoop exits.
#import "SecondViewController.h"

@interface SecondViewController ()
@property (nonatomic, strong) NSThread *aThread;
@property (nonatomic, assign) BOOL stopped;
@end

@implementation SecondViewController

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 添加一个停止RunLoop的按钮
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    stopButton.frame = CGRectMake(180, 180, 100, 50);
    stopButton.titleLabel.font = [UIFont systemFontOfSize:20];
    [stopButton setTitle:@"stop" forState:UIControlStateNormal];
    stopButton.tintColor = [UIColor blueColor];
    [stopButton addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
    
    // 用于返回的按钮
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:backButton];
    backButton.frame = CGRectMake(180, 380, 100, 50);
    backButton.titleLabel.font = [UIFont systemFontOfSize:20];
    [backButton setTitle:@"back" forState:UIControlStateNormal];
    backButton.tintColor = [UIColor orangeColor];
    [backButton addTarget:self action:@selector(back) forControlEvents:UIControlEventTouchUpInside];

    
    self.stopped = NO;
    __weak typeof(self) weakSelf = self;
    self.aThread = [[NSThread alloc] initWithBlock:^{
    
    
        NSLog(@"go");
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
    
    
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"ok");
    }];
    [self.aThread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    [self performSelector:@selector(doSomething) onThread:self.aThread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)doSomething {
    
    
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)stop {
    
    
    // 在子线程调用stop
    if (self.aThread) {
    
    
        // 在子线程调用stop
        [self performSelector:@selector(stopThread) onThread:self.aThread withObject:nil waitUntilDone:YES];
    }
}

// 用于停止子线程的RunLoop
- (void)stopThread {
    
    
    // 设置标记为NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
    self.aThread = nil;
}

- (void)dealloc {
    
    
    NSLog(@"%s", __func__);
}


- (void)back {
    
    
    [self stop];
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

One thing to note is that if our ViewController has been destroyed, the thread is not dead, which will cause a memory leak. So we should pay attention to stop the thread before the ViewController is dealloc.
insert image description here

Guess you like

Origin blog.csdn.net/m0_62386635/article/details/130528545