runloop阻塞线程的正确写法 & 子线程常驻后台

2019-08-23  本文已影响0人  路漫漫其修远兮Wzt

转载自: iOS RunLoop 初识
深入理解RunLoop
iOS多线程编程指南(三)Run Loop

1.Runloop实现后台子线程常驻,并在不使用时睡眠,使用时唤醒
2.使用runloop阻塞线程的正确写法
3.线程间通信之performSelector:
1.Runloop实现后台子线程常驻,并在不使用时睡眠,使用时唤醒

创建一个单例类,在单例类里面创建一个子线程,实现子线程可以常驻后台,在不使用的时候休眠,使用的时候可以被激活;
(1)创建单例类

+(NSThread*)shareThread {
    
    static NSThread *thread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        thread = [[NSThread alloc]initWithTarget:self selector:@selector(testShareThread) object:nil];
        
        [thread setName:@"WT share thread test"];
        
        [thread start];
    });
    return thread;
}

+(void)testShareThread {
    NSLog(@"%@ - %@ start", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}

(2)调用这个单例类,并在里面的子线程中执行方法:

[self performSelector:@selector(testVC) onThread:[WTRunloopTest shareThread] withObject:nil waitUntilDone:NO];

-(void)testVC {
    NSLog(@"%@ - %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}

(3)运行代码结果发现,这个单例的子线程在运行完后,就直接退出了,导致我们调用这个线程执行方法的时候,没有调用到。
怎么样才能是这个单例子线程常驻后台,在不使用的时候休眠,使用的时候唤醒呢;while循环可以使线程常驻后台,runloop可以做到不使用的时候休眠,使用的时候唤醒;
于是我们修改单例类的子线程方法:

+(void)testShareThread {

    static BOOL flag = NO;
    while (!flag) {
        [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}

搞定。

可以有更高级点的写法:

+ (void)threadTest
{
    @autoreleasepool {

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

给runloop添加事件源,更多runloop和runloop端口、源的只知识可以参考这里iOS多线程编程指南(三)Run Loop

2.使用runloop阻塞线程的正确写法

Runloop可以阻塞线程,等待其他线程执行后再执行。
比如:

@implementation ViewController{
     BOOL end;
}

– (void)viewDidLoad
{
     [super viewDidLoad]; 

     NSLog(@”start new thread …”);
     [NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];    
     while (!end) {
         NSLog(@”runloop…”);
         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
         NSLog(@”runloop end.”);
     }
     NSLog(@”ok”);
}
-(void)runOnNewThread{
     NSLog(@”run for new thread …”);
     sleep(1);
     end=YES;
     NSLog(@”end.”);
}

但是这样做,运行时会发现,while循环后执行的语句会在很长时间后才被执行。

那是不是可以这样:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

缩短runloop的休眠时间,看起来解决了上面出现的问题。

不过这样也又问题,runloop对象被经常性的唤醒,这违背了runloop的设计初衷。runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。

那么怎么做呢?正确的写法是:

-(void)runOnNewThread{
 
      NSLog(@”run for new thread …”);
     sleep(1);
     [self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
     NSLog(@”end.”);
}
-(void)setEnd{
     end=YES;
}

见黑体斜体字部分,要将直接设置变量,改为向主线程发送消息,执行方法。问题得到解决。

这里要说一下,造成while循环后语句延缓执行的原因是,runloop未被唤醒。因为,改变变量的值,runloop对象根本不知道。延缓的时长总是不定的,这是因为,有其他事件在某个时点唤醒了主线程,这才结束了while循环。那么,向主线程发送消息,将唤醒runloop,因此问题就解决了。

3.线程间通信之performSelector:

参考原文:2019 iOS面试题-----进程、线程、多进程、多线程、任务、队列、NSThread、GCD、NSOprationQueue...
performSelector...只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
在编译阶段并不会去检查方法是否有效存在,如果不存在只会给出警告

  //在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
    [self performSelector:@selector(aaa) withObject:nil afterDelay:1];
  // 回到主线程。waitUntilDone:是否将该回调方法执行完在执行后面的代码,如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;如果是NO:就是不等回调方法结束,不会阻塞当前线程
    [self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES];
  //开辟子线程
    [self performSelectorInBackground:@selector(aaa) withObject:nil];
  //在指定线程执行
    [self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]

需要注意的是:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];

而performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行

上一篇 下一篇

猜你喜欢

热点阅读