在這篇文章中假勿,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項态鳖。當然也會給出幾種多線程的案例转培,在實際使用中感受它們的區(qū)別。還有一點需要說明的是浆竭,這篇文章將會使用 Swift 和 Objective-c 兩種語言講解浸须,雙語幼兒園。OK邦泄,let't begin!
概述
這篇文章中删窒,我不會說多線程是什么、線程和進程的區(qū)別虎韵、多線程有什么用易稠,當然我也不會說什么是串行、什么是并行等問題包蓝,這些我們應該都知道的驶社。
在 iOS 中其實目前有 4 套多線程方案,他們分別是:
Pthreads
NSThread
GCD
NSOperation & NSOperationQueue
所以接下來测萎,我會一一講解這些方案的使用方法和一些案例亡电。在將這些內容的時候,我也會順帶說一些多線程周邊產品硅瞧。比如: 線程同步份乒、 延時執(zhí)行棍苹、 單例模式 等等惜互。
Pthreads
其實這個方案不用說的,只是拿來充個數(shù)橙依,為了讓大家了解一下就好了枣接。百度百科里是這么說的:
POSIX線程(POSIX threads)颂暇,簡稱Pthreads,是線程的POSIX標準但惶。該標準定義了創(chuàng)建和操縱線程的一整套API耳鸯。在類Unix操作系統(tǒng)(Unix湿蛔、Linux、Mac OS X等)中县爬,都使用Pthreads作為操作系統(tǒng)的線程阳啥。
簡單地說,這是一套在很多操作系統(tǒng)上都通用的多線程API财喳,所以移植性很強(然并卵)察迟,當然在 iOS 中也是可以的。不過這是基于 c語言 的框架纲缓,使用起來這酸爽卷拘!感受一下:
OBJECTIVE-C
當然第一步要包含頭文件
#import
然后創(chuàng)建線程喊废,并執(zhí)行任務
- (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;
}
打印輸出:
2015-07-27 23:57:21.689 testThread[10616:2644653] {number = 2, name = (null)}
看代碼就會發(fā)現(xiàn)他需要 c語言函數(shù)祝高,這是比較蛋疼的,更蛋疼的是你需要手動處理線程的各個狀態(tài)的轉換即管理生命周期污筷,比如工闺,這段代碼雖然創(chuàng)建了一個線程,但并沒有銷毀瓣蛀。
SWIFT
很遺憾陆蟆,在我目前的 swift1.2 中無法執(zhí)行這套方法,原因是這個函數(shù)需要傳入一個函數(shù)指針 CFunctionPointer類型惋增,但是目前 swift 無法將方法轉換成此類型叠殷。聽說 swift 2.0 引入一個新特性 @convention(c), 可以完成 Swift 方法轉換成 c 語言指針的。在這里可以看到
那么诈皿,Pthreads 方案的多線程我就介紹這么多林束,畢竟做 iOS 開發(fā)幾乎不可能用到。但是如果你感興趣的話稽亏,或者說想要自己實現(xiàn)一套多線程方案壶冒,從底層開始定制,那么可以去搜一下相關資料截歉。
NSThread
這套方案是經過蘋果封裝后的胖腾,并且完全面向對象的。所以你可以直接操控線程對象瘪松,非常直觀和方便咸作。但是,它的生命周期還是需要我們手動管理宵睦,所以這套方案也是偶爾用用记罚,比如 [NSThread currentThread],它可以獲取當前線程類状飞,你就可以知道當前線程的各種屬性毫胜,用于調試十分方便书斜。下面來看看它的一些用法。
創(chuàng)建并啟動
先創(chuàng)建線程類酵使,再啟動
OBJECTIVE-C
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動
[thread start];
SWIFT
//創(chuàng)建
let thread = NSThread(target: self, selector: "run:", object: nil)
//啟動
thread.start()
創(chuàng)建并自動啟動
OBJECTIVE-C
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
SWIFT
NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)
使用 NSObject 的方法創(chuàng)建并自動啟動
OBJECTIVE-C
[self performSelectorInBackground:@selector(run:) withObject:nil];
SWIFT
很遺憾 too! 蘋果認為 performSelector: 不安全荐吉,所以在 Swift 去掉了這個方法。
Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
其他方法
除了創(chuàng)建啟動外口渔,NSThread 還以很多方法样屠,下面我列舉一些常見的方法,當然我列舉的并不完整缺脉,更多方法大家可以去類的定義里去看痪欲。
OBJECTIVE-C
//取消線程
- (void)cancel;
//啟動線程
- (void)start;
//判斷某個線程的狀態(tài)的屬性
@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;
//使當前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
SWIFT
Swift的方法名字和OC的方法名都一樣攻礼,我就不浪費空間列舉出來了业踢。
其實,NSThread 用起來也挺簡單的礁扮,因為它就那幾種方法知举。同時,我們也只有在一些非常簡單的場景才會用 NSThread, 畢竟它還不夠智能太伊,不能優(yōu)雅地處理多線程中的其他高級概念雇锡。所以接下來要說的內容才是重點。
GCD
Grand Central Dispatch僚焦,聽名字就霸氣锰提。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核芳悲、四核)立肘,最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調度任務芭概、銷毀線程)赛不,完全不需要我們管理,我們只需要告訴干什么就行罢洲。同時它使用的也是 c語言踢故,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便惹苗,而且靈活殿较。所以基本上大家都使用 GCD 這套方案,老少咸宜桩蓉,實在是居家旅行淋纲、殺人滅口,必備良藥院究。不好意思洽瞬,有點中二本涕,咱們繼續(xù)。
任務和隊列
在 GCD 中伙窃,加入了兩個非常重要的概念: 任務 和 隊列菩颖。
任務:即操作,你想要干什么为障,說白了就是一段代碼晦闰,在 GCD 中就是一個 Block,所以添加任務十分方便鳍怨。任務有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行呻右,他們之間的區(qū)別是 是否會創(chuàng)建新的線程。
同步執(zhí)行:只要是同步執(zhí)行的任務鞋喇,都會在當前線程執(zhí)行声滥,不會另開線程。
異步執(zhí)行:只要是異步執(zhí)行的任務确徙,都會另開線程醒串,在別的線程執(zhí)行。
更新:
這里說的并不準確鄙皇,同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當前線程,直到 Block 中的任務執(zhí)行完畢仰挣!
如果是 同步(sync) 操作伴逸,它會阻塞當前線程并等待 Block 中的任務執(zhí)行完畢,然后當前線程才會繼續(xù)往下運行膘壶。
如果是 異步(async)操作错蝴,當前線程會直接往下執(zhí)行,它不會阻塞當前線程颓芭。
隊列:用于存放任務顷锰。一共有兩種隊列, 串行隊列 和 并行隊列亡问。
串行隊列 中的任務會根據(jù)隊列的定義 FIFO 的執(zhí)行官紫,一個接一個的先進先出的進行執(zhí)行。
更新:放到串行隊列的任務州藕,GCD 會 FIFO(先進先出) 地取出來一個束世,執(zhí)行一個,然后取下一個床玻,這樣一個一個的執(zhí)行毁涉。
并行隊列 中的任務根據(jù)同步或異步有不同的執(zhí)行方式。雖然很繞锈死,但請看下表:
更新:放到串行隊列的任務贫堰,GCD 也會 FIFO的取出來穆壕,但不同的是,它取出來一個就會放到別的線程其屏,然后再取出來一個又放到另一個的線程粱檀。這樣由于取的動作很快,忽略不計漫玄,看起來茄蚯,所有的任務都是一起執(zhí)行的。不過需要注意睦优,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量渗常,所以如果任務很多,它并不會讓所有任務同時執(zhí)行汗盘。
blob.png
創(chuàng)建隊列
主隊列:這是一個特殊的 串行隊列皱碘。什么是主隊列,大家都知道吧隐孽,它用于刷新 UI癌椿,任何需要刷新 UI 的工作都要在主隊列執(zhí)行,所以一般耗時的任務都要放到別的線程執(zhí)行菱阵。
//OBJECTIVE-C
dispatch_queue_t queue = ispatch_get_main_queue();
//SWIFT
let queue = ispatch_get_main_queue()
自己創(chuàng)建的隊列:凡是自己創(chuàng)建的隊列都是 串行隊列踢俄。其中第一個參數(shù)是標識符,用于 DEBUG 的時候標識唯一的隊列晴及,可以為空都办。大家可以看xcode的文檔查看參數(shù)意義。
更新:自己可以創(chuàng)建 串行隊列, 也可以創(chuàng)建 并行隊列虑稼×斩ぃ看下面的代碼(代碼已更新),它有兩個參數(shù)蛛倦,第一個上面已經說了歌懒,第二個才是最重要的。
第二個參數(shù)用來表示創(chuàng)建的隊列是串行的還是并行的溯壶,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊列及皂。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊列。
//OBJECTIVE-C
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
//SWIFT
let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
全局并行隊列:這應該是唯一一個并行隊列茸塞,只要是并行任務一般都加入到這個隊列躲庄。
//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)建任務
同步任務:不會另開線程 (SYNC)
OBJECTIVE-C
dispatch_sync(, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
SWIFT
dispatch_sync(, { () -> Void in
//code here
println(NSThread.currentThread())
})
異步任務:會另開線程 (ASYNC)
OBJECTIVE-C
dispatch_async(, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
Swift
dispatch_async(, { () -> Void in
//code here
println(NSThread.currentThread())
})
更新:
為了更好的理解同步和異步,和各種隊列的使用钾虐,下面看兩個示例:
示例一:
以下代碼在主線程調用噪窘,結果是什么?
NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())
答案:
只會打印第一句:之前 - {number = 1, name = main} ,然后主線程就卡死了倔监,你可以在界面上放一個按鈕直砂,你就會發(fā)現(xiàn)點不了了。
解釋:
同步任務會阻塞當前線程浩习,然后把 Block 中的任務放到指定的隊列中執(zhí)行静暂,只有等到 Block 中的任務完成后才會讓當前線程繼續(xù)往下運行。
那么這里的步驟就是:打印完第一句后谱秽,dispatch_sync 立即阻塞當前的主線程洽蛀,然后把 Block 中的任務放到 main_queue 中,可以 main_queue 中的任務會被取出來放到主線程中執(zhí)行疟赊,但主線程這個時候已經被阻塞了郊供,所以 Block 中的任務就不能完成,它不完成近哟,dispatch_sync 就會一直阻塞主線程驮审,這就是死鎖現(xiàn)象。導致主線程一直卡死吉执。
示例二:
以下代碼會產生什么結果疯淫?
let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
NSLog("之前 - %@", NSThread.currentThread())
dispatch_async(queue, { () -> Void in
NSLog("sync之前 - %@", NSThread.currentThread())
dispatch_sync(queue, { () -> Void in
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之后 - %@ 沒有被打印出來!這是為什么呢戳玫?我們再來分析一下:
分析:
我們按執(zhí)行順序一步步來哦:
使用 DISPATCH_QUEUE_SERIAL 這個參數(shù)熙掺,創(chuàng)建了一個 串行隊列。
打印出 之前 - %@ 這句量九。
dispatch_async 異步執(zhí)行适掰,所以當前線程不會被阻塞,于是有了兩條線程荠列,一條當前線程繼續(xù)往下打印出 之后 - %@這句, 另一臺執(zhí)行 Block 中的內容打印 sync之前 - %@ 這句。因為這兩條是并行的载城,所以打印的先后順序無所謂肌似。
注意,高潮來了∷咄撸現(xiàn)在的情況和上一個例子一樣了川队。dispatch_sync同步執(zhí)行,于是它所在的線程會被阻塞睬澡,一直等到 sync 里的任務執(zhí)行完才會繼續(xù)往下固额。于是 sync 就高興的把自己 Block 中的任務放到 queue 中,可誰想 queue 是一個串行隊列煞聪,一次執(zhí)行一個任務斗躏,所以 sync 的 Block 必須等到前一個任務執(zhí)行完畢,可萬萬沒想到的是 queue 正在執(zhí)行的任務就是被 sync 阻塞了的那個昔脯。于是又發(fā)生了死鎖啄糙。所以 sync 所在的線程被卡死了笛臣。剩下的兩句代碼自然不會打印。
隊列組
隊列組可以將很多隊列添加到一個組里隧饼,這樣做的好處是沈堡,當這個組里所有的任務都執(zhí)行完了,隊列組會通過一個方法通知我們燕雁。下面是使用方法诞丽,這是一個很實用的功能。
OBJECTIVE-C
//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í)行任務, 只有異步方法
//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]);
});
SWIFT
//1.創(chuàng)建隊列組
let group = dispatch_group_create()
//2.創(chuàng)建隊列
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//3.多次使用隊列組的方法執(zhí)行任務, 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue) { () -> Void in
for _ in 0.. Void in
for _ in 0.. Void in
for _ in 0.. Void in
NSLog("完成 - %@", NSThread.currentThread())
}
打印結果
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 的基本功能拐格,但是它的能力遠不止這些僧免,等講完 NSOperation 后,我們再來看看它的一些其他方面用途禁荒。而且猬膨,只要你想象力夠豐富,你可以組合出更好的用法呛伴。
更新:關于GCD勃痴,還有兩個需要說的:
func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
這個方法重點是你傳入的 queue,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時热康,這個方法會阻塞這個 queue(注意是阻塞 queue 沛申,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執(zhí)行完成后才會開始執(zhí)行自己姐军,自己執(zhí)行完畢后铁材,再會取消阻塞,使這個 queue 中排在它后面的任務繼續(xù)執(zhí)行奕锌。
如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了著觉。
func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
這個方法的使用和上一個一樣,傳入 自定義的并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT)惊暴,它和上一個方法一樣的阻塞 queue饼丘,不同的是 這個方法還會 阻塞當前線程。
如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了辽话。
NSOperation和NSOperationQueue
NSOperation 是蘋果公司對 GCD 的封裝肄鸽,完全面向對象,所以使用起來更好理解油啤。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 典徘。操作步驟也很好理解:
將要執(zhí)行的任務封裝到一個 NSOperation 對象中。
將此任務添加到一個 NSOperationQueue 對象中益咬。
然后系統(tǒng)就會自動在執(zhí)行任務逮诲。至于同步還是異步、串行還是并行請繼續(xù)往下看:
添加任務
值得說明的是,NSOperation 只是一個抽象類汛骂,所以不能封裝任務罕模。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation 和 NSBlockOperation 帘瞭。創(chuàng)建一個 Operation 后淑掌,需要調用 start 方法來啟動任務,它會 默認在當前隊列同步執(zhí)行蝶念。當然你也可以在中途取消一個任務抛腕,只需要調用其 cancel 方法即可。
NSInvocationOperation : 需要傳入一個方法名媒殉。
OBJECTIVE-C
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
SWIFT
在 Swift 構建的和諧社會里担敌,是容不下 NSInvocationOperation 這種不是類型安全的敗類的。蘋果如是說廷蓉。這里有相關解釋
NSBlockOperation
OBJECTIVE-C
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務
[operation start];
SWIFT
//1.創(chuàng)建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
println(NSThread.currentThread())
}
//2.開始任務
operation.start()
之前說過這樣的任務全封,默認會在當前線程執(zhí)行。但是 NSBlockOperation 還有一個方法:addExecutionBlock: 桃犬,通過這個方法可以給 Operation 添加多個執(zhí)行 Block刹悴。這樣 Operation 中的任務 會并發(fā)執(zhí)行,它會 在主線程和其它的多個線程 執(zhí)行這些任務攒暇,注意下面的打印結果:
OBJECTIVE-C
//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.開始任務
[operation start];
SWIFT
//1.創(chuàng)建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
NSLog("%@", NSThread.currentThread())
}
//2.添加多個Block
for i in 0.. Void in
NSLog("第%ld次 - %@", i, NSThread.currentThread())
}
}
//2.開始任務
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í)行土匀,否則就會報錯:
‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'
NOTE:大家可能發(fā)現(xiàn)了一個問題,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢形用?原因是使用 print() / println() 輸出的話就轧,它會簡單地使用 流(stream) 的概念,學過 C++ 的都知道田度。它會把需要輸出的每個字符一個一個的輸出到控制臺妒御。普通使用并沒有問題,可是當多線程同步輸出的時候問題就來了镇饺,由于很多 println() 同時打印携丁,就會導致控制臺上的字符混亂的堆在一起,而NSLog() 就沒有這個問題兰怠。到底是什么樣子的呢?你可以把上面 NSLog() 改為 println() 李茫,然后一試便知揭保。 更多 NSLog() 與 println() 的區(qū)別看這里
自定義Operation
除了上面的兩種 Operation 以外,我們還可以自定義 Operation魄宏。自定義 Operation 需要繼承 NSOperation 類秸侣,并實現(xiàn)其 main() 方法,因為在調用 start() 方法的時候,內部會調用 main() 方法完成相關邏輯味榛。所以如果以上的兩個類無法滿足你的欲望的時候椭坚,你就需要自定義了。你想要實現(xiàn)什么功能都可以寫在里面搏色。除此之外善茎,你還需要實現(xiàn) cancel() 在內的各種方法。所以這個功能提供給高級玩家频轿,我在這里就不說了垂涯,等我需要用到時在研究它,到時候可能會再做更新航邢。
創(chuàng)建隊列
看過上面的內容就知道耕赘,我們可以調用一個 NSOperation 對象的 start() 方法來啟動這個任務,但是這樣做他們默認是 同步執(zhí)行 的膳殷。就算是 addExecutionBlock 方法操骡,也會在 當前線程和其他線程 中執(zhí)行,也就是說還是會占用當前線程赚窃。這是就要用到隊列 NSOperationQueue 了册招。而且,按類型來說的話一共有兩種類型:主隊列考榨、其他隊列跨细。只要添加到隊列,會自動調用任務的 start() 方法
主隊列
細心的同學就會發(fā)現(xiàn)河质,每套多線程方案都會有一個主線程(當然啦冀惭,說的是iOS中,像 pthread 這種多系統(tǒng)的方案并沒有掀鹅,因為 UI線程 理論需要每種操作系統(tǒng)自己定制)散休。這是一個特殊的線程,必須串行乐尊。所以添加到主隊列的任務都會一個接一個地排著隊在主線程處理戚丸。
//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//SWIFT
let queue = NSOperationQueue.mainQueue()
其他隊列
因為主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列扔嵌。那么通過初始化產生的隊列就是其他隊列了限府,因為只有這兩種隊列,除了主隊列痢缎,其他隊列就不需要名字了胁勺。
注意:其他隊列的任務會在其他線程并行執(zhí)行。
OBJECTIVE-C
7
//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.隊列添加任務
[queue addOperation:operation];
SWIFT
//1.創(chuàng)建其他隊列
let queue = NSOperationQueue()
//2.創(chuàng)建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
NSLog("%@", NSThread.currentThread())
}
//3.添加多個Block
for i in 0.. Void in
NSLog("第%ld次 - %@", i, NSThread.currentThread())
}
}
//4.隊列添加任務
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, 這時應該發(fā)問了独旷,大家將 NSOperationQueue 與 GCD的隊列 相比較就會發(fā)現(xiàn)署穗,這里沒有并行隊列寥裂,那如果我想要10個任務在其他線程串行的執(zhí)行怎么辦?
這就是蘋果封裝的妙處案疲,你不用管串行封恰、并行、同步褐啡、異步這些名詞诺舔。NSOperationQueue 有一個參數(shù) maxConcurrentOperationCount 最大并發(fā)數(shù),用來設置最多可以讓多少個任務同時執(zhí)行春贸。當你把它設置為 1 的時候顾画,他不就是串行了嘛击困!
NSOperationQueue 還有一個添加任務的方法,- (void)addOperationWithBlock:(void (^)(void))block; ,這是不是和 GCD 差不多弹砚?這樣就可以添加一個任務到隊列中了食棕,十分方便暖庄。
NSOperation 有一個非常實用的功能问芬,那就是添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片类垫,B:給這張圖片加個水印司光,C:把圖片返回給服務器。這時就可以用到依賴了:
OBJECTIVE-C
//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印? - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.設置依賴
[operation2 addDependency:operation1];? ? ? //任務二依賴任務一
[operation3 addDependency:operation2];? ? ? //任務三依賴任務二
//5.創(chuàng)建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
//1.任務一:下載圖片
let operation1 = NSBlockOperation { () -> Void in
NSLog("下載圖片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//2.任務二:打水印
let operation2 = NSBlockOperation { () -> Void in
NSLog("打水印? - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//3.任務三:上傳圖片
let operation3 = NSBlockOperation { () -> Void in
NSLog("上傳圖片 - %@", NSThread.currentThread())
NSThread.sleepForTimeInterval(1.0)
}
//4.設置依賴
operation2.addDependency(operation1)? ? //任務二依賴任務一
operation3.addDependency(operation2)? ? //任務三依賴任務二
//5.創(chuàng)建隊列并加入任務
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)
打印結果
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)}
注意:不能添加相互依賴悉患,會死鎖残家,比如 A依賴B,B依賴A售躁。
可以使用 removeDependency 來解除依賴關系坞淮。
可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的陪捷,和隊列沒關系回窘。
其他方法
以上就是一些主要方法, 下面還有一些常用方法需要大家注意:
NSOperation
BOOL executing; //判斷任務是否正在執(zhí)行
BOOL finished; //判斷任務是否完成
void (^completionBlock)(void); //用來設置完成后需要執(zhí)行的操作
- (void)cancel; //取消任務
- (void)waitUntilFinished; //阻塞當前線程直到此任務執(zhí)行完畢
NSOperationQueue
NSUInteger operationCount; //獲取隊列的任務數(shù)
- (void)cancelAllOperations; //取消隊列中所有的任務
- (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執(zhí)行完畢
[queue setSuspended:YES]; // 暫停queue
[queue setSuspended:NO]; // 繼續(xù)queue
好啦,到這里差不多就講完了市袖。當然啡直,我講的并不完整,可能有一些知識我并沒有講到苍碟,但作為常用方法酒觅,這些已經足夠了。不過我在這里只是告訴你了一些方法的功能微峰,只是怎么把他們用到合適的地方阐滩,就需要多多實踐了。下面我會說一些關于多線程的案例县忌,是大家更加什么地了解掂榔。
其他用法
在這部分,我會說一些和多線程知識相關的案例症杏,可能有些很簡單装获,大家早都知道的,不過因為這篇文章講的是多線程嘛厉颤,所以應該盡可能的全面嘛穴豫。還有就是,我會盡可能的使用多種方法實現(xiàn)逼友,讓大家看看其中的區(qū)別精肃。
線程同步
所謂線程同步就是為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題,所采取的一種措施帜乞。當然也有很多實現(xiàn)方法司抱,請往下看:
互斥鎖 :給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊黎烈。
OBJECTIVE-C
@synchronized(self) {
//需要執(zhí)行的代碼塊
}
SWIFT
objc_sync_enter(self)
//需要執(zhí)行的代碼塊
objc_sync_exit(self)
同步執(zhí)行 :我們可以使用多線程的知識习柠,把多個線程都要執(zhí)行此段代碼添加到同一個串行隊列,這樣就實現(xiàn)了線程同步的概念照棋。當然這里可以使用 GCD 和 NSOperation 兩種方案资溃,我都寫出來。
OBJECTIVE-C
//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. 設置 queue 的 maxConcurrentOperationCount 為 1
//? ? ? 3. 如果后續(xù)操作需要Block中的結果溶锭,就需要調用每個操作的waitUntilFinished,阻塞當前線程符隙,一直等到當前操作完成趴捅,才允許執(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ù)要做的事
SWIFT
這里的 swift 代碼驻售,我就不寫了,因為每句都一樣更米,只是語法不同而已欺栗,照著 OC 的代碼就能寫出 Swift 的。這篇文章已經老長老長了征峦,我就不浪費篇幅了迟几,又不是高中寫作文。
延遲執(zhí)行
所謂延遲執(zhí)行就是延時一段時間再執(zhí)行某段代碼栏笆。下面說一些常用方法类腮。
perform
OBJECTIVE-C
// 3秒后自動調用self的run:方法,并且傳遞參數(shù):@"abc"
[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
SWIFT
之前就已經說過蛉加,Swift 里去掉了這個方法蚜枢。
GCD
可以使用 GCD 中的 dispatch_after 方法缸逃,OC 和 Swift 都可以使用,這里只寫 OC 的厂抽,Swift 的是一樣的需频。
OBJECTIVE-C
// 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設置延時,單位秒
double delay = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
// 3秒后需要執(zhí)行的任務
});
NSTimer
NSTimer 是iOS中的一個計時器類筷凤,除了延遲執(zhí)行還有很多用法昭殉,不過這里直說延遲執(zhí)行的用法。同樣只寫 OC 版的藐守,Swift 也是相同的挪丢。
OBJECTIVE-C
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
單例模式
至于什么是單例模式,我也不多說卢厂,我只說說一般怎么實現(xiàn)乾蓬。在 Objective-C 中,實現(xià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
這里之所以將單例模式族奢,是因為其中用到了 GCD 的 dispatch_once 方法。下面看 Swift 中的單例模式丹鸿,在Swift中單例模式非常簡單越走!想知道怎么從 OC 那么復雜的方法變成下面的寫法的,請看這里
SWIFT
class Tool: NSObject {
static let sharedTool = Tool()
// 私有化構造方法靠欢,阻止其他對象使用這個類的默認的'()'構造方法
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(), { () -> Void in
})
NSOperationQueue
//Objective-C
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
//Swift
NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in
}
總結
好的吧掷空,總算寫完了肋殴,純手敲6k多字,感動死我了坦弟』ご福花了兩天,時間跨度有點大酿傍,所以可能有些地方上段不接下段或者有的地方不完整烙懦,如果你看著比較費力或者有什么地方有問題,都可以在評論區(qū)告訴我赤炒,我會及時修改的氯析。當然啦亏较,多線程的東西也不止這些,題目也就只是個題目魄鸦,不要當真宴杀。想要了解更多的東西,還得自己去網上挖掘相關資料拾因。多看看官方文檔。實在是編不下去了旷余,大家好好看~绢记。
更新:第一次放出來的時候,有很多地方有錯誤正卧,很感謝有朋友提出來了蠢熄。如果你看到有錯誤的地方,一定記得指出來炉旷,這樣對大家都有幫助签孔。還有一點對初學者來說,遇到不懂的方法窘行,最好的辦法就是查看官方文檔饥追,那里是最準確的,就算有幾個單詞不認識罐盔,查一下就好了但绕,不會影響對整體的理解。