利用RunLoop来实现线程的保活,并封装子线程任务执行

1、基本思路

1.1、创建一个Helper类对NSThread类进行封装;
1.2、Helper类内部实现线程的创建、执行、销毁等一系列操作;
1.3、核心点在于RunLoop的循环执行,调用runMode:beforeDate方法让线程处于休眠状态,再通过while循环,使runloop执行结束后再次开启,循环条件外部控制,这样就能达到控制runloop的生命周期,从而保全线程的生命周期;
1.4、Helper类在销毁时,要及时更改runloop的循环条件,并唤醒或停止子线程中的runloop,runloop停止后线程结束,否则两者会一直存在内存当中,runloop因为没有及时被唤醒而一直处于休眠状态,线程因为runloop没有停止而无法释放;
1.5、Helper在使用NSThread创建子线程时也要注意循环引用的问题;

2、代码示例
2.1、内部实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//
//  SAThreadHelper.h
//  RunLoop
//
//  Created by 余西安 on 2018/11/24.
//  Copyright © 2018 yusian. All rights reserved.
//
 
#import "SAThreadHelper.h"
@interface _SAInnerThread : NSThread
@end
@implementation _SAInnerThread
- (instancetype)init
{
    if (self = [super init]){
        self.name = @"com.yusian.inner-thread";
    }
    return self;
}
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end
 
@interface SAThreadHelper ()
@property (nonatomic, assign, getter=isStopped) BOOL    stopped;
@property (nonatomic, strong)   _SAInnerThread     *innerThread;
@end
@implementation SAThreadHelper
- (instancetype)init
{
    if (self = [super init]){
        self.stopped = NO;
        __weak typeof(self) weakself = self;
        self.innerThread = [[_SAInnerThread alloc] initWithBlock:^{
            // 1、获取当前线程的RunLoop
            NSRunLoop *runloop = [NSRunLoop currentRunLoop];
            // 2、给RunLoop添加一个Source,以免自动销毁
            [runloop addPort:NSPort.new forMode:NSDefaultRunLoopMode];
            // 3、RunLoop循环,任务执行结束后如没有再次开启则结束
            while (weakself && !weakself.isStopped) {
                // 3.1、开启RunLoop,当没有任务时自动进入休眠状态;
                // 3.2、进入休眠状态表明当前任务没有结束,线程就不会销毁
                [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            NSLog(@"--------RunLoop was stopped...");
        }];
        [self.innerThread start];
        NSLog(@"--------RunLoop is Starting...");
    }
    return self;
}
#pragma mark - 外部调用
- (void)executeWithBlock:(void (^)(void))block
{
    // 外部调用时,如果当前线程已销毁或没有执行内容则返回;
    if (!self.innerThread || !block) return;
    // 在创建的子线程中执行外部任务,waitUntilDone可设置为NO,异步执行;
    [self performSelector:@selector(__threadExecuteWithBlock:) onThread:self.innerThread withObject:block waitUntilDone:NO];
}
- (void)stop
{
    if (!self.innerThread) return;
    // 结束当前线程工作,只要停止当前线程中的RunLoop,线程中RunLoop没有任务可执行线程即将结束;
    // 注意这里waitUntilDone要设置为YES,同步进行,因当子线程会和当前控制器的主线程有线程间通讯,如果异步进行会因为主线程提前被销毁而出现野指针异常
    [self performSelector:@selector(__threadStop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
    self.innerThread = nil;
}
#pragma mark - 内部实现
- (void)__threadExecuteWithBlock:(void (^)(void))block
{
    if (block) block();
}
- (void)__threadStop
{
    // 在当前线程执行一个任务,并且执行的是停止当前RunLoop动作
    // 不执行停止动作,RunLoop也会因为执行了动作而被唤醒,结束
    CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)dealloc
{
    NSLog(@"%s", __func__);
    self.stopped = YES;
    [self stop];
}
@end

2.2、外部调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "ViewController.h"
#import "SAThreadHelper.h"
@interface ViewController ()
@property (nonatomic, strong)   SAThreadHelper     *threadHelper;
@end
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.threadHelper = [SAThreadHelper new];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.threadHelper executeWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
}
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

2.3、运行结果:
2018-11-24 12:06:14.303814+0800 RunLoop[2704:138157] ——–RunLoop is Starting…
2018-11-24 12:06:16.289095+0800 RunLoop[2704:138157] -[ViewController dealloc]
2018-11-24 12:06:16.289543+0800 RunLoop[2704:138157] -[SAThreadHelper dealloc]
2018-11-24 12:06:16.290330+0800 RunLoop[2704:144194] ——–RunLoop was stopped…
2018-11-24 12:06:16.291644+0800 RunLoop[2704:144194] -[_SAInnerThread dealloc]

One thought on “利用RunLoop来实现线程的保活,并封装子线程任务执行

  1. Sian Post author

    线程创建过程中开启RunLoop这个关键过程也可以通过CoreFoundtion中的CFRunLoopRef相关函数来实现,其实NSRunLoop就是NSFoundation中利用Objective-C对CFRunLoopRef相关方法的封装,所以直接使用CFRunLoopRef相关函数更加灵活

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    
    @interface SAThreadHelper ()
    @property (nonatomic, strong)   _SAInnerThread  *innerThread;
    @end
    @implementation SAThreadHelper
    - (instancetype)init
    {
        if (self = [super init]){
            self.innerThread = [[_SAInnerThread alloc] initWithBlock:^{
                // 1、获取当前线程RunLoop
                CFRunLoopRef runloop = CFRunLoopGetCurrent();
                // 2.1、创建一个RunLoopSource上下文
                CFRunLoopSourceContext context = {0};
                // 2.2、创建一个RunLoopSource
                CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
                // 2.3、当前RunLoop添加一个Source,以免RunLoop提前被销毁
                CFRunLoopAddSource(runloop, source, kCFRunLoopDefaultMode);
                CFRelease(source);
                // 3、开启RunLoop,第三个参数:returnAfterSourceHandled,如果传false,则RunLoop执行完任务后自动进行下一次循环
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
                NSLog(@"--------RunLoop was stopped...");
            }];
            NSLog(@"--------RunLoop is Starting...");
            [self.innerThread start];
        }
        return self;
    }
    #pragma mark - 外部调用
    - (void)executeWithBlock:(void (^)(void))block
    {
        // 外部调用时,如果当前线程已销毁或没有执行内容则返回;
        if (!self.innerThread || !block) return;
        // 在创建的子线程中执行外部任务,waitUntilDone可设置为NO,异步执行;
        [self performSelector:@selector(__threadExecuteWithBlock:) onThread:self.innerThread withObject:block waitUntilDone:NO];
    }
    - (void)stop
    {
        if (!self.innerThread) return;
        // 结束当前线程工作,只要停止当前线程中的RunLoop,线程中RunLoop没有任务可执行线程即将结束;
        // 注意这里waitUntilDone要设置为YES,同步进行,因当子线程会和当前控制器的主线程有线程间通讯,如果异步进行会因为主线程提前被销毁而出现野指针异常
        [self performSelector:@selector(__threadStop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
        self.innerThread = nil;
    }
    #pragma mark - 内部实现
    - (void)__threadExecuteWithBlock:(void (^)(void))block
    {
        if (block) block();
    }
    - (void)__threadStop
    {
        // 在当前线程执行一个任务,并且执行的是停止当前RunLoop动作
        // 不执行停止动作,RunLoop也会因为执行了动作而被唤醒,结束
        // CFRunLoopRunInMode()函数中returnAfterSourceHandled为false,则必须调用CFRunLoopStop()来停止RunLoop
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
    - (void)dealloc
    {
        NSLog(@"%s", __func__);
        [self stop];
    }
    @end

    运行结果:
    2018-11-24 13:29:35.737409+0800 RunLoop[3713:273009] ——–RunLoop is Starting…
    2018-11-24 13:29:36.664308+0800 RunLoop[3713:273326] <_sainnerthread: 0x600000158bc0>{number = 3, name = com.yusian.inner-thread}
    2018-11-24 13:29:37.361354+0800 RunLoop[3713:273326] <_sainnerthread: 0x600000158bc0>{number = 3, name = com.yusian.inner-thread}
    2018-11-24 13:29:39.810810+0800 RunLoop[3713:273009] -[ViewController dealloc]
    2018-11-24 13:29:39.811084+0800 RunLoop[3713:273009] -[SAThreadHelper dealloc]
    2018-11-24 13:29:39.811946+0800 RunLoop[3713:273326] ——–RunLoop was stopped…
    2018-11-24 13:29:39.813122+0800 RunLoop[3713:273326] -[_SAInnerThread dealloc]

Leave a Reply