什么是進程瘾带?
-
進程
是指在系統(tǒng)中正在運行的一個應用程序匹颤。 - 每個進程之間是
獨立的
偏塞,每個進程均運行在其專用且受保護
的內(nèi)存空間內(nèi)利术。
什么是線程?
- 1個進程要想執(zhí)行任務,
必須
得有線程(每1個進程至少要有1條線程
)汉额。 - 線程是進程的基本執(zhí)行單元曹仗,一個進程(程序)的所有任務都在線程中執(zhí)行。
小拓展
:
- 線程的串行(就像烤串一樣)
- 1個線程中任務的執(zhí)行是串行的蠕搜。
- 如果要在1個線程中執(zhí)行多個任務怎茫,那么只能一個一個地按順序執(zhí)行這些任務。
- 在`同一時間內(nèi)`妓灌,1個線程只能執(zhí)行1個任務轨蛤。
什么是多線程?
1個進程中可以開啟多條線程虫埂,每條線程可以并行(同時)執(zhí)行不同的任務祥山。
-
線程的并行(同時執(zhí)行)
- 比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B掉伏、文件C缝呕。
-
多線程并發(fā)執(zhí)行的原理:
- 在同一時間里,CPU只能處理1條線程斧散,只有1條線程在工作(執(zhí)行)岳颇。
- 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)颅湘,如果CPU調(diào)度線程的時間足夠快话侧,就造成了多線程并發(fā)執(zhí)行的假象。(如下圖)
多線程優(yōu)缺點:
- 優(yōu)點
- 能適當提高程序的執(zhí)行效率闯参。
- 能適當提高資源利用率(CPU瞻鹏、內(nèi)存利用率)
- 缺點
- 開啟線程需要占用一定的內(nèi)存空間(默認情況下,主線程占用1M鹿寨,子線程占用512KB)新博,如果開啟大量的線程,會占用大量的內(nèi)存空間脚草,降低程序的性能赫悄。
- 線程越多,CPU在調(diào)度線程上的開銷就越大馏慨。
- 程序設計更加復雜:比如線程之間的通信埂淮、多線程的數(shù)據(jù)共享
多線程在iOS開發(fā)中的應用
- 主線程
- 一個iOS程序運行后,默認會在自己的進程中開啟1條線程写隶,稱為“主線程”也叫“UI線程”倔撞。
- 作用:刷新顯示UI,處理UI事件。
- 使用注意
- 不要將耗時操作放到主線程中去處理慕趴,因為會卡住主線程痪蝇,造成UI卡頓(用戶體驗差)鄙陡。
- 和UI相關的刷新操作`必須`放到主線程中進行處理。
線程的狀態(tài)
- 線程的各種狀態(tài):新建-就緒-運行-阻塞-死亡
- 常用的控制線程狀態(tài)的方法
[NSThread exit];//退出當前線程 [NSThread sleepForTimeInterval:7.0];//阻塞線程 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:7.0]];//阻塞線程
注意:線程死亡后不能復生
線程安全:
- 前提:多個線程同時訪問同一塊資源會發(fā)生數(shù)據(jù)安全問題
解決方案
:加互斥鎖 - 相關代碼:@synchronized(self){}
- 專業(yè)術語-線程同步
- 原子和非原子屬性(是否對setter方法加鎖)
IOS中多線程的實現(xiàn)方案
方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
---|---|---|---|---|
pthread | 一套通用的多線程API (跨平臺\可移植) |
C語言 | 程序員管理 | 幾乎不用 |
NSThread |
使用更加面向?qū)ο? (簡單易用躏啰,可直接操作線程對象) |
OC語言 | 程序員管理 | 偶爾使用 |
GCD |
為了替代NSThread為生 ( 充分利用設備多核 ) |
C語言 | 系統(tǒng)自動管理 | 經(jīng)常使用 |
NSOperation |
基于GCD ( 更加面向?qū)ο?更方便地設置線程之間的依賴 監(jiān)聽線程狀態(tài)KVO ) |
OC語言 | 系統(tǒng)自動管理 | 經(jīng)常使用 |
pthread簡單使用
1.包含頭文件(必須)
#import <pthread.h>
2.創(chuàng)建線程
// 創(chuàng)建線程
/**
*
* 參數(shù)一:線程對象(傳地址)
* 參數(shù)二:線程的屬性(名稱\優(yōu)先級)
* 參數(shù)三:只想函數(shù)的指針
* 參數(shù)四:函數(shù)需要接受的字符串參數(shù)趁矾,可以不傳遞(注:由于我們創(chuàng)建的是OC的字符串,所以在傳值的時候需要將其轉(zhuǎn)換成C的字符串)
*/
pthread_t thread;
NSString *num = @"123";
pthread_create(&thread, NULL, task, (__bridge void *)(num));
3.定義參數(shù)所需要的函數(shù)指針
void *task(void *num)
{
NSLog(@"當前線程 -- %@,傳入的參數(shù):-- %@", [NSThread currentThread], num);
return NULL;
}
如果需要退出線程的話只需調(diào)用下面代碼
pthread_exit(NULL);
運行結(jié)果:
NSThread簡單使用
這邊介紹NSThread創(chuàng)建線程的4種方式:
- 第一種 (alloc nitWithTarget:selector:object:)
- 特點:需要手動開啟線程给僵,可以拿到線程對象進行詳細設置
- 優(yōu)缺點:
- 缺點:需要手動開啟線程執(zhí)行任務
- 優(yōu)點:可以拿到線程對象
// 創(chuàng)建線程
/**
* 參數(shù)一:目標對象
* 參數(shù)二:方法選擇器(線程啟動后調(diào)用的方法)
* 參數(shù)三:調(diào)用方法需要接受的參數(shù)
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(task)
object:nil];
// 開始執(zhí)行
[thread start];
- 第二種(分離出一條子線程)
- 特點:自動啟動線程毫捣,無法對線程進行更詳細的設置
- 優(yōu)缺點:
- 缺點:無法拿到線程對象 進行更詳細設置
- 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
// 創(chuàng)建線程
/**
* 參數(shù)一:要調(diào)用的方法
* 參數(shù)二:目標對象 self
* 參數(shù)三:調(diào)用方法需傳遞的參數(shù)
*/
[NSThread detachNewThreadSelector:@selector(task)
toTarget:self
withObject:nil];
- 第三種(后臺線程)
- 特點:自動啟動線程,無法進行更詳細設置
- 優(yōu)缺點:
- 缺點:無法拿到線程對象 進行更詳細設置
- 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
/**
* NSThread創(chuàng)建一條后臺線程
*/
- (void)nsthreadTest3
{
// 創(chuàng)建線程
/**
* 參數(shù)一:要調(diào)用的方法
* 參數(shù)二:調(diào)用方法需傳遞的參數(shù)
*/
[self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];
}
- (void)run:(NSString *)str
{
NSLog(@"當前線程:%@ -- 接收到的參數(shù):%@", [NSThread currentThread], str);
}
- 第四種(自定義NSThread類并重寫內(nèi)部的方法實現(xiàn))
- 特點:可以不暴露一些實現(xiàn)細節(jié)想际,使代碼增加隱蔽性培漏。(一般出現(xiàn)在第三方框架內(nèi))
- 優(yōu)缺點:
- 缺點:繁瑣溪厘,且需要手動開啟線程執(zhí)行
- 優(yōu)點:增加代碼隱蔽性
1.創(chuàng)建自定義類繼承自NSThread
2.重寫NSThread類中的main
方法
- (void)main
{
NSLog(@"當前線程--%@", [NSThread currentThread]);
}
3.創(chuàng)建線程對象
/**
* NSThread創(chuàng)建一條后臺線程
*/
- (void)nsthreadTest4
{
// 創(chuàng)建線程
SJThread *thread = [[SJThread alloc] init];
// 開啟執(zhí)行
[thread start];
}
線程間通信
有時候我們會從服務器上下載圖片然后再展示出來胡本,下載的操作我們會放到子線程,而UI刷新的操作只能在主線程中執(zhí)行
畸悬。這樣就涉及到線程間的通信
侧甫。接下來我們分三種方式來簡單實現(xiàn)一下:
- 方式一:
- (void)viewDidLoad {
[super viewDidLoad];
// 開啟一條線程下載圖片
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}
- (void)downloadImage
{
// 網(wǎng)絡圖片url
NSURL *url = [NSURL URLWithString:@"http://img3.imgtn.bdimg.com/it/u=3841157212,2135341815&fm=206&gp=0.jpg"];
// 根據(jù)url下載圖片數(shù)據(jù)到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
// 把下載到本地的二進制數(shù)據(jù)轉(zhuǎn)成圖片
UIImage *image = [UIImage imageWithData:imageData];
// 回到主線程刷新UI
// 第一種方式
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
// 第二種方式
// 直接調(diào)用iconView里面的setImage:方法就可以實現(xiàn)刷新
// [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
// 第三種方式
// 此方法可以方便自由在主線程和其它線程切換
// [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
- (void)showImage:(UIImage *)image
{
self.iconView.image = image;
}
GCD簡單使用
什么是GCD
- GCD全稱是Grand Central Dispatch(牛逼的中樞調(diào)度器)
- 純C語言,提供了非常多強大的函數(shù)
GCD優(yōu)勢
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核
- GCD會自動關了線程生命周期(創(chuàng)建蹋宦、調(diào)度披粟、銷毀線程)
- GCD性能很好(接近底層)
GCD的組合方式
- 異步函數(shù)+并發(fā)隊列:開啟多條線程,并發(fā)執(zhí)行任務
- 異步函數(shù)+串行隊列:開啟一條線程冷冗,串行執(zhí)行任務
- 同步函數(shù)+并發(fā)隊列:不開線程守屉,串行執(zhí)行任務
- 同步函數(shù)+串行隊列:不開線程,串行執(zhí)行任務
- 異步函數(shù)+主隊列:不開線程蒿辙,在主線程中串行執(zhí)行任務
- 同步函數(shù)+主隊列:不開線程拇泛,串行執(zhí)行任務(注意死鎖發(fā)生
注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異
GCD的任務和隊列
- 任務:執(zhí)行什么操作
- 隊列:用來存放任務(GCD中提供了2種隊列)
- 串行隊列
- 并發(fā)隊列
GCD的使用
- 定制任務 —— 確定需要做的操作
- 將任務添加到隊列中
- GCD會自動將隊列中的任務取出,存放到線程中執(zhí)行
- 任務的取出遵循隊列的FIFO原則(先進先出思灌,后進后出)
GCD創(chuàng)建線程
- 接下來看看同步函數(shù)和異步函數(shù)有什么區(qū)別:
1.先來看看異步并發(fā)隊列
- (void)test
{
/**
* 參數(shù)一:C語言的字符串俺叭,給隊列起一個名字或標識
* 參數(shù)二:隊列類型
DISPATCH_QUEUE_CONCURRENT 并發(fā)
DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
/**
* 使用函數(shù)封裝任務
* 參數(shù)一:獲取隊列
* 參數(shù)二:需要執(zhí)行的任務
*/
dispatch_async(queue, ^{
NSLog(@"在:%@線程執(zhí)行了任務",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
}
執(zhí)行結(jié)果:
2.再來看看同步并發(fā)隊列
- (void)test
{
/**
* 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
* 參數(shù)二:隊列類型
DISPATCH_QUEUE_CONCURRENT 并發(fā)
DISPATCH_QUEUE_SERIAL 串行(串行隊列可以用NULL表示)
*/
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
/**
* 使用函數(shù)封裝任務
* 參數(shù)一:獲取隊列
* 參數(shù)二:需要執(zhí)行的任務
*/
dispatch_sync(queue, ^{
NSLog(@"在:%@線程執(zhí)行了任務",[NSThread currentThread]);
});
NSLog(@"結(jié)束");
}
執(zhí)行結(jié)果:
結(jié)論:
從上面的2個運行結(jié)果的時間可以看出
1.異步并發(fā)隊列泰偿,會開啟一條子線程來處理任務熄守,以達到主線程和子線程同時執(zhí)行的并發(fā)效果。
2.同步并發(fā)隊列耗跛,不會開線程裕照,必須等block塊中的代碼先執(zhí)行完畢才會繼續(xù)執(zhí)行以外的任務,所以并發(fā)隊列對于同步函數(shù)來說等同于“無效”
- 再看看并發(fā)隊列對異步函數(shù)和同步函數(shù)的影響:
1.同步函數(shù)+并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
執(zhí)行結(jié)果:同步函數(shù)+并發(fā)隊列沒有開啟子線程的能力
2.異步函數(shù)+并發(fā)隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:異步函數(shù)+并發(fā)隊列會自動開啟3條子線程執(zhí)行任務
結(jié)論:
從上面可以看出调塌,異步函數(shù)擁有開啟子線程的能力牍氛,而同步函數(shù)沒有開啟子線程的能力。
- GCD中烟阐,除了并發(fā)隊列外搬俊,還有串行隊列紊扬,我們來看看如果把并發(fā)隊列換成串行隊列會有怎樣的變化
1.同步函數(shù)+串行隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:進一步證明同步函數(shù)沒有開啟子線程的能力,他的所有任務都在主線程中執(zhí)行
2.異步函數(shù)+串行隊列
- (void)test2
{
dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@",[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:開啟了一條子線程唉擂,在子線程中依次執(zhí)行任務
結(jié)論
1.在同步函數(shù)+串行隊列中餐屎,任務依舊是在主線程中執(zhí)行。
2.在異步函數(shù)+串行隊列中玩祟,會自動開啟一條子線程腹缩,在子線程中依次執(zhí)行任務
3.再一次證明同步函數(shù)沒有開啟子線程的能力
系統(tǒng)提供的4個全局并發(fā)隊列
- 在iOS中系統(tǒng)默認給我們提供了4個全局并發(fā)隊列
- (void)test3
{
// 獲取全局并發(fā)隊列
// 系統(tǒng)內(nèi)部默認提供4個全局并發(fā)隊列
/**
* 參數(shù)一:優(yōu)先級
* 參數(shù)二:時間(傳0即可)
*/
//優(yōu)先級:DISPATCH_QUEUE_PRIORITY_HIGH 2
// DISPATCH_QUEUE_PRIORITY_DEFAULT 0
// DISPATCH_QUEUE_PRIORITY_LOW (-2)
// DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 級別最低
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"1當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"6當前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:在結(jié)果中我們看到GCD創(chuàng)建了6條線程,但是實際上GCD創(chuàng)建多少條線程完全由系統(tǒng)當前情況而定空扎,我們是無法控制的藏鹊。
特殊的串行隊列 —— 主隊列(與主線程相關聯(lián)的隊列)
- 主隊列是GCD自帶的一種特殊的串行隊列
- 放在主隊列中的人物,都會放到主線程中執(zhí)行
- 使用dispatch_get_main_queue()的方式可獲取主隊列
-
特點
- 1.放在主隊列中的任務转锈,必須在主線程中執(zhí)行
- 2.主隊列執(zhí)行任務的時候盘寡,在調(diào)度任務的時候,會先調(diào)用主線程的狀態(tài)撮慨,如果當前有任務在做竿痰,則會等待主線程執(zhí)行完任務再執(zhí)行自己的任務
-
1.主隊列+異步函數(shù)
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務
dispatch_async(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:任務都在主線程中執(zhí)行
2.同步函數(shù)+主隊列
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
進入死鎖狀態(tài),因為主隊列執(zhí)行任務的時候萧朝,在調(diào)度任務的時候何址,會先調(diào)用主線程的狀態(tài)偎血,如果當前有任務在做谒亦,則會等待主線程執(zhí)行完任務再執(zhí)行自己的任務
如果要解決以上的情況廓旬,那么可以將任務添加到子線程中,這樣就不會出現(xiàn)死鎖的情況终畅,程序也就能夠正常執(zhí)行了
[self performSelectorInBackground:@selector(test4) withObject:nil];
- (void)test4
{
// 獲取主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
// 添加任務
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"當前線程:%@", [NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
總結(jié)
函數(shù)類型 | 并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列 |
---|---|---|---|
同步 (sync) | 1.沒有開啟新線程 2.串行執(zhí)行任務 |
1.有開啟新線程 2.串行執(zhí)行任務 |
死鎖 |
異步(async) | 1.有開啟新線程 2.并發(fā)執(zhí)行任務 |
1.有開啟新線程 2.串行執(zhí)行任務 |
1.沒有開啟新線程 2.串行執(zhí)行任務 |
注意
使用sync函數(shù)往當前串行隊列中添加任務絮识,會卡主當前的串行隊列挪圾。
GCD線程間的通信
- 有時候我們需要在子線程進行一些耗時操作也殖,等耗時操作完成后再回到主線程進行相應的UI刷新闪湾,那么就可以使用下面的方式在子線程和主線程之間進行通信
- (void)test5
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"在%@線程中執(zhí)行任務", [NSThread currentThread]);
// 回到主線程
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"在%@線程中執(zhí)行任務", [NSThread currentThread]);
});
});
}
執(zhí)行結(jié)果:
GCD延遲執(zhí)行
- 特點:可以選擇在哪個線程中執(zhí)行任務
- (void)test6
{
NSLog(@"方法開始運行");
/**
* GCD延遲執(zhí)行方法
*
* 參數(shù)一: 要延遲的時間 (以秒為單位)
* 參數(shù)二: 在哪個線程中執(zhí)行
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"GCD定時器");
});
}
執(zhí)行結(jié)果:
一次性代碼
- 特點:
- 能保證整個程序運行過程中黔夭,block內(nèi)的代碼塊只會被執(zhí)行一次
- 線程是安全的
- 應用:
簡單的
單例模式(單例模式實現(xiàn)點我) - 注意點:不可放在懶加載中
- (void)test8
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"一次性代碼運行");
});
}
柵欄函數(shù)
- 作用:能夠控制并發(fā)隊列里面任務的執(zhí)行順序
- 注意:不能使用全局并發(fā)隊列(會沒有任何區(qū)別,文檔中有注釋——只對自己創(chuàng)建的并發(fā)隊列有效)
- (void)test7
{
// 創(chuàng)建隊列
dispatch_queue_t queue = dispatch_queue_create(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"1");
}
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"2");
}
});
dispatch_barrier_async(queue, ^{
NSLog(@"進入柵欄函數(shù)");
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"3");
}
});
dispatch_async(queue, ^{
for (int i = 0; i<5; i++) {
NSLog(@"4");
}
});
}
執(zhí)行結(jié)果:
GCD迭代開發(fā)(遍歷)
- 一般我們傳統(tǒng)的遍歷方式如下浇衬,它的缺點就是在處理比較耗時的操作時效率較低餐济,因為只在一個線程中執(zhí)行任務耘擂。
// 傳統(tǒng)的遍歷方式
for (int i ; i< 10; i++) {
NSLog(@"%d -- 當前線程%@", i, [NSThread currentThread]);
}
執(zhí)行結(jié)果:
- 在GCD中,為我們提供了一個迭代函數(shù)絮姆,可以開啟子線程快速進行遍歷醉冤,這樣就可以大大提高效率,而且使用非常簡單。接下來使用迭代函數(shù)來進行文件復制的操作:
- (void)test9
{
// 獲得文件原始路徑(上層文件夾得路徑)
NSString *fromPath = @"/Users/yeshaojian/Desktop/test";
// 獲得文件的目標路徑
NSString *toPath = @"/Users/yeshaojian/Desktop/test2";
// 得到文件路徑下面的所有文件
NSArray *subpaths = [[NSFileManager defaultManager] subpathsAtPath:fromPath];
NSLog(@"文件名:%@",subpaths);
// 獲取數(shù)組中文件的個數(shù)
NSInteger count = subpaths.count;
// 將要迭代的操作放到迭代函數(shù)內(nèi)
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index){
// 拼接需要復制的文件的全路徑
NSString *fromFullpath = [fromPath stringByAppendingPathComponent:subpaths[index]];
// 拼接目標目錄的全路徑
NSString *toFullpath = [toPath stringByAppendingPathComponent:subpaths[index]];
// 執(zhí)行文件剪切操作
/*
* 參數(shù)一:文件在哪里的全路徑
* 參數(shù)二:文件要被剪切到哪里的全路徑
*/
[[NSFileManager defaultManager] moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
NSLog(@"拼接需要復制的文件的全路徑:%@ -- 拼接目標目錄的全路徑:%@ -- 當前線程:%@",fromFullpath,toFullpath,[NSThread currentThread]);
});
}
執(zhí)行結(jié)果:
隊列組
- 假如開發(fā)中有多個任務篙悯,要求在所有任務都在子線程中并發(fā)執(zhí)行蚁阳,且不能使用柵欄函數(shù),當所有任務都執(zhí)行完成后打印“完成”鸽照。這樣的需求就需要用到GCD中的隊列組螺捐。
- 應用場合:
- 對多個任務有強制依賴性,缺一不可時使用
1.隊列組的基本使用
- (void)test10
{
// 獲取隊列組矮燎,用來管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲取并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("cs", DISPATCH_QUEUE_CONCURRENT);
// 添加任務
dispatch_group_async(group, queue, ^{
NSLog(@"cs1---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"cs2---%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"cs3---%@", [NSThread currentThread]);
});
// 攔截通知:當隊列組中所有的任務都執(zhí)行完畢后定血,會調(diào)用下面方法的block塊
dispatch_group_notify(group, queue, ^{
NSLog(@"完成");
});
}
執(zhí)行結(jié)果:
隊列組函數(shù)內(nèi)部操作簡要流程
處理流程:
1.封裝任務
2.把任務提交到隊列
3.把當前任務的執(zhí)行情況納入到隊列注的監(jiān)聽范圍
注意:下面方法本身是異步的
dispatch_group_notify(group, queue, ^{
});
拓展:
在一些框架或者早期項目中,可能會見到下面2種隊列組的使用方法诞外,在這邊順帶提及一下澜沟,但不推薦使用,因為太過繁瑣浅乔。
第一種
- (void)test11
{
// 獲得隊列組,管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲得并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
// 表示開始把后面的異步任務納入到監(jiān)聽范圍
//dispatch_group_enter & dispatch_group_leave
dispatch_group_enter(group);
// 使用異步函數(shù)封裝任務
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
// 通知隊列組該任務已經(jīng)執(zhí)行完畢
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 攔截通知
dispatch_group_notify(group, queue, ^{
NSLog(@"--完成---");
});
}
第二種
- (void)test11
{
// 獲得隊列組,管理隊列
dispatch_group_t group = dispatch_group_create();
// 獲得并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
// 表示開始把后面的異步任務納入到監(jiān)聽范圍
//dispatch_group_enter & dispatch_group_leave
dispatch_group_enter(group);
// 使用異步函數(shù)封裝任務
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
// 通知隊列組該任務已經(jīng)執(zhí)行完畢
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 等待DISPATCH_TIME_FOREVER 死等,一直要等到所有的任務都執(zhí)行完畢之后才會繼續(xù)往下執(zhí)行
// 同步執(zhí)行
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00001 * NSEC_PER_SEC);
// 等待timer m的時間 不管隊列中的任務有沒有執(zhí)行完畢都繼續(xù)往下執(zhí)行,如果在該時間內(nèi)所有事任務都執(zhí)行完畢了那么會返回一個0,否則是非0值
long n = dispatch_group_wait(group, timer);
NSLog(@"%ld",n);
NSLog(@"--完成---");
}
補充:同步\異步函數(shù)另一種創(chuàng)建方式
- 其實同步函數(shù)和異步函數(shù)還有另外的創(chuàng)建方式倔喂,但是使用起來比較不方便铝条,所以上面就沒提及靖苇,想想還是補充一下好了
1.異步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的異步函數(shù))
- (void)test12
{
/**
* 參數(shù)一:隊列
* 參數(shù)二:要傳給函數(shù)的參數(shù)
* 參數(shù)三:函數(shù)
*/
dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}
void testTask(void *param)
{
NSLog(@"%@", [NSThread currentThread]);
}
2.同步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的同步函數(shù))
- (void)test12
{
/**
* 參數(shù)一:隊列
* 參數(shù)二:要傳給函數(shù)的參數(shù)
* 參數(shù)三:函數(shù)
*/
dispatch_sync_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}
void testTask(void *param)
{
NSLog(@"%@", [NSThread currentThread]);
}
上面使用的是函數(shù)來封裝要處理的代碼,使用比較不方便班缰,且block是輕量級的數(shù)據(jù)結(jié)構(gòu)贤壁,更推薦使用block封裝代碼的形式創(chuàng)建同步\異步函數(shù)。
GCD一些需要注意的細節(jié)
- 全局并發(fā)隊列是默認存在的(在我們程序運行的時候就存在)
- 全局隊列根據(jù)隊列的優(yōu)先級分為 (高埠忘,默認脾拆,低,后臺優(yōu)先級)4個并發(fā)隊列
- iOS 6之前莹妒,我們通過創(chuàng)建的線程名船,是要自己手動施放的
- 施放的方式 —— dispatch_release()
- 使用柵欄函數(shù),蘋果官方文檔明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)創(chuàng)建的筆法隊列一起使用才有效
- 暫時就想到這么多O(∩_∩)O旨怠,因為GCD已經(jīng)開源渠驼,想研究的朋友可以到網(wǎng)上搜索一下,有哪里不對的可以聯(lián)系我鉴腻,謝謝迷扇!
NSOperation簡單使用
NSOperation作用
- 配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程
NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟
- 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
- 然后將NSOperation對象添加到NSOperation對象中
- 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
- 將取出來的NSOperation封裝的操作放到一條新線程中執(zhí)行
NSOperation的子類
- NSOperation是個抽象類百揭,并不具備封裝操作的能力,必須使用它的子類
- 使用NSOperation子類的方式有3種
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應的方法
NSOperation封裝操作
- 第一種方式 —— NSInvocationOperation
- (void)invocationTest
{
/**
* 參數(shù)一:目標對象
* 參數(shù)二:調(diào)用方法
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
// 開啟任務
[op1 start];
}
- (void)download
{
NSLog(@"下載:%@",[NSThread currentThread]);
}
執(zhí)行結(jié)果:需要和隊列并用才會開啟子線程執(zhí)行任務
- 第二種方式 —— Block
- (void)blockTest
{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"增加的下載:%@", [NSThread currentThread]);
}];
// 開啟任務
[op1 start];
[op2 start];
[op3 start];
}
- (void)download
{
NSLog(@"下載:%@",[NSThread currentThread]);
}
執(zhí)行結(jié)果:如果一條線程中執(zhí)行的操作大于1就會開啟新線程并發(fā)執(zhí)行
- 方式三 —— 自定義NSOperation
1.先創(chuàng)建一個繼承自NSOperation的類并重寫main方法
- (void)main
{
NSLog(@"當前線程:%@", [NSThread currentThread]);
}
2.在需要使用的類中引用自定義的類,并創(chuàng)建開啟任務
- (void)custom
{
SJOperation *op1 = [[SJOperation alloc] init];
[op1 start];
}
執(zhí)行結(jié)果:需要手動開啟線程或者與隊列并用才會開啟子線程
NSOperation中的隊列
- 主隊列 (獲取方式:+mainQueue)
- 所有在主隊列中的任務都在主線程中執(zhí)行
- 本質(zhì)上是串行隊列
- (void)invocationQueue
{
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
// 獲取主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
執(zhí)行結(jié)果:所有任務都在主隊列中執(zhí)行蜓席,且是串行隊列
- 非主隊列(獲取方式:alloc init)
- 同時具備并發(fā)和串行功能
- 默認下是并發(fā)的
- (void)invocationQueue
{
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
// 獲取非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
執(zhí)行結(jié)果:所有任務在子線程中并發(fā)執(zhí)行
注意:addOperation:內(nèi)部已經(jīng)幫我們執(zhí)行了開啟任務方法器一,所有不需要另外實現(xiàn)。
NSBlockOperation與隊列并用的簡單寫法
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載:%@",[NSThread currentThread]);
}];
執(zhí)行結(jié)果:所有任務都在子線程中并發(fā)執(zhí)行
設置最大并發(fā)數(shù)
- 在NSOperation中厨内,我們要想控制串行隊列或者并發(fā)隊列祈秕,只需要設置
maxConcurrentOperationCount
屬性即可- 一般我們要使用串行隊列,只需設置值為1即可
- 如果值大于1雏胃,則為并發(fā)隊列
1.串行隊列示例
- (void)blockQueue
{
// 創(chuàng)建非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設置最大并發(fā)數(shù)為1踢步,則隊列為串行隊列
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
}
執(zhí)行結(jié)果:按照任務添加順序執(zhí)行,所以是串行隊列
2.并發(fā)隊列示例
- (void)blockQueue
{
// 創(chuàng)建非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設置最大并發(fā)數(shù)為6丑掺,一般子線程控制在6以內(nèi)获印,太多線程會使設備壓力過大
queue.maxConcurrentOperationCount = 6;
[queue addOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
}
執(zhí)行結(jié)果:程序并沒有按照添加順序完成任務,所以是并發(fā)執(zhí)行
注意:
- 一般子線程控制在6以內(nèi)街州,太多線程會使設備壓力過大
-
maxConcurrentOperationCount
默認值為-1(在計算機中兼丰,-1一般指最大值) - 如果將
maxConcurrentOperationCount
設置為0,說明同一時間內(nèi)執(zhí)行0個任務唆缴,所以任務將不會執(zhí)行鳍征。
NSOperation暫停、恢復和取消功能
- 在NSOperation中面徽,已經(jīng)為我們提供了暫停艳丛、恢復和取消的功能,我們只需調(diào)用相應的方法即可趟紊。
1.暫停
// 暫停
[queue setSuspended:YES];
2.恢復
// 取消
[queue setSuspended:NO];
3.取消
// 取消隊列中所有操作,且取消后的任務不可恢復
[queue cancelAllOperations];
注意:
1.隊列中的的任務是有狀態(tài)的氮双,分別是 —— 等待;執(zhí)行霎匈;完成三種狀態(tài)戴差,且暫停、恢復和取消操作并不能作用于當前正處于執(zhí)行狀態(tài)的任務铛嘱,只能作用于等待狀態(tài)的任務暖释。
2.如果是自定義的NSOperation,會發(fā)現(xiàn)暫停墨吓、恢復操作對其無效球匕,對于這種情況,可以用以下方式解決 —— 使用取消操作
- (void)main
{
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"1當前線程:%@", [NSThread currentThread]);
}
// 判斷當前狀態(tài)帖烘,如果已經(jīng)取消亮曹,直接返回
if (self.cancelled) return;
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"2當前線程:%@", [NSThread currentThread]);
}
// 判斷當前狀態(tài),如果已經(jīng)取消,直接返回
if (self.cancelled) return;
// 模擬耗時操作
for (int i = 0; i< 200; i++) {
NSLog(@"3當前線程:%@", [NSThread currentThread]);
}
// 判斷當前狀態(tài)乾忱,如果已經(jīng)取消讥珍,直接返回
if (self.cancelled) return;
}
解決問題思路:其實這是蘋果官方文檔中的建議 —— 因為,當我們調(diào)用cancelAllOperations:
方法的時候窄瘟,他內(nèi)部的cancelled
屬性就會為真衷佃,每執(zhí)行完一個耗時操作后都進行一次判斷,如果發(fā)現(xiàn)已經(jīng)取消蹄葱,則退出執(zhí)行
氏义。如果想更精確操控的話,也可以將判斷操作放到耗時操作中图云,但是不建議這樣做惯悠,因為這樣性能極差。
NSOperation中的依賴操作
- NSOperation提供了一套非常便捷好用的操作依賴方式竣况,比起GCD克婶,那種酸爽簡直不敢相信
- (void)blockQueue
{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載1:%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載2:%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載3:%@",[NSThread currentThread]);
}];
// 設置依賴關系
// op1依賴op2,只有當op2執(zhí)行完畢后丹泉,才會執(zhí)行op1
[op1 addDependency:op2];
// op2依賴op3情萤,只有當op3執(zhí)行完畢后,才會執(zhí)行op2
[op2 addDependency:op3];
// 獲取主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
執(zhí)行結(jié)果:先執(zhí)行完op3摹恨,等op3執(zhí)行完成后才執(zhí)行op2筋岛,當op2執(zhí)行完畢后,才執(zhí)行op1
注意
- NSOperation提供的操作依賴功能特別強大晒哄,可以設置不同隊列的依賴
- 但是不能循環(huán)依賴睁宰,比如op1依賴op2,op2又依賴op1寝凌,而且并不會報錯柒傻,但會發(fā)生死鎖,且有關任務都不執(zhí)行硫兰。
NSOperation的監(jiān)聽
- 我們經(jīng)常有這樣的需要:在某些任務執(zhí)行完成后诅愚,再執(zhí)行指定的某些操作,那么NSOperation中的監(jiān)聽功能就派上用場了,使用非常簡單
NSOperation *op = [[NSOperation alloc] init];
op.completionBlock = ^{
NSLog(@"下載完成");
};
[op start];
NSOperation線程間通信
- NSOperation線程間的通信類似于GCD劫映,所以就不多敘述了,直接上代碼
- (void)downloadPhoto
{
// 獲取非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建下載任務
[queue addOperationWithBlock:^{
// 圖片地址
NSURL *url = [NSURL URLWithString:@"http://cdn.duitang.com/uploads/item/201512/05/20151205092106_aksZU.jpeg"];
// 下載圖片
NSData *imageData = [NSData dataWithContentsOfURL:url];
// 轉(zhuǎn)換圖片
UIImage *image = [UIImage imageWithData:imageData];
// 回到主線程刷新
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"回%@線程刷新UI", [NSThread currentThread]);
self.imageView.image = image;
}];
}];
}
執(zhí)行結(jié)果:
GCD和NSOperation區(qū)別
在開發(fā)中最常用的就是GCD和NSOperation來進行多線程開發(fā)刹前,NSThread更多是在測試時輔助使用泳赋,pthread則很少看見,這里為大家簡單整理一下他們之間的區(qū)別
-
GCD和NSOperation的對比
- GCD是純C語言的API喇喉,而操作隊列(NSOperation)則是Object-C的對象
- 在GCD中祖今,任務用Block塊來表示,而塊是輕量級的數(shù)據(jù)結(jié)構(gòu),相反千诬,操作隊列(NSOperation)中的
操作
NSOperation是比較重量級的Object-C對象
-
那么在開發(fā)中如何選擇呢耍目?
- 一般如果任務之間有依賴關系或者需要監(jiān)聽任務執(zhí)行的過程(KVO),首選NSOperation
- 單純進行一些耗時操作則選用GCD徐绑,因為相比NSOperation,GCD效率更高邪驮,性能更好
-
NSOperation和NSOperationQueue好處
- NSOperation可以方便設置操作優(yōu)先級(表示操作在隊列中與其它操作之間的優(yōu)先關系,級別越高越先執(zhí)行)
- NSOperation可以通過KVO的方式對NSOperation對象進行監(jiān)聽控制(監(jiān)聽當前操作是處于完成傲茄,取消還是執(zhí)行狀態(tài))
- NSOperation可以方便設置操作之間的依賴關系
- 通過自定義NSOperation子類可以實現(xiàn)操作復用