關(guān)于iOS多線程,這邊勉強(qiáng)可以看看(OC&Swift)

搜多線程谷歌給的第一張圖??

iOS開(kāi)發(fā)多線程總是繞不過(guò)的坎掘殴,看了很多前輩們優(yōu)秀的文章赚瘦,如:關(guān)于iOS多線程,我說(shuō)奏寨,你聽(tīng)起意,沒(méi)準(zhǔn)你就懂了!病瞳、談iOS多線程(NSThread揽咕、NSOperation悲酷、GCD)編程iOS多線程:『GCD』詳盡總結(jié)亲善、iOS多線程:『pthread舔涎、NSThread』詳盡總結(jié)iOS多線程:『NSOperation逗爹、NSOperationQueue』詳盡總結(jié)亡嫌、關(guān)于iOS多線程,你看我就夠了等掘而,但不自己整理一下挟冠,不敲一下代碼總是感覺(jué)不深刻 ,于是就有這篇文章袍睡,斷斷續(xù)續(xù)整理了好久知染。

示例我盡量把不同知識(shí)點(diǎn)代碼獨(dú)立開(kāi),看著簡(jiǎn)單一些斑胜,容易理解控淡。示例代碼我都用OC和Swift分別寫了,不過(guò)文中都是以O(shè)C說(shuō)明的止潘,因?yàn)槿绻脙煞N語(yǔ)言一起掺炭,看起來(lái)可能比較亂,文章也會(huì)更加長(zhǎng)(已經(jīng)非常長(zhǎng)了????)凭戴。
Swift寫法可能有比較大區(qū)別涧狮,也有些功能取消,但看一下代碼應(yīng)該能明白了么夫。
文中的所有示例代碼:andyRon/iOS-Multithreading

由于簡(jiǎn)書沒(méi)有Markdown的目錄功能者冤,我截了Typora的大綱,先看個(gè)大概:


1 簡(jiǎn)介

一些幫助理解的概念档痪,可以先簡(jiǎn)單看一下涉枫,然后讀完全文再回來(lái)看一下這一部分,可能就比較清晰了腐螟。??

1.1 一些概念

  • 系統(tǒng)中正在運(yùn)行的每一個(gè)應(yīng)用程序都是一個(gè) 進(jìn)程(Process) 愿汰,每個(gè)進(jìn)程系統(tǒng)都會(huì)分配給它獨(dú)立的內(nèi)存運(yùn)行。也就是說(shuō)遭垛,在iOS系統(tǒng)中中尼桶,每一個(gè)應(yīng)用都是一個(gè)進(jìn)程。

  • 一個(gè)進(jìn)程的所有任務(wù)都在 線程(Thread) 中進(jìn)行锯仪,因此每個(gè)進(jìn)程至少要有一個(gè)線程泵督,也就是主線程。那多線程其實(shí)就是一個(gè)進(jìn)程開(kāi)啟多條線程庶喜,讓所有任務(wù)并發(fā)執(zhí)行小腊。

  • iOS App一旦運(yùn)行救鲤,默認(rèn)就會(huì)開(kāi)啟一條線程。這條線程秩冈,通常稱作為“主線程”本缠。在iOS應(yīng)用中主線程的作用一般是:
    刷新UI;
    處理UI事件入问,例如點(diǎn)擊丹锹、滾動(dòng)、拖拽芬失。

  • 如果主線程的操作太多楣黍、太耗時(shí),就會(huì)造成App卡頓現(xiàn)象嚴(yán)重棱烂。所以租漂,通常我們都會(huì)把耗時(shí)的操作放在子線程中進(jìn)行,獲取到結(jié)果之后颊糜,回到主線程去刷新UI哩治。

  • 多線程在一定意義上實(shí)現(xiàn)了進(jìn)程內(nèi)的資源共享,以及效率的提升衬鱼。同時(shí)业筏,在一定程度上相對(duì)獨(dú)立,它是程序執(zhí)行流的最小單元馁启,是進(jìn)程中的一個(gè)實(shí)體驾孔,是執(zhí)行程序最基本的單元,有自己棧和寄存器惯疙。

  • 同步: 只能在當(dāng)前線程按先后順序依次執(zhí)行,不開(kāi)啟新線程妖啥。

  • 異步: 可以在當(dāng)前線程開(kāi)啟多個(gè)新線程執(zhí)行霉颠,可不按順序執(zhí)行。

  • 隊(duì)列: 裝載線程任務(wù)的隊(duì)形結(jié)構(gòu)荆虱。

  • 并發(fā): 線程執(zhí)行可以同時(shí)一起進(jìn)行執(zhí)行蒿偎。

  • 串行: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行。

通過(guò)確保主線程自由響應(yīng)用戶事件怀读,并發(fā)可以很好地提高應(yīng)用的響應(yīng)性诉位。通過(guò)將工作分配到多核,還能提高應(yīng)用處理的性能菜枷。但是并發(fā)也帶來(lái)一定的額外開(kāi)銷(調(diào)度)苍糠,并且使代碼更加復(fù)雜,更難編寫和調(diào)試啤誊。

1.2 多線程概念補(bǔ)充

  • 多線程的原理:

    同一時(shí)間岳瞭,CPU只能處理一條線程拥娄,也就是只有一條線程在工作。所謂多線程并發(fā)(同時(shí))執(zhí)行瞳筏,其實(shí)是CPU快速的在多線程之間調(diào)度(切換)稚瘾。如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象姚炕。

  • 在實(shí)際項(xiàng)目開(kāi)發(fā)中并不是線程越多越好摊欠,如果開(kāi)了大量的線程,會(huì)消耗大量的CPU資源柱宦,CPU會(huì)被累死些椒,所以一般手機(jī)只開(kāi)1~3個(gè)線程為宜,不超過(guò)5個(gè)捷沸。

  • 多線程的優(yōu)點(diǎn):

    能適當(dāng)提高程序的執(zhí)行效率
    能適當(dāng)提高資源的利用率摊沉,這個(gè)利用率表現(xiàn)在(CPU,內(nèi)存的利用率)

  • 多線程的缺點(diǎn):

    1. 開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下痒给,主線程占用1M说墨,子線程占用512KB,如果開(kāi)啟大量的線程苍柏,會(huì)占用大量的內(nèi)存空間尼斧,降低程序的性能)

    2. 線程越多,CPU在調(diào)度線程上的開(kāi)銷就越大

    3. 線程越多试吁,程序設(shè)計(jì)就越復(fù)雜棺棵,比如線程之間的通信,多線程的數(shù)據(jù)共享熄捍,這些都需要程序的處理烛恤,增加了程序的復(fù)雜度。

  • 在iOS開(kāi)發(fā)中使用線程的注意事項(xiàng):

    1. 別將比較耗時(shí)的操作放在主線程中
    2. 耗時(shí)操作會(huì)卡住主線程余耽,嚴(yán)重影響UI的流暢度缚柏,給用戶一種“卡”的壞體驗(yàn)

2 四種解決方案對(duì)比

  • 目前iOS多線程有四種方法:pthread,NSThread碟贾,GCD币喧, NSOperation,四種方案的簡(jiǎn)單對(duì)比一下袱耽。
多線程的四種方案對(duì)比

由于pthread平常幾乎用不到杀餐,我暫時(shí)就不學(xué)了。

  • 每個(gè)NSThread對(duì)象對(duì)應(yīng)一個(gè)線程朱巨,真正最原始的線程史翘,相對(duì)簡(jiǎn)單,但是需要手動(dòng)管理所有的線程活動(dòng),如生命周期恶座、線程同步搀暑、睡眠等。
  • 怎么選擇 跨琳?
    簡(jiǎn)單而安全的選擇NSOperation實(shí)現(xiàn)多線程即可自点。
    處理大量并發(fā)數(shù)據(jù),又追求性能效率的選擇GCD脉让。

3 NSTread

生命周期還是需要程序員手動(dòng)管理桂敛,所以這套方案也是偶爾用用,不過(guò)獲取當(dāng)前線程([NSThread currentThread])和獲取主線程([NSThread mainThread])的這兩個(gè)方法會(huì)經(jīng)常使用溅潜。

3.1 NSThread三種線程開(kāi)啟方式

  • 動(dòng)態(tài)開(kāi)啟
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:(@"NSThread1")];
[thread1 start];
  • 靜態(tài)開(kāi)啟
// 創(chuàng)建好之后直接啟動(dòng)
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:(@"NSTread2")];
  • 隱式開(kāi)啟
// 創(chuàng)建好之后也是直接啟動(dòng)
[self performSelectorInBackground:@selector(doSomething3:) withObject:(@"NSTread3")];

3.2 NSThread拓展

  • 獲取當(dāng)前線程
NSThread *current = [NSThread currentThread]; 
  • 獲取主線程
NSThread *main = [NSThread mainThread];   
  • 暫停當(dāng)前線程一段時(shí)間
[NSThread sleepForTimeInterval:2];
  • 暫停當(dāng)前線程到某個(gè)時(shí)間
[NSThread  sleepUntilDate: date];
  • 線程之間通信
//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];

4. GCD

GCD為Grand Central Dispatch的縮寫术唬。Grand Central Dispatch (GCD)是Apple開(kāi)發(fā)的一個(gè)多核編程的較新的解決方法。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)滚澜。

4.1 GCD的優(yōu)點(diǎn)

  • GCD 可用于多核的并行運(yùn)算
  • GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核粗仓、四核)
  • GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)设捐、銷毀線程)
  • 程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)借浊,不需要編寫任何線程管理代碼

4.2 任務(wù)和隊(duì)列

  • 任務(wù): 表現(xiàn)上就是一段代碼,OC就對(duì)應(yīng)一個(gè)Block萝招。任務(wù) 有兩種執(zhí)行方式蚂斤,是否會(huì)創(chuàng)建新的線程會(huì)不會(huì)阻塞當(dāng)前線程

    1. 同步執(zhí)行(sync):在當(dāng)前線程執(zhí)行任務(wù)槐沼,不會(huì)開(kāi)辟新的線程曙蒸。必須等到Block函數(shù)執(zhí)行完畢后,dispatch函數(shù)才會(huì)返回岗钩。
    2. 異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù)纽窟,但不一定會(huì)開(kāi)辟新的線程。dispatch函數(shù)會(huì)立即返回, 然后Block在后臺(tái)異步執(zhí)行兼吓。
  • 隊(duì)列:任務(wù)管理方式师倔。分為 串行并行兩種方式,都是按照 FIFO(先進(jìn)先出)原則依次觸發(fā)任務(wù)周蹭。

    1. 串行隊(duì)列 : 所有任務(wù)會(huì)在一條線程中執(zhí)行(有可能是當(dāng)前線程也有可能是新開(kāi)辟的線程),并且一個(gè)任務(wù)執(zhí)行完畢后疲恢,才開(kāi)始執(zhí)行下一個(gè)任務(wù)凶朗。(等待完成)
    2. 并行隊(duì)列: 可以開(kāi)啟多條線程并行執(zhí)行任務(wù)(但不一定會(huì)開(kāi)啟新的線程),并且當(dāng)一個(gè)任務(wù)放到指定線程開(kāi)始執(zhí)行時(shí)显拳,下一個(gè)任務(wù)就可以開(kāi)始執(zhí)行了棚愤。(等待發(fā)生)

兩者的區(qū)別:執(zhí)行順序不同,以及開(kāi)啟線程數(shù)不同。

  • 兩個(gè)特殊隊(duì)列:

    1. 主隊(duì)列: 系統(tǒng)創(chuàng)建好的一個(gè) 串行隊(duì)列宛畦,它管理必須在主線程中執(zhí)行的任務(wù)瘸洛。
    2. 全局隊(duì)列:系統(tǒng)為我們創(chuàng)建好的一個(gè)并行隊(duì)列,使用起來(lái)與我們自己創(chuàng)建的并行隊(duì)列無(wú)本質(zhì)差別次和。
  • 不同隊(duì)列創(chuàng)建獲取方式:

- (void)create {
    // dispatch_queue_create 第一個(gè)參數(shù)是隊(duì)列名字反肋,一般用app的Bundle Identifier命名方式命名;第二個(gè)參數(shù)為NULL時(shí)表是串行隊(duì)列
    //串行隊(duì)列
    dispatch_queue_t serialQueue = dispatch_queue_create("q1.andyron.com", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
    //并行隊(duì)列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
    //全局并行隊(duì)列    DISPATCH_QUEUE_PRIORITY_DEFAULT表示優(yōu)先級(jí)
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //主隊(duì)列獲取
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
}
同步執(zhí)行 異步執(zhí)行
串行隊(duì)列 當(dāng)前線程踏施,一個(gè)一個(gè)執(zhí)行 其他線程石蔗,一個(gè)一個(gè)執(zhí)行
并行隊(duì)列 當(dāng)前線程,一個(gè)一個(gè)執(zhí)行 開(kāi)很多線程畅形,一起執(zhí)行

4.3 下面??以一個(gè)一個(gè)??來(lái)學(xué)習(xí)GCD养距,幫助搞清上面的概念

  • 例子一:線程死鎖(主隊(duì)列 + 同步執(zhí)行)
- (void)case1 {
    NSLog(@"A=====%@", [NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"B=====%@", [NSThread currentThread]);
    });
    NSLog(@"C=====%@", [NSThread currentThread]);
}

運(yùn)行結(jié)果崩潰:


打印結(jié)果:

GCD(OC)[51511:6351422] A=====<NSThread: 0x600000064340>{number = 1, name = main}

解釋:默認(rèn)就一個(gè)主隊(duì)列和一個(gè)主線程,因此case1函數(shù)這段任務(wù)就在主隊(duì)列中同步執(zhí)行日熬,dispatch_sync...這段代碼表示把B處任務(wù)加入主隊(duì)列中棍厌,并且同步執(zhí)行,這就出問(wèn)題竖席,B處任務(wù)要等主隊(duì)列中同步執(zhí)行之前的case1這段任務(wù)結(jié)束后執(zhí)行耘纱,但B處任務(wù)在case1這段任務(wù)中,case1又要等B處任務(wù)執(zhí)行完才能繼續(xù)執(zhí)行怕敬。case1任務(wù)要等B處完成才能繼續(xù)揣炕,但case1又排在B處前面,這就尷尬了东跪, ̄□ ̄||畸陡,因此崩潰了??

  • 例子二:主隊(duì)列 + 異步執(zhí)行
- (void)case2 {
    NSLog(@"A=====%@", [NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"B=====%@", [NSThread currentThread]);
    });
     NSLog(@"C=====%@", [NSThread currentThread]);
}

結(jié)果:

GCD(OC)[52567:6465354] A=====<NSThread: 0x600000068d40>{number = 1, name = main}
GCD(OC)[52567:6465354] C=====<NSThread: 0x600000068d40>{number = 1, name = main}
GCD(OC)[52567:6465354] B=====<NSThread: 0x600000068d40>{number = 1, name = main}

解釋:任務(wù)都在主隊(duì)列(串行),而且只要一個(gè)主線程(name都是main)虽填,B處任務(wù)由于是異步執(zhí)行丁恭,等case2任務(wù)完成后執(zhí)行。

  • 例子三:串行隊(duì)列 + 同步執(zhí)行
// 串行隊(duì)列 + 同步執(zhí)行
- (void)case3 {
    dispatch_queue_t serialQueue = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"1======%@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"2======%@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"3======%@", [NSThread currentThread]);
    });
    NSLog(@"4======%@", [NSThread currentThread]);
}

結(jié)果:

GCD(OC)[53734:6582112] 1======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 2======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 3======<NSThread: 0x604000261700>{number = 1, name = main}
GCD(OC)[53734:6582112] 4======<NSThread: 0x604000261700>{number = 1, name = main}

解釋:??1中的主隊(duì)列也是串行隊(duì)列斋日,但和這邊不同牲览,這邊是新建了另一個(gè)串行隊(duì)列,不會(huì)出現(xiàn)沖突恶守,并且都在主線程中運(yùn)行第献,這也說(shuō)明了同步執(zhí)行不具備創(chuàng)建新線程的能力。

  • 列子四:串行隊(duì)列 + 異步執(zhí)行
// 串行隊(duì)列 + 異步執(zhí)行
- (void)case4 {
    dispatch_queue_t serialQueue = dispatch_queue_create("q2.andyron.com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結(jié)果:

GCD(OC)[53970:6604711] 4========<NSThread: 0x60000007c880>{number = 1, name = main}
GCD(OC)[53970:6604933] 1========<NSThread: 0x600000460280>{number = 3, name = (null)}
GCD(OC)[53970:6604933] 2========<NSThread: 0x600000460280>{number = 3, name = (null)}
GCD(OC)[53970:6604933] 3========<NSThread: 0x600000460280>{number = 3, name = (null)}

解釋:現(xiàn)在打印了4兔港,后打印了1庸毫,2,3衫樊,這是一部執(zhí)行的結(jié)果飒赃,并且4在主線程利花,其它在子線程打印,這也說(shuō)明了異步執(zhí)行可以創(chuàng)建新線程载佳。

  • 列子五:并行隊(duì)列 + 同步執(zhí)行
/// 并行隊(duì)列 + 同步執(zhí)行
- (void)case5 {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結(jié)果:

GCD(OC)[54401:6646454] 1========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 2========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 3========<NSThread: 0x600000260e80>{number = 1, name = main}
GCD(OC)[54401:6646454] 4========<NSThread: 0x600000260e80>{number = 1, name = main}

解釋:都在主線程執(zhí)行炒事,由于只有一個(gè)線程,結(jié)果看上去是順序執(zhí)行蔫慧。

  • 列子六: 并行隊(duì)列 + 異步執(zhí)行
// 并行隊(duì)列 + 異步執(zhí)行
- (void)case6 {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("q3.andyron.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1========%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2========%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"3========%@",[NSThread currentThread]);
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印結(jié)果:

GCD(OC)[54687:6675227] 2========<NSThread: 0x600000466600>{number = 5, name = (null)}
GCD(OC)[54687:6675036] 4========<NSThread: 0x604000078d00>{number = 1, name = main}
GCD(OC)[54687:6675226] 1========<NSThread: 0x600000463fc0>{number = 4, name = (null)}
GCD(OC)[54687:6675229] 3========<NSThread: 0x60400067d400>{number = 6, name = (null)}

解釋:除了打印4的是主線挠乳,其他又開(kāi)啟了三個(gè)線程來(lái)執(zhí)行三個(gè)任務(wù),當(dāng)天開(kāi)啟幾個(gè)線程是有CPU自己決定的藕漱,任務(wù)的執(zhí)行是隨機(jī)的欲侮。

4.4 GCD 重點(diǎn)

  • 只要是串行隊(duì)列,肯定要等上一個(gè)任務(wù)執(zhí)行完成肋联,才能開(kāi)始下一個(gè)任務(wù)威蕉。但是并行隊(duì)列當(dāng)上一個(gè)任務(wù)開(kāi)始執(zhí)行后,下一個(gè)任務(wù)就可以開(kāi)始執(zhí)行橄仍。
  • 同步+串行:未開(kāi)辟新線程材泄,串行執(zhí)行任務(wù)竞漾;同步+并行:未開(kāi)辟新線程娄蔼,串行執(zhí)行任務(wù)赖条;異步+串行:新開(kāi)辟一條線程,串行執(zhí)行任務(wù)宪哩;異步+并行:開(kāi)辟多條新線程娩贷,并行執(zhí)行任務(wù);在主線程中同步使用主隊(duì)列執(zhí)行任務(wù)锁孟,會(huì)造成死鎖彬祖。

4.5 GCD其他相關(guān)方法

  • 延遲執(zhí)行方法: void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
    指定時(shí)間后執(zhí)行某個(gè)任務(wù),dispatch_after函數(shù)指定的時(shí)間是指多長(zhǎng)后將任務(wù)加到某個(gè)隊(duì)列中品抽,而不是具體執(zhí)行時(shí)間储笑,具體時(shí)間要看CPU執(zhí)行時(shí)間了,可以看做是個(gè)大約延遲時(shí)間圆恤。
- (void)after {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后異步追加任務(wù)代碼到主隊(duì)列突倍,并開(kāi)始執(zhí)行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當(dāng)前線程
    });
}
  • dispatch_once:在生命周期內(nèi)只執(zhí)行一次。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"就一次%@",[NSThread currentThread]);
    });
}

不管點(diǎn)擊多少次只有一次輸出盆昙。

GCD的內(nèi)容很豐富羽历,還有很多函數(shù),2016和2017的WWDC都有專門講到GCD淡喜,想更近一步可以參考:Modernizing Grand Central Dispatch Usage窄陡,Concurrent Programming With GCD in Swift 3

5 NSOperation 和 NSOperationQueue

5.1 概念

  • NSOperation(操作) 和 NSOperationQueue操作隊(duì)列) 是蘋果對(duì)GCD的封裝

  • NSOperationNSOperationQueue 分別相當(dāng)于 GCD 的 任務(wù)隊(duì)列

  • NSOperation 只是一個(gè)抽象類拆火,不能直接使用,使用其 2 個(gè)子類:NSInvocationOperationNSBlockOperation

  • NSOperation的使用除了其現(xiàn)有的子類们镜,還可以自定義子類币叹。

  • 操作隊(duì)列通過(guò)設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來(lái)控制并發(fā)、串行模狭。

  • NSOperationQueue 為我們提供了兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列颈抚。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺(tái)執(zhí)行嚼鹉。

  • NSOperation 需要配合 NSOperationQueue 來(lái)使用贩汉。否則,NSOperation 單獨(dú)使用時(shí)系統(tǒng)默認(rèn)同步執(zhí)行操作锚赤,配合 NSOperationQueue 我們能更好的實(shí)現(xiàn)異步執(zhí)行匹舞。

5. 2 NSOperation 實(shí)現(xiàn)多線程的步驟

  1. 創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中。
  2. 創(chuàng)建隊(duì)列:創(chuàng)建 NSOperationQueue 對(duì)象线脚。
  3. 將操作加入到隊(duì)列中:將 NSOperation 對(duì)象添加到NSOperationQueue 對(duì)象中赐稽。
  4. 之后,系統(tǒng)就會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來(lái)浑侥,在新線程中執(zhí)行操作姊舵。

5.3 使用NSOperation的子類NSInvocationOperation

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[operation start];

在沒(méi)有使用NSOperationQueue時(shí),NSInvocationOperation會(huì)在當(dāng)前線程(主線程或其他線程)內(nèi)運(yùn)行寓落。

5.4 使用NSOperation的子類NSBlockOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
[operation start];

結(jié)果:

NSOperation(OC)[97406:5880178] 1---<NSThread: 0x60400006e700>{number = 1, name = main}
NSOperation(OC)[97406:5880178] 1---<NSThread: 0x60400006e700>{number = 1, name = main}

在沒(méi)有使用NSOperationQueue時(shí)括丁,NSBlockOperation也會(huì)在當(dāng)前線程(主線程或其他線程)內(nèi)運(yùn)行。

另外伶选,NSBlockOperation還提供了一個(gè)方法 addExecutionBlock:史飞,用來(lái)添加額外的操作:

// 1.創(chuàng)建 NSBlockOperation 對(duì)象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    
   NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程

}];
// 2.添加額外的操作
[op addExecutionBlock:^{
    [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
    NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
    
}];
[op addExecutionBlock:^{
    [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
    NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
    
}];
[op addExecutionBlock:^{
    [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
    NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
    
}];
[op addExecutionBlock:^{
    [NSThread sleepForTimeInterval:1]; // 模擬耗時(shí)操作
    NSLog(@"5---%@", [NSThread currentThread]); // 打印當(dāng)前線程
    
}];
// 3.調(diào)用 start 方法開(kāi)始執(zhí)行操作
[op start];

某一次的運(yùn)行結(jié)果:

NSOperation(OC)[97709:5888750] 1---<NSThread: 0x600000469740>{number = 3, name = (null)}
NSOperation(OC)[97709:5888750] 5---<NSThread: 0x600000469740>{number = 3, name = (null)}
NSOperation(OC)[97709:5888748] 3---<NSThread: 0x600000469e40>{number = 4, name = (null)}
NSOperation(OC)[97709:5888498] 4---<NSThread: 0x60400007a600>{number = 1, name = main}
NSOperation(OC)[97709:5888758] 2---<NSThread: 0x604000463480>{number = 5, name = (null)}

addExecutionBlock:添加的操作和之前blockOperationWithBlock添加的操作是否在主線程或者是否開(kāi)多線程,是由系統(tǒng)決定考蕾,它們的地位是相同的祸憋,所以每一次執(zhí)行的結(jié)果可能不同。

5.5 NSOperation的自定義子類

除了上面兩個(gè)子類外肖卧,還可以通過(guò)重寫main方法來(lái)自定義子類蚯窥。

#import "AROperation.h"

@implementation AROperation

- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"自定義Operation---%@", [NSThread currentThread]);
        }
    }
}
@end

使用:

AROperation *op = [[AROperation alloc] init];
[op start];

5.6 NSOperationQueue

上面幾種情況都是沒(méi)有操作隊(duì)列,一般只在主線程運(yùn)行塞帐。而使用操作隊(duì)列就可以實(shí)現(xiàn)多線程了拦赠。操作隊(duì)列分兩種:
主隊(duì)列 : 凡是添加到主隊(duì)列中的操作,都會(huì)放到主線程中執(zhí)行
自定義隊(duì)列: 操作自動(dòng)放到子線程中執(zhí)行葵姥,同時(shí)包含了:串行荷鼠、并發(fā)功能。

5.6.1 創(chuàng)建隊(duì)列

// 主隊(duì)列獲取方法
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 自定義隊(duì)列創(chuàng)建方法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

5.6.2 添加操作到隊(duì)列中

兩種不同的添加方法:

  • - (void)addOperation:(NSOperation *)op;
- (void)addOperationToQueue {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.創(chuàng)建操作
    // 使用 NSInvocationOperation 創(chuàng)建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    // 使用 NSInvocationOperation 創(chuàng)建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    
    // 使用 NSBlockOperation 創(chuàng)建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    // 3.使用 addOperation: 添加所有操作到隊(duì)列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}
- (void) task1 {
    NSLog(@"1---%@", [NSThread currentThread]);
}
- (void) task2 {
    NSLog(@"2---%@", [NSThread currentThread]);
}

某一次的運(yùn)行結(jié)果:

NSOperation(OC)[7557:6218702] 2---<NSThread: 0x60400027f900>{number = 4, name = (null)}
NSOperation(OC)[7557:6218699] 1---<NSThread: 0x600000461a00>{number = 3, name = (null)}
NSOperation(OC)[7557:6218701] 4---<NSThread: 0x6040004606c0>{number = 5, name = (null)}
NSOperation(OC)[7557:6218700] 3---<NSThread: 0x604000460d40>{number = 6, name = (null)}
NSOperation(OC)[7557:6218700] 3---<NSThread: 0x604000460d40>{number = 6, name = (null)}
NSOperation(OC)[7557:6218701] 4---<NSThread: 0x6040004606c0>{number = 5, name = (null)}

并發(fā)運(yùn)行榔幸,執(zhí)行次序不確定允乐。

  • - (void)addOperationWithBlock:(void (^)(void))block; 不需要先創(chuàng)建操作矮嫉,直接添加block
- (void)addOperationWithBlockToQueue {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.使用 addOperationWithBlock: 添加操作到隊(duì)列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
}

某一次的運(yùn)行結(jié)果:

NSOperation(OC)[7772:6229770] 2---<NSThread: 0x600000277640>{number = 5, name = (null)}
NSOperation(OC)[7772:6229772] 1---<NSThread: 0x60400026bfc0>{number = 4, name = (null)}
NSOperation(OC)[7772:6229780] 3---<NSThread: 0x6040002686c0>{number = 3, name = (null)}
NSOperation(OC)[7772:6229770] 2---<NSThread: 0x600000277640>{number = 5, name = (null)}
NSOperation(OC)[7772:6229780] 3---<NSThread: 0x6040002686c0>{number = 3, name = (null)}
NSOperation(OC)[7772:6229772] 1---<NSThread: 0x60400026bfc0>{number = 4, name = (null)}

5.6.3 maxConcurrentOperationCount

NSOperationQueue 提供一個(gè)maxConcurrentOperationCount最大并發(fā)操作數(shù))屬性來(lái)控制串行還是并發(fā)。
maxConcurrentOperationCount 控制的不是并發(fā)線程的數(shù)量牍疏,而是一個(gè)隊(duì)列中同時(shí)能并發(fā)執(zhí)行的最大操作數(shù)蠢笋。而且一個(gè)操作也并非只能在一個(gè)線程中運(yùn)行。
maxConcurrentOperationCount默認(rèn)情況下為-1鳞陨,表示不進(jìn)行限制昨寞。

- (void)setMaxConcurrentOperationCount {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 2.設(shè)置最大并發(fā)操作數(shù)
    queue.maxConcurrentOperationCount = 1; // 串行隊(duì)列
    //     queue.maxConcurrentOperationCount = 2; // 并發(fā)隊(duì)列
    //     queue.maxConcurrentOperationCount = 8; // 并發(fā)隊(duì)列
    
    // 3.添加操作
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
}

當(dāng)最大并發(fā)操作數(shù)為1時(shí),也就是串行執(zhí)行時(shí)厦滤,控制臺(tái)中返回的結(jié)果順序是固定的援岩。而大于1時(shí),也就是并發(fā)執(zhí)行掏导,每一次執(zhí)行的順序就可能不同(控制臺(tái)中返回的額結(jié)果順序可能不同)享怀。當(dāng)然開(kāi)啟的線程數(shù)量是有系統(tǒng)決定的。

5.7 NSOperation之間的依賴

并行時(shí)碘菜,各個(gè)的操作的執(zhí)行順序是有系統(tǒng)決定凹蜈,程序員不能直接控制。但是NSOperation提高了依賴忍啸,來(lái)解決這個(gè)問(wèn)題仰坦。相關(guān)方法和屬性:

  • - (void)addDependency:(NSOperation *)op; 添加依賴,使當(dāng)前操作依賴于操作 op 的完成计雌。
  • - (void)removeDependency:(NSOperation *)op; 移除依賴悄晃,取消當(dāng)前操作對(duì)操作 op 的依賴。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組凿滤。
- (void)addDependency {
    // 1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 2.創(chuàng)建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }
    }];
    // 3.添加依賴
    [op2 addDependency:op1]; // 讓op2 依賴于 op1妈橄,則先執(zhí)行op1,再執(zhí)行op2
    
    // 4.添加操作到隊(duì)列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

運(yùn)行結(jié)果是固定的:

NSOperation(OC)[8977:6302811] 1---<NSThread: 0x604000277f80>{number = 3, name = (null)}
NSOperation(OC)[8977:6302811] 1---<NSThread: 0x604000277f80>{number = 3, name = (null)}
NSOperation(OC)[8977:6302810] 2---<NSThread: 0x604000274240>{number = 4, name = (null)}
NSOperation(OC)[8977:6302810] 2---<NSThread: 0x604000274240>{number = 4, name = (null)}

注意:不能添加相互依賴翁脆,會(huì)死鎖眷蚓,比如 A依賴B,B依賴A反番。
可以使用 removeDependency 來(lái)解除依賴關(guān)系沙热。
可以在不同的隊(duì)列之間依賴,反正就是這個(gè)依賴是添加到任務(wù)身上的罢缸,和隊(duì)列沒(méi)關(guān)系篙贸。

5.8 NSOperation 常用屬性和方法

  • 取消操作方法
    - (void)cancel; 實(shí)質(zhì)是標(biāo)記 isCancelled 狀態(tài)。

  • 判斷操作狀態(tài)方法

    • - (BOOL)isFinished; 判斷操作是否已經(jīng)結(jié)束枫疆。
    • - (BOOL)isCancelled; 判斷操作是否已經(jīng)標(biāo)記為取消爵川。
    • - (BOOL)isExecuting; 判斷操作是否正在在運(yùn)行。
    • - (BOOL)isReady; 判斷操作是否處于準(zhǔn)備就緒狀態(tài)息楔,這個(gè)值和操作的依賴關(guān)系相關(guān)寝贡。
  • 操作同步

    • - (void)waitUntilFinished; 阻塞當(dāng)前線程扒披,直到該操作結(jié)束⊥酶剩可用于線程執(zhí)行順序的同步谎碍。
    • - (void)setCompletionBlock:(void (^)(void))block; completionBlock 會(huì)在當(dāng)前操作執(zhí)行完畢時(shí)執(zhí)行 completionBlock。
    • - (void)addDependency:(NSOperation *)op; 添加依賴洞焙,使當(dāng)前操作依賴于操作 op 的完成。
    • - (void)removeDependency:(NSOperation *)op; 移除依賴拯啦,取消當(dāng)前操作對(duì)操作 op 的依賴澡匪。
    • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開(kāi)始執(zhí)行之前完成執(zhí)行的所有操作對(duì)象數(shù)組。

5.9 NSOperationQueue 常用屬性和方法

  1. 取消/暫停/恢復(fù)操作
  • - (void)cancelAllOperations; 可以取消隊(duì)列的所有操作褒链。
  • - (BOOL)isSuspended; 判斷隊(duì)列是否處于暫停狀態(tài)唁情。 YES 為暫停狀態(tài),NO 為恢復(fù)狀態(tài)甫匹。
  • - (void)setSuspended:(BOOL)b; 可設(shè)置操作的暫停和恢復(fù)甸鸟,YES 代表暫停隊(duì)列,NO 代表恢復(fù)隊(duì)列兵迅。
  1. 操作同步
  • - (void)waitUntilAllOperationsAreFinished; 阻塞當(dāng)前線程抢韭,直到隊(duì)列中的操作全部執(zhí)行完畢。
  1. 添加/獲取操作
  • - (void)addOperationWithBlock:(void (^)(void))block; 向隊(duì)列中添加一個(gè) NSBlockOperation 類型操作對(duì)象恍箭。
  • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向隊(duì)列中添加操作數(shù)組刻恭,wait 標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束
  • - (NSArray *)operations; 當(dāng)前在隊(duì)列中的操作數(shù)組(某個(gè)操作執(zhí)行結(jié)束后會(huì)自動(dòng)從這個(gè)數(shù)組清除)。
  • - (NSUInteger)operationCount; 當(dāng)前隊(duì)列中的操作數(shù)扯夭。
  1. 獲取隊(duì)列
  • + (id)currentQueue; 獲取當(dāng)前隊(duì)列鳍贾,如果當(dāng)前線程不是在 NSOperationQueue 上運(yùn)行則返回 nil。
  • + (id)mainQueue; 獲取主隊(duì)列交洗。

6 后記

雖然總結(jié)了很多骑科,但還有很多內(nèi)容沒(méi)有涉及和深入。
由于個(gè)人能力有限构拳,時(shí)間緊湊(實(shí)際我已經(jīng)花了很多時(shí)間?????♀????♀?)咆爽,文中難免有錯(cuò)誤,希望小伙伴批評(píng)指正隐圾。

示例代碼:andyRon/iOS-Multithreading

參考:
關(guān)于iOS多線程伍掀,我說(shuō),你聽(tīng)暇藏,沒(méi)準(zhǔn)你就懂了蜜笤!
談iOS多線程(NSThread、NSOperation盐碱、GCD)編程
iOS多線程:『GCD』詳盡總結(jié)
iOS多線程:『pthread把兔、NSThread』詳盡總結(jié)
iOS多線程:『NSOperation沪伙、NSOperationQueue』詳盡總結(jié)
關(guān)于iOS多線程,你看我就夠了
Grand Central Dispatch (GCD) and Dispatch Queues in Swift 3
WWDC-2017-Modernizing Grand Central Dispatch Usage

本文標(biāo)題: 關(guān)于iOS多線程县好,這邊勉強(qiáng)可以看看(OC&Swift)
本文作者: AndyRon
本文鏈接: http://andyron.com/2018/ios-multithreading.html
版權(quán)聲明: 本博客所有文章除特別聲明外围橡,均采用 CC BY-NC-SA 3.0 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處缕贡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翁授,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晾咪,更是在濱河造成了極大的恐慌收擦,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谍倦,死亡現(xiàn)場(chǎng)離奇詭異塞赂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)昼蛀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門宴猾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叼旋,你說(shuō)我怎么就攤上這事仇哆。” “怎么了送淆?”我有些...
    開(kāi)封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵税产,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我偷崩,道長(zhǎng)辟拷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任阐斜,我火速辦了婚禮衫冻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谒出。我一直安慰自己隅俘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布笤喳。 她就那樣靜靜地躺著为居,像睡著了一般。 火紅的嫁衣襯著肌膚如雪杀狡。 梳的紋絲不亂的頭發(fā)上蒙畴,一...
    開(kāi)封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼膳凝。 笑死碑隆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蹬音。 我是一名探鬼主播上煤,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼著淆!你這毒婦竟也來(lái)了劫狠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤永部,失蹤者是張志新(化名)和其女友劉穎嘉熊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體扬舒,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年凫佛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讲坎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愧薛,死狀恐怖晨炕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毫炉,我是刑警寧澤瓮栗,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瞄勾,受9級(jí)特大地震影響费奸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜进陡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一愿阐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趾疚,春花似錦缨历、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赡磅,卻和暖如春魄缚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仆邓。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工鲜滩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伴鳖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓徙硅,卻偏偏與公主長(zhǎng)得像榜聂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗓蘑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容