關(guān)于iOS多線程涌乳,你看我就夠了(已更新)

作者:@翁呀偉呀授權(quán)本站轉(zhuǎn)載。
在這篇文章中甜癞,我將為你整理一下 iOS 開發(fā)中幾種多線程方案夕晓,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案例悠咱,在實(shí)際使用中感受它們的區(qū)別蒸辆。還有一點(diǎn)需要說明的是,這篇文章將會(huì)使用 Swift 和 Objective-c 兩種語言講解析既,雙語幼兒園躬贡。OK,let't begin!
概述
這篇文章中眼坏,我不會(huì)說多線程是什么拂玻、線程和進(jìn)程的區(qū)別、多線程有什么用宰译,當(dāng)然我也不會(huì)說什么是串行檐蚜、什么是并行等問題,這些我們應(yīng)該都知道的沿侈。
在 iOS 中其實(shí)目前有 4 套多線程方案闯第,他們分別是:
Pthreads
NSThread
GCD
NSOperation & NSOperationQueue
所以接下來,我會(huì)一一講解這些方案的使用方法和一些案例缀拭。在將這些內(nèi)容的時(shí)候咳短,我也會(huì)順帶說一些多線程周邊產(chǎn)品肃廓。比如:線程同步、 延時(shí)執(zhí)行诲泌、 單例模式等等盲赊。
Pthreads
其實(shí)這個(gè)方案不用說的,只是拿來充個(gè)數(shù)敷扫,為了讓大家了解一下就好了哀蘑。百度百科里是這么說的:
POSIX線程(POSIX threads),簡(jiǎn)稱Pthreads葵第,是線程的POSIX標(biāo)準(zhǔn)绘迁。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix卒密、Linux缀台、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程哮奇。
簡(jiǎn)單地說膛腐,這是一套在很多操作系統(tǒng)上都通用的多線程API,所以移植性很強(qiáng)(然并卵)鼎俘,當(dāng)然在 iOS 中也是可以的哲身。不過這是基于 c語言 的框架,使用起來這酸爽贸伐!感受一下:
OBJECTIVE-C
當(dāng)然第一步要包含頭文件
1

import

然后創(chuàng)建線程勘天,并執(zhí)行任務(wù)
- (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {
pthread_t thread;
//創(chuàng)建一個(gè)線程并自動(dòng)執(zhí)行
pthread_create(&thread, NULL, start, NULL);
}
void start(void data) {
NSLog(@"%@", [NSThread currentThread]);
returnNULL;
}
// 打印輸出: 2015-07-27 23:57:21.689 testThread[10616:2644653] {number = 2, name = (null)}
看代碼就會(huì)發(fā)現(xiàn)他需要
c語言函數(shù)
,這是比較蛋疼的捉邢,更蛋疼的是你需要手動(dòng)處理線程的各個(gè)狀態(tài)的轉(zhuǎn)換即管理生命周期脯丝,比如,這段代碼雖然創(chuàng)建了一個(gè)線程伏伐,但并沒有銷毀宠进。
SWIFT
很遺憾,在我目前的 swift1.2 中無法執(zhí)行這套方法秘案,原因是這個(gè)函數(shù)需要傳入一個(gè)函數(shù)指針 CFunctionPointer類型砰苍,但是目前 swift 無法將方法轉(zhuǎn)換成此類型潦匈。聽說 swift 2.0 引入一個(gè)新特性 @convention(c), 可以完成 Swift 方法轉(zhuǎn)換成 c 語言指針的阱高。在這里可以看到
那么,Pthreads 方案的多線程我就介紹這么多茬缩,畢竟做 iOS 開發(fā)幾乎不可能用到赤惊。但是如果你感興趣的話,或者說想要自己實(shí)現(xiàn)一套多線程方案凰锡,從底層開始定制未舟,那么可以去搜一下相關(guān)資料圈暗。
NSThread
這套方案是經(jīng)過蘋果封裝后的,并且完全面向?qū)ο蟮脑0颉K阅憧梢灾苯硬倏鼐€程對(duì)象员串,非常直觀和方便。但是昼扛,它的生命周期還是需要我們手動(dòng)管理寸齐,所以這套方案也是偶爾用用,比如 [NSThread currentThread]抄谐,它可以獲取當(dāng)前線程類渺鹦,你就可以知道當(dāng)前線程的各種屬性,用于調(diào)試十分方便蛹含。下面來看看它的一些用法毅厚。
創(chuàng)建并啟動(dòng)
先創(chuàng)建線程類,再啟動(dòng)
OBJECTIVE-C

// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動(dòng)
[thread start];

SWIFT

//創(chuàng)建
let thread = NSThread(target: self, selector:"run:", object: nil)
//啟動(dòng)
thread.start()

// 創(chuàng)建并自動(dòng)啟動(dòng)
OBJECTIVE-C

[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

SWIFT

 NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)

使用 NSObject 的方法創(chuàng)建并自動(dòng)啟動(dòng)
OBJECTIVE-C

[self performSelectorInBackground:@selector(run:) withObject:nil];

SWIFT
很遺憾 too! 蘋果認(rèn)為 performSelector: 不安全浦箱,所以在 Swift 去掉了這個(gè)方法吸耿。
Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
其他方法
除了創(chuàng)建啟動(dòng)外,NSThread 還以很多方法酷窥,下面我列舉一些常見的方法珍语,當(dāng)然我列舉的并不完整,更多方法大家可以去類的定義里去看竖幔。
OBJECTIVE-C

//取消線程
- (void)cancel;
//啟動(dòng)線程
- (void)start;
//判斷某個(gè)線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當(dāng)前線程暫停一段時(shí)間板乙,或者暫停到某個(gè)時(shí)刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

SWIFT
Swift的方法名字和OC的方法名都一樣,我就不浪費(fèi)空間列舉出來了拳氢。
其實(shí)募逞,NSThread 用起來也挺簡(jiǎn)單的,因?yàn)樗湍菐追N方法馋评。同時(shí)放接,我們也只有在一些非常簡(jiǎn)單的場(chǎng)景才會(huì)用 NSThread, 畢竟它還不夠智能,不能優(yōu)雅地處理多線程中的其他高級(jí)概念留特。所以接下來要說的內(nèi)容才是重點(diǎn)纠脾。
GCD
Grand Central Dispatch,聽名字就霸氣蜕青。它是蘋果為多核的并行運(yùn)算提出的解決方案苟蹈,所以會(huì)自動(dòng)合理地利用更多的CPU內(nèi)核(比如雙核、四核)右核,最重要的是它會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程慧脱、調(diào)度任務(wù)、銷毀線程)贺喝,完全不需要我們管理菱鸥,我們只需要告訴干什么就行宗兼。同時(shí)它使用的也是 c語言,不過由于使用了 Block(Swift里叫做閉包)氮采,使得使用起來更加方便殷绍,而且靈活。所以基本上大家都使用 GCD 這套方案鹊漠,老少咸宜篡帕,實(shí)在是居家旅行、殺人滅口贸呢,必備良藥镰烧。不好意思,有點(diǎn)中二楞陷,咱們繼續(xù)怔鳖。
任務(wù)和隊(duì)列
在 GCD 中,加入了兩個(gè)非常重要的概念:任務(wù)隊(duì)列固蛾。
任務(wù):即操作结执,你想要干什么,說白了就是一段代碼艾凯,在 GCD 中就是一個(gè) Block献幔,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行趾诗,他們之間的區(qū)別是 是否會(huì)創(chuàng)建新的線程蜡感。
同步執(zhí)行:只要是同步執(zhí)行的任務(wù),都會(huì)在當(dāng)前線程執(zhí)行恃泪,不會(huì)另開線程郑兴。
異步執(zhí)行:只要是異步執(zhí)行的任務(wù),都會(huì)另開線程贝乎,在別的線程執(zhí)行情连。
更新:
這里說的并不準(zhǔn)確,同步(sync) 和 異步(async) 的主要區(qū)別在于會(huì)不會(huì)阻塞當(dāng)前線程览效,直到 Block 中的任務(wù)執(zhí)行完畢却舀!
如果是 同步(sync) 操作,它會(huì)阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢锤灿,然后當(dāng)前線程才會(huì)繼續(xù)往下運(yùn)行挽拔。
如果是 異步(async)操作,當(dāng)前線程會(huì)直接往下執(zhí)行衡招,它不會(huì)阻塞當(dāng)前線程篱昔。
隊(duì)列:用于存放任務(wù)。一共有兩種隊(duì)列始腾, 串行隊(duì)列 和 并行隊(duì)列州刽。
串行隊(duì)列 中的任務(wù)會(huì)根據(jù)隊(duì)列的定義 FIFO 的執(zhí)行,一個(gè)接一個(gè)的先進(jìn)先出的進(jìn)行執(zhí)行浪箭。
更新:放到串行隊(duì)列的任務(wù)穗椅,GCD 會(huì) FIFO(先進(jìn)先出) 地取出來一個(gè),執(zhí)行一個(gè)奶栖,然后取下一個(gè)匹表,這樣一個(gè)一個(gè)的執(zhí)行。
并行隊(duì)列 中的任務(wù)根據(jù)同步或異步有不同的執(zhí)行方式宣鄙。雖然很繞袍镀,但請(qǐng)看下表:
更新:放到串行隊(duì)列的任務(wù),GCD 也會(huì) FIFO的取出來冻晤,但不同的是苇羡,它取出來一個(gè)就會(huì)放到別的線程,然后再取出來一個(gè)又放到另一個(gè)的線程鼻弧。這樣由于取的動(dòng)作很快设江,忽略不計(jì),看起來攘轩,所有的任務(wù)都是一起執(zhí)行的叉存。不過需要注意,GCD 會(huì)根據(jù)系統(tǒng)資源控制并行的數(shù)量度帮,所以如果任務(wù)很多歼捏,它并不會(huì)讓所有任務(wù)同時(shí)執(zhí)行。

創(chuàng)建隊(duì)列
主隊(duì)列:這是一個(gè)特殊的 串行隊(duì)列笨篷。什么是主隊(duì)列甫菠,大家都知道吧,它用于刷新 UI冕屯,任何需要刷新 UI 的工作都要在主隊(duì)列執(zhí)行寂诱,所以一般耗時(shí)的任務(wù)都要放到別的線程執(zhí)行。

//OBJECTIVE-C
dispatch_queue_t queue = ispatch_get_main_queue();
//SWIFT
let queue = ispatch_get_main_queue()

自己創(chuàng)建的隊(duì)列:凡是自己創(chuàng)建的隊(duì)列都是 串行隊(duì)列安聘。其中第一個(gè)參數(shù)是標(biāo)識(shí)符痰洒,用于 DEBUG 的時(shí)候標(biāo)識(shí)唯一的隊(duì)列,可以為空浴韭。大家可以看xcode的文檔查看參數(shù)意義丘喻。
更新:自己可以創(chuàng)建 串行隊(duì)列, 也可以創(chuàng)建 并行隊(duì)列∧罹保看下面的代碼(代碼已更新)泉粉,它有兩個(gè)參數(shù),第一個(gè)上面已經(jīng)說了,第二個(gè)才是最重要的嗡靡。
第二個(gè)參數(shù)用來表示創(chuàng)建的隊(duì)列是串行的還是并行的跺撼,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊(duì)列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊(duì)列讨彼。

//OBJECTIVE-C
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
//SWIFT
let queue = dispatch_queue_create("tk.bourne.testQueue", nil);

全局并行隊(duì)列:這應(yīng)該是唯一一個(gè)并行隊(duì)列歉井,只要是并行任務(wù)一般都加入到這個(gè)隊(duì)列。

//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//SWIFT
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

創(chuàng)建任務(wù)
同步任務(wù):不會(huì)另開線程 (SYNC)
OBJECTIVE-C

dispatch_sync(, ^{
//code here
  NSLog(@"%@", [NSThread currentThread]);
});

SWIFT

dispatch_sync(, { () -> Voidin
//code here
  println(NSThread.currentThread())
})

異步任務(wù):會(huì)另開線程 (ASYNC)
OBJECTIVE-C

dispatch_async(, ^{
//code here
  NSLog(@"%@", [NSThread currentThread]);
});

SWIFT

dispatch_async(, { () -> Voidin
//code here
  println(NSThread.currentThread())
})

更新:
為了更好的理解同步和異步哈误,和各種隊(duì)列的使用哩至,下面看兩個(gè)示例:
示例一:
以下代碼在主線程調(diào)用,結(jié)果是什么蜜自?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Voidin
  NSLog("sync - %@", NSThread.currentThread())
})
  NSLog("之后 - %@", NSThread.currentThread())

答案:
只會(huì)打印第一句:之前 -{number = 1, name = main} 菩貌,然后主線程就卡死了,你可以在界面上放一個(gè)按鈕重荠,你就會(huì)發(fā)現(xiàn)點(diǎn)不了了箭阶。
解釋:
同步任務(wù)會(huì)阻塞當(dāng)前線程,然后把 Block 中的任務(wù)放到指定的隊(duì)列中執(zhí)行晚缩,只有等到 Block 中的任務(wù)完成后才會(huì)讓當(dāng)前線程繼續(xù)往下運(yùn)行尾膊。
那么這里的步驟就是:打印完第一句后,dispatch_sync 立即阻塞當(dāng)前的主線程荞彼,然后把 Block 中的任務(wù)放到 main_queue 中冈敛,可以 main_queue 中的任務(wù)會(huì)被取出來放到主線程中執(zhí)行,但主線程這個(gè)時(shí)候已經(jīng)被阻塞了鸣皂,所以 Block 中的任務(wù)就不能完成抓谴,它不完成,dispatch_sync 就會(huì)一直阻塞主線程寞缝,這就是死鎖現(xiàn)象癌压。導(dǎo)致主線程一直卡死。
示例二:
以下代碼會(huì)產(chǎn)生什么結(jié)果荆陆?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
NSLog("之前 - %@", NSThread.currentThread())
dispatch_async(queue, { () -> Voidin
NSLog("sync之前 - %@", NSThread.currentThread())
dispatch_sync(queue, { () -> Voidin
NSLog("sync - %@", NSThread.currentThread())
})
NSLog("sync之后 - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())
/*答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 -{number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 -{number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 -{number = 1, name = main}*/

很明顯 sync - %@ 和 sync之后 - %@ 沒有被打印出來滩届!這是為什么呢?我們?cè)賮矸治鲆幌拢?br> 分析:
我們按執(zhí)行順序一步步來哦:
使用 DISPATCH_QUEUE_SERIAL 這個(gè)參數(shù)被啼,創(chuàng)建了一個(gè) 串行隊(duì)列帜消。
打印出 之前 - %@ 這句。
dispatch_async 異步執(zhí)行浓体,所以當(dāng)前線程不會(huì)被阻塞泡挺,于是有了兩條線程,一條當(dāng)前線程繼續(xù)往下打印出 之后 - %@這句, 另一臺(tái)執(zhí)行 Block 中的內(nèi)容打印 sync之前 - %@ 這句命浴。因?yàn)檫@兩條是并行的娄猫,所以打印的先后順序無所謂贱除。
注意,高潮來了∠蹦纾現(xiàn)在的情況和上一個(gè)例子一樣了月幌。dispatch_sync同步執(zhí)行,于是它所在的線程會(huì)被阻塞褂删,一直等到 sync 里的任務(wù)執(zhí)行完才會(huì)繼續(xù)往下飞醉。于是 sync 就高興的把自己 Block 中的任務(wù)放到 queue 中冲茸,可誰想 queue 是一個(gè)串行隊(duì)列屯阀,一次執(zhí)行一個(gè)任務(wù),所以 sync 的 Block 必須等到前一個(gè)任務(wù)執(zhí)行完畢轴术,可萬萬沒想到的是 queue 正在執(zhí)行的任務(wù)就是被 sync 阻塞了的那個(gè)难衰。于是又發(fā)生了死鎖。所以 sync 所在的線程被卡死了逗栽。剩下的兩句代碼自然不會(huì)打印盖袭。
隊(duì)列組
隊(duì)列組可以將很多隊(duì)列添加到一個(gè)組里,這樣做的好處是彼宠,當(dāng)這個(gè)組里所有的任務(wù)都執(zhí)行完了鳄虱,隊(duì)列組會(huì)通過一個(gè)方法通知我們。下面是使用方法凭峡,這是一個(gè)很實(shí)用的功能拙已。
OBJECTIVE-C

//1.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊(duì)列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
for(NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主隊(duì)列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for(NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue, ^{
for(NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后會(huì)自動(dòng)通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});

SWIFT

//1.創(chuàng)建隊(duì)列組
let group = dispatch_group_create()
//2.創(chuàng)建隊(duì)列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//3.多次使用隊(duì)列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue) { () -> Voidin
for_in0.. Voidin
for_in0.. Voidin
for_in0.. Voidin
NSLog("完成 - %@", NSThread.currentThread())
}
/*打印結(jié)果
2015-07-28 03:40:34.277 test[12540:3319271] group-03 - {number = 3, name = (null)}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319271] group-03 - {number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - {number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - {number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.277 test[12540:3319273] group-01 - {number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319271] group-03 - {number = 3, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - {number = 2, name = (null)}
2015-07-28 03:40:34.278 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.278 test[12540:3319273] group-01 - {number = 2, name = (null)}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] group-02 - {number = 1, name = main}
2015-07-28 03:40:34.279 test[12540:3319146] 完成 - {number = 1, name = main}*/

這些就是 GCD 的基本功能,但是它的能力遠(yuǎn)不止這些摧冀,等講完 NSOperation 后倍踪,我們?cè)賮砜纯此囊恍┢渌矫嬗猛尽6宜靼海灰阆胂罅蜇S富建车,你可以組合出更好的用法。
更新:關(guān)于GCD椒惨,還有兩個(gè)需要說的:
func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
這個(gè)方法重點(diǎn)是你傳入的 queue缤至,當(dāng)你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時(shí),這個(gè)方法會(huì)阻塞這個(gè) queue(注意是阻塞 queue 康谆,而不是阻塞當(dāng)前線程)领斥,一直等到這個(gè) queue 中排在它前面的任務(wù)都執(zhí)行完成后才會(huì)開始執(zhí)行自己,自己執(zhí)行完畢后秉宿,再會(huì)取消阻塞戒突,使這個(gè) queue 中排在它后面的任務(wù)繼續(xù)執(zhí)行。
如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了描睦。
func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
這個(gè)方法的使用和上一個(gè)一樣膊存,傳入 自定義的并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT),它和上一個(gè)方法一樣的阻塞 queue,不同的是 這個(gè)方法還會(huì) 阻塞當(dāng)前線程隔崎。
如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了今艺。
NSOperation和NSOperationQueue
NSOperation 是蘋果公司對(duì) GCD 的封裝,完全面向?qū)ο缶糇洌允褂闷饋砀美斫狻?大家可以看到 NSOperation 和 NSOperationQueue 分別對(duì)應(yīng) GCD 的 任務(wù) 和 隊(duì)列 虚缎。操作步驟也很好理解:
將要執(zhí)行的任務(wù)封裝到一個(gè) NSOperation 對(duì)象中。
將此任務(wù)添加到一個(gè) NSOperationQueue 對(duì)象中钓株。
然后系統(tǒng)就會(huì)自動(dòng)在執(zhí)行任務(wù)实牡。至于同步還是異步、串行還是并行請(qǐng)繼續(xù)往下看:
添加任務(wù)
值得說明的是轴合,NSOperation 只是一個(gè)抽象類创坞,所以不能封裝任務(wù)。但它有 2 個(gè)子類用于封裝任務(wù)受葛。分別是:NSInvocationOperation 和 NSBlockOperation 题涨。創(chuàng)建一個(gè) Operation 后,需要調(diào)用 start 方法來啟動(dòng)任務(wù)总滩,它會(huì) 默認(rèn)在當(dāng)前隊(duì)列同步執(zhí)行纲堵。當(dāng)然你也可以在中途取消一個(gè)任務(wù),只需要調(diào)用其 cancel 方法即可闰渔。
NSInvocationOperation : 需要傳入一個(gè)方法名席函。
OBJECTIVE-C

//1.創(chuàng)建NSInvocationOperation對(duì)象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selecto    r:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];

SWIFT
在 Swift 構(gòu)建的和諧社會(huì)里,是容不下 NSInvocationOperation 這種不是類型安全的敗類的澜建。蘋果如是說向挖。這里有相關(guān)解釋
NSBlockOperation
OBJECTIVE-C

//1.創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務(wù)
[operation start];

SWIFT

//1.創(chuàng)建NSBlockOperation對(duì)象
let operation = NSBlockOperation { () -> Voidin
println(NSThread.currentThread())
}
//2.開始任務(wù)
operation.start()

之前說過這樣的任務(wù),默認(rèn)會(huì)在當(dāng)前線程執(zhí)行炕舵。但是 NSBlockOperation 還有一個(gè)方法:addExecutionBlock: 何之,通過這個(gè)方法可以給 Operation 添加多個(gè)執(zhí)行 Block。這樣 Operation 中的任務(wù) 會(huì)并發(fā)執(zhí)行咽筋,它會(huì) 在主線程和其它的多個(gè)線程 執(zhí)行這些任務(wù)溶推,注意下面的打印結(jié)果:
OBJECTIVE-C

//1.創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多個(gè)Block
for(NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.開始任務(wù)
[operation start];

SWIFT

//1.創(chuàng)建NSBlockOperation對(duì)象
let operation = NSBlockOperation { () -> Voidin
NSLog("%@", NSThread.currentThread())
}
//2.添加多個(gè)Block
foriin0.. Voidin
NSLog("第%ld次 - %@", i, NSThread.currentThread())
}
}
//2.開始任務(wù)
operation.start()
/*打印輸出
2015-07-28 17:50:16.585 test[17527:4095467] 第2次 -{number = 1, name = main}
2015-07-28 17:50:16.585 test[17527:4095666] 第1次 -{number = 4, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095665]{number = 3, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095662] 第0次 -{number = 2, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095666] 第3次 -{number = 4, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095467] 第4次 -{number = 1, name = main}*/

NOTE:addExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會(huì)報(bào)錯(cuò):
‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'
NOTE:大家可能發(fā)現(xiàn)了一個(gè)問題奸攻,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢蒜危?原因是使用 print() / println() 輸出的話,它會(huì)簡(jiǎn)單地使用 流(stream) 的概念睹耐,學(xué)過 C++ 的都知道辐赞。它會(huì)把需要輸出的每個(gè)字符一個(gè)一個(gè)的輸出到控制臺(tái)。普通使用并沒有問題硝训,可是當(dāng)多線程同步輸出的時(shí)候問題就來了响委,由于很多 println() 同時(shí)打印新思,就會(huì)導(dǎo)致控制臺(tái)上的字符混亂的堆在一起,而NSLog() 就沒有這個(gè)問題赘风。到底是什么樣子的呢夹囚?你可以把上面 NSLog() 改為 println() ,然后一試便知邀窃。更多 NSLog() 與 println() 的區(qū)別看這里
自定義Operation
除了上面的兩種 Operation 以外荸哟,我們還可以自定義 Operation。自定義 Operation 需要繼承 NSOperation 類瞬捕,并實(shí)現(xiàn)其 main() 方法鞍历,因?yàn)樵谡{(diào)用 start() 方法的時(shí)候,內(nèi)部會(huì)調(diào)用 main() 方法完成相關(guān)邏輯山析。所以如果以上的兩個(gè)類無法滿足你的欲望的時(shí)候堰燎,你就需要自定義了掏父。你想要實(shí)現(xiàn)什么功能都可以寫在里面笋轨。除此之外,你還需要實(shí)現(xiàn) cancel() 在內(nèi)的各種方法赊淑。所以這個(gè)功能提供給高級(jí)玩家爵政,我在這里就不說了,等我需要用到時(shí)在研究它陶缺,到時(shí)候可能會(huì)再做更新钾挟。
創(chuàng)建隊(duì)列
看過上面的內(nèi)容就知道,我們可以調(diào)用一個(gè) NSOperation 對(duì)象的 start() 方法來啟動(dòng)這個(gè)任務(wù)饱岸,但是這樣做他們默認(rèn)是 同步執(zhí)行 的掺出。就算是 addExecutionBlock 方法,也會(huì)在 當(dāng)前線程和其他線程 中執(zhí)行苫费,也就是說還是會(huì)占用當(dāng)前線程汤锨。這是就要用到隊(duì)列 NSOperationQueue 了。而且百框,按類型來說的話一共有兩種類型:主隊(duì)列闲礼、其他隊(duì)列。只要添加到隊(duì)列铐维,會(huì)自動(dòng)調(diào)用任務(wù)的 start() 方法
主隊(duì)列
細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn)柬泽,每套多線程方案都會(huì)有一個(gè)主線程(當(dāng)然啦诸典,說的是iOS中佣渴,像 pthread 這種多系統(tǒng)的方案并沒有,因?yàn)?UI線程 理論需要每種操作系統(tǒng)自己定制)五垮。這是一個(gè)特殊的線程睬棚,必須串行第煮。所以添加到主隊(duì)列的任務(wù)都會(huì)一個(gè)接一個(gè)地排著隊(duì)在主線程處理有决。
1
2
3
4//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//SWIFT
let queue = NSOperationQueue.mainQueue()
其他隊(duì)列
因?yàn)橹麝?duì)列比較特殊,所以會(huì)單獨(dú)有一個(gè)類方法來獲得主隊(duì)列空盼。那么通過初始化產(chǎn)生的隊(duì)列就是其他隊(duì)列了书幕,因?yàn)橹挥羞@兩種隊(duì)列,除了主隊(duì)列揽趾,其他隊(duì)列就不需要名字了台汇。
注意:其他隊(duì)列的任務(wù)會(huì)在其他線程并行執(zhí)行。
OBJECTIVE-C

//1.創(chuàng)建一個(gè)其他隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多個(gè)Block
for(NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//4.隊(duì)列添加任務(wù)
[queue addOperation:operation];

SWIFT

//1.創(chuàng)建其他隊(duì)列
let queue = NSOperationQueue()
//2.創(chuàng)建NSBlockOperation對(duì)象
let operation = NSBlockOperation { () -> Voidin
NSLog("%@", NSThread.currentThread())
}
//3.添加多個(gè)Block
foriin0.. Voidin
NSLog("第%ld次 - %@", i, NSThread.currentThread())
}
}
//4.隊(duì)列添加任務(wù)
queue.addOperation(operation)
/*打印輸出
2015-07-28 20:26:28.463 test[18622:4443534]{number = 5, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443536] 第2次 -{number = 2, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443535] 第0次 -{number = 4, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443533] 第1次 -{number = 3, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443534] 第3次 -{number = 5, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443536] 第4次 -{number = 2, name = (null)}*/

OK, 這時(shí)應(yīng)該發(fā)問了篱瞎,大家將 NSOperationQueue 與 GCD的隊(duì)列 相比較就會(huì)發(fā)現(xiàn)苟呐,這里沒有并行隊(duì)列,那如果我想要10個(gè)任務(wù)在其他線程串行的執(zhí)行怎么辦俐筋?
這就是蘋果封裝的妙處牵素,你不用管串行、并行澄者、同步笆呆、異步這些名詞。NSOperationQueue 有一個(gè)參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù)粱挡,用來設(shè)置最多可以讓多少個(gè)任務(wù)同時(shí)執(zhí)行赠幕。當(dāng)你把它設(shè)置為 1 的時(shí)候,他不就是串行了嘛询筏!
NSOperationQueue 還有一個(gè)添加任務(wù)的方法榕堰,- (void)addOperationWithBlock:(void (^)(void))block; ,這是不是和 GCD 差不多嫌套?這樣就可以添加一個(gè)任務(wù)到隊(duì)列中了逆屡,十分方便。
NSOperation 有一個(gè)非常實(shí)用的功能踱讨,那就是添加依賴魏蔗。比如有 3 個(gè)任務(wù):A: 從服務(wù)器上下載一張圖片,B:給這張圖片加個(gè)水印勇蝙,C:把圖片返回給服務(wù)器沫勿。這時(shí)就可以用到依賴了:
OBJECTIVE-C

//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印   - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.設(shè)置依賴
[operation2 addDependency:operation1];//任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2];//任務(wù)三依賴任務(wù)二
//5.創(chuàng)建隊(duì)列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

SWIFT

//1.任務(wù)一:下載圖片
let operation1 = NSBlockOperation { () -> Voidin
NSLog("下載圖片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//2.任務(wù)二:打水印
let operation2 = NSBlockOperation { () -> Voidin
NSLog("打水印   - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
 }
//3.任務(wù)三:上傳圖片
let operation3 = NSBlockOperation { () -> Voidin
NSLog("上傳圖片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//4.設(shè)置依賴
operation2.addDependency(operation1)//任務(wù)二依賴任務(wù)一
operation3.addDependency(operation2)//任務(wù)三依賴任務(wù)二
//5.創(chuàng)建隊(duì)列并加入任務(wù)
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished:false)
/*打印結(jié)果
2015-07-28 21:24:28.622 test[19392:4637517] 下載圖片 -{number = 2, name = (null)}
2015-07-28 21:24:29.622 test[19392:4637515] 打水印 -{number = 3, name = (null)}
2015-07-28 21:24:30.627 test[19392:4637515] 上傳圖片 -{number = 3, name = (null)}*//

注意:不能添加相互依賴,會(huì)死鎖味混,比如 A依賴B产雹,B依賴A。
可以使用 removeDependency 來解除依賴關(guān)系翁锡。
可以在不同的隊(duì)列之間依賴蔓挖,反正就是這個(gè)依賴是添加到任務(wù)身上的,和隊(duì)列沒關(guān)系馆衔。
其他方法
以上就是一些主要方法, 下面還有一些常用方法需要大家注意:
NSOperation

BOOL executing;//判斷任務(wù)是否正在執(zhí)行
BOOL finished;//判斷任務(wù)是否完成
void (^completionBlock)(void);//用來設(shè)置完成后需要執(zhí)行的操作
- (void)cancel;//取消任務(wù)
- (void)waitUntilFinished;//阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢

NSOperationQueue

NSUInteger operationCount;//獲取隊(duì)列的任務(wù)數(shù)
- (void)cancelAllOperations;//取消隊(duì)列中所有的任務(wù)
- (void)waitUntilAllOperationsAreFinished;//阻塞當(dāng)前線程直到此隊(duì)列中的所有任務(wù)執(zhí)行完畢
[queue setSuspended:YES];// 暫停queue
[queue setSuspended:NO];// 繼續(xù)queue

好啦瘟判,到這里差不多就講完了怨绣。當(dāng)然,我講的并不完整拷获,可能有一些知識(shí)我并沒有講到篮撑,但作為常用方法,這些已經(jīng)足夠了匆瓜。不過我在這里只是告訴你了一些方法的功能赢笨,只是怎么把他們用到合適的地方,就需要多多實(shí)踐了驮吱。下面我會(huì)說一些關(guān)于多線程的案例茧妒,是大家更加什么地了解。
其他用法
在這部分左冬,我會(huì)說一些和多線程知識(shí)相關(guān)的案例桐筏,可能有些很簡(jiǎn)單,大家早都知道的拇砰,不過因?yàn)檫@篇文章講的是多線程嘛梅忌,所以應(yīng)該盡可能的全面嘛。還有就是毕匀,我會(huì)盡可能的使用多種方法實(shí)現(xiàn)铸鹰,讓大家看看其中的區(qū)別。
線程同步
所謂線程同步就是為了防止多個(gè)線程搶奪同一個(gè)資源造成的數(shù)據(jù)安全問題皂岔,所采取的一種措施。當(dāng)然也有很多實(shí)現(xiàn)方法展姐,請(qǐng)往下看:
互斥鎖:給需要同步的代碼塊加一個(gè)互斥鎖躁垛,就可以保證每次只有一個(gè)線程訪問此代碼塊。
OBJECTIVE-C

@synchronized(self) {
//需要執(zhí)行的代碼塊
}

SWIFT

objc_sync_enter(self)
//需要執(zhí)行的代碼塊
objc_sync_exit(self)

同步執(zhí)行:我們可以使用多線程的知識(shí)圾笨,把多個(gè)線程都要執(zhí)行此段代碼添加到同一個(gè)串行隊(duì)列教馆,這樣就實(shí)現(xiàn)了線程同步的概念。當(dāng)然這里可以使用 GCD 和 NSOperation 兩種方案擂达,我都寫出來土铺。
OBJECTIVE-C

//GCD
//需要一個(gè)全局變量queue,要讓所有線程的這個(gè)操作都加到一個(gè)queue中
dispatch_sync(queue, ^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
});
//NSOperation & NSOperationQueue
//重點(diǎn):1. 全局的 NSOperationQueue, 所有的操作添加到同一個(gè)queue中
//       2. 設(shè)置 queue 的 maxConcurrentOperationCount 為 1
//       3. 如果后續(xù)操作需要Block中的結(jié)果板鬓,就需要調(diào)用每個(gè)操作的waitUntilFinished悲敷,阻塞當(dāng)前線程,一直等到當(dāng)前操作完成俭令,才允許執(zhí)行后面的后德。waitUntilFinished 要在添加到隊(duì)列之后!
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
}];
[queue addOperation:operation];
[operation waitUntilFinished];
//后續(xù)要做的事

SWIFT
這里的 swift 代碼抄腔,我就不寫了瓢湃,因?yàn)槊烤涠家粯永碚牛皇钦Z法不同而已,照著 OC 的代碼就能寫出 Swift 的绵患。這篇文章已經(jīng)老長(zhǎng)老長(zhǎng)了雾叭,我就不浪費(fèi)篇幅了,又不是高中寫作文落蝙。
延遲執(zhí)行
所謂延遲執(zhí)行就是延時(shí)一段時(shí)間再執(zhí)行某段代碼拷况。下面說一些常用方法。
perform
OBJECTIVE-C

// 3秒后自動(dòng)調(diào)用self的run:方法掘殴,并且傳遞參數(shù):@"abc"
[self performSelector:@selector(run:) withObject:@"abc"afterDelay:3];

SWIFT
之前就已經(jīng)說過赚瘦,Swift 里去掉了這個(gè)方法。
GCD
可以使用 GCD 中的 dispatch_after 方法奏寨,OC 和 Swift 都可以使用起意,這里只寫 OC 的,Swift 的是一樣的病瞳。
OBJECTIVE-C

// 創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設(shè)置延時(shí)揽咕,單位秒
double delay = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
// 3秒后需要執(zhí)行的任務(wù)
});
NSTimer
NSTimer 是iOS中的一個(gè)計(jì)時(shí)器類,除了延遲執(zhí)行還有很多用法套菜,不過這里直說延遲執(zhí)行的用法亲善。同樣只寫 OC 版的,Swift 也是相同的逗柴。

OBJECTIVE-C

[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc"repeats:NO];

單例模式
至于什么是單例模式蛹头,我也不多說,我只說說一般怎么實(shí)現(xiàn)戏溺。在 Objective-C 中渣蜗,實(shí)現(xiàn)單例的方法已經(jīng)很具體了,雖然有別的方法旷祸,但是一般都是用一個(gè)標(biāo)準(zhǔn)的方法了耕拷,下面來看看。
OBJECTIVE-C

@interface Tool : NSObject
+ (instancetype)sharedTool;
@end
@implementation Tool
static id _instance;
+ (instancetype)sharedTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[Tool alloc] init];
});
return_instance;
}
@end

這里之所以將單例模式托享,是因?yàn)槠渲杏玫搅?GCD 的 dispatch_once 方法骚烧。下面看 Swift 中的單例模式,在Swift中單例模式非常簡(jiǎn)單闰围!想知道怎么從 OC 那么復(fù)雜的方法變成下面的寫法的赃绊,請(qǐng)看這里
SWIFT

class Tool: NSObject {
static let sharedTool = Tool()
// 私有化構(gòu)造方法,阻止其他對(duì)象使用這個(gè)類的默認(rèn)的'()'構(gòu)造方法
private override init() {}
}

從其他線程回到主線程的方法
我們都知道在其他線程操作完成后必須到主線程更新UI辫诅。所以凭戴,介紹完所有的多線程方案后,我們來看看有哪些方法可以回到主線程炕矮。
NSThread

//Objective-C
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
//Swift
//swift 取消了 performSelector 方法么夫。

GCD

//Objective-C
dispatch_async(dispatch_get_main_queue(), ^{
});
//Swift
dispatch_async(dispatch_get_main_queue(), { () -> Voidin
})

NSOperationQueue

//Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Voidin
}

總結(jié)
好的吧者冤,總算寫完了,純手敲6k多字档痪,感動(dòng)死我了涉枫。花了兩天腐螟,時(shí)間跨度有點(diǎn)大愿汰,所以可能有些地方上段不接下段或者有的地方不完整,如果你看著比較費(fèi)力或者有什么地方有問題乐纸,都可以在評(píng)論區(qū)告訴我衬廷,我會(huì)及時(shí)修改的。當(dāng)然啦汽绢,多線程的東西也不止這些吗跋,題目也就只是個(gè)題目,不要當(dāng)真宁昭。想要了解更多的東西跌宛,還得自己去網(wǎng)上挖掘相關(guān)資料。多看看官方文檔积仗。實(shí)在是編不下去了疆拘,大家好好看~。對(duì)了寂曹,看我寫的這么賣力哎迄,不打賞也得點(diǎn)個(gè)喜歡吧。
更新:第一次放出來的時(shí)候稀颁,有很多地方有錯(cuò)誤芬失,很感謝有朋友提出來了。如果你看到有錯(cuò)誤的地方匾灶,一定記得指出來,這樣對(duì)大家都有幫助租漂。還有一點(diǎn)對(duì)初學(xué)者來說阶女,遇到不懂的方法,最好的辦法就是查看官方文檔哩治,那里是最準(zhǔn)確的秃踩,就算有幾個(gè)單詞不認(rèn)識(shí),查一下就好了业筏,不會(huì)影響對(duì)整體的理解憔杨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蒜胖,隨后出現(xiàn)的幾起案子消别,更是在濱河造成了極大的恐慌抛蚤,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寻狂,死亡現(xiàn)場(chǎng)離奇詭異岁经,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛇券,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門缀壤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纠亚,你說我怎么就攤上這事塘慕。” “怎么了蒂胞?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵图呢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我啤誊,道長(zhǎng)岳瞭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任蚊锹,我火速辦了婚禮瞳筏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘牡昆。我一直安慰自己姚炕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布丢烘。 她就那樣靜靜地躺著柱宦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪播瞳。 梳的紋絲不亂的頭發(fā)上掸刊,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音赢乓,去河邊找鬼忧侧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛牌芋,可吹牛的內(nèi)容都是我干的蚓炬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躺屁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肯夏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤驯击,失蹤者是張志新(化名)和其女友劉穎烁兰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體余耽,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缚柏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碟贾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片币喧。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袱耽,靈堂內(nèi)的尸體忽然破棺而出杀餐,到底是詐尸還是另有隱情,我是刑警寧澤朱巨,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布史翘,位于F島的核電站,受9級(jí)特大地震影響冀续,放射性物質(zhì)發(fā)生泄漏琼讽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一洪唐、第九天 我趴在偏房一處隱蔽的房頂上張望钻蹬。 院中可真熱鬧,春花似錦凭需、人聲如沸问欠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顺献。三九已至,卻和暖如春枯怖,著一層夾襖步出監(jiān)牢的瞬間注整,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工度硝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留设捐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓塘淑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蚂斤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子存捺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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

  • 在這篇文章中捌治,我將為你整理一下 iOS 開發(fā)中幾種多線程方案岗钩,以及其使用方法和注意事項(xiàng)。當(dāng)然也會(huì)給出幾種多線程的案...
    伯恩的遺產(chǎn)閱讀 274,515評(píng)論 251 2,332
  • 學(xué)習(xí)多線程肖油,轉(zhuǎn)載兩篇大神的帖子兼吓,留著以后回顧!第一篇:關(guān)于iOS多線程森枪,你看我就夠了 第二篇:GCD使用經(jīng)驗(yàn)與技巧...
    John_LS閱讀 620評(píng)論 0 3
  • 在這篇文章中视搏,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)县袱。當(dāng)然也會(huì)給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 603評(píng)論 0 0
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了浑娜!去的時(shí)候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似式散。...
    Dely閱讀 9,238評(píng)論 21 42
  • 在這篇文章中筋遭,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)暴拄。當(dāng)然也會(huì)給出幾種多線程的案...
    被吹落的風(fēng)閱讀 123評(píng)論 0 0