美文网首页
iOS 多线程

iOS 多线程

作者: NextStepPeng | 来源:发表于2019-07-17 18:05 被阅读0次

iOS使用线程的方式

  1. pthread
  2. NSThread
  3. GCD
  4. NSOperation

NSThread线程的创建与启动

  • 创建线程后启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
 [thread start];
  • 创建线程后自动启动线程
NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • 隐式创建线程
[self performSelectorInBackground:@selector(run) withObject:nil];

NSThread线程相关方法

  • 获取当前线程
[NSThread currentThread];
//获取主线程
[NSThread mainThread];

  • 是否是主线程
[NSThread isMainThread];
  • 设置线程名称
[[NSThread currentThread] setName:@"设置线程名称"];
  • 启动线程
 [[NSThread currentThread] start];
  • 阻塞线程 让线程休息会
//[NSThread sleepUntilDate:[]]
    [NSThread sleepForTimeInterval:2];

NSThread线程间通信

  • 子线程->主线程 子线程->指定线程
self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
  • 举例说明(项目中经典例子 图片下载及 图片UI显示)
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
//在子线程下载图片
- (void)downloadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"图片地址"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    UIImage *image = [UIImage imageWithData:imageData];
    //在主线程将图片显示
    [self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:true];
}


- (void)refreshOnMainThread:(UIImage *) image {
    UIImageView *imageV = [[UIImageView alloc] initWithImage:image];
}

NSThread线程安全

  • @synchronized (self)
    性能是较差的,但是很方便,不用自己创建锁,不用加锁,一般项目都能满足
- (void)myMethod:(id)anObj
{
    @synchronized(anObj)
    {
        // Everything between the braces is protected by the @synchronized directive.
    }
}
  • NSLock
    在Cocoa程序中NSLock中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁
- (NSLock *)lock
{
    if (!_lock)
    {
        _lock = [[NSLock alloc] init];
    }
    
    return _lock;
}
[self.lock lock];
    self->_myVTKPolyData->SetPoints(m_Points);
    self->_myVTKPolyData->SetLines(lines);
    self->_myVTKPolyData->GetPointData()->SetScalars(colors);
    [self.lock unlock];

-dispatch_semaphore 利用信号量控制(dispatch_semaphore_t sema = dispatch_semaphore_create(1);)

- (void)initTicketStatusSafe{
    semaphoreLock = dispatch_semaphore_create(1); //一次只有一个
    self.ticketSurplusCount = 50;
    //代表售卖窗口1
    dispatch_queue_t queue1 = dispatch_queue_create("peng1", DISPATCH_QUEUE_SERIAL);
    
    //代表售卖窗口2
    dispatch_queue_t queue2 = dispatch_queue_create("peng2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
    
    
}

- (void)saleTicketSafe {
    while (1) {
        //如果还有票,继续售卖
        //        @synchronized (self) {
        
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount --;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
            [NSThread sleepForTimeInterval:0.5];
        }
        //如果已卖完,关闭售票窗口
        else {
            NSLog(@"所有火车票均已售完");
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        //        }
        
        dispatch_semaphore_signal(semaphoreLock);
    }
}

既然讲到了GCD信号量,就直接切入GCD多线程,GCD还是非常实用

GCD

  • GCD优点
    1、GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    2、GCD会自动利用CPU内核(特别现在都是多核CPU,可以发挥淋漓尽致)
  • GCD 任务和队列
    1. 任务
      任务具体干些什么事,执行任务有两种方式:同步执行(sync)和异步执行(async)
    • 同步执行(sync)
      同步执行只能在当前线程中执行任务,不具备开启线程的能力,同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行
    • 异步执行(async)
      异步执行具备开启新线程的能力,可以在新线程中执行任务,异步执行添加到队列中,它不需要等待,可以继续执行任务。
    1. 队列 在GCD中有两个队列 串型队列和并发队列,既然是队列就都符合FIFO原则,两者的区别是:执行的顺序不同和开启的线程不同
    • 串型队列(Serial Dispatch Queue)
      每次只有一个任务执行。所有任务在同一个线程中执行(如果是同步不会开启线程,如果是异步有且只能开启一个新线程,队列里面同时有异步和同步例外,有异步就会有且只能再开一个线程),满足FIFO原则,执行一个任务完毕才执行下一个任务;

    • 并行队列(Concurrent Dispatch Queue)
      可以让多个任务同时执行(可以开启多个线程)并发队列的并发功能只有在异步async任务下才有用;
      举个栗子,你要打电话给女朋友A 和女朋友B。
      同步执行就是,你打电话给女朋友A的时候,就不能同时打电话给女朋友B,等到给女朋友A打完电话,才可以打电话给女朋友B。而且只能用当前的电话(不具备开启新线程的能力)
      而异步执行就是,你打电话给女朋友A的时候,不等和女朋友A通话结束,还能直接给女朋友B打电话,不用等和女朋友A通话结束之后再打(不用等待任务执行结束)。除了当前电话,你还可以使用其他电话(具备开启新线程的能力);

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表。采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

GCD的使用步骤

GCD使用步骤只有两步
1、创建一个队列(串型队列或者并发队列)
备注:主队列是一个特殊的串队列

    //串型队列
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    //并型队列
    dispatch_queue_t queueC = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

2、将任务追加到任务的队列 中去,然后系统就会根据任务类型执行任务(同步执行或者异步执行)

    dispatch_async(queue, ^{
        // 异步追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 异步追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_sync(queue, ^{
        // 同步追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

组合

image.png

注意,在主队列中 异步是不会开启新线程执行

dispatch_queue_t queue = dispatch_get_main_queue();
    //并型队列
    //dispatch_queue_t queueC = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    
    dispatch_async(queue, ^{
        // 异步追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 异步追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 异步追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    NSLog(@"syncConcurrent---end");
//打印
currentThread---<NSThread: 0x600003b2a940>{number = 1, name = main}
syncConcurrent---begin
syncConcurrent---end
1---<NSThread: 0x600003b2a940>{number = 1, name = main}
1---<NSThread: 0x600003b2a940>{number = 1, name = main}
2---<NSThread: 0x600003b2a940>{number = 1, name = main}
2---<NSThread: 0x600003b2a940>{number = 1, name = main}
3---<NSThread: 0x600003b2a940>{number = 1, name = main}
3---<NSThread: 0x600003b2a940>{number = 1, name = main}

GCD 线程间的通信

- (void)GCDCommunication{
    //获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
       //异步追加任务
        for (int i = 0; i < 2; i++){
            [NSThread sleepForTimeInterval:2];
            //打印当前线程
            NSLog(@"1-----%@",[NSThread currentThread]);
        }
        
        //回到主线程
        dispatch_async(mainQueue, ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2----%@",[NSThread currentThread]);
        });
    });
}
//打印
1-----<NSThread: 0x6000021bea80>{number = 3, name = (null)}
1-----<NSThread: 0x6000021bea80>{number = 3, name = (null)}
 2----<NSThread: 0x6000021b1400>{number = 1, name = main}

GCD的其它方法

GCD栅栏方法: dispatch_barrier_async

使用场景:有时我们需要异步执行两组任务,而且第一组的任务执行完之后,才能开始执行第二组任务,这样我们就需要一个一个相当于“栅栏”一样的一个方法将两组异步执行任务分割气啦,这个时候需要用到dispatch_barrier_async方法在两个任务组将形成栅栏

- (void)barrier{
    
    dispatch_queue_t queue = dispatch_queue_create("peng", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //异步追加任务1
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1----%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //异步追加任务2
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2----%@",[NSThread currentThread]);
        }
    });
    
    //dispatch_barrier_sync 也可以 只是会切换到调用线程  主线程调用就是 main
    
    dispatch_barrier_async(queue, ^{
        //追加任务barrier
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"barrier----%@",[NSThread currentThread]);
        }
    });
    
    
    dispatch_async(queue, ^{
        //异步追加任务3
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3----%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //异步追加任务4
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4----%@",[NSThread currentThread]);
        }
    });
}
2019-07-17 15:03:33.907484+0800 YBStockChartView[1988:60887] 1----<NSThread: 0x600001476380>{number = 4, name = (null)}
2019-07-17 15:03:33.907484+0800 YBStockChartView[1988:60889] 2----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:35.910472+0800 YBStockChartView[1988:60887] 1----<NSThread: 0x600001476380>{number = 4, name = (null)}
2019-07-17 15:03:35.910479+0800 YBStockChartView[1988:60889] 2----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:37.914415+0800 YBStockChartView[1988:60889] barrier----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:39.915950+0800 YBStockChartView[1988:60889] barrier----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:41.920864+0800 YBStockChartView[1988:60889] 3----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:41.920869+0800 YBStockChartView[1988:60887] 4----<NSThread: 0x600001476380>{number = 4, name = (null)}
2019-07-17 15:03:43.923980+0800 YBStockChartView[1988:60889] 3----<NSThread: 0x600001400b80>{number = 3, name = (null)}
2019-07-17 15:03:43.924004+0800 YBStockChartView[1988:60887] 4----<NSThread: 0x600001476380>{number = 4, name = (null)}

GCD延时操作

- (void)after{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        //2秒后异步追加到主队列执行
        NSLog(@"after-----%@",[NSThread currentThread]);
    });
}

GCD 只有执行一次性:dispatch_once

在整个程序运行过程中只执行一次

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //只执行一次
        NSLog(@"只执行一次");
    });
}

GCD快速迭代方法:dispatch_apply

与FOR 循环区别,for 循环 顺序遍历 ,而 dispatch_apply 不是

- (void)dispatch_apply{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply-- begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd----%@",index,[NSThread currentThread]);
    });
    
    NSLog(@"apply---end");
}
2019-07-17 15:25:12.435116+0800 YBStockChartView[2352:77865] apply-- begin
2019-07-17 15:25:12.435942+0800 YBStockChartView[2352:77901] 4----<NSThread: 0x6000017a8980>{number = 5, name = (null)}
2019-07-17 15:25:12.435946+0800 YBStockChartView[2352:77900] 2----<NSThread: 0x6000017bfe80>{number = 4, name = (null)}
2019-07-17 15:25:12.435948+0800 YBStockChartView[2352:77904] 1----<NSThread: 0x6000017c9b00>{number = 3, name = (null)}
2019-07-17 15:25:12.435948+0800 YBStockChartView[2352:77865] 0----<NSThread: 0x6000017f1400>{number = 1, name = main}
2019-07-17 15:25:12.435955+0800 YBStockChartView[2352:77903] 3----<NSThread: 0x6000017c9a40>{number = 6, name = (null)}
2019-07-17 15:25:12.435965+0800 YBStockChartView[2352:77902] 5----<NSThread: 0x6000017a0b40>{number = 7, name = (null)}
2019-07-17 15:25:12.436115+0800 YBStockChartView[2352:77865] apply---end

GCD 队列组 dispatch_group

- (void)dispatch_group{
    dispatch_group_t gruop = dispatch_group_create();
    
    
    dispatch_group_async(gruop, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //追加耗时任务
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1====%@",[NSThread currentThread]);
        }
    });
    
    dispatch_group_async(gruop, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //追加耗时任务
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2====%@",[NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(gruop, dispatch_get_main_queue(), ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3====%@",[NSThread currentThread]);
        }
        NSLog(@"group--end");
    });
}

2019-07-17 15:39:32.459319+0800 YBStockChartView[2552:87029] 2====<NSThread: 0x600002ee4780>{number = 3, name = (null)}
2019-07-17 15:39:32.459320+0800 YBStockChartView[2552:87028] 1====<NSThread: 0x600002ed8200>{number = 4, name = (null)}
2019-07-17 15:39:34.464453+0800 YBStockChartView[2552:87028] 1====<NSThread: 0x600002ed8200>{number = 4, name = (null)}
2019-07-17 15:39:34.464480+0800 YBStockChartView[2552:87029] 2====<NSThread: 0x600002ee4780>{number = 3, name = (null)}
2019-07-17 15:39:36.465933+0800 YBStockChartView[2552:86994] 3====<NSThread: 0x600002eba940>{number = 1, name = main}
2019-07-17 15:39:38.466517+0800 YBStockChartView[2552:86994] 3====<NSThread: 0x600002eba940>{number = 1, name = main}
2019-07-17 15:39:38.466685+0800 YBStockChartView[2552:86994] group--end

GCD dispatch_group_wait

暂停当前线程(阻塞),等待指定的group中的任务执行完成后,才会往下执行

- (void)groupWait{
    NSLog(@"currentThread====%@",[NSThread currentThread]);
    NSLog(@"group--begin");
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       //追加
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1=====%@",[NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //追加
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2=====%@",[NSThread currentThread]);
        }
    });
    
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

GCD dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 意思是一个任务追加到group 执行一次,相当于group中的未执行完毕任务书+1
  • dispatch_group_leave 意思是一个任务执行完毕离开group ,执行一次,相当于group中的未执行完毕任务书-1
  • 当group中未执行完毕任务数未0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify的任务中去
- (void)gruopEnterAndLeave{
    NSLog(@"currentThread===%@",[NSThread currentThread]);
    NSLog(@"group--begin");
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1==-%@",[NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2==-%@",[NSThread currentThread]);
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        for (int i = 0; i < 2; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3==-%@",[NSThread currentThread]);
        }
        NSLog(@"group====end");
    });
    
  
}
2019-07-17 16:07:42.775761+0800 YBStockChartView[2954:104854] currentThread===<NSThread: 0x600003cb6940>{number = 1, name = main}
2019-07-17 16:07:42.775845+0800 YBStockChartView[2954:104854] group--begin
2019-07-17 16:07:44.778363+0800 YBStockChartView[2954:104888] 2==-<NSThread: 0x600003cfa480>{number = 3, name = (null)}
2019-07-17 16:07:44.778373+0800 YBStockChartView[2954:104887] 1==-<NSThread: 0x600003cdcb00>{number = 4, name = (null)}
2019-07-17 16:07:46.783729+0800 YBStockChartView[2954:104887] 1==-<NSThread: 0x600003cdcb00>{number = 4, name = (null)}
2019-07-17 16:07:46.783748+0800 YBStockChartView[2954:104888] 2==-<NSThread: 0x600003cfa480>{number = 3, name = (null)}
2019-07-17 16:07:48.784472+0800 YBStockChartView[2954:104854] 3==-<NSThread: 0x600003cb6940>{number = 1, name = main}
2019-07-17 16:07:50.785241+0800 YBStockChartView[2954:104854] 3==-<NSThread: 0x600003cb6940>{number = 1, name = main}
2019-07-17 16:07:50.785515+0800 YBStockChartView[2954:104854] group====end

GCD信号量 dispatch_semaphone

将异步线程转换为同步线程 还可以利用信号量保证线程安全

- (void)semaphoneSync{
    NSLog(@"currentThread===%@",[NSThread currentThread]);
    NSLog(@"semaphone--begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_semaphore_t semaphone = dispatch_semaphore_create(0);
//创建一个 Semaphore 并初始化信号的总量
    
    __block int number = 0;
    dispatch_async(queue, ^{
       //追加任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1======%@",[NSThread currentThread]);
        
        number =100;
        //发送一个信号,让信号总量加 1
        dispatch_semaphore_signal(semaphone);
        
    });
    //dispatch_semaphore_signal(semaphone);
//可以使总信号量减 1,信号总量小于 1 时就会一直等待(阻塞所在线程),否则就可以正常执行
    dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER);
    
    NSLog(@"semaphore---end,num == %d",number);
}

2019-07-17 16:33:22.483822+0800 YBStockChartView[3373:125495] currentThread===<NSThread: 0x6000029fe940>{number = 1, name = main}
2019-07-17 16:33:22.483902+0800 YBStockChartView[3373:125495] semaphone--begin
2019-07-17 16:33:24.484306+0800 YBStockChartView[3373:125556] 1======<NSThread: 0x6000029a6a00>{number = 3, name = (null)}
2019-07-17 16:33:24.484620+0800 YBStockChartView[3373:125495] semaphore---end,num == 100

NSOperation

是苹果提供给开发者一套多线程解决方案,实际上基于GCD更高一层的封装,完全面向对象。但比GCD 更简单易用、代码可读性也更高
1、可以使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
2、设定操作执行的优先级
3、可以很方便的取消一个操作的执行

NSOperation、NSOperationQueue 操作和操作队列,

可以结合GCD的任务和任务队列理解

  • 操作 operation
  • 操作队列 (Operation Queues)
    操作队列可以通过设置最大并发操作数(maxConcurrentOperationCount来控制并发、串行
    NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。
    主队列运行在主线程上,而自定义队列在后台执行

NSOperation、NSOperationQueue 使用步骤

NSOperation需要配合NSOperationQueue来实现多线程。默认情况下
NSOperation 单独使用时系统同步执行操作,配合NSOperationQueue 能更好的实现异步执行

分为三步
1、创建操作:先将需要执行的操作封装到一个NSOperation对象中
2、创建队列:创建NSOperationQueue 对象
3、将操作加入到队列中:将NSOperation对象添加到NSOperatinoQueue对象中

然后系统就会自动将NSOperationQueue 中的NSOperation 取出来,在新线程中执行操作。

NSOperation 和 NSOperationQueue 基本使用

创建

NSOperation是个抽象类,需要使用他的子类来封装操作,

  • 1、使用子类NSInvocationOperation
  • 2、使用子类NSBlockOperation
  • 3、自定义继承自NSOperation的子类,实现内部方法来封装操作。
使用子类NSInvocationOperation
- (void)useInvocationOperation{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    [op start];
}

-(void)task1{
    for (int i = 0; i < 5; i++) {
        NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
    }
}
2019-07-17 17:05:52.389871+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
2019-07-17 17:05:52.389995+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
2019-07-17 17:05:52.390056+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
2019-07-17 17:05:52.390132+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
2019-07-17 17:05:52.390232+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}

上面打印可以看出,在没有使用NSOperationQueue,在主线程中单独使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程

使用NSBlockOperation
- (void)useBlockOperation{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op start];
}

并没有开新线程,但是 NSBlockOperation 的addExecutionBlock 可以在开启新线程

- (void)useBlockOperation{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"2---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"3---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"4---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op addExecutionBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"5---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    
    [op start];
}
2019-07-17 17:13:33.847155+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
2019-07-17 17:13:33.847161+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
2019-07-17 17:13:33.847160+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
2019-07-17 17:13:33.847160+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
2019-07-17 17:13:33.847162+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
2019-07-17 17:13:33.847288+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
2019-07-17 17:13:33.847289+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
2019-07-17 17:13:33.847289+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
2019-07-17 17:13:33.847290+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
2019-07-17 17:13:33.847296+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
2019-07-17 17:13:33.847347+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
2019-07-17 17:13:33.847372+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
2019-07-17 17:13:33.847430+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
2019-07-17 17:13:33.847596+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
2019-07-17 17:13:33.847715+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
2019-07-17 17:13:33.847797+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
2019-07-17 17:13:33.847913+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
2019-07-17 17:13:33.847990+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
2019-07-17 17:13:33.848107+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
2019-07-17 17:13:33.848199+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}

一般情况下,如果一个NSBlockOperation对象封装了多个操作,是否开启新线程,取决操作的个数,操作个数多就会开启多线程。具体个数有系统决定

使用自定义继承自NSOperatrion的子类 重写main方法
//NSOperation
- (void)main{
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"PengOperation====%@", [NSThread currentThread]);
            
        }
    }
}

创建队列 NSOperationQueue

  • 主队列
    凡是添加到主队列中的操作,都会放到主线程中执行(注意:不包括操作使用addExecutionBlock:添加的额外操作)
 NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 非主队列
    会自动放到子线程中执行
    同时包含串性和并发
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入到队列中

- (void)addOprationToQueue{
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //创建操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"op3---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"other==op3---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    
    [queue addOperation:op];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

- (void)addOperationWithBlock:(void (^)(void))block;

无需先创建操作,在block中添加操作,直接将包含block加入到队列中

- (void)addOperationWithBlockToQueue{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1----%@",[NSThread currentThread]);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2----%@",[NSThread currentThread]);
        }
    }];
    
    
}
2019-07-17 17:39:52.279263+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
2019-07-17 17:39:52.279275+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
2019-07-17 17:39:54.279991+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
2019-07-17 17:39:54.279997+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
2019-07-17 17:39:56.283783+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
2019-07-17 17:39:56.283804+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
2019-07-17 17:39:58.285930+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
2019-07-17 17:39:58.285930+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}

NSOperatrion控制串行、并发执行---maxConcurrentOperationCount

注意:这里maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数,而且一个操作也并非只能在一个线程中运行
默认情况maxConcurrentOperationCount = -1

  • maxConcurrentOperationCount 为1时,队列为串行队列
  • maxConcurrentOperationCount 大于1时,队列为并发队列
- (void)addOperationWithBlockToQueue{
    NSLog(@"currentThread----%@",[NSThread currentThread]);
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //queue.maxConcurrentOperationCount = 1; //串行队列,切记并非只有一个线程,刻意有多个线程,但并发数最大为1
    queue.maxConcurrentOperationCount = 6;
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1----%@",[NSThread currentThread]);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2----%@",[NSThread currentThread]);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 4; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3----%@",[NSThread currentThread]);
        }
    }];
    
     NSLog(@"currentThread---end----%@",[NSThread currentThread]);
}

NSOperatrion 操作依赖

- (void)addDependency{
    
    //1/创建队列
    NSLog(@"currentThread----%@",[NSThread currentThread]);
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //queue.maxConcurrentOperationCount = 1; //串行队列,切记并非只有一个线程,刻意有多个线程,但并发数最大为1
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"op3---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"op4---%@",[NSThread currentThread]); //打印当前线程
        }
    }];
    
    
    [op3 addDependency:op4];
    
    [queue addOperation:op3];
    [queue addOperation:op4];
    
    NSLog(@"currentThread---end----%@",[NSThread currentThread]);
    
}
2019-07-17 18:04:28.581372+0800 YBStockChartView[4808:189453] currentThread----<NSThread: 0x600002c42940>{number = 1, name = main}
2019-07-17 18:04:28.581706+0800 YBStockChartView[4808:189453] currentThread---end----<NSThread: 0x600002c42940>{number = 1, name = main}
2019-07-17 18:04:28.581756+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
2019-07-17 18:04:28.581834+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
2019-07-17 18:04:28.581897+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
2019-07-17 18:04:28.581978+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
2019-07-17 18:04:28.582085+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
2019-07-17 18:04:28.582262+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
2019-07-17 18:04:28.582342+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
2019-07-17 18:04:28.593061+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
2019-07-17 18:04:28.593160+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
2019-07-17 18:04:28.593247+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}

NSOperation 、NSOperationQueue线程间的通信

- (void)communication{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"1=====%@",[NSThread currentThread]);
        }
        
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"主线程刷新UI%@",[NSThread currentThread]);
        }];
    }];
}

2019-07-17 20:57:03.534990+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
2019-07-17 20:57:03.535153+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
2019-07-17 20:57:03.535227+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
2019-07-17 20:57:03.537354+0800 YBStockChartView[5714:238126] 主线程刷新UI<NSThread: 0x6000008aa940>{number = 1, name = main}

NSOperation 常用属性和方法

  • 取消操作方法
    -(void)cancel;
  • 判断状态方法
    -(BOOL)isFinished;
    -(BOOL)isCancelled;
    -(BOOL)isExecuting;
    -(BOOL)isReady;
  • 操作同步
    -(void)waitupntifinished 阻塞当前线程,知道该操作结束
    -(void)addDependency:(NSOperation)op 添加依赖
    -(void)removeDependency:(NSOperation
    )op 移除依赖

NSOperationQueue 常用属性和方法

  • 1、取消/暂停/恢复操作
    -(void)cancelAllOperations;可以取消队列的所有操作;
    -(BOOL)isSuspended;判断队列是否处于暂停状态
  • (void)setSuspended:(BOOL)b;可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
  • 2、操作同步
  • (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕
  • 3、添加/获取操作
  • (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象
  • (void)addOperations:(NSArray *)ops waitUntilFinished:
    -(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
  • (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
  • (NSUInteger)operationCount; 当前队列中的操作数
  • 4、获取队列
  • (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil
  • (id)mainQueue; 获取主队列

注意:

这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作

相关文章

网友评论

      本文标题:iOS 多线程

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