详解RunLoop与多线程

栏目: IOS · 发布时间: 5年前

内容简介:通过上一篇首先定义定义一个YZThread继承自NSThread 重写它的dealloc方法,为了检测是否销毁 然后,创建该类,并执行方法,如下图所示运行结果

通过上一篇 详解RunLoop之源码分析 我们知道了runlLoop每条线程都有唯一的一个与之对应的RunLoop对象 并且 RunLoop会在线程结束时销毁 接下来就具体分析RunLoop会在线程结束时销毁这个特点

首先定义定义一个YZThread继承自NSThread 重写它的dealloc方法,为了检测是否销毁 然后,创建该类,并执行方法,如下图所示

/** YZThread **/
@interface YZThread : NSThread
@end
@implementation YZThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@interface ViewController ()
@property (strong, nonatomic) YZThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
      YZThread *thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];

}
// 这个方法
- (void)run {
     NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

复制代码

运行结果

Interview03-线程保活[7854:134789] -[ViewController run] <YZThread: 0x600000a13b80>{number = 3, name = (null)}
Interview03-线程保活[7854:134789] -[YZThread dealloc]

复制代码

从结果中,我们发现,当执行完run这个方法之后,该线程就销毁了。

但是在项目中,或者很多第三方中(比如AFN),可能会遇到这个线程我们经常做事情,如果每次都销毁、创建、销毁、、、那么对性能也是一种损耗.我们就需要我们自己控制线程的销毁和创建。 上面线程之所以立即销毁,根据上篇文章我们知道,

  • 是因为 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出 那我们给它加上这些东西不就行了么?来试试看吧。
/** YZThread **/
@interface YZThread : NSThread
@end
@implementation YZThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@interface ViewController ()
@property (strong, nonatomic) YZThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
      YZThread *thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];

}
// 这个方法
- (void)run {
     NSLog(@"%s %@", __func__, [NSThread currentThread]);
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s ----end----", __func__);
}

复制代码

运行结果

Interview03-线程保活[7967:136867] -[ViewController run] <YZThread: 0x600003105b00>{number = 3, name = (null)}

复制代码

如上所示,添加了port之后,该线程就没有调用dealloc方法了。上一篇介绍了,Source1包含了基于Port的线程间通信,也就是说。添加了port之后,相当于与有了source1 那么线程就不会退出,也就不会调用dealloc方法了。

但是如果我们想在这个线程执行自己的操作,那就需要我们持有这个线程了。如下图所示,增加个属性,并且屏幕点击事件中调用test方法

#import "ViewController.h"

/** YZThread **/
@interface YZThread : NSThread
@end
@implementation YZThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

@interface ViewController ()
@property (strong, nonatomic) YZThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}


- (void)run {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s ----end----", __func__);
}

// 这个方法的目的:线程保活
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"点击屏幕");
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
复制代码

运行结果:

Interview03-线程保活[8035:138457] -[ViewController run] <YZThread: 0x60000232d4c0>{number = 3, name = (null)}
 Interview03-线程保活[8035:138399] 点击屏幕
Interview03-线程保活[8035:138457] -[ViewController test] <YZThread: 0x60000232d4c0>{number = 3, name = (null)}
复制代码

果然是线程不会销毁,而且 由 number = 3 可知,确实是在我们自己创建的线程中执行的操作

接下来我们再看一个问题,首先在控制器中写上dealloc方法,

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

复制代码

然后给个导航控制器跳转到当前控制器,之后返回的时候发现dealloc并没有执行 这是因为代码中用的

self.thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
复制代码

这个方法,会导致线程持有当前控制器,导致循环引用,如果解除循环引用,需要把这个initWithTarget 换成initWithBlock

我们将之前的

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[YZThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
- (void)run {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"%s ----end----", __func__);
}
复制代码

改成

- (void)viewDidLoad {
    [super viewDidLoad];
    
   self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        // 这个方法会不能退出循环
        [[NSRunLoop currentRunLoop] run];
               
    }];
    [self.thread start];
}

复制代码

可以发现

Interview03-线程保活[8035:138457] -[ViewController dealloc]
复制代码

这次控制器可以销毁了。然后,你可能已经发现了,虽然控制器可以销毁,但是线程并没有销毁。换句话说,虽然没有循环引用了。而且控制器销毁了,然后线程依然没有销毁,这是为什么呢。

注意[[NSRunLoop currentRunLoop] run]

其实关键的一句代码是

[[NSRunLoop currentRunLoop] run];
复制代码

看一下官方对这个方法的解释

Discussion

If no input sources or timers are attached to the run loop, this method exits immediately;
 otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. 
 In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
复制代码

也就是说,NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop) 就相当于

while (1) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
复制代码

就算我们手动调用停止RunLoop的方法,

// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
    
复制代码

也无法停止这个线程,因为这个只能停止这一次的RunLoop,下次循环,依然可以继续进行下去

我们把代码修改为如下的写法

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
      
    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

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

复制代码

我们用一个bool值做未标记,当下次进来的时候。判断已经停止了。那就不进入下次RunLoop了,就可以退出循环

注意

// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
复制代码

至此,我们代码如下所示

#import "ViewController.h"
#import "MJThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        // 这个方法会不能退出循环
//        [[NSRunLoop currentRunLoop] run];
        while (!weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"%@----end----", [NSThread currentThread]);

        
    }];
    [self.thread start];
}

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

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

- (IBAction)stop {
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

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

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
//    [self stop];
}

复制代码

封装线程

到这里,我们已经可以控制线程的创建,执行,销毁了。 但是,这也太复杂了吧。每次我们都需要写这么多代码,如果我们每次,都这么干的话,岂不是疯掉了。所以我们要抽取出来,这样,以后用的时候,直接拿来就可以了。

首先新建一个继承自 NSObject的类MJPermenantThread 然后持有一个NSThread的属性,注意这里不是直接继承NSThread 是因为,如果继承了NSThread 那么外界调用的时候,可以直接使用NSThread的方法,会影响线程的使用。所以,本着简洁,可控的原则,定义的类继承自NSThread 然后拥有属性NSThread,这样,外界只能使用我们暴露出去接口。

.h中代码如下

typedef void (^MJPermenantThreadTask)(void);

@interface MJPermenantThread : NSObject

/**
 开启线程
 */
//- (void)run;

/**
 在当前子线程执行一个任务
 */
- (void)executeTask:(MJPermenantThreadTask)task;

/**
 结束线程
 */
- (void)stop;



复制代码

.m中代码如下

/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@end

@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"begin----");
            
            // 创建上下文(要初始化一下结构体)
            CFRunLoopSourceContext context = {0};
            
            // 创建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            // 销毁source
            CFRelease(source);
            
            // 启动
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            
            //            while (weakSelf && !weakSelf.isStopped) {
            //                // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
            //                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
            //            }
            
            NSLog(@"end----");
        }];
        
        [self.innerThread start];
    }
    return self;
}

//- (void)run
//{
//    if (!self.innerThread) return;
//
//    [self.innerThread start];
//}

- (void)executeTask:(MJPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(MJPermenantThreadTask)task
{
    task();
}

@end


复制代码

MJPermenantThread.h 就是这样的

#import <Foundation/Foundation.h>

typedef void (^MJPermenantThreadTask)(void);

@interface MJPermenantThread : NSObject

/**
 开启线程
 */
//- (void)run;

/**
 在当前子线程执行一个任务
 */
- (void)executeTask:(MJPermenantThreadTask)task;

/**
 结束线程
 */
- (void)stop;

@end

复制代码

MJPermenantThread.m 就是这样的

#import "MJPermenantThread.h"

/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@end

@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"begin----");
            
            // 创建上下文(要初始化一下结构体)
            CFRunLoopSourceContext context = {0};
            
            // 创建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            // 销毁source
            CFRelease(source);
            
            // 启动
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            
            //            while (weakSelf && !weakSelf.isStopped) {
            //                // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
            //                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
            //            }
            
            NSLog(@"end----");
        }];
        
        [self.innerThread start];
    }
    return self;
}

//- (void)run
//{
//    if (!self.innerThread) return;
//
//    [self.innerThread start];
//}

- (void)executeTask:(MJPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(MJPermenantThreadTask)task
{
    task();
}

@end

复制代码

这样就简单封装了线程,而且因为是用 C语言 创建的,能更灵活的控制 看官方对CFRunLoopAddSource的定义,第三个参数,如果是false,那么这个线程,执行完当前这次RunLoop,不会退出。这样写的话,就可以保证线程一直存在。

//第三个参数 :A flag indicating whether the run loop should exit after processing one source. 
 //If false, the run loop continues processing events until seconds has passed.
 CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
复制代码

封装之后的使用

使用的时候,我们可以主动调用stop 来销毁掉线程,也可以,不做任何处理,当控制器销毁时候,MJPermenantThread对象就会销毁,根据

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}
- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)__stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}
复制代码

可知,RunLoop也会停止,线程也就退出了。所以使用的时候,如下所示

#import "ViewController.h"
#import "MJPermenantThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJPermenantThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
     self.thread = [[MJPermenantThread alloc] init];
       
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.thread executeTask:^{
        NSLog(@"执行任务 - %@", [NSThread currentThread]);
    }];
    
}

- (IBAction)stop {
    [self.thread stop];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end
复制代码

至此。就说完了RunLoop和线程直接关系,以及线程的保活,以及自定义线程。这样使用起来更加得心用手。

本文相关代码github地址 github

本文参考资料:

RunLoop官方源码

iOS底层原理


以上所述就是小编给大家介绍的《详解RunLoop与多线程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

第二曲线:跨越“S型曲线”的二次增长

第二曲线:跨越“S型曲线”的二次增长

[英]查尔斯·汉迪(Charles Handy) / 苗青 / 机械工业出版社 / 2017-6 / 49.00

S型曲线是每个组织和企业在预测未来时一定会参考的工具,一切事物的发展都逃不开S型曲线(“第一曲线”)。 然而,从公司组织、企业治理、市场的变化,到个人职业发展、社会人际关系以及未来的教育与社会价值,多维度地探讨这个世界需要重新以不同的角度来思考问题,不能够总是停留在“第一曲线”的世界。 如果组织和企业能在第一曲线到达巅峰之前,找到带领企业二次腾飞的“第二曲线”,并且第二曲线必须在第一曲......一起来看看 《第二曲线:跨越“S型曲线”的二次增长》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换