美文网首页
runloop阻塞线程的正确写法 & 子线程常驻后台

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

作者: 路漫漫其修远兮Wzt | 来源:发表于2019-08-23 14:35 被阅读0次

转载自: 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中也能执行

相关文章

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

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

  • RunLoop开辟常驻线程

    runLoop创建一个常驻线程,多次用到子线程去处理事件,避免频繁的创建,销毁线程,每条线程都有一个RunLoop...

  • runloop常驻子线程

    开启线程需要占用一定的内存空间,且每次开辟子线程都会消耗CPU。如果频繁使用子线程的情况下,频繁开辟释放子线程会消...

  • 实现后台常驻线程

    添加一条用于常驻内存的强引用的子线程,在该线程的RunLoop下添加一个Sources,开启RunLoop。

  • RunLoop 基本使用

    RunLoop应用场景 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)在子线程...

  • runloop

    runloop跟线程师一一对应的。 自己创建的线程师没有runloop 如何实现一个常驻线程: 1 为当前线程开启...

  • iOS 多线程面试题(NSThread+runloop实现常驻线

    NSThread+runloop实现常驻线程 NSThread在实际开发中比较常用到的场景就是去实现常驻线程。 由...

  • runloop深入探究,手动开启和关闭一个runloop

    提到iOS中开启一个常驻线程,网上最常见的就是在子线程中开启一个runloop,然后给runloop添加一个por...

  • 多线程-交替打印字符串的3种写法

    写法1:使用信号量进行线程间的阻塞 写法2:使用CyclicBarrier 进行线程间的阻塞 写法3:利用原子类A...

  • RunLoop常驻线程

    在开发中,经常遇到的便是开启一个线程,当线程的任务完成,该线程便会被自动收回。但是如果我们需要一个线程一直存在,等...

网友评论

      本文标题:runloop阻塞线程的正确写法 & 子线程常驻后台

      本文链接:https://www.haomeiwen.com/subject/bgxtectx.html