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閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容