一 ) 為什么使用多線程界阁?
每個(gè)iOS應(yīng)用程序都有個(gè)專門用來更新顯示UI界面、處理用戶的觸摸事件的主線程胖喳,因此不能將其他太耗時(shí)的操作放在主線程中執(zhí)行泡躯,不然會(huì)造成主線程堵塞(出現(xiàn)卡機(jī)現(xiàn)象),帶來極壞的用戶體驗(yàn)丽焊。一般的解決方案就是將那些耗時(shí)的操作放到另外一個(gè)線程中去執(zhí)行较剃,多線程編程是防止主線程堵塞,增加運(yùn)行效率的最佳方法技健。
這里有兩個(gè)概念是進(jìn)程和線程写穴。 進(jìn)程就是負(fù)責(zé)程序運(yùn)行的內(nèi)存分配,而線程就是進(jìn)程中得一個(gè)獨(dú)立的執(zhí)行那個(gè)路徑雌贱。它是程序的執(zhí)行路徑啊送,負(fù)責(zé)程序中代碼的實(shí)際運(yùn)行。這好比進(jìn)程就是火車欣孤,線程就是火車的車廂删掀,沒有火車,車廂也跑不起來當(dāng)然一個(gè)火車也不能只有一個(gè)車廂导街。
二)? ios 中常用的三種多線程是什么披泪?
1? Thread: 這是相對(duì)輕量級(jí)別的,抽象級(jí)別相對(duì)來說比較低搬瑰,但是需要管理線程的生命周期款票,同步以及加鎖,這會(huì)導(dǎo)致一定的性能開銷
2 Operations:? 這個(gè)是基于OC實(shí)現(xiàn)的泽论,以面向?qū)ο蟮姆绞椒庋b了需要執(zhí)行的操作艾少,我們可以不必關(guān)心線程的管理和同步的問題。它可以開始翼悴,取消線程執(zhí)行缚够。他有兩個(gè)默認(rèn)的實(shí)現(xiàn)方法:NSInvocationOperation和NSBlockOperation。當(dāng)然我們也可以自定NSOperations鹦赎,只有實(shí)現(xiàn)里面的main方法即可
3? GCD:也是重點(diǎn)講的一個(gè)谍椅,它是ios4才開始使用的,當(dāng)然現(xiàn)在我們ios9都出來了古话,GCD的成熟度足可以讓我們放心的使用雏吭,它是基于C實(shí)現(xiàn)的,性能上要相對(duì)來說好一些陪踩,而且可以用最簡(jiǎn)單的代碼去實(shí)現(xiàn)復(fù)雜的線程問題杖们。
三) NSThread的基本使用方法
1.動(dòng)態(tài)初始化
NSThread threadOne = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//其中run是一個(gè)方法悉抵,我們通過方法來執(zhí)行多線程中得處理
[_threadOne setName:@"one"]; //給線程初始化一個(gè)別名
[_threadOne start];//開始執(zhí)行
2 靜態(tài)初始化
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; //通過這一句代碼我們就可以創(chuàng)建出另一個(gè)線程出來并去執(zhí)行
3 創(chuàng)建隱式線程:
[self performSelectorInBackground:@selector(run) withObject:nil];
注:這是NSObject的一個(gè)方法,他可以讓一個(gè)耗時(shí)間的處理放入后臺(tái)去執(zhí)行摘完,但是在swift中拋棄了姥饰,原因是蘋果覺得這個(gè)方法是線程不安全的
4 獲取當(dāng)前的線程
NSThread *current = [NSThread currentThread];//返回的是目前的線程
5 返回主線程(刷新UI控件必須在主線程中執(zhí)行)
[self performSelectorOnMainThread:@selector(main:) withObject:nil waitUntilDone:YES];
6 等待(暫停)當(dāng)前的線程
[NSThread sleepForTimeInterval:3.0f] ;//3秒之后執(zhí)行
或者
NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date]; 等待date完成后再去執(zhí)行
注:前者是等待時(shí)間的完成孝治,后者是等待數(shù)據(jù)的完成
四)? NSOperation使用方法:
NSOperation 實(shí)例封裝了需要執(zhí)行的操作和執(zhí)行操作所需的數(shù)據(jù)列粪,并且能夠以并發(fā)或非并發(fā)的方式執(zhí)行這個(gè)操作。NSOperation在ios4后也基于GCD實(shí)現(xiàn)荆秦,但是相對(duì)于GCD來說可控性更強(qiáng)篱竭,并且可以加入操作依賴。NSOperation提供了ready cancelled executing finished這幾個(gè)狀態(tài)變化步绸,我們的開發(fā)也是必須處理自己關(guān)心的其中的狀態(tài)掺逼。這些狀態(tài)都是基于keypath的KVO通知決定,所以在你手動(dòng)改變自己關(guān)心的狀態(tài)時(shí)瓤介,請(qǐng)別忘了手動(dòng)發(fā)送通知吕喘。這里面每個(gè)屬性都是相互獨(dú)立的,同時(shí)只可能有一個(gè)狀態(tài)是YES刑桑。finished這個(gè)狀態(tài)在操作完成后請(qǐng)及時(shí)設(shè)置為YES氯质,因?yàn)镹SOperationQueue所管理的隊(duì)列中,只有isFinished為YES時(shí)才將其移除隊(duì)列祠斧,這點(diǎn)在內(nèi)存管理和避免死鎖很關(guān)鍵闻察。
1.NSInvocationOperation:
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];//紅色多線程中處理的方法
[op start]; //開始
[op cancel];//取消
2? NSBlockOperation
NSBlockOperation *block = [NSBlockOperation? ? blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]); //處理線程的block方法
}];
3 NSOperationQueue
一個(gè)NSOperation對(duì)象可以通過調(diào)用start方法來執(zhí)行任務(wù),默認(rèn)是同步執(zhí)行的琢锋。也可以將NSOperation添加到一個(gè)NSOperationQueue(操作隊(duì)列)中去執(zhí)行辕漂,而且是異步執(zhí)行的。一旦NSoperation添加到NSoperationQueue中吴超,用戶就無權(quán)對(duì)NSoperation管理钉嘹,都有NSoperationQueue來執(zhí)行。
3.1 添加一個(gè)隊(duì)列
NSoperationQueue *myQueue = [[NSOperationQueue alloc]init];
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download:) object:nil];
[myQueue addOperation:op];
3.2 主隊(duì)列(任何刷新UI的方法都必須在主隊(duì)列中執(zhí)行)
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
//可以執(zhí)行刷新UI控件的方法
}];
3.3 隊(duì)列直接可以設(shè)置依賴鲸阻,比如隊(duì)列1需要隊(duì)列2執(zhí)行完畢后才能執(zhí)行(addDependency)
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[block1 addDependency:block2];
[myQueue addOperation:block1];
[myQueue addOperation:block2];
3.4可以設(shè)置最大并發(fā)的操作數(shù)量
[myQueue setMaxConcurrentOperationCount:2];
3.5 一旦添加到隊(duì)列,隊(duì)列就擁有了這個(gè)Operation對(duì)象并且不能被? 刪除,唯一能做的事情是取消跋涣。
[myQueue cancelAllOperations];
3.6獲取NSOperation
myQueue.operations 這是一個(gè)數(shù)組,里面存放這添加進(jìn)入這個(gè)隊(duì)列的所有任務(wù)
3.7 如果你想臨時(shí)暫停Operations的執(zhí)行,可以使用queue的setSuspended:方法暫停queue鸟悴。
[myQueue setSuspended:YES];
四)GCD
1 GCD是Grand Central Dispatch的簡(jiǎn)稱陈辱,它是基于C語言的。如果使用GCD遣臼,完全由系統(tǒng)管理線程性置,我們不需要編寫線程代碼。只需定義想要執(zhí)行的任務(wù),然后添加到適當(dāng)?shù)恼{(diào)度隊(duì)列(dispatch queue)揍堰。GCD會(huì)負(fù)責(zé)創(chuàng)建線程和調(diào)度你的任務(wù)鹏浅,系統(tǒng)直接提供線程管理。
2 GCD的操作思想是講操作放在隊(duì)列中去執(zhí)行
1? 操作是用block來實(shí)現(xiàn)的
2 隊(duì)列是先進(jìn)先出的,它是負(fù)責(zé)調(diào)度任務(wù)執(zhí)行所在的線程
3 GCD分為串行和并行屏歹,有自定義隐砸,主隊(duì)列和全局隊(duì)列三種。一個(gè)同步函數(shù)只在完成了它預(yù)定的任務(wù)后才返回蝙眶。一個(gè)異步函數(shù)季希,剛好相反,會(huì)立即返回幽纷,預(yù)定的任務(wù)會(huì)完成但不會(huì)等它完成式塌。因此,一個(gè)異步函數(shù)不會(huì)阻塞當(dāng)前線程去執(zhí)行下一個(gè)函數(shù)友浸。如果在主隊(duì)列中執(zhí)行同步的話峰尝,會(huì)造成死鎖的發(fā)生
3 串行和并行
串行:一次只能執(zhí)行一個(gè)任務(wù), 當(dāng)前任務(wù)完成才開始出列并啟動(dòng)下一個(gè)任務(wù)
并行:則盡可能多地啟動(dòng)任務(wù)并發(fā)執(zhí)行
4GCD基本使用方法
1 自定義隊(duì)列
dispatch_queue_t q = dispatch_queue_create("gcdDemo1", DISPATCH_QUEUE_SERIAL); DISPATCH_QUEUE_SERIAL:串行,可以傳入nil 默認(rèn)是串行收恢,"gcdDemo1”是這個(gè)線程的別名武学,可以傳nil
dispatch_queue_t q = dispatch_queue_create("gcdDemo2", DISPATCH_QUEUE_CONCURRENT);? DISPATCH_QUEUE_CONCURRENT:并行
dispatch_async(q, ^{? ? 注:異步執(zhí)行,可以開辟多個(gè)線程去執(zhí)行伦意,無需等待
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(q, ^{? 注:同步執(zhí)行火窒,只開辟一個(gè)線程,需要等待上一個(gè)任務(wù)的完成才能執(zhí)行
NSLog(@"%@",[NSThread currentThread]);
});
2 全局隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
DISPATCH_QUEUE_PRIORITY_DEFAULT:默認(rèn)的驮肉,全局隊(duì)列是有優(yōu)先級(jí)的
DISPATCH_QUEUE_PRIORITY_HIGH? ? ? 最高(優(yōu)先執(zhí)行)
DISPATCH_QUEUE_PRIORITY_DEFAULT? ? 默認(rèn)
DISPATCH_QUEUE_PRIORITY_LOW? ? ? ? 最低
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后臺(tái)
3 主隊(duì)列
dispatch_queue_t q = dispatch_get_main_queue();
每一個(gè)應(yīng)用程序都有一個(gè)主線程? 在ios中所有的ui刷新都再主線程中執(zhí)行熏矿!這是因?yàn)樘O果為了提高性能,大部分庫都是線程不安全的离钝,如果在子線程刷新控件會(huì)造成一些問題票编,所有所有的UI控件的刷新都由主線程上刷新
4 延遲執(zhí)行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"我是3秒才執(zhí)行的!");
});
5 dispatchGroup組隊(duì)列
dispatchGroup作用:當(dāng) dispatch_group_async函數(shù)將多個(gè)任務(wù)關(guān)聯(lián)到一個(gè)Dispatch Group和相應(yīng)的queue中,group會(huì)并發(fā)地同時(shí)執(zhí)行這些任務(wù)奈辰。而且Dispatch Group可以用來阻塞一個(gè)線程, 直到group關(guān)聯(lián)的所有的任務(wù)完成執(zhí)行栏妖。有時(shí)候你必須等待任務(wù)完成的結(jié)果,然后才能繼續(xù)后面的處理。
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, q, ^{
NSLog(@"1 == %@",[NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"2 == %@",[NSThread currentThread]);
});
dispatch_group_async(group, q, ^{
NSLog(@"3 == %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{//不管前面的線程誰先執(zhí)行奖恰,最后都會(huì)執(zhí)行notify方法吊趾。
NSLog(@"4 == %@",[NSThread currentThread]);
});
6 dispathc_apply
dispathc_apply是dispatch_sync 和dispatch_group的關(guān)聯(lián)API.它以指定的次數(shù)將指定的Block加入到指定的隊(duì)列中。并等待隊(duì)列中操作全部完成.
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, globalQueue, ^(size_t index) {
NSLog(@"%zu",index);
dispatch_source_merge_data(socurce, 1);
});
7 dispatch_source_t 信號(hào)源
dispatch source是一個(gè)監(jiān)視某些類型事件的對(duì)象瑟啃。當(dāng)這些事件發(fā)生時(shí)论泛,它自動(dòng)將一個(gè)block放入一個(gè)dispatch queue的執(zhí)行例程中。書中定義蛹屿。我的理解就是多線程中得KVO屁奏,它檢測(cè)用戶事件,它是由dispatch_source_merge_data函數(shù)來向自己發(fā)送信號(hào)错负,然后通過dispatch_source_set_event_handler這個(gè)函數(shù)去執(zhí)行坟瓢。
這是我寫了一個(gè)進(jìn)度條的例子
執(zhí)行部分:? __weak __typeof(self)weakSelf = self;
dispatch_source_t? socurce = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(socurce, ^{
[weakSelf.progressIndicator setProgress:dispatch_source_get_data(socurce)? animated:YES];
});
dispatch_resume(socurce);
監(jiān)聽部分:? ? dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, globalQueue, ^(size_t index) {
NSLog(@"%zu",index);
dispatch_source_merge_data(socurce, 1);
});
/*
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);
第1個(gè)參數(shù):要監(jiān)聽的事件類型
第2個(gè)參數(shù):可以理解為句柄勇边、索引或id,假如要監(jiān)聽進(jìn)程折联,需要傳入進(jìn)程的ID
第3個(gè)參數(shù):根據(jù)參數(shù)2粒褒,可以理解為描述,提供更詳細(xì)的描述诚镰,讓它知道具體要監(jiān)聽什么
第4個(gè)參數(shù):當(dāng)事件發(fā)生時(shí)奕坟,將block添加至哪個(gè)隊(duì)列來執(zhí)行
**/
8 dispatch_semaphore信號(hào)量
當(dāng)我們?cè)谔幚硪幌盗芯€程的時(shí)候,當(dāng)數(shù)量達(dá)到一定量清笨,在以前我們可能會(huì)選擇使用NSOperationQueue來處理并發(fā)控制月杉,在GCD中我們需要通過dispatch_semaphore來控制它的并發(fā)數(shù)量。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 10; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果技術(shù)器的數(shù)值大于等于1的時(shí)候進(jìn)行-1操作
dispatch_group_async(group, queue, ^{
NSLog(@"----%d",i);
sleep(2);
dispatch_semaphore_signal(semaphore);//計(jì)數(shù)器+1
});
}
簡(jiǎn)單的介紹一下這一段代碼抠艾,創(chuàng)建了一個(gè)初使值為5的semaphore苛萎,每一次for循環(huán)都會(huì)創(chuàng)建一個(gè)新的線程,線程結(jié)束的時(shí)候會(huì)發(fā)送一個(gè)信號(hào)跌帐,線程創(chuàng)建之前會(huì)信號(hào)等待首懈,所以當(dāng)同時(shí)創(chuàng)建了5個(gè)線程之后,for循環(huán)就會(huì)阻塞谨敛,等待有線程結(jié)束之后會(huì)增加一個(gè)信號(hào)才繼續(xù)執(zhí)行究履,如此就形成了對(duì)并發(fā)的控制,如上就是一個(gè)并發(fā)數(shù)為5的一個(gè)線程隊(duì)列脸狸。