美文网首页
线程保活

线程保活

作者: 一毛钱 | 来源:发表于2019-07-22 21:18 被阅读0次

线程保活是在多线程中进行耗时操作常用的功能:

常规开启方式,会出现内存泄漏

通过 [runloop run]直接开启运行循环,这种方式代码如下,会出现内存泄漏.而且即使手动调用stopThread,仍然会出现内存泄漏.

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createThread];
}

- (void)createThread{
    for (int i = 0; i < 2000; ++i) {
        NSThread* tempThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction) object:nil];
        [tempThread start];
//手动调用stop方法
        [self performSelector:@selector(stopThread) onThread:tempThread withObject:nil waitUntilDone:NO];
        NSLog(@"----i is == %d",i);
    }
}

- (void)stopThread{
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSThread* thread = [NSThread currentThread];
    [thread cancel];
}

- (void)threadAction{
    if (self.tempPort == nil) {
        self.tempPort = [NSMachPort port];
    }
    
    NSRunLoop* runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:self.tempPort forMode:NSDefaultRunLoopMode];
    [runloop run];    
//[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
 NSLog(@"------------------");//这行代码没有打印,说明运行循环已经开启,运行循环正常结束后会调用
}

[runloop run]; 调用时 NSLog(@"------------------");打印的代码没有出现,说明运行循环开启了,并且没有正常结束.所以运行循环一直没有被关闭. 所以可以知道,通过[runloop run]; 启动的运行循环,是无法被CFRunLoopStop(CFRunLoopGetCurrent());结束的.
内存效果如下:

run内存图.png
只是一个简单的demo,内存会飙升到80M以上.

如果将[runloop run]; 替换成
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
并且 在指定手动调用stop,这时打印信息就会打印,这就说明运行循环被手动停止了.而且不会出现内存泄漏情况.内存效果如下:

指定模式运行.png

原因

runloop 启动有三种方法. 如下:
1.run

  • 立即启动,无条件,最简单的做法,会使线程进入死循环,结束循环的唯一方法是kill 它.
  1. runUntilDate:
  • 给定一个截止时间,运行到指定时间点后停止运行循环. 或者当runloop完成事件后,也会退出循环,此时可以选择重新开启循环.
  1. runMode: beforeDate:
  • 这种方式相对更优秀,相比于第二种,可以指定模式运行.

我查阅了一下资料,其实run 以及 runUntilDate: 内部实际上是循环调用runMode: beforeDate:方法. runMode: beforeDate: 表示循环的单次调用,另外两个表示多次调用.
前面两种方法run, runUntilDate:无法通过CFRunLoopStop()结束循环. 因为CFRunLoopStop()只能结束单次循环,而run, runUntilDate:都是多次调用 runMode: beforeDate:的,所以不能推出相应的循环.
如果想进行线程保活,明显依赖某个时间点才推出循环,是不太合理的,所以采用 runUntilDate:进行线程保活不可靠,那就只有调用第三种方法了runMode: beforeDate:.

利用runMode: beforeDate:进行线程保活

因为该方法只调用一次循环,所以运行一次循环执行某事件后,如果没有再次重复调用,循环不再运行.

//创建线程时调用的方法
- (void)runthread{
    NSLog(@"current thread is = %@",[NSThread currentThread]);
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    //为什么要添加指定的端口,可以再写一篇关于Runloop的文章
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    //[self printSomeThing];
    BOOL needRun = YES;
    _needRun = needRun;
    
    //返回一个bool值
    [runLoop runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
    NSLog(@"------");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//本质也是加入到运行循环
    [self performSelector:@selector(printfunction) onThread:self.tempThread withObject:nil waitUntilDone:NO];
}

- (void)printfunction{
    //NSLog(@"%s", __FUNCTION__);
    NSLog(@"thread is ===  %@",[NSThread currentThread]);
}

如果没有添加定时源,点击屏幕后,会调用一次printfunction方法,之后运行循环就会自动关闭,你再次点击屏幕不再会处理时间.因为performSelector 原理也是将事件加入到运行循环. 所以线程保活方法是需要调用runMode:beforeDate: 不过明显只调用一次是不够的,需要多次调用. 通过for循环调用操作.

有效方法
- (void)viewDidLoad {
    [super viewDidLoad];
    //[self createThread];
    [self createThread33];
    UIButton* tmpButton = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 60, 60)];
    tmpButton.backgroundColor = [UIColor redColor];
    [tmpButton addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:tmpButton];
}

//能够进行线程保活,并且能够制定时间点退出. perfect
- (void)buttonClick{
     self.needRun = NO;
    [self performSelector:@selector(stopThreadsec) onThread:self.tempThread withObject:nil waitUntilDone:NO];
}

- (void)stopThreadsec{
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSThread* thread = [NSThread currentThread];
    [thread cancel];
}

- (void)runthread{
    NSLog(@"current thread is = %@",[NSThread currentThread]);
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
    //为什么要添加指定的端口,可以再写一篇关于Runloop的文章
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    //[self printSomeThing];
    BOOL needRun = YES;
    _needRun = needRun;
    
    //通过这种方式进行线程保活,有效能够启动,能够退出,不会出现内存泄漏
    while(_needRun&&[runLoop runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]){
        
    }

    NSLog(@"------");
}

通过上面的方式启动循环,并且不会每次点击屏幕都会打印信息,当点击退出按钮后,不会再打印. 说明运行循环被正常启动,并且退出后能够正常退出. 所以这种方式进行线程保活是可用有效的.

相关文章

  • RunLoop -- 在实际开发中的应用

    1、控制线程生命周期<线程保活> 线程保活 2、解决NSTimer在滑动时失效的问题 当scrollView滑动的...

  • iOS底层原理——浅谈RunLoop

    RunLoop应用:线程保活 线程保活、控制销毁 iOS-浅谈RunLoop8iOS底层原理总结 - RunLoo...

  • iOS NSThread 保活线程代码封装

    iOS NSThread 保活线程代码封装

  • iOS Runloop的理解与使用

    Runloop的概念 Runloop的存在主要就是为了线程保活,线程保活是为了线程能够及时的处理事件,不会在其执行...

  • 线程保活

    在实际开发中,我们可能很多操作需要放在子线程中操作,可能会重复的创建线程。这个时候我们就需要创建一个线程并不让其销...

  • 线程保活

    线程保活是在多线程中进行耗时操作常用的功能: 常规开启方式,会出现内存泄漏 通过 [runloop run]直接...

  • 线程保活

    #import "ViewController.h" #import "WZJthread.h" @interfa...

  • 线程保活

    线程保活 当子线程中的任务执行完毕后,线程就被立刻销毁了。如果程序中,需要经常在子线程中执行任务,频繁的创建和销毁...

  • 线程保活

    ViewController.h ViewController.m

  • iOS底层探索 --- RunLoop(实战)

    日常开发中我们常用的RunLoop场景有: 线程保活 Timer相关 APP卡顿检测 线程保活首先我们应该达成的共...

网友评论

      本文标题:线程保活

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