在 iOS 中其實目前有 4 套多線程方案俱饿,他們分別是:
- Pthreads
- NSThread
- GCD
- NSOperation & NSOperationQueue
使用方法
1.Pthreads (很少用)
POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API来氧。在類Unix操作系統(tǒng)(Unix攒钳、Linux挽荠、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程德挣。
簡單地說恭垦,這是一套在很多操作系統(tǒng)上都通用的多線程API,所以移植性很強(qiáng)(然并卵),當(dāng)然在 iOS 中也是可以的番挺。不過這是基于 c語言 的框架唠帝,使用起來諸多不便。
使用方法:
1.導(dǎo)入頭文件
#import <pthread.h>
2.然后創(chuàng)建線程玄柏,并執(zhí)行任務(wù)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
pthread_t thread;
//創(chuàng)建一個線程并自動執(zhí)行
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
這種多線程方式需要手動處理線程的各個狀態(tài)的轉(zhuǎn)換即管理生命周期襟衰,比如,這段代碼雖然創(chuàng)建了一個線程粪摘,但并沒有銷毀瀑晒。
2.NSThread
這種方式是完全面向?qū)ο蟮摹K阅憧梢灾苯硬倏鼐€程對象徘意,非常直觀和方便苔悦。但是,它的生命周期還是需要我們手動管理椎咧,所以這套方案也是偶爾用用间坐,比如 [NSThread currentThread],它可以獲取當(dāng)前線程類邑退,你就可以知道當(dāng)前線程的各種屬性竹宋,用于調(diào)試十分方便。下面來看看它的一些用法地技。
使用方法:
- 先創(chuàng)建線程類蜈七,再啟動
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動
[thread start];
- 創(chuàng)建并自動啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
其實,NSThread 用起來也挺簡單的莫矗,因為它就那幾種方法飒硅。同時,我們也只有在一些非常簡單的場景才會用 NSThread作谚。
3.GCD
Grand Central Dispatch三娩,宏大的中央處理中樞。它是蘋果為多核的并行運算提出的解決方案妹懒,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核雀监、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程眨唬、調(diào)度任務(wù)会前、銷毀線程),完全不需要我們管理匾竿,我們只需要告訴干什么就行瓦宜。同時它使用的也是 c語言,不過由于使用了 Block(Swift里叫做閉包)岭妖,使得使用起來更加方便临庇,而且靈活反璃。所以基本上大家都在使用 GCD 。
首先理解幾個概念
任務(wù):即操作假夺,你想要干什么淮蜈,說白了就是一段代碼,在 GCD 中就是一個 Block侄泽,所以添加任務(wù)十分方便礁芦。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間的區(qū)別是 是否會創(chuàng)建新的線程悼尾。
同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程柿扣,直到 Block 中的任務(wù)執(zhí)行完畢!
如果是 同步(sync) 操作闺魏,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢未状,然后當(dāng)前線程才會繼續(xù)往下運行。
如果是 異步(async)操作析桥,當(dāng)前線程會直接往下執(zhí)行司草,它不會阻塞當(dāng)前線程。** 隊列**:用于存放任務(wù)泡仗。一共有兩種隊列埋虹, 串行隊列 和 并行隊列。
串行隊列 中的任務(wù)會根據(jù)隊列的定義 FIFO 的執(zhí)行娩怎,一個接一個的先進(jìn)先出的進(jìn)行執(zhí)行搔课。放到串行隊列的任務(wù),GCD 會 FIFO(先進(jìn)先出) 地取出來一個截亦,執(zhí)行一個爬泥,然后取下一個,這樣一個一個的執(zhí)行崩瓤。
并行隊列 中的任務(wù) 根據(jù)同步或異步有不同的執(zhí)行方式袍啡。放到并行隊列的任務(wù),GCD 也會 FIFO的取出來却桶,但不同的是境输,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程肾扰。這樣由于取的動作很快畴嘶,忽略不計,看起來集晚,所有的任務(wù)都是一起執(zhí)行的。不過需要注意区匣,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量偷拔,所以如果任務(wù)很多蒋院,它并不會讓所有任務(wù)同時執(zhí)行。
創(chuàng)建隊列
- 主隊列:這是一個特殊的 串行隊列莲绰。什么是主隊列欺旧,大家都知道吧,它用于刷新 UI蛤签,任何需要刷新 UI 的工作都要在主隊列執(zhí)行辞友,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。
//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_main_queue();
- 自己創(chuàng)建的隊列:其中第一個參數(shù)是標(biāo)識符震肮,用于 DEBUG 的時候標(biāo)識唯一的隊列称龙,可以為空。大家可以看xcode的文檔查看參數(shù)意義戳晌。
//OBJECTIVE-C
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
- 全局并行隊列: 只要是并行任務(wù)一般都加入到這個隊列鲫尊。這是系統(tǒng)提供的一個并發(fā)隊列。
//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
創(chuàng)建任務(wù)
- 同步任務(wù):會阻塞當(dāng)前線程 (SYNC)
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
- 異步任務(wù):不會阻塞當(dāng)前線程 (ASYNC)
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
隊列組
隊列組可以將很多隊列添加到一個組里沦偎,這樣做的好處是疫向,當(dāng)這個組里所有的任務(wù)都執(zhí)行完了,隊列組會通過一個方法通知我們豪嚎。下面是使用方法搔驼,這是一個很實用的功能。
//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊列組的方法執(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.主隊列執(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.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
4.NSOperation & NSOperationQueue
NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο筇跆颍允褂闷饋砀美斫狻?大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊列 魄健。操作步驟也很好理解:
- 將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。
- 將此任務(wù)添加到一個 NSOperationQueue 對象中泼菌。
然后系統(tǒng)就會自動在執(zhí)行任務(wù)。至于同步還是異步啦租、串行還是并行請繼續(xù)往下看:
添加任務(wù)
值得說明的是哗伯,NSOperation 只是一個抽象類,所以不能封裝任務(wù)篷角。但它有 2 個子類用于封裝任務(wù)焊刹。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個 Operation 后恳蹲,需要調(diào)用 start 方法來啟動任務(wù)虐块,它會 默認(rèn)在當(dāng)前隊列同步執(zhí)行。當(dāng)然你也可以在中途取消一個任務(wù)嘉蕾,只需要調(diào)用其 cancel 方法即可贺奠。
- NSInvocationOperation : 需要傳入一個方法名。
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
- NSBlockOperation
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務(wù)
[operation start];
之前說過這樣的任務(wù)错忱,默認(rèn)會在當(dāng)前線程執(zhí)行儡率。但是 NSBlockOperation 還有一個方法:addExecutionBlock: 挂据,通過這個方法可以給 Operation 添加多個執(zhí)行 Block。這樣 Operation 中的任務(wù) 會并發(fā)執(zhí)行儿普,它會 在主線程和其它的多個線程 執(zhí)行這些任務(wù)崎逃,注意下面的打印結(jié)果:
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多個Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.開始任務(wù)
[operation start];
打印輸出
2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
注意:addExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會報錯:
‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'
創(chuàng)建隊列
看過上面的內(nèi)容就知道眉孩,我們可以調(diào)用一個 NSOperation 對象的 start() 方法來啟動這個任務(wù)个绍,但是這樣做他們默認(rèn)是 同步執(zhí)行 的。就算是 addExecutionBlock 方法浪汪,也會在 當(dāng)前線程和其他線程 中執(zhí)行巴柿,也就是說還是會占用當(dāng)前線程。這是就要用到隊列 NSOperationQueue 了吟宦。而且篮洁,按類型來說的話一共有兩種類型:主隊列、其他隊列殃姓。只要添加到隊列袁波,會自動調(diào)用任務(wù)的 start() 方法
- 主隊列
//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];
- 其他隊列
因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列蜗侈。那么通過初始化產(chǎn)生的隊列就是其他隊列了篷牌,因為只有這兩種隊列,除了主隊列踏幻,其他隊列就不需要名字了枷颊。
注意:其他隊列的任務(wù)會在其他線程并行執(zhí)行。
//1.創(chuàng)建一個其他隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//4.隊列添加任務(wù)
[queue addOperation:operation];
NSOperationQueue 有一個參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù)该面,用來設(shè)置最多可以讓多少個任務(wù)同時執(zhí)行夭苗。當(dāng)你把它設(shè)置為 1 的時候,那就是串行隔缀!
NSOperationQueue 還有一個添加任務(wù)的方法题造,- (void)addOperationWithBlock:(void (^)(void))block; ,這是不是和 GCD 差不多猾瘸?這樣就可以添加一個任務(wù)到隊列中了界赔,十分方便。
NSOperation 有一個非常實用的功能牵触,那就是添加依賴淮悼。比如有 3 個任務(wù):A: 從服務(wù)器上下載一張圖片,B:給這張圖片加個水印揽思,C:把圖片返回給服務(wù)器袜腥。這時就可以用到依賴了:
//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)建隊列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
注意:不能添加相互依賴,會死鎖钉汗,比如 A依賴B瞧挤,B依賴A锡宋;可以使用 removeDependency 來解除依賴關(guān)系儡湾;可以在不同的隊列之間依賴特恬,反正就是這個依賴是添加到任務(wù)身上的,和隊列沒關(guān)系徐钠。
其他用法
線程同步
所謂線程同步就是為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題癌刽,所采取的一種措施。當(dāng)然也有很多實現(xiàn)方法尝丐,請往下看:
- 互斥鎖 :給需要同步的代碼塊加一個互斥鎖显拜,就可以保證每次只有一個線程訪問此代碼塊。
@synchronized(self) {
//需要執(zhí)行的代碼塊
}
- 同步執(zhí)行 :我們可以使用多線程的知識爹袁,把多個線程都要執(zhí)行此段代碼添加到同一個串行隊列远荠,這樣就實現(xiàn)了線程同步的概念。當(dāng)然這里可以使用 GCD 和 NSOperation 兩種方案失息,我都寫出來譬淳。
//GCD
//需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中
dispatch_sync(queue, ^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
});
//NSOperation & NSOperationQueue
//重點:1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中
// 2. 設(shè)置 queue 的 maxConcurrentOperationCount 為 1
// 3. 如果后續(xù)操作需要Block中的結(jié)果盹兢,就需要調(diào)用每個操作的waitUntilFinished邻梆,阻塞當(dāng)前線程,一直等到當(dāng)前操作完成绎秒,才允許執(zhí)行后面的浦妄。waitUntilFinished 要在添加到隊列之后!
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ù)要做的事
延遲執(zhí)行
所謂延遲執(zhí)行就是延時一段時間再執(zhí)行某段代碼见芹。下面說一些常用方法剂娄。
- perform
// 3秒后自動調(diào)用self的run:方法,并且傳遞參數(shù):@"abc"
[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
- NSTimer
NSTimer 是iOS中的一個計時器類玄呛,除了延遲執(zhí)行還有很多用法阅懦,不過這里直說延遲執(zhí)行的用法。同樣只寫 OC 版的把鉴,Swift 也是相同的故黑。
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
單例模式
@interface Tool : NSObject <NSCopying>
+ (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
文章摘自:伯恩的遺產(chǎn)