美文网首页
多线程NSThread和NSOperation

多线程NSThread和NSOperation

作者: 简_爱SimpleLove | 来源:发表于2018-10-22 18:02 被阅读6次

    NSThread

    NSThread 一线程对象 对应一个线程,当执行完之后,不能再重新开启同一个线程

    开启线程有两种方法:
    法一:主动开启

        // 主动开线程
    [NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
    

    法二:手动开启

        //手动开启
        _thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
        [_thread start];
    

    取消线程,并不能真正的主动结束取消线程,只是设置了一个节点(即标记)然后你再主动结束线程。如下:

    - (void)threadOne{
        NSLog(@"threadOne:%@", [NSThread currentThread]);
    }
    
    - (void)threadTwo{
        NSLog(@"threadTwo::%@", [NSThread currentThread]);
        for (int i = 0; i < 5; i++) {
            if([NSThread currentThread].isCancelled){
                return;
            }
            sleep(2);
            NSLog(@"%d", i);
            // 取消,正在要取消线程要设置取消节点
            if([NSThread currentThread].isCancelled){
                return;
            }
        }
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [_thread cancel];
    }
    

    自定义的NSOperation中结束线程也是一样的。

    - (void)main{
    
        NSLog(@"main");
        for (int i = 0; i < 30; i++) {
            sleep(1);
            NSLog(@"%d", i);
            // 取消节点设置越多,取消的效率越高
            if (self.isCancelled) {
                // 当是取消状态时,主动结束线程操作
                return;
            }
        }
    }
    

    NSOperation

    Operation抽象类必须用其子类实现,子类有NSBlockOperation和NSInvocationOperation

    operation 可以自动开启,也可以手动开启
    自动开启:是要添加到线程队列NSOperationQueue里面,添加进去就自动开始了,不用再start
    手动开启:手动开启有start和main两种方法,手动开启的时候至少会分配一个到主线程上面,如果是自动开启都没有在主线程上。
    两个的区别是:
    start 没有重复开启: 因为在start方法里面对blockOperation的状态finish状态进行判断, 如果finish=YES,就不会执行了
    main 直接是调用block,会重复执行

    NSBlockOperation

    block并不是先添加就先执行(因为是并行的,但是一般情况,比较少,和操作也不复杂的情况下,是先添加就先执行的),当operation执行,就不能再添加block,但是在添加到队列过后,还可以再添加block。
    NSBlockOperation里面block块全部结束,那么这个NSBlockOperation才算结束(即finished = YES)

    //运用 :多个任务处理(任务A,任务B,任务C)三个任务完成了,通过KVO监听得到 (任务是block块)

    #import "EOCBlockOperationVC.h"
    /*
     operation 也可以手动开启
     block并不是先添加就先执行(因为是并行的),当operation执行,就不能再添加block
     NSBlockOperation里面block块全部结束,那么这个NSBlockOperation才算结束(即finished = YES)
     
     //运用 :多个任务处理(任务A,任务B,任务C)三个任务完成了,通过KVO监听得到 (任务是block块)
    
     手动开启的时候,会分配一个到主线程上执行
     
     */
    
    @interface EOCBlockOperationVC (){
        NSBlockOperation *blockOperation;
        NSOperationQueue *operationQueue;
    }
    
    @end
    
    @implementation EOCBlockOperationVC
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       
        void (^tblock)() = ^{
            
           NSLog(@"tblock:%@", [NSThread currentThread]);
        };
        
        // 新开一个队列
        operationQueue = [NSOperationQueue new];
        blockOperation = [[NSBlockOperation alloc] init];
        
        [blockOperation addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:@"1"];
        
        // 将Block添加到Operation
        [blockOperation addExecutionBlock:^{
            NSLog(@"one block:%@", [NSThread currentThread]);
        }];
        [blockOperation addExecutionBlock:^{
            NSLog(@"two block:%@", [NSThread currentThread]);
        }];
        
        [blockOperation addExecutionBlock:tblock];
        
        // 将Operation放到队列operationQueue中后就会自动执行,不用再start
        [operationQueue addOperation:blockOperation];
        
        [blockOperation addExecutionBlock:^{
            NSLog(@"three block:%@", [NSThread currentThread]);
        }];
    
        
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        
        NSLog(@"%@", keyPath);
        NSLog(@"%@", change);
        
        /*
         打印结果:(1就是YES的意思)context可以用来区分线程,判断线程是否结束,如果结束了,就可以再重新开启新的线程,执行新的任务
         kind = 1;
         new = 1;
         */
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
     
        /*
         start 没有重复开启: 因为在start方法里面对blockOperation的状态finish状态进行判断,
         如果finish=YES,就不会执行了
         
         main 直接是调用block,会重复执行
         
         需要注意的点
         */
        [blockOperation start];
    
    //    [blockOperation main];
    }
    

    NSInvocationOperation

    当参数只有一个或者两个的时候,我们需要传参数过去,并且在其他线程执行,可以用下面的方法之一:

     [NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>]
     [self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
    

    但是当参数比较多的时候就不行了,就需要用到NSInvocationOperation。
    需要注意的是:初始化NSInvocationOperation的时候需要方法签名,将方法对象化,并配置参数。如下:

    #import "EOCInvocationOperationVC.h"
    
    @interface EOCInvocationOperationVC (){
        
        NSInvocationOperation *_invocationOperation;
        NSInvocation *invation ;
        NSString *backStr;
    }
    
    @end
    
    @implementation EOCInvocationOperationVC
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        
        // 3 selector  方法签名(方法的对象结构,相关的结构信息:返回值(return value),调用者(target 一般为self),方法名(selector),参数(argument))
        NSMethodSignature *signture = [self methodSignatureForSelector:@selector(name:age:sex:)];
        
        // 2 signture  NSInvocation可以将方法对象化
        invation = [NSInvocation invocationWithMethodSignature:signture] ;
        invation.target = self;
        invation.selector = @selector(name:age:sex:); //和签名的seletor要对应起来
        
        // 配置参数
        NSString *name = @"eoc";
        NSString *age = @"2";
        NSString *sex = @"男";
        
        [invation setArgument:&name atIndex:2]; // 前面的target和selector占了0和1,所以从2算起
        [invation setArgument:&age atIndex:3];
        [invation setArgument:&sex atIndex:4];
        
        // 1 初始化 NSInvocationOperation 需要 invation
        _invocationOperation = [[NSInvocationOperation alloc] initWithInvocation:invation];
        
        // 手动调用
    //    [invation invoke];
        
        // 将它放到队列中执行
        NSOperationQueue *queue = [NSOperationQueue new];
        [queue addOperation:_invocationOperation];
    }
    /*
     把这个方法 看作一个比较耗时业务,需要放到线程中执行
     因为参数比较多,不好用下面的方法,因为下面的方法只能传一个参数object
     [NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>]
     [self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
     
     这时因为参数多,就可以用NSInvocationOperation来操作
     */
    - (NSString*)name:(NSString*)name age:(NSString*)age sex:(NSString*)sex{
        NSLog(@"name: age: sex:%@", [NSThread currentThread]);
        return [NSString stringWithFormat:@"%@%@%@", name, age, sex];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //    注意下,NSInvocation在有返回值的时候,注意用__unsafe_unretained 这种类型来获取,不然会被释放掉,报内存错误。
    
        // 手动调用,获取返回值,最好不要弄返回值,因为返回值对象是不安全类型,没有被强引用,要么会报内存错误,要么或者随便乱指一个对象
        // 将它放到队列中执行,获取返回值不会有问题
        __unsafe_unretained NSString *returnValue;
        [invation getReturnValue:&returnValue];
        NSLog(@"returnValue:%@", returnValue);
    }
    @end
    

    自定义NSOperation

    • 只是重写start方法,并不会调用dealloc方法,因为start方法中没有对方法做一个结束的通知,_finished = NO,所以dealloc 没被执行。
    • 只是重写main方法,会调用dealloc方法。
    • 如果start和main方法都重写,需要在start方法中手动调用main方法。
    • start方法一般做异常处理,main方法做主要的业务逻辑处理。
    • 如果要开启定时器之类的,我们还需要在main方法中开启RunLoop,定时器才会执行。
    #import "EocOperation.h"
    /*
      改变_finished状态 YES
     */
    @implementation EocOperation{
        
        NSTimer *_timer;
    }
    
    // 前面finished是属性,后面_finished是变量,我们可以通过后面的变量来对前面的属性进行操作。
    // 因为self.finished属性是仅读的,所以我们用这种方法来修改
    @synthesize finished = _finished;
    
    
    - (void)main{
    
        // 主要的业务逻辑放到这里处理
        NSLog(@"main2");
        _finished = YES;
    
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeCount) userInfo:nil repeats:YES];
        
        // 还是要管理Operation的生命周期,开启RunLoop,定时器才会执行
        [[NSRunLoop currentRunLoop] run];
    }
    
    - (void)timeCount{
        static int count = 0;
        count++;
        if (count > 5) {
            [_timer invalidate];
        }
        NSLog(@"timeCount");
    }
    
    
    - (void)start{
    
        //异常处理
        NSLog(@"%@", [NSThread currentThread]);
        
        // 重写start和main方法,需要在start方法中主动调用main方法,不然不会主动执行,方法有下面两种:
        /*
         法一:
        [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
         法二:
        [self main];
         */
    
        // 防止手动调用多次,已经结束和正在执行就返回。
        if (_finished) {
            return;
        }
        if (self.isExecuting) {
            return;
        }
        NSLog(@"start");
        
        [self main];
    //    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    
        
    }
    
    /*
     只是重写start方法,start没有对方法做一个结束的通知,_finished = NO,所以dealloc 没被执行。
     dealloc 没被执行,是因为EocOperation的状态为未完成状态 _finished = NO;
     self.finished属性是仅读的,所以重新赋值一个_finished来改变
     */
    - (void)dealloc{
        
        NSLog(@"start::%s", __func__);
    }
    

    NSOperation依赖关系

    当一个线程A要另一个线程B结束过后才能开启,我们就说线程A依赖于线程B。并且可以对线程A和B添加观察者KVO,来对当线程结束过后进行的操作。

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.title = @"依赖";
        queue = [NSOperationQueue new];
        
        NSBlockOperation *eocOperation = [[NSBlockOperation alloc] init];
        [eocOperation addExecutionBlock:^{
            NSLog(@"one block task");
        }];
        [eocOperation addExecutionBlock:^{
            NSLog(@"two block task");
        }];
        
        NSBlockOperation *twoOperation = [[NSBlockOperation alloc] init];
        [twoOperation addExecutionBlock:^{
            NSLog(@"Three block task");
        }];
        [twoOperation addExecutionBlock:^{
            NSLog(@"Four block task");
        }];
        
        // 先后顺序关系 twoOperation 先,然后再eocOperation
        // 在执行之前,把依赖关系建立好
        [eocOperation addDependency:twoOperation];
        // 也有对应的移除依赖
    //    [eocOperation removeDependency:twoOperation];
        [queue addOperation:eocOperation];
        [queue addOperation:twoOperation];
        // 对twoOperation的完成状态,添加监听
        [twoOperation addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"%@", keyPath);
    }
    

    僵尸线程

    在当前线程开启的定时器这类的,需要在当前线程结束定时器这些操作,不然会造成线程不能结束,我们说着就是僵尸线程。
    在当前RunLoop里面添加的端口,也需要在当前RunLoop中移除端口。
    在子线程中开启的定时器,后面需要跟上运行当前RunLoop的代码,才会执行定时器。

    #import "ThreadDeadViewCtr.h"
    
    @interface ThreadDeadViewCtr (){
        
        NSPort *port;
        NSTimer *_timer;
    }
    
    @end
    
    @implementation ThreadDeadViewCtr
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [NSThread detachNewThreadSelector:@selector(ThreadTwo) toTarget:self withObject:nil];
    }
    
    - (void)ThreadOne{
        
        NSLog(@"start thread");
        port = [NSPort new];
        [self performSelector:@selector(endThread:) withObject:nil afterDelay:2];
        [[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
        
        NSLog(@"finish thread");
        // 扫尾工作,RunLoop结束过后,需要进行的操作
        for (int i = 0; i < 5; i ++) {
            NSLog(@"%d", i);
        }
    }
    
    - (void)ThreadTwo{
        
        NSLog(@"start thread");
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(stopTimer:) userInfo:nil repeats:YES];
        // 因为是在子线程添加的定时器,需要运行当前RunLoop,定时器才会运行
        [[NSRunLoop currentRunLoop] run];
        
        NSLog(@"finish thread");
        // 扫尾工作,RunLoop结束过后,需要进行的操作
        for (int i = 0; i < 5; i ++) {
            NSLog(@"%d", i);
        }
    }
    
    - (IBAction)startThread:(id)sender{
        // 在主线程结束定时器,并不会结束定时器所在的那个子线程,会造成内存泄漏
        // 因为这里是在主线程结束定时器的
        [_timer invalidate];
    }
    
    - (IBAction)endThread:(id)sender{
        
        // 在当前线程添加的,需要在当前线程移除,要一一对应
        [[NSRunLoop currentRunLoop] removePort:port forMode:NSDefaultRunLoopMode];
        NSLog(@"IBAction Two");
    }
    
    - (IBAction)stopTimer:(id)sender{
        
        NSLog(@"stopTimer");
        static int count = 0;
        count++;
        if (count > 5) {
            // 当前线程开启的,需要在当前线程移除,不然会内存泄漏
            // 这是在添加定时器的那个线程中结束的定时器,因为stopTimer这个方法就在那个线程中
            [_timer invalidate];
        }
    }
    

    拓展文献:
    iOS开发-Runloop详解(简书)
    iOS - RunLoop 深入理解

    相关文章

      网友评论

          本文标题:多线程NSThread和NSOperation

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