iOS『多线程』使用总结(NSThread、NSOperation、GCD)

iOS多线程使用总结(NSThread,GCD,NSOperation)

前文

本文主要用于学习记录,概念问题简单说一下,主要写一些使用案例。

iOS 常用的多线程方案有3种.

  • NSThread
  • GCD
  • NSOperation

其中用的最多的就是GCD了,其实还有一种Pthreads,但是实在不常用,所以不太了解,就不说了。

文章中主要使用Objective-C语言,示例代码会用Swift翻译过来,如有错误请指出。

Swift打印的时候最好使用NSLog,这样可以看到打印时间,以及线程信息。

1. NSThread

NSThread面向对象,比较直观,但是需要手动管理生命周期,虽然不常用,但是有些方法还是比较好用的,比如[NSThread currentThread]可以获取当前线程,用来调试给常好用。后面我们会经常用到。

  • 创建+启动线程
Objective-C:
   //套路:不要先直接alloc init 先看一下头文件 类方法 如果没有合适的类方法 才 init
   // 1. 创建线程
    - (void)createThread{
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task) object:nil];
        //开启线程
        [thread start];
    }
    // 新线程调用方法,里边为需要执行的任务
    - (void)task {
         NSLog(@"%@", [NSThread currentThread]);
    }

//-------------------------------------------------
Swift:
    // 1. 创建线程
    func createThread() {
        let thread = Thread(target: self, selector: #selector(task), object: nil)
        //启动
        thread.start()
    }
    // 新线程调用方法,里边为需要执行的任务
    @objc func task()  {
        NSLog("%@", Thread.current)
    }
  • 创建并自动启动
Objective-C:
    //类方法 -- 简单 不能拿到对象  没有机会进行更加详细的设置
    [NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
    - (void)task {
         NSLog(@"%@", [NSThread currentThread]);
    }

//-----------------------------------------------------
Swift:
//Swift写法
    Thread.detachNewThreadSelector(#selector(task), toTarget: self, with: nil)
    @objc func task()  {
         NSLog("%@", Thread.current)
    }
  • 隐式创建并启动线程
Objective-C:
    //隐式创建
    [self performSelectorInBackground:@selector(task) withObject:nil];
//---------------------------------------------------------------------
Swift:
    performSelector(inBackground: #selector(task), with: nil)
  • 其他方法(只列OC方法)
//取消线程
- (void)cancel;

//启动线程
- (void)start;

//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
// 判断是否为主线程(对象方法)
- (BOOL)isMainThread;
// 判断是否为主线程(类方法)
+ (BOOL)isMainThread;    

//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
  • 线程之间的通信
    例如:子线程下载任务,完成后回到主线程刷新UI
// 在主线程上执行操作
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
  // equivalent to the first method with kCFRunLoopCommonModes

// 在指定线程上执行操作
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

// 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

示例DEMO:(Objective-C)

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //创建线程下载图片    
         NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(getIMG) object:nil];
        [thread start];
    }
    - (void)getIMG{
        [NSThread sleepForTimeInterval:5]; //模拟耗时任务
    
        NSString *strAddress =  @"http://pic41.nipic.com/20140501/2531170_162158900000_2.jpg";
        NSURL *url = [NSURL URLWithString:strAddress];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        // 回到主线程刷新界面    
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
        NSLog(@"over");
    }

    //回到主线程刷新UI
    - (void)updateUI:(UIImage *)image{
        _imageView.image = image;
        NSLog(@"current thread -- %@", [NSThread currentThread]);
    }

示例DEMO:(Swift)

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let thread = Thread(target: self, selector: #selector(task), object: nil)
        thread.start()
    }
    
    func getIMG() {
        Thread.sleep(forTimeInterval: 5)  //模拟耗时任务
        let strAddress = "http://pic41.nipic.com/20140501/2531170_162158900000_2.jpg"
        let url = URL.init(string: strAddress)            
        do {
            let data = try Data(contentsOf: url!)
            let image = UIImage(data: data)
            performSelector(onMainThread: #selector(updateUI), with: image, waitUntilDone: false)
           // 回到主线程刷新界面
            NSLog("over")
        } catch { }        
    }
    @objc func updateUI(image: UIImage) {
        NSLog("current thread -- %@", Thread.current);
    }

2. GCD

  1. GCD简介:略
  2. GCD好处:
    GCD 可用于多核的并行运算,最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),非常方便.
  3. GCD底层使用的是C语言.但是经过苹果封装非常好用,老少咸宜.

2.1任务和队列

  • 任务: 就是执行操作的意思,放在代码里,就是执行一段代码。在GCD中有一个Block,将要执行的代码(即任务)放进去即可。任务有两种执行方式:同步执行异步执行,他们主要的区别:是否开启新线程。

    同步执行(sync):

    • 不具备开线程的能力
    • 同步添加到指定的队列中去,在添加的任务执行完成之前会一直等待,等待前边的任务执行完成之后才继续执行。

    异步执行(async):

    • 具备开线程的能力
    • 异步添加任务到执行队列中,它不会做任何等待,可以继续执行任务
  • 队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。

    串行队列

    • 每次只有一个任务被执行,任务是一个接着一个地执行。(只会开启一个线程)

    并发队列

    • 可以让多个任务并发(同时)执行。(可以开启多个线程,同时执行任务)

2.2队列的创建以及获取

队列的创建:

Objective-C:
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_CONCURRENT);

//---------------------------------------------------
 // 串行队列的创建方法
 let queue1 = dispatch_queue_serial_t(label: "gcd.name")
 let queue2 = DispatchQueue(label: "gcd.name", attributes: DispatchQueue.Attributes.concurrent)
      
 // 并发队列的创建方法
 let queue3 = dispatch_queue_concurrent_t(label: "gcd.name")
 let queue4 = DispatchQueue(label: "gcd.name")

主队列是一个比较特殊的串行队列:

Objective-C:
    // 主队列获取方法
    dispatch_queue_t queue = dispatch_get_main_queue();
//------------------------------------------------
Swift:
    //主队列
    let mainQueue = DispatchQueue.main;

GCD 还提供了一个全局并发队列:

Objective-C:
   // 全局并发队列的获取方法
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//--------------------------------------------------------
Swift:
// 全局并发队列的获取方法
   let globalQueue = DispatchQueue.global()

2.3任务的创建

同步执行任务的创建方法: dispatch_sync
异步执行任务创建方法 :dispatch_async。

Objective-C:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

//------------------------------------------------
Swift

// 同步执行任务创建方法 
 queue.sync {
      // 这里放同步执行任务代码
 }
// 异步执行任务创建方法
 queue2.async {
      // 这里放异步执行任务代码
 }

2.4任务和队列不同的组合方式

GCD提供了两种任务执行方式(同步执行和异步执行),提供了两种队列:(串行队列和并行队列),在加上主队列,故而就有多种不同的组合方式。

1.同步执行 + 串行队列
2.同步执行 + 并发队列
3.异步执行 + 串行队列
4.异步执行 + 并发队列
5.同步执行 + 主队列
6.异步执行 + 主队列

区别:

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行队列 没有开启新线程,串行执行任务 死锁,不执行
异步(async) 开启新线程,并发执行任务 开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

2.5 GCD的基本使用(6种组合)

每种情况,我们简单介绍一下,直接代码+打印结果+结论,展示出来
这里的代码以及结论参考了行走少年郎的一篇多线程文章,写的很好,比我的好,所以就把我原来的替换了。大家可以去看他写的。

2.5.1 同步执行 + 串行队列

  • 不会开启新线程,在当前线程执行任务,串行执行任务。

Objective-C:

    //打印当前线程
    NSLog(@"当前线程 --- %@",[NSThread currentThread]);
    NSLog(@"同步-串行  ---  begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_SERIAL);
    
    //执行任务
    dispatch_sync(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"同步-串行  ---  end");

Swift:

    //打印当前线程
    NSLog("当前线程 --- %@",Thread.current);
    NSLog("同步-串行  ---  begin");
    let queue = dispatch_queue_serial_t(label: "gcd.name")
    
    //执行任务
    queue.sync {
        //追加任务 1
        Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
        NSLog("1 --- %@",Thread.current); //打印当前线程
    }
    queue.sync {
        //追加任务 2
        Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
        NSLog("2 --- %@",Thread.current); //打印当前线程
    }
    queue.sync {
        //追加任务 3
        Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
        NSLog("3 --- %@",Thread.current); //打印当前线程
    }
    queue.sync {
        //追加任务 4
        Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
        NSLog("4 --- %@",Thread.current); //打印当前线程
    }
    NSLog("同步-串行  ---  end");

打印结果:

2020-08-18 15:09:05.019655+0800 KX_GCD_Demo[27130:517117] 当前线程 --- <NSThread: 0x600002134f00>{number = 1, name = main}
2020-08-18 15:09:05.019782+0800 KX_GCD_Demo[27130:517117] 同步-串行  ---  begin
2020-08-18 15:09:07.021050+0800 KX_GCD_Demo[27130:517117] 1 --- <NSThread: 0x600002134f00>{number = 1, name = main}
2020-08-18 15:09:09.021304+0800 KX_GCD_Demo[27130:517117] 2 --- <NSThread: 0x600002134f00>{number = 1, name = main}
2020-08-18 15:09:11.021541+0800 KX_GCD_Demo[27130:517117] 3 --- <NSThread: 0x600002134f00>{number = 1, name = main}
2020-08-18 15:09:13.022790+0800 KX_GCD_Demo[27130:517117] 4 --- <NSThread: 0x600002134f00>{number = 1, name = main}
2020-08-18 15:09:13.022947+0800 KX_GCD_Demo[27130:517117] 同步-串行  ---  end

结论:

  1. 所有任务都在当前线程即主线程中执行,没有开启新的线程(同步执行不具备开启线程的能力)
  2. 所有的任务都在beginend之间执行,说明同步执行任务需要等待队列中的任务执行结束
  3. 任务是按照1,2,3,4顺序来执行的,说明串行队列每次只能执行一个任务,需要一个接着一个按顺序执行

2.5.2 同步执行 + 并发队列

  • 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
    Objective-C:
    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    
    NSLog(@"同步-并发 --- begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_CONCURRENT);
    
    //执行任务
    dispatch_sync(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"同步-并发 --- end");
   

Swift:

private func text2() {
        //打印当前线程
        NSLog("当前线程 --- %@",Thread.current);    //打印当前线程
        NSLog("同步-并发 --- begin");
        let queue = dispatch_queue_concurrent_t(label: "gcd.name")
        
        //执行任务
        queue.sync {
            //追加任务 1
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("1 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 2
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("2 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 3
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("3 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 4
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("4 --- %@",Thread.current); //打印当前线程
        }
        NSLog("同步-并发  ---  end");
    }

打印结果:

2020-08-18 15:31:20.826512+0800 KX_GCD_Demo[27676:531403] 当前线程 --- <NSThread: 0x600002db0d40>{number = 1, name = main}
2020-08-18 15:31:20.826660+0800 KX_GCD_Demo[27676:531403] 同步-并发 --- begin
2020-08-18 15:31:22.828083+0800 KX_GCD_Demo[27676:531403] 1 --- <NSThread: 0x600002db0d40>{number = 1, name = main}
2020-08-18 15:31:24.829041+0800 KX_GCD_Demo[27676:531403] 2 --- <NSThread: 0x600002db0d40>{number = 1, name = main}
2020-08-18 15:31:26.830545+0800 KX_GCD_Demo[27676:531403] 3 --- <NSThread: 0x600002db0d40>{number = 1, name = main}
2020-08-18 15:31:28.832078+0800 KX_GCD_Demo[27676:531403] 4 --- <NSThread: 0x600002db0d40>{number = 1, name = main}
2020-08-18 15:31:28.832398+0800 KX_GCD_Demo[27676:531403] 同步-并发 --- end

结论:

  1. 所有任务都是在当前线程即主线程中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)
  2. 所有任务都在begin和end之间执行的(同步执行需要等待队列中,前边的任务执行完毕,再继续执行)
  3. 任务是按顺序执行.这里特殊说明一下:虽然并发队列可以开启多个线程,同时执行多个任务.但是因为本身不具备开线程的能力,只有当前这一个线程(同步执行不具备开线程能力).
    所以不能并发执行,只能顺序执行.而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续执行.
    所以任务只能一个接着一个顺序执行,不能同时被执行.

2.5.3 异步执行 + 串行队列

  • 会开启一条新线程,串行执行,执行完一个任务,再执行下一个任务
    Objective-C:
    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    
    NSLog(@"异步-串行 --- begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_SERIAL);
    
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"异步-串行 --- end");

Swift:

     NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    NSLog(@"异步-串行 --- begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_SERIAL);
    
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"异步-串行 --- end");

打印结果:

2020-08-18 15:58:20.620164+0800 KX_GCD_Demo[27846:545723] 当前线程 --- <NSThread: 0x60000269c240>{number = 1, name = main}
2020-08-18 15:58:20.620322+0800 KX_GCD_Demo[27846:545723] 异步-串行 --- begin
2020-08-18 15:58:20.620464+0800 KX_GCD_Demo[27846:545723] 异步-串行 --- end
2020-08-18 15:58:22.621062+0800 KX_GCD_Demo[27846:545800] 1 --- <NSThread: 0x6000026c4180>{number = 7, name = (null)}
2020-08-18 15:58:24.625413+0800 KX_GCD_Demo[27846:545800] 2 --- <NSThread: 0x6000026c4180>{number = 7, name = (null)}
2020-08-18 15:58:26.626524+0800 KX_GCD_Demo[27846:545800] 3 --- <NSThread: 0x6000026c4180>{number = 7, name = (null)}
2020-08-18 15:58:28.629949+0800 KX_GCD_Demo[27846:545800] 4 --- <NSThread: 0x6000026c4180>{number = 7, name = (null)}

结论:

  1. 开启了一条新的线程(异步执行具备开线程的能力,串行队列只能开启一个线程)
  2. 所有任务都是在begin和end之后才开始执行(异步执行不会做任何等待,可以开线程继续执行任务)
  3. 任务是顺序执行的(串行队列每次只能执行一个任务,任务是按顺序执行的)

2.5.4 异步执行 + 并发队列

  • 可以开启多个线程,任务交替(同时)执行。
    OBJECTIVE-C:
    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    
    NSLog(@"异步-并发 --- begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_CONCURRENT);
    
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"异步-并发 --- end");

SWIFT:

     NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    NSLog(@"异步-并发 --- begin");
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_CONCURRENT);
    
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"异步-并发 --- end");

打印结果:

2020-08-18 16:04:42.114241+0800 KX_GCD_Demo[28014:554532] 当前线程 --- <NSThread: 0x6000024d0ec0>{number = 1, name = main}
2020-08-18 16:04:42.114406+0800 KX_GCD_Demo[28014:554532] 异步-并发 --- begin
2020-08-18 16:04:42.114557+0800 KX_GCD_Demo[28014:554532] 异步-并发 --- end
2020-08-18 16:04:44.116285+0800 KX_GCD_Demo[28014:554707] 4 --- <NSThread: 0x600002480500>{number = 4, name = (null)}
2020-08-18 16:04:44.116285+0800 KX_GCD_Demo[28014:554706] 2 --- <NSThread: 0x600002495800>{number = 5, name = (null)}
2020-08-18 16:04:44.116285+0800 KX_GCD_Demo[28014:554705] 1 --- <NSThread: 0x600002487880>{number = 8, name = (null)}
2020-08-18 16:04:44.116360+0800 KX_GCD_Demo[28014:554708] 3 --- <NSThread: 0x60000249cd40>{number = 6, name = (null)}

结论:

  1. 除了当前线程(主线程),系统又开启4个新的线程,并且任务是交替执行的(同时执行)。说明异步执行具备开线程的能力,并且并发队列可以开启多个线程,同时执行多个任务。
  2. 所有任务都是在begin和end之后执行的,说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务的。

2.5.5 同步执行 + 主队列(在主线程调用)

  • 在主线程调用结果:死锁
  • 在子线程调用结果:不会开启新线程,执行完一个任务,再执行下一个任务

今天只演示在主线程调用:
OBJECTIVE-C:

- (void)text5{

    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    
    NSLog(@"同步执行-主队列 --- begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //执行任务
    dispatch_sync(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_sync(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"同步执行-主队列 --- end");
 }

Swift:

 private func text5() {
        //主线程是一个特殊的线程.相对于主线程其他线程都叫做子线程.
        NSLog("当前线程 --- %@",Thread.current);//打印当前线程
        NSLog("同步执行-主队列 --- begin");
        
        let queue = DispatchQueue.main
        //执行任务
        queue.sync {
            //追加任务 1
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("1 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 2
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("2 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 3
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("3 --- %@",Thread.current); //打印当前线程
        }
        queue.sync {
            //追加任务 4
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("4 --- %@",Thread.current); //打印当前线程
        }
        NSLog("同步执行-主队列 --- end");        
    }

打印结果:

2020-08-18 16:27:17.592790+0800 KX_GCD_Demo[28140:565766] 当前线程 --- <NSThread: 0x600003954d40>{number = 1, name = main}
2020-08-18 16:27:17.592934+0800 KX_GCD_Demo[28140:565766] 同步执行-主队列 --- begin
(lldb)

结论:
同步执行 + 主队列
结论:

  • 追加到主线程的任务都不再执行,而是直接报崩溃了.
    这是因为我们在主线程中执行方法text5,相当于把text5任务放到了主线程的队列中,而同步执行会等待当前队列中的任务执行完毕,才会继续执行.那么我们把任务1追加到主队列中,任务1就在等待主线程把处理完text5任务,而text5任务需要等待任务1执行完毕才能接着执行。

  • 这种情况下,text5任务1都在等对方执行完毕。就这样大家相互等待,所以就卡主了。所以我们的任务就执行不了,
    后面的代码也一直没有执行.

面试经常会问这个问题。

2.5.6 异步执行 + 主队列

  • 只在主线程中执行任务,执行完一个任务,再执行下一个任务。
    Objective-C:
    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    NSLog(@"异步执行-主队列 --- begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });
    NSLog(@"异步执行-主队列 --- end");

Swift:

 private func text6() {
        NSLog("当前线程 --- %@",Thread.current);//打印当前线程
        NSLog("同步执行-主队列 --- begin");
        
        let queue = DispatchQueue.main
        //执行任务
        queue.async {
            //追加任务 1
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("1 --- %@",Thread.current); //打印当前线程
        }
        queue.async {
            //追加任务 2
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("2 --- %@",Thread.current); //打印当前线程
        }
        queue.async {
            //追加任务 3
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("3 --- %@",Thread.current); //打印当前线程
        }
        queue.async {
            //追加任务 4
            Thread.sleep(forTimeInterval: 2)  //模拟耗时操作
            NSLog("4 --- %@",Thread.current); //打印当前线程
        }
        NSLog("异步执行-主队列 --- end");
    }

打印结果:

2020-08-18 20:40:28.667081+0800 KX_GCD_Demo[41989:696384] 当前线程 --- <NSThread: 0x6000015ccac0>{number = 1, name = main}
2020-08-18 20:40:28.667221+0800 KX_GCD_Demo[41989:696384] 异步执行-主队列 --- begin
2020-08-18 20:40:28.667368+0800 KX_GCD_Demo[41989:696384] 异步执行-主队列 --- end
2020-08-18 20:40:30.682547+0800 KX_GCD_Demo[41989:696384] 1 --- <NSThread: 0x6000015ccac0>{number = 1, name = main}
2020-08-18 20:40:32.683854+0800 KX_GCD_Demo[41989:696384] 2 --- <NSThread: 0x6000015ccac0>{number = 1, name = main}
2020-08-18 20:40:34.684356+0800 KX_GCD_Demo[41989:696384] 3 --- <NSThread: 0x6000015ccac0>{number = 1, name = main}
2020-08-18 20:40:36.685728+0800 KX_GCD_Demo[41989:696384] 4 --- <NSThread: 0x6000015ccac0>{number = 1, name = main}

结论:

  1. 所有任务都是在当前线程执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但是你因为是主队列,所以所有任务都在主线程中)
  2. 所有任务是在begin和end之后才执行的.(异步不等待,可以继续执行)
  3. 任务是按顺序执行的,主队列其实也是一个串行的队列

2.6 线程间的通信

线程通信案例:子线程处理完耗时操作后,回到主线程刷新UI,这样就完成了线程之间的通信。
案例:
Objective-C

    //获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        //异步追加任务1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
        //回到主线程
        dispatch_async(mainQueue, ^{
            [NSThread sleepForTimeInterval:2];  //模拟耗时操作
            NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
        });
    });

Swift

private func text7() {
        //线程间的通信比较好理解,就是两个线程之间传递信息        
        //获取全局并发队列
        let queue = DispatchQueue.global()
        //获取主队列
        let mainQueue = DispatchQueue.main
        
        queue.async {
            //异步追加任务1
            Thread.sleep(forTimeInterval: 2) //模拟耗时操作
            NSLog("1 --- %@",Thread.current); //打印当前线程
            
            //回到主线程
            mainQueue.async {
                Thread.sleep(forTimeInterval: 2) //模拟耗时操作
                NSLog("2 --- %@",Thread.current); //打印当前线程
            }
        }
    }

2.7GCD中的其他方法

2.7.1栅栏方法

使用情况:第一组(多个)任务执行完成之后,再进行第二组操作。相当于在两组之间添加了一个栅栏,阻隔开两组任务的执行。这里就用到了dispatch_barrier_async方法。

    //栅栏方法
    dispatch_queue_t queue = dispatch_queue_create("gcd.name", DISPATCH_QUEUE_CONCURRENT);
    
    //执行任务
    dispatch_async(queue, ^{
        //追加任务 1
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"1 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 2
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"2 --- %@",[NSThread currentThread]); //打印当前线程
    });
    
    //栅栏方法
    dispatch_barrier_async(queue, ^{
        //追加任务barrier
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"barrier --- %@",[NSThread currentThread]); //打印当前线程
    });
    
    dispatch_async(queue, ^{
        //追加任务 3
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"3 --- %@",[NSThread currentThread]); //打印当前线程
    });
    dispatch_async(queue, ^{
        //追加任务 4
        [NSThread sleepForTimeInterval:2];  //模拟耗时操作
        NSLog(@"4 --- %@",[NSThread currentThread]); //打印当前线程
    });

打印结果:

     2020-08-19 11:40:11.637863+0800 KX_GCD_Demo[44619:935467] 2 --- <NSThread: 0x600001cc6d40>{number = 5, name = (null)}
     2020-08-19 11:40:11.637871+0800 KX_GCD_Demo[44619:935470] 1 --- <NSThread: 0x600001cc6bc0>{number = 3, name = (null)}
     2020-08-19 11:40:13.638746+0800 KX_GCD_Demo[44619:935467] barrier --- <NSThread: 0x600001cc6d40>{number = 5, name = (null)}
     2020-08-19 11:40:15.640520+0800 KX_GCD_Demo[44619:935470] 4 --- <NSThread: 0x600001cc6bc0>{number = 3, name = (null)}
     2020-08-19 11:40:15.640521+0800 KX_GCD_Demo[44619:935467] 3 --- <NSThread: 0x600001cc6d40>{number = 5, name = (null)}

结论:在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

2.7.2 延时执行 dispatch_after

在项目中延时执行有多种方法,dispatch_after就是其中一个。

    NSLog(@"当前线程 --- %@",[NSThread currentThread]);//打印当前线程
    NSLog(@"after --- begin");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,  (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after --- %@",[NSThread currentThread]);//打印当前线程
    });

打印结果:

2020-08-19 15:24:52.379680+0800 KX_GCD_Demo[45859:1033251] 当前线程 --- <NSThread: 0x600002e1c0c0>{number = 1, name = main}
2020-08-19 15:24:52.379818+0800 KX_GCD_Demo[45859:1033251] after --- begin
2020-08-19 15:24:54.380257+0800 KX_GCD_Demo[45859:1033251] after --- <NSThread: 0x600002e1c0c0>{number = 1, name = main}

2.7.3 单通道执行(只执行一次):dispatch_once

常用的就是单例的情况了:

static id _instance;
+ (instancetype)shared{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc]init];
    });
    return _instance;
}

2.7.4 队列组:dispatch_group

案例情况:执行多个任务,执行完成之后再回到主线程去执行任务。
有多种队列组的方法可以实现。

  • 通过dispatch_group_async将任务放入到队列中,然后将队列放入队列组中
  • 使用dispatch_group_enterdispatch_group_leave实现,和dispatch_group_async功能一样,只是方法不同而已。
  • 调用队列组的dispatch_group_notify回到指定线程执行任务。
  • 使用 dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)。
  1. dispatch_group_notify
    监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
    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), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"group---end");

打印结果:

2020-08-25 22:24:40.382897+0800 KX_GCD_Demo[1429:33018] currentThread---<NSThread: 0x600001638ec0>{number = 1, name = main}
2020-08-25 22:24:40.383599+0800 KX_GCD_Demo[1429:33018] group---begin
2020-08-25 22:24:40.384450+0800 KX_GCD_Demo[1429:33018] group---end
2020-08-25 22:24:42.387804+0800 KX_GCD_Demo[1429:33234] 2---<NSThread: 0x600001678080>{number = 5, name = (null)}
2020-08-25 22:24:42.387766+0800 KX_GCD_Demo[1429:33237] 1---<NSThread: 0x600001672700>{number = 6, name = (null)}
2020-08-25 22:24:44.389329+0800 KX_GCD_Demo[1429:33018] 3---<NSThread: 0x600001638ec0>{number = 1, name = main}
  1. dispatch_group_wait
    阻塞当前线程,等待指定的 group 中所有的任务执行完成后,才会继续往下执行。
    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), ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");

打印结果:

2020-08-25 22:28:13.240155+0800 KX_GCD_Demo[1461:35597] currentThread---<NSThread: 0x600001ba8380>{number = 1, name = main}
2020-08-25 22:28:13.240345+0800 KX_GCD_Demo[1461:35597] group---begin
2020-08-25 22:28:15.242472+0800 KX_GCD_Demo[1461:35685] 1---<NSThread: 0x600001becc00>{number = 5, name = (null)}
2020-08-25 22:28:15.242493+0800 KX_GCD_Demo[1461:35687] 2---<NSThread: 0x600001bfcd80>{number = 7, name = (null)}
2020-08-25 22:28:15.242974+0800 KX_GCD_Demo[1461:35597] group---end

注意看打印结果的时间,是阻塞线程之后再执行的end.

  1. 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 中的任务。
    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, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"group---end");

打印结果:

2020-08-25 22:33:53.365593+0800 KX_GCD_Demo[1511:38898] currentThread---<NSThread: 0x600003128900>{number = 1, name = main}
2020-08-25 22:33:53.365755+0800 KX_GCD_Demo[1511:38898] group---begin
2020-08-25 22:33:53.365962+0800 KX_GCD_Demo[1511:38898] group---end
2020-08-25 22:33:55.368889+0800 KX_GCD_Demo[1511:38963] 2---<NSThread: 0x600003169ec0>{number = 4, name = (null)}
2020-08-25 22:33:55.368890+0800 KX_GCD_Demo[1511:38966] 1---<NSThread: 0x6000031632c0>{number = 7, name = (null)}
2020-08-25 22:33:57.370401+0800 KX_GCD_Demo[1511:38898] 3---<NSThread: 0x600003128900>{number = 1, name = main}
  1. 信号量:dispatch_semaphore
    Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
    Dispatch Semaphore提供了三个方法:
  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
    这一块涉及到线程安全,加锁的问题。比较复杂,我在另一篇文章中有详细介绍。

3. NSOperation和NSOperationQueue

NSOperation、NSOperationQueue是完全级别的封装,完全面向对象。比 GCD 更简单易用、代码可读性也更高。

执行步骤:

  1. 将要执行的任务封装到一个 NSOperation 对象中。
  2. 将此任务添加到一个 NSOperationQueue 对象中。

值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

3.1 子类NSInvocationOperation

- (void)demo {
    // NSOperation *op = [[NSOperation alloc]init];
    //NSOperation 是抽象类(有定义没有实现),你不能直接使用,需要用子类,或者系统已经给你提供好了2个之类 NSInvocationOperation NSBlockOperation
    
    //1.创建NSInvocationOperation对象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];

  //2.开始执行
  [operation start];
}

- (void)task{
    NSLog(@"task %@",[NSThread currentThread]);
}

在没有使用NSOperationQueue的情况下,NSInvocationOperation是在当前线程执行的操作。
住:Swift语言环境下,是不支持NSInvocationOperation的,应为Swift觉得NSInvocationOperation不安全。

3.2 子类NSBlockOperation

Obejective-C:
- (void)useBlockOperation {
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    // 2.开始执行操作
    [op start];
}

//------------------------------------------------------------------

Swift:
func useBlockOperation() {
        // 1.创建 NSBlockOperation 对象
        let op = BlockOperation {
            for _ in 0...2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("1---%@", Thread.current) // 打印当前线程
            }
        }
        // 2.开始执行操作
        op.start()
    }

操作是在当前线程执行的,并没有开启新线程。

NSBlockOperation还提供了addExecutionBlock方法,追加可执行的block。
例如:

Objective-C:
    NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1  %@",[NSThread currentThread]);
    }];
    
    //追加可执行的block
    [op1 addExecutionBlock:^{
       
        NSLog(@"task2  %@",[NSThread currentThread]);
 
    }];
    [op1 start];

//----------------------------------------------------

Swift:
       let op1 = BlockOperation {
            NSLog("task1  %@",Thread.current)
        }
        //追加可执行的block
        op1.addExecutionBlock {
            NSLog("task2  %@",Thread.current)
        }
        op1.start()

3.3创建队列

  1. 自定义队列
    • 添加到这种队列中的操作,就会自动放到子线程中执行。
    • 同时包含了:串行、并发功能。
Objective-C:
// 主队列获取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

//----------------------------------------------------
Swift:
let queue = OperationQueue.main
  1. 主队列
    -凡是添加到主队列中的操作,都会放到主线程中执行(特殊情况除外)
Objective-C:
  // 自定义队列创建方法
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  //----------------------------------------------------------------
Swift:
  let queue = OperationQueue()

3.4 将操作添加到队列中

3.4.1 使用addOperation

简单的例子:

    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
    //2.队列 并发
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //把操作添加到队列中
    [queue addOperation:op1];

还比如:

Objective-C:
- (void)demo2{
    NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task1  %@",[NSThread currentThread]);
    }];
    
    //追加可执行的block
    [op1 addExecutionBlock:^{
       
        NSLog(@"task2  %@",[NSThread currentThread]);
 
    }];

      queue = [[NSOperationQueue alloc]init];
    //开了新的线程
    [queue addOperation:op1];
}

//--------------------------------------------------------
Swift:
  func demo2() {
        
      let op1 = BlockOperation {
          NSLog("task1  %@",Thread.current);
      }
      //追加可执行的closur
      op1.addExecutionBlock {
          NSLog("task2  %@",Thread.current);
      }
        
      let queue = OperationQueue()
      //开了新的线程
      queue.addOperation(op1)    
  }

开启了新的线程。

也可以两种子类结合起来用:

/**
 * 使用 addOperation: 将操作加入到操作队列中
 */
- (void)addOperationToQueue {

    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.创建操作
    // 使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];

    // 3.使用 addOperation: 添加所有操作到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
    
    NSLog(@"over");
}

- (void)task1{
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
- (void)task2{
    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
    NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}

打印结果:

2020-08-26 21:52:45.953187+0800 05.NSOperation[1059:29510] over
2020-08-26 21:52:47.954189+0800 05.NSOperation[1059:29738] 1---<NSThread: 0x60000155b280>{number = 6, name = (null)}
2020-08-26 21:52:47.954199+0800 05.NSOperation[1059:29736] 3---<NSThread: 0x600001554000>{number = 5, name = (null)}
2020-08-26 21:52:47.954193+0800 05.NSOperation[1059:29739] 4---<NSThread: 0x600001550180>{number = 2, name = (null)}
2020-08-26 21:52:47.954209+0800 05.NSOperation[1059:29737] 2---<NSThread: 0x600001550680>{number = 4, name = (null)}
2020-08-26 21:52:49.958189+0800 05.NSOperation[1059:29739] 4---<NSThread: 0x600001550180>{number = 2, name = (null)}
2020-08-26 21:52:49.958218+0800 05.NSOperation[1059:29736] 3---<NSThread: 0x600001554000>{number = 5, name = (null)}

结论:先打印的over,全部在子线程,并发执行。
使用 NSOperation 子类创建操作,使用 addOperation:将操作加入到操作队列后有开启新线程的能力。

另外:操作队列添加操作还有一个方法,可以一次添加多个操作。

Objective-C:
/**
 添加多个操作到队列
 一个操作只能够被添加到一个队列中
 */
- (void)demo3 {
   NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"task1  %@",[NSThread currentThread]);
   }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"task2  %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:0.5];
    }];
    
//    [self.queue addOperation:op1];
//    [self.queue addOperation:op2];

    [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
    //当NO的时候不阻塞,当yes的时候会阻塞后面的代码
    NSLog(@"over");
}

//---------------------------------------------------------
Swift:
func demo3() {
      let op1 = BlockOperation {
          NSLog("task1  %@",Thread.current);
      }
      let op2 = BlockOperation {
          NSLog("task2  %@",Thread.current);
          Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
      }
      let queue = OperationQueue()      
      queue.addOperations([op1,op2], waitUntilFinished: false)
      NSLog("over")
  }

[self.queue addOperations:@[op1,op2] waitUntilFinished:NO];
当NO的时候不阻塞,当yes的时候会阻塞后面的代码

3.4.2 使用addOperationWithBlock

这种方法不需要事先创建操作,直接将操作添加到block中。

Objective-C :
 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 [queue addOperationWithBlock:^{
        NSLog(@"task--  %@",[NSThread currentThread]);
 }];

//------------------------------------------------------
Swift:
   let queue = OperationQueue()
   queue.addOperation {
          NSLog("task--  %@",Thread.current);
   }

打印结果:

2020-08-26 22:03:29.328099+0800 05.NSOperation[1110:35213] task1  <NSThread: 0x600003fe4080>{number = 3, name = (null)}

仅仅添加了一个方法,就已经开线程了,说明使用
addOperationWithBlock将操作加入到操作队列后能够开启新线程。
那我们多使用几次addOperationWithBlock就会发现不但可以开线程,而且是并发执行。

3.5 NSOperationQueue 控制串行执行、并发执行

这也是我任务NSOperationQueue 最厉害的地方,它可以控制开线程的个数。
关键属性最大并发操作数maxConcurrentOperationCount,用来控制队列。
理解为开多少个线程不够全面,但是方便理解。正确解释为:设置队列中同一时间能同时运行多少个类型的任务操作
引自网络博主江苏小白龙的一条评论,接的说的挺有道理,摘抄过来 :认为maxConcurrentOperationCount = 1,NSOperationQueue就成了串行队列不够严谨,当队列中有2种类型的任务操作时而其中一种任务类型追加了2个子类型的任务操作,当maxConcurrentOperationCount = 1时只能同时运行一种任务操作串行运行的,当他运行到有子类型任务操作的时候那么他会开启新的线程同步执行追加的任务。所以本人认为maxConcurrentOperationCount属性的作用是:设置队列中同一时间片能同时运行多少个类型的任务操作。

案例:
Objective-C:

/**
 * 设置 MaxConcurrentOperationCount(最大并发操作数)
 */
- (void)setupMaxConcurrentOperationCountDemo {
    
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.设置最大并发操作数
    queue.maxConcurrentOperationCount = 1; // 串行队列
    // queue.maxConcurrentOperationCount = 2; // 并发队列
    // queue.maxConcurrentOperationCount = 4; // 并发队列
    
    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"5---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    
    NSLog(@"end");
}

Swift:

    func setupMaxConcurrentOperationCountDemo() {
        
        // 1.创建队列
        let queue = OperationQueue()
        // 2.设置最大并发操作数
        queue.maxConcurrentOperationCount = 1; // 串行队列
        // queue.maxConcurrentOperationCount = 2; // 并发队列
        // queue.maxConcurrentOperationCount = 4; // 并发队列
        
        //添加操作
        queue.addOperation {
            for _ in 0..<2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("1--- %@",Thread.current);// 打印当前线程
            }
        }
        queue.addOperation {
            for _ in 0..<2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("2--- %@",Thread.current);// 打印当前线程
            }
        }
        queue.addOperation {
            for _ in 0..<2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("3--- %@",Thread.current);// 打印当前线程
            }
        }
        queue.addOperation {
            for _ in 0..<2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("4--- %@",Thread.current);// 打印当前线程
            }
        }
        queue.addOperation {
            for _ in 0..<2 {
                Thread.sleep(forTimeInterval: 2)    // 模拟耗时操作
                NSLog("5--- %@",Thread.current);// 打印当前线程
            }
        }
        NSLog("end");
    }

最大并发操作数为1 时,打印结果:

2020-08-26 22:16:17.101511+0800 05.NSOperation[1158:40941] end
2020-08-26 22:16:19.106345+0800 05.NSOperation[1158:40992] 1---<NSThread: 0x600000a2e280>{number = 4, name = (null)}
2020-08-26 22:16:21.112007+0800 05.NSOperation[1158:40992] 1---<NSThread: 0x600000a2e280>{number = 4, name = (null)}
2020-08-26 22:16:23.116690+0800 05.NSOperation[1158:40992] 2---<NSThread: 0x600000a2e280>{number = 4, name = (null)}
2020-08-26 22:16:25.122301+0800 05.NSOperation[1158:40992] 2---<NSThread: 0x600000a2e280>{number = 4, name = (null)}
2020-08-26 22:16:27.123054+0800 05.NSOperation[1158:41041] 3---<NSThread: 0x600000a39080>{number = 7, name = (null)}
2020-08-26 22:16:29.124319+0800 05.NSOperation[1158:41041] 3---<NSThread: 0x600000a39080>{number = 7, name = (null)}
2020-08-26 22:16:31.129149+0800 05.NSOperation[1158:41041] 4---<NSThread: 0x600000a39080>{number = 7, name = (null)}
2020-08-26 22:16:33.133247+0800 05.NSOperation[1158:41041] 4---<NSThread: 0x600000a39080>{number = 7, name = (null)}
2020-08-26 22:16:35.138359+0800 05.NSOperation[1158:41041] 5---<NSThread: 0x600000a39080>{number = 7, name = (null)}
2020-08-26 22:16:37.143127+0800 05.NSOperation[1158:41041] 5---<NSThread: 0x600000a39080>{number = 7, name = (null)}

最大并发操作数为2 时,打印结果:

2020-08-26 22:19:27.567659+0800 05.NSOperation[1195:42965] end
2020-08-26 22:19:29.571120+0800 05.NSOperation[1195:43066] 1---<NSThread: 0x600002a84c80>{number = 6, name = (null)}
2020-08-26 22:19:29.571145+0800 05.NSOperation[1195:43068] 2---<NSThread: 0x600002a9c0c0>{number = 5, name = (null)}
2020-08-26 22:19:31.576032+0800 05.NSOperation[1195:43068] 2---<NSThread: 0x600002a9c0c0>{number = 5, name = (null)}
2020-08-26 22:19:31.576042+0800 05.NSOperation[1195:43066] 1---<NSThread: 0x600002a84c80>{number = 6, name = (null)}
2020-08-26 22:19:33.577990+0800 05.NSOperation[1195:43066] 4---<NSThread: 0x600002a84c80>{number = 6, name = (null)}
2020-08-26 22:19:33.578004+0800 05.NSOperation[1195:43070] 3---<NSThread: 0x600002a9b380>{number = 3, name = (null)}
2020-08-26 22:19:35.582109+0800 05.NSOperation[1195:43070] 3---<NSThread: 0x600002a9b380>{number = 3, name = (null)}
2020-08-26 22:19:35.582133+0800 05.NSOperation[1195:43066] 4---<NSThread: 0x600002a84c80>{number = 6, name = (null)}
2020-08-26 22:19:37.584611+0800 05.NSOperation[1195:43068] 5---<NSThread: 0x600002a9c0c0>{number = 5, name = (null)}
2020-08-26 22:19:39.588468+0800 05.NSOperation[1195:43068] 5---<NSThread: 0x600002a9c0c0>{number = 5, name = (null)}

最大并发操作数为4 时,打印结果:

2020-08-26 22:20:12.711985+0800 05.NSOperation[1213:43733] end
2020-08-26 22:20:14.715188+0800 05.NSOperation[1213:43826] 1---<NSThread: 0x600000bfd900>{number = 4, name = (null)}
2020-08-26 22:20:14.715204+0800 05.NSOperation[1213:43825] 4---<NSThread: 0x600000bb92c0>{number = 8, name = (null)}
2020-08-26 22:20:14.715239+0800 05.NSOperation[1213:43827] 2---<NSThread: 0x600000bfca80>{number = 6, name = (null)}
2020-08-26 22:20:14.715304+0800 05.NSOperation[1213:43828] 3---<NSThread: 0x600000bb9380>{number = 7, name = (null)}
2020-08-26 22:20:16.717543+0800 05.NSOperation[1213:43827] 2---<NSThread: 0x600000bfca80>{number = 6, name = (null)}
2020-08-26 22:20:16.717550+0800 05.NSOperation[1213:43826] 1---<NSThread: 0x600000bfd900>{number = 4, name = (null)}
2020-08-26 22:20:16.717543+0800 05.NSOperation[1213:43825] 4---<NSThread: 0x600000bb92c0>{number = 8, name = (null)}
2020-08-26 22:20:16.717544+0800 05.NSOperation[1213:43828] 3---<NSThread: 0x600000bb9380>{number = 7, name = (null)}
2020-08-26 22:20:18.721737+0800 05.NSOperation[1213:43827] 5---<NSThread: 0x600000bfca80>{number = 6, name = (null)}
2020-08-26 22:20:20.726078+0800 05.NSOperation[1213:43827] 5---<NSThread: 0x600000bfca80>{number = 6, name = (null)}

结论:当最大并发操作数为1时,操作是按顺序串行执行的。当最大操作并发数为2时,操作是并发执行的,可以同时执行两个操作。而开启线程数量是由系统决定的,不需要我们来管理。当最大操作并发数为4时,操作是并发执行的,可以同时执行4个操作。

3.5操作依赖

NSOperation 有一个非常实用的功能,那就是添加依赖。
比如我们有 4 个任务:1. 登录APP,2.才能付款买东西,3.下载我们要的资料,4. 回到主线程刷新UI。这时就可以用到依赖了:
Demo:

Objective-C :

- (void)demo {
    
    /**
     1.登录
     2.付款
     3.下载
     4.UI
     
     1,2,3,4 有序执行
     1,2,3 都是耗时任务 --> 子线程
     4 --> 主线程
     串行队列,异步执行
     */
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
       
        NSLog(@"登录 ,%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"付款 ,%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"下载 ,%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"UI ,%@",[NSThread currentThread]);
    }];
    
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    [op4 addDependency:op3];
    
    [self.queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
    
    [[NSOperationQueue mainQueue] addOperation:op4];
}

Swift:

 func demo6() {
        let op1 = BlockOperation {
            NSLog("登录 ,%@",Thread.current);
        }
        let op2 = BlockOperation {
            NSLog("付款 ,%@",Thread.current);
        }
        let op3 = BlockOperation {
            NSLog("下载 ,%@",Thread.current);
        }
        let op4 = BlockOperation {
            NSLog("UI ,%@",Thread.current);
        }
        
        op2.addDependency(op1)
        op3.addDependency(op2)
        op4.addDependency(op3)

        let queue = OperationQueue()
        queue.addOperations([op1,op2,op3], waitUntilFinished: false)
        OperationQueue.main.addOperation(op4)
    }

还有一些其他方法:
NSOperation:

BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
void (^completionBlock)(void); //用来设置完成后需要执行的操作
- (void)cancel; //取消任务
- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSOperationQueue

NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations; //取消队列中所有的任务
- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue

总结:这大概就是GCD,NSThread,NSOperation的全部用法了,但是还有一些如线程安全的没有写。有空再写吧。
这里面很多demo参考了几个优秀博主的代码。文中有写出来,除此之外还,整体结构还参考了伯恩的遗产的一篇文章。非常感谢。
后续会把文中代码转换为Swift再列出来。

喜欢就点个赞再走吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,861评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,263评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,033评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,999评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,000评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,483评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,850评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,827评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,366评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,404评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,525评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,130评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,853评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,293评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,426评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,082评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,590评论 2 366

推荐阅读更多精彩内容