多线程NSThread和NSOperation

2018-10-22  本文已影响6人  简_爱SimpleLove

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

#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 深入理解

上一篇下一篇

猜你喜欢

热点阅读