NSThread
第一種:通過(guò)NSThread的對(duì)象方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(longOperation:) object:@"THREAD"];
[thread start]
第二種:通過(guò)NSThread的類方法
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];
第三種:通過(guò)NSObject的方法
[self performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
阻塞
當(dāng)滿足某個(gè)預(yù)定條件時(shí),可以使用休眠或鎖阻塞線程執(zhí)行
sleepForTimeInterval:休眠指定時(shí)長(zhǎng)
sleepUntilDate:休眠到指定日期
@synchronized(self):互斥鎖
控制線程狀態(tài)的方法
啟動(dòng)
[_thread start]
線程進(jìn)入就緒狀態(tài),當(dāng)線程執(zhí)行完畢后自動(dòng)進(jìn)入死亡狀態(tài)
休眠
方法執(zhí)行過(guò)程中,符合某一條件時(shí),可以利用 sleep 方法讓線程進(jìn)入 阻塞 狀態(tài)
sleepForTimeInterval 從現(xiàn)在起睡多少秒
sleepUntilDate 從現(xiàn)在起睡到指定的日期
死亡
[NSThread exit]
一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會(huì)被執(zhí)行
注意:在終止線程之前,應(yīng)該注意釋放之前分配的對(duì)象!
取消
[_thread cancel]
并不會(huì)直接取消線程
只是給線程對(duì)象添加 isCancelled 標(biāo)記
需要在線程內(nèi)部的關(guān)鍵代碼位置,增加判斷,決定是否取消當(dāng)前線程
線程的屬性
常用屬性
name - 線程名稱
設(shè)置線程名稱可以當(dāng)線程執(zhí)行的方法內(nèi)部出現(xiàn)異常時(shí),記錄異常和當(dāng)前線程
stackSize - 棧區(qū)大小
默認(rèn)情況下,無(wú)論是主線程還是子線程,棧區(qū)大小都是 512K
棧區(qū)大小可以設(shè)置 [NSThread currentThread].stackSize = 1024 * 1024;
必須是 4KB 的倍數(shù)
isMainThread - 是否主線程
threadPriority - 線程優(yōu)先級(jí)
優(yōu)先級(jí),是一個(gè)浮點(diǎn)數(shù),取值范圍從 0~1.0
1.0 表示優(yōu)先級(jí)最高
0.0 表示優(yōu)先級(jí)最低
默認(rèn)優(yōu)先級(jí)是 0.5
優(yōu)先級(jí)高只是保證 CPU 調(diào)度的可能性會(huì)高
互斥鎖 vs 自旋鎖
相同點(diǎn)
能夠保證同一時(shí)間,只有一條線程執(zhí)行鎖定范圍的代碼
不同點(diǎn)
互斥鎖
如果發(fā)現(xiàn)其他線程正在執(zhí)行鎖定代碼,線程會(huì)進(jìn)入休眠(就緒狀態(tài))
等待其他線程時(shí)間片到打開(kāi)鎖后,線程會(huì)被喚醒(執(zhí)行)
自旋鎖
如果發(fā)現(xiàn)有其他線程正在鎖定代碼,線程會(huì)用死循環(huán)的方式,一直等待鎖定的代碼執(zhí)行完成
結(jié)論
自旋鎖更適合執(zhí)行非常短的代碼
無(wú)論什么鎖,都是要付出代價(jià)
線程安全
線程安全
多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然能夠得到正確結(jié)果,被稱為線程安全
要實(shí)現(xiàn)線程安全,必須要用到鎖
主線程(UI線程)
幾乎所有 UIKit ??提供的類都是線程不安全的,所有更新UI的操作都在主線程上執(zhí)行
所有包含 MSMutable 的類都是線程不安全的
GCD
全局隊(duì)列 和 主隊(duì)列
全局隊(duì)列
為了方便程序員的使用,蘋(píng)果提供了全局隊(duì)列 dispatch_get_global_queue(0, 0)
全局隊(duì)列是一個(gè)并發(fā)隊(duì)列
在使用多線程開(kāi)發(fā)時(shí),如果對(duì)隊(duì)列沒(méi)有特殊需求,在執(zhí)行異步任務(wù)時(shí),可以直接使用全局隊(duì)列
主隊(duì)列
為了方便線程間通訊,異步執(zhí)行完網(wǎng)絡(luò)任務(wù),在主線程更新 UI
蘋(píng)果提供了主隊(duì)列 dispatch_get_main_queue()
主隊(duì)列專門用于在主線程上調(diào)度任務(wù)執(zhí)行
異步執(zhí)行任務(wù)
- (void)gcdDemo1 {
// 1. 全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 任務(wù)
dispatch_block_t task = ^ {
NSLog(@"hello gcd %@", [NSThread currentThread]);
};
// 3. 將任務(wù)添加到隊(duì)列,并且指定異步執(zhí)行
dispatch_async(queue, task);
}
NSThread 的對(duì)比
所有的代碼寫(xiě)在一起的,讓代碼更加簡(jiǎn)單,易于閱讀和維護(hù)
NSThread 通過(guò) @selector 指定要執(zhí)行的方法,代碼分散
GCD 通過(guò) block 指定要執(zhí)行的代碼,代碼集中
使用 GCD 不需要管理線程的創(chuàng)建/銷毀/復(fù)用的過(guò)程!程序員不用關(guān)心線程的生命周期
如果要開(kāi)多個(gè)線程 NSThread 必須實(shí)例化多個(gè)線程對(duì)象
NSThread 靠 NSObject 的分類方法實(shí)現(xiàn)的線程間通訊,GCD 靠 block
線程間通訊
#pragma mark - 線程間通訊
- (void)gcdDemo3 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 異步下載圖像
NSLog(@"下載圖像 %@", [NSThread currentThread]);
// 主線程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線程更新 UI %@", [NSThread currentThread]);
});
});
}
以上代碼是 GCD 最常用代碼組合!
網(wǎng)絡(luò)下載圖片
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 準(zhǔn)備 URL
NSString *urlString = @"http://image.tianjimedia.com/uploadImages/2011/286/8X76S7XD89VU.jpg";
NSURL *url = [NSURL URLWithString:urlString];
// 2. 下載圖像
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 1> 所有從網(wǎng)絡(luò)返回的都是二進(jìn)制數(shù)據(jù)
NSData *data = [NSData dataWithContentsOfURL:url];
// 2> 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成 image
UIImage *image = [UIImage imageWithData:data];
// 3> 主線程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
// 1> 設(shè)置圖像
_imageView.image = image;
// 2> 調(diào)整大小
[_imageView sizeToFit];
// 3> 設(shè)置 contentSize
_scrollView.contentSize = image.size;
});
});
}
GCD串行隊(duì)列
特點(diǎn)
以先進(jìn)先出的方式,順序調(diào)度隊(duì)列中的任務(wù)執(zhí)行
無(wú)論隊(duì)列中所指定的執(zhí)行任務(wù)函數(shù)是同步還是異步,都會(huì)等待前一個(gè)任務(wù)執(zhí)行完成后,再調(diào)度后面的任務(wù)
隊(duì)列創(chuàng)建
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.ith", NULL);
串行隊(duì)列演練
串行隊(duì)列 同步執(zhí)行
/// 串行隊(duì)列 - 同步執(zhí)行
/// 提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
/// 回答:不會(huì)開(kāi)線程/順序執(zhí)行/最后
- (void)gcdDemo1 {
// 1. 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
// 2. 添加同步執(zhí)行的任務(wù)
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
串行隊(duì)列 異步執(zhí)行
/// 串行隊(duì)列 - 異步執(zhí)行
/// 提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
/// 回答:會(huì)開(kāi)一條線程/順序執(zhí)行/不確定
- (void)gcdDemo2 {
// 1. 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
// 2. 添加異步執(zhí)行的任務(wù)
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
GCD 并發(fā)隊(duì)列特點(diǎn)
以先進(jìn)先出的方式,并發(fā)調(diào)度隊(duì)列中的任務(wù)執(zhí)行
如果當(dāng)前調(diào)度的任務(wù)是同步執(zhí)行的,會(huì)等待任務(wù)執(zhí)行完成后,再調(diào)度后續(xù)的任務(wù)
如果當(dāng)前調(diào)度的任務(wù)是異步執(zhí)行的,同時(shí)底層線程池有可用的線程資源,會(huì)再新的線程調(diào)度后續(xù)任務(wù)的執(zhí)行
隊(duì)列創(chuàng)建
dispatch_queue_t queue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
并發(fā)隊(duì)列演練
并發(fā)隊(duì)列 異步執(zhí)行
/// 并發(fā)隊(duì)列 - 異步執(zhí)行
/// 提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
/// 回答:會(huì)開(kāi)線程(取決于底層線程池可用資源)/不是順序執(zhí)行/不確定
- (void)gcdDemo2 {
// 1. 并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加異步執(zhí)行的任務(wù)
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ %zd", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
并發(fā)隊(duì)列 同步執(zhí)行
/// 并發(fā)隊(duì)列 - 同步執(zhí)行
/// 提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
/// 回答:不開(kāi)線程/順序執(zhí)行/最后
- (void)gcdDemo1 {
// 1. 并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 添加同步執(zhí)行的任務(wù)
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@ %zd", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}GCD全局隊(duì)列
是系統(tǒng)為了方便程序員開(kāi)發(fā)提供的,其工作表現(xiàn)與并發(fā)隊(duì)列一致
全局隊(duì)列 & 并發(fā)隊(duì)列的區(qū)別
全局隊(duì)列
沒(méi)有名稱
無(wú)論 MRC & ARC 都不需要考慮釋放
日常開(kāi)發(fā)中,建議使用"全局隊(duì)列"
并發(fā)隊(duì)列
有名字,和 NSThread 的 name 屬性作用類似
如果在 MRC 開(kāi)發(fā)時(shí),需要使用 dispatch_release(q); 釋放相應(yīng)的對(duì)象
dispatch_barrier 必須使用自定義的并發(fā)隊(duì)列
開(kāi)發(fā)第三方框架時(shí),建議使用并發(fā)隊(duì)列
全局隊(duì)列 異步任務(wù)
/**
提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
*/
- (void)gcdDemo8 {
// 1. 隊(duì)列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 執(zhí)行任務(wù)
for (int i = 0; i < 10; ++i) {
dispatch_async(q, ^{
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
運(yùn)行效果與并發(fā)隊(duì)列相同
參數(shù)
服務(wù)質(zhì)量(隊(duì)列對(duì)任務(wù)調(diào)度的優(yōu)先級(jí))/iOS 7.0 之前,是優(yōu)先級(jí)
iOS 8.0
QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望最快完成-不能用太耗時(shí)的操作)
QOS_CLASS_USER_INITIATED 0x19, 用戶期望(希望快,也不能太耗時(shí))
QOS_CLASS_DEFAULT 0x15, 默認(rèn)(用來(lái)底層重置隊(duì)列使用的,不是給程序員用的)
QOS_CLASS_UTILITY 0x11, 實(shí)用工具(專門用來(lái)處理耗時(shí)操作!)
QOS_CLASS_BACKGROUND 0x09, 后臺(tái)
QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 適配
iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認(rèn)優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級(jí)
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后臺(tái)優(yōu)先級(jí)
為未來(lái)保留使用的,應(yīng)該永遠(yuǎn)傳入0
結(jié)論:如果要適配 iOS 7.0 & 8.0,使用以下代碼: dispatch_get_global_queue(0, 0);
GCD主隊(duì)列特點(diǎn)專門用來(lái)在主線程上調(diào)度任務(wù)的隊(duì)列不會(huì)開(kāi)啟線程以先進(jìn)先出的方式,在主線程空閑時(shí)才會(huì)調(diào)度隊(duì)列中的任務(wù)在主線程執(zhí)行如果當(dāng)前主線程正在有任務(wù)執(zhí)行,那么無(wú)論主隊(duì)列中當(dāng)前被添加了什么任務(wù),都不會(huì)被調(diào)度隊(duì)列獲取主隊(duì)列是負(fù)責(zé)在主線程調(diào)度任務(wù)的會(huì)隨著程序啟動(dòng)一起創(chuàng)建主隊(duì)列只需要獲取不用創(chuàng)建dispatch_queue_t queue = dispatch_get_main_queue();主隊(duì)列演練主隊(duì)列,異步執(zhí)行- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
[self gcdDemo1];
[NSThread sleepForTimeInterval:1.0];
NSLog(@"over");
}
/// 主隊(duì)列異步
/// 提問(wèn):是否開(kāi)線程?是否順序執(zhí)行?come here 的位置?
/// 回答:不開(kāi)/順序/最前面
- (void)gcdDemo1 {
// 1. 主隊(duì)列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2. 異步任務(wù)
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
NSLog(@"come here");
}
在主線程空閑時(shí)才會(huì)調(diào)度 主隊(duì)列 中的任務(wù)在主線程執(zhí)行
主隊(duì)列,同步執(zhí)行
/// 主隊(duì)列同步
- (void)gcdDemo2 {
NSLog(@"begin");
// 1. 主隊(duì)列
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello");
});
NSLog(@"end");
}
主隊(duì)列和主線程相互等待會(huì)造成死鎖
GCD 同步任務(wù)的作用
同步任務(wù),可以讓其他異步執(zhí)行的任務(wù),依賴某一個(gè)同步任務(wù)
例如:在用戶登錄之后,再異步下載文件!
- (void)gcdDemo1 {
// 1. 隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 同步執(zhí)行登錄
dispatch_sync(queue, ^{
NSLog(@"Login %@", [NSThread currentThread]);
});
// 3. 異步下載文件
dispatch_async(queue, ^{
NSLog(@"Download File A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"Download File B %@", [NSThread currentThread]);
});
}
代碼改造,讓登錄也在異步執(zhí)行
- (void)gcdDemo2 {
// 1. 隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_CONCURRENT);
// 2. 同步執(zhí)行登錄
dispatch_block_t task = ^ {
dispatch_sync(queue, ^{
NSLog(@"Login %@", [NSThread currentThread]);
});
// 3. 異步下載文件
dispatch_async(queue, ^{
NSLog(@"Download File A %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"Download File B %@", [NSThread currentThread]);
});
};
// 3. 將任務(wù)添加到隊(duì)列
dispatch_async(queue, task);
}
主隊(duì)列調(diào)度同步隊(duì)列不死鎖
- (void)gcdDemo3 {
// 1. 串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", DISPATCH_QUEUE_SERIAL);
// 2. 串行隊(duì)列異步
dispatch_async(queue, ^{
NSLog(@"begin %@", [NSThread currentThread]);
// 3. 主隊(duì)列同步執(zhí)行任務(wù)
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"end %@", [NSThread currentThread]);
});
}
主隊(duì)列在主線程空閑時(shí)才會(huì)調(diào)度隊(duì)列中的任務(wù)在主線程執(zhí)行
GCD高級(jí)
目標(biāo)
掌握 GCD 常用的幾個(gè)高級(jí)技巧
Barrier(阻塞)
Once(一次性執(zhí)行)
After(延遲)
Group(調(diào)度組)
GCD Delay 延遲操作
目標(biāo)
掌握 dispatch_after 的基本使用
知道 performSelector:withObject:afterDelay: 的使用
代碼演練
- (void)delay {
NSLog(@"%s", __FUNCTION__);
/**
從現(xiàn)在開(kāi)始,經(jīng)過(guò)多少納秒,由"隊(duì)列"調(diào)度異步執(zhí)行 block 中的代碼
參數(shù)
1. when? ? 從現(xiàn)在開(kāi)始,經(jīng)過(guò)多少納秒
2. queue? 隊(duì)列
3. block? 異步執(zhí)行的任務(wù)
*/
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
dispatch_block_t task = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 主隊(duì)列異步
dispatch_after(when, dispatch_get_main_queue(), task);
// 全局隊(duì)列異步
dispatch_after(when, dispatch_get_global_queue(0, 0), task);
// 串行隊(duì)列異步
dispatch_after(when, dispatch_queue_create("queue", NULL), task);
NSLog(@"end");
}
- (void)after {
[self.view performSelector:@selector(setBackgroundColor:) withObject:[UIColor orangeColor] afterDelay:1.0];
NSLog(@"%@", [NSThread currentThread]);
}
GCD Once 一次性執(zhí)行有的時(shí)候,在程序開(kāi)發(fā)中,有些代碼只想從程序啟動(dòng)就只執(zhí)行一次,典型的應(yīng)用場(chǎng)景就是“單例”單例的特點(diǎn)在內(nèi)存中只有一個(gè)實(shí)例提供一個(gè)全局的訪問(wèn)點(diǎn)提示:?jiǎn)卫氖褂迷?iOS 中非常普遍,以下代碼在很多公司的面試中,都要求能夠手寫(xiě)出來(lái)- (void)once {? ? static dispatch_once_t onceToken;? ? NSLog(@"%zd", onceToken);? ? // onceToken == 0 的時(shí)候執(zhí)行 block 中的代碼? ? // block 中的代碼是同步執(zhí)行的? ? dispatch_once(&onceToken, ^{? ? ? ? NSLog(@"執(zhí)行了");? ? });? ? NSLog(@"come here");}dispatch 內(nèi)部也有一把鎖,是能夠保證"線程安全"的!而且是蘋(píng)果公司推薦使用的以下代碼用于測(cè)試多線程的一次性執(zhí)行- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self once];
});
}
}
單例測(cè)試
單例的特點(diǎn)
在內(nèi)存中只有一個(gè)實(shí)例
提供一個(gè)全局的訪問(wèn)點(diǎn)
懶漢式單例實(shí)現(xiàn)
所謂懶漢式單例,表示在使用時(shí)才會(huì)創(chuàng)建
@implementation NetworkTools
+ (instancetype)sharedTools {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
@end
面試時(shí)只要實(shí)現(xiàn)上面 sharedTools 方法即可
餓漢式單例實(shí)現(xiàn)
所謂餓漢式單例,表示盡早地創(chuàng)建單例實(shí)例,可以利用 initialize 方法建立單例
static id instance;
/// initialize 會(huì)在類第一次被使用時(shí)調(diào)用
/// initialize 方法的調(diào)用是線程安全的
+ (void)initialize {
NSLog(@"創(chuàng)建單例");
instance = [[self alloc] init];
}
+ (instancetype)sharedTools {
return instance;
}
GCD 調(diào)度組
目標(biāo)
掌握 GCD 調(diào)度組的使用
在網(wǎng)絡(luò)開(kāi)發(fā)時(shí),可以統(tǒng)一監(jiān)聽(tīng)一組網(wǎng)絡(luò)請(qǐng)求結(jié)束后,再更新 UI
常規(guī)用法
- (void)group1 {
// 1. 創(chuàng)建調(diào)度組
dispatch_group_t group = dispatch_group_create();
// 2. 全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 3. 調(diào)度組異步
dispatch_group_async(group, queue, ^{
NSLog(@"下載圖像 A %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下載圖像 B %@", [NSThread currentThread]);
});
// 4. 監(jiān)聽(tīng)調(diào)度組完成通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 %@", [NSThread currentThread]);
});
NSLog(@"come here");
}
enter & leavel
在終端輸入 $ man dispatch_group_enter 按兩次空格,可以看到
dispatch_group_async() 的說(shuō)明,是以下代碼的方便寫(xiě)法:
void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_retain(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
block();
dispatch_group_leave(group);
dispatch_release(group);
});
}
以下寫(xiě)法在實(shí)際開(kāi)發(fā)中常用:
dispatch_group_enter(group); 之后,可以監(jiān)聽(tīng)其后的 block 執(zhí)行
在其后的 block 的末尾調(diào)用 dispatch_group_leave(group);,通知 group 一個(gè)任務(wù)完成
group 為空,所有任務(wù)完成!
注意:dispatch_group_enter 和 dispatch_group_leave 一定要配對(duì)使用!
- (void)group2 {
// 1. 創(chuàng)建調(diào)度組
dispatch_group_t group = dispatch_group_create();
// 2. 全局隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 3. 添加任務(wù)
// 1> 添加第 1 個(gè)任務(wù)
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download A %@", [NSThread currentThread]);
// dispatch_group_leave 必須是 block 的最后一句
dispatch_group_leave(group);
});
// 1> 添加第 2 個(gè)任務(wù)
dispatch_group_enter(group);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3.0];
NSLog(@"download B %@", [NSThread currentThread]);
// dispatch_group_leave 必須是 block 的最后一句
dispatch_group_leave(group);
});
// 4. 監(jiān)聽(tīng)調(diào)度組完成通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 %@", [NSThread currentThread]);
});
NSLog(@"come here");
}
GCD Barrier主要用于在多個(gè)異步操作完成之后,統(tǒng)一對(duì)非線程安全的對(duì)象進(jìn)行更新適合于大規(guī)模的 I/O 操作代碼演練準(zhǔn)備工作 —— 定義照片數(shù)組和隊(duì)列@interface ViewController ()/// 照片數(shù)組@property (nonatomic, strong) NSMutableArray *photos;@end@implementation ViewController {? ? dispatch_queue_t _photoQueue;}NSMutableArray 是非線程安全的viewDidLoad- (void)viewDidLoad {? ? [super viewDidLoad];? ? // 1. 創(chuàng)建并發(fā)隊(duì)列? ? _photoQueue = dispatch_queue_create("cn.itcast.photo", DISPATCH_QUEUE_CONCURRENT);? ? // 2. 實(shí)例化照片可變數(shù)組? ? _photos = [NSMutableArray array];? ? // 3. 加載照片? ? [self loadPhotos];}/// 模擬異步下載圖像- (void)loadPhotos {}模擬下載照片并在完成后添加到數(shù)組- (void)loadPhotos {? ? NSInteger count = 10 * 100;? ? for (NSInteger i = 0; i < count; i++) {? ? ? ? dispatch_async(_photoQueue, ^{? ? ? ? ? ? NSString *imageName = [NSString stringWithFormat:@"%02zd.jpg", (i % 10 + 1)];? ? ? ? ? ? NSURL *url? = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];? ? ? ? ? ? NSData *data = [NSData dataWithContentsOfURL:url];? ? ? ? ? ? UIImage *image = [UIImage imageWithData:data];//? ? ? ? ? ? NSLog(@"下載圖像 %@ %@", imageName, [NSThread currentThread]);? ? ? ? ? ? // 添加到圖像數(shù)組? ? ? ? ? ? [self.photos addObject:image];//? ? ? ? ? ? ? ? NSLog(@"添加到數(shù)組 %@ %@", imageName, [NSThread currentThread]);? ? ? ? });? ? }}- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
NSLog(@"%zd", self.photos.count);
}
運(yùn)行測(cè)試
由于 NSMutableArray 是非線程安全的
如果出現(xiàn)兩個(gè)線程在同一時(shí)間向數(shù)組中添加對(duì)象,可能會(huì)出現(xiàn)程序崩潰的情況
也可能出現(xiàn)數(shù)組數(shù)據(jù)錯(cuò)亂的情況
解決辦法
// 添加到圖像數(shù)組
dispatch_barrier_async(_photoQueue, ^{
[self.photos addObject:image];
NSLog(@"添加到數(shù)組 %@ %@", imageName, [NSThread currentThread]);
});
使用 dispatch_barrier_async 添加的 block 會(huì)在之前添加的 block 全部運(yùn)行結(jié)束之后,才在同一個(gè)線程順序執(zhí)行,從而保證對(duì)非線程安全的對(duì)象進(jìn)行正確的操作!
Barrier 工作示意圖
注意:dispatch_barrier_async 必須使用自定義隊(duì)列,否則執(zhí)行效果和全局隊(duì)列一致
NSOperation 簡(jiǎn)介
NSOperation
是 OC 語(yǔ)言中基于 GCD 的面向?qū)ο蟮姆庋b
使用起來(lái)比 GCD 更加簡(jiǎn)單(面向?qū)ο螅?/p>
提供了一些用 GCD 不好實(shí)現(xiàn)的功能
蘋(píng)果推薦使用,使用 NSOperation 不用關(guān)心線程以及線程的生命周期
核心概念
將 操作(異步執(zhí)行的任務(wù)) 添加到 隊(duì)列(并發(fā)隊(duì)列)
NSOperation 是一個(gè)抽象類,不能直接使用
抽象類的用處是定義子類共有的屬性和方法
已經(jīng)學(xué)習(xí)過(guò)的抽象類包括:
UICollectionViewLayout
UIGestureRecognizer
CAAnimation
CAPropertyAnimation
在蘋(píng)果的頭文件中,有些抽象類和子類的定義是在同一個(gè)頭文件中的
子類:
NSInvocationOperation (調(diào)用)
NSBlockOperation (塊)
自定義 Operation
NSOperationQueue 隊(duì)列
使用步驟
NSOperation 和 NSOperationQueue 實(shí)現(xiàn)多線程的具體步驟:
程序員
先將需要執(zhí)行的操作封裝到一個(gè) NSOperation 對(duì)象中
然后將 NSOperation 對(duì)象添加到 NSOperationQueue 中
系統(tǒng)
系統(tǒng)會(huì)自動(dòng)將 NSOperationQueue 中的 NSOperation 取出來(lái)
將取出的 NSOperation 封裝的操作放到一條新線程中執(zhí)行
NSOperation 基本演練
目標(biāo)
通過(guò) NSInvocationOperation 體會(huì)將操作添加到隊(duì)列異步執(zhí)行
知道 start 方法
NSOperationQueue - 并發(fā)隊(duì)列
NSOperation - 異步執(zhí)行的任務(wù)
代碼演練
NSInvocationOperation
start 方法
- (void)opDemo1 {
// 1. 創(chuàng)建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(__FUNCTION__)];
// 2. 啟動(dòng)操作
/**
Begins the execution of the operation.
在當(dāng)前線程開(kāi)始執(zhí)行操作
The default implementation of this method updates the execution state of the operation and calls the receiver’s main method. This method also performs several checks to ensure that the operation can actually run. For example, if the receiver was cancelled or is already finished, this method simply returns without calling main.
- 此方法的默認(rèn)實(shí)現(xiàn)更新操作的執(zhí)行狀態(tài),并且調(diào)用接收者的 main 方法
- 此方法同時(shí)會(huì)執(zhí)行一系列檢查,以確保操作可以被正確執(zhí)行
- 例如,如果接收者已經(jīng)被取消或者已經(jīng)結(jié)束,此方法直接返回而不再調(diào)用 main 方法
You can call this method explicitly if you want to execute your operations manually. However, it is a programmer error to call this method on an operation object that is already in an operation queue or to queue the operation after calling this method. Once you add an operation object to a queue, the queue assumes all responsibility for it.
- 如果希望手動(dòng)執(zhí)行操作,可以調(diào)用此方法
- 然而,如果一個(gè)操作已經(jīng)被添加進(jìn)操作隊(duì)列,再調(diào)用 start 方法是錯(cuò)誤的
- 因?yàn)椋坏⒉僮鲗?duì)象添加到隊(duì)列,后續(xù)的操作應(yīng)該由隊(duì)列負(fù)責(zé)
*/
[op start];
}
- (void)demo:(id)obj {
NSLog(@"%@ %@", [NSThread currentThread], obj);
}
默認(rèn)情況下,調(diào)用 start 方法后并不會(huì)開(kāi)一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
只有將 NSOperation 放到一個(gè) NSOperationQueue 中,才會(huì)異步執(zhí)行操作
將操作添加到隊(duì)列
- (void)opDemo2 {
// 1. 創(chuàng)建操作
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(__FUNCTION__)];
// 2. 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 3. 將操作添加到隊(duì)列
/**
Once added, the specified operation remains in the queue until it finishes executing.
- 一旦添加,指定的操作會(huì)保留在隊(duì)列中,直至執(zhí)行完畢
- 將操作添加到隊(duì)列后,系統(tǒng)會(huì)從隊(duì)列中取出操作,并新建線程,執(zhí)行操作指定方法
*/
[queue addOperation:op];
}
添加多個(gè)操作
@implementation ViewController {
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
[self opDemo3];
}
#pragma mark - NSOperation
/// 添加多個(gè)操作
- (void)opDemo3 {
for (NSInteger i = 0; i < 10; i++) {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo:) object:@(i)];
[_queue addOperation:op];
}
}
執(zhí)行效果:會(huì)開(kāi)啟多條線程,而且不是順序執(zhí)行。與 GCD 中 并發(fā)隊(duì)列&異步執(zhí)行 效果一樣!
一般在使用 NSOpeartion 時(shí),會(huì)建立一個(gè)全局隊(duì)列,統(tǒng)一調(diào)度所有的異步操作
結(jié)論,在 NSOperation 中:
操作 -> 異步執(zhí)行的任務(wù)
隊(duì)列 -> 并發(fā)隊(duì)列
NSBlockOperation
目標(biāo)
熟悉 NSBlockOperation 的使用
NSBlockOperation blockOperationWithBlock 可以創(chuàng)建塊操作
qualityOfService 可以指定優(yōu)先級(jí)
completionBlock 可以設(shè)置操作完成回調(diào)
完成回調(diào)同樣在異步執(zhí)行,并且沒(méi)有任何參數(shù)
代碼演練
基本代碼演練
@implementation ViewController {
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
[self opDemo];
}
- (void)opDemo {
// 1. 新建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
// 2. 將操作添加到隊(duì)列
[_queue addOperation:op];
}
@end
相比較 NSInvocationOperation,NSBlockOperation 使用更加簡(jiǎn)單
更簡(jiǎn)單的使用
/// 更簡(jiǎn)單的使用
- (void)opDemo2 {
// 添加 block 操作
[_queue addOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
}
直接添加 block 更加簡(jiǎn)單,不過(guò)會(huì)少了一些控制,例如:完成監(jiān)聽(tīng)、優(yōu)先級(jí)等
設(shè)置完成監(jiān)聽(tīng)
/// 完成監(jiān)聽(tīng)
- (void)opDemo3 {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@", [NSThread currentThread]);
}];
// 設(shè)置完成 block - 會(huì)另外新建一條線程執(zhí)行完成回調(diào)
[op setCompletionBlock:^{
NSLog(@"完成 %@", [NSThread currentThread]);
}];
[_queue addOperation:op];
}
設(shè)置優(yōu)先級(jí)
/// 設(shè)置優(yōu)先級(jí)
- (void)opDemo4 {
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"%@ %@", [NSThread currentThread], @(i));
}
}];
op1.qualityOfService = NSQualityOfServiceUserInteractive;
[_queue addOperation:op1];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"%@ %@", [NSThread currentThread], @(i));
}
}];
op2.qualityOfService = NSQualityOfServiceBackground;
[_queue addOperation:op2];
}
設(shè)置執(zhí)行塊 —— 知道即可
/// 設(shè)置執(zhí)行塊
- (void)opDemo5 {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"==> %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"block 1 %@", [NSThread currentThread]);
}];
[_queue addOperation:op];
NSLog(@"%@", op.executionBlocks);
}
執(zhí)行塊和操作享有共同的屬性設(shè)置
當(dāng) NSBlockOperation 封裝的 操作數(shù) > 1,就會(huì)異步執(zhí)行操作
線程間通訊
目標(biāo)
掌握 NSOperation 的線程間通訊
NSOperationQueue mainQueue 可以獲得主隊(duì)列,實(shí)現(xiàn)線程間通訊
NSOperationQueue currentQueue 當(dāng)前隊(duì)列
隊(duì)列操作方法
addOperation: 添加操作
addOperations:waitUntilFinished: 添加多個(gè)操作,并且指定是否等待操作完成
addOperationWithBlock: 添加塊操作
cancelAllOperations 取消所有操作
隊(duì)列屬性
name 隊(duì)列名稱
operations 操作數(shù)組
operationCount 操作數(shù)量
maxConcurrentOperationCount 最大并發(fā)數(shù)
suspended 掛起
qualityOfService 服務(wù)質(zhì)量
代碼操作
常見(jiàn)的線程間通訊代碼
/// 線程間通訊
- (void)opDemo6 {
[_queue addOperationWithBlock:^{
NSLog(@"耗時(shí)操作 %@", [NSThread currentThread]);
NSString *json = @"我是 json";
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%@ %@", json, [NSThread currentThread]);
}];
}];
}
NSOperation高級(jí)操作目標(biāo)掌握 NSOperation 的高級(jí)操作設(shè)置最大并發(fā)數(shù)(同時(shí)執(zhí)行的任務(wù)數(shù)量,不是開(kāi)啟的線程數(shù)量)暫停、繼續(xù)和取消設(shè)置 suspended 可以暫停和繼續(xù)隊(duì)列的調(diào)度掛起隊(duì)列,不會(huì)影響正在執(zhí)行中的操作cancelAllOperations 可以給隊(duì)列中所有任務(wù)發(fā)送 cancel 消息如果操作還沒(méi)有調(diào)度,會(huì)被從隊(duì)列中移除如果操作已經(jīng)被執(zhí)行,需要通過(guò)自定義操作取消執(zhí)行,否則操作會(huì)繼續(xù)執(zhí)行,直至完成代碼演練代碼準(zhǔn)備@implementation ViewController {? ? NSOperationQueue *_queue;}- (void)viewDidLoad {? ? [super viewDidLoad];? ? _queue = [[NSOperationQueue alloc] init];? ? [self demo];}- (void)demo {? ? for (NSInteger i = 0; i < 20; i++) {? ? ? ? [_queue addOperationWithBlock:^{? ? ? ? ? ? // 注意休眠的位置? ? ? ? ? ? [NSThread sleepForTimeInterval:1.0];? ? ? ? ? ? NSLog(@"%@ %@", [NSThread currentThread], @(i));? ? ? ? }];? ? }}@end運(yùn)行測(cè)試開(kāi)啟的線程數(shù)量很多- (void)viewDidLoad {? ? [super viewDidLoad];? ? _queue = [[NSOperationQueue alloc] init];? ? // 設(shè)置最大并發(fā)數(shù)? ? _queue.maxConcurrentOperationCount = 2;? ? [self demo];}注意:最大并發(fā)數(shù),是同時(shí)執(zhí)行的任務(wù)數(shù)量,不是開(kāi)啟的線程數(shù)量!隊(duì)列的暫停、繼續(xù)和取消暫停和繼續(xù)- (IBAction)pause:(id)sender {? ? if (_queue.operationCount == 0) {? ? ? ? NSLog(@"隊(duì)列中沒(méi)有操作");? ? ? ? return;? ? }? ? _queue.suspended = !_queue.isSuspended;? ? NSLog(@"%@", (_queue.suspended ? @"暫停": @"繼續(xù)"));? ? NSLog(@"操作數(shù)量 %zd", _queue.operationCount);}注意:掛起隊(duì)列只會(huì)暫停隊(duì)列中的操作繼續(xù)被調(diào)度當(dāng)前正在執(zhí)行的操作不會(huì)被掛起操作完成之后,會(huì)被從隊(duì)列中移除如果將隊(duì)列設(shè)置為掛起,后續(xù)再添加操作,隊(duì)列同樣不會(huì)被調(diào)度取消全部- (IBAction)cancelAll:(id)sender {? ? [_queue cancelAllOperations];? ? NSLog(@"取消全部 %zd", _queue.operationCount);}注意當(dāng)前正在執(zhí)行的操作不會(huì)被取消自定義操作除外自定義操作的取消新建操作@interface CZDemoOperation : NSOperation@property (nonatomic, assign) NSInteger num;@end實(shí)現(xiàn) main 方法main 方法是每一個(gè)線程的入口注意:需要在 main 方法中添加自動(dòng)釋放池@implementation CZDemoOperation- (void)main {? ? @autoreleasepool {? ? ? ? if (self.isCancelled) {? ? ? ? ? ? NSLog(@"被取消 %zd", _num);? ? ? ? ? ? return;? ? ? ? }? ? ? ? [NSThread sleepForTimeInterval:1.0];? ? ? ? if (self.isCancelled) {? ? ? ? ? ? NSLog(@"被取消 %zd", _num);? ? ? ? ? ? return;? ? ? ? }? ? ? ? NSLog(@"%@ %zd", [NSThread currentThread], _num);? ? }}@end測(cè)試取消代碼@implementation ViewController {? ? NSOperationQueue *_queue;}- (void)viewDidLoad {? ? [super viewDidLoad];? ? _queue = [[NSOperationQueue alloc] init];? ? _queue.maxConcurrentOperationCount = 2;? ? for (NSInteger i = 0; i < 20; i++) {? ? ? ? CZDemoOperation *op = [[CZDemoOperation alloc] init];? ? ? ? op.num = i;? ? ? ? [_queue addOperation:op];? ? }}- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
NSLog(@"取消所有操作");
[_queue cancelAllOperations];
}
操作的依賴關(guān)系
掌握操作的依賴關(guān)系
通過(guò) addDependency 可以設(shè)置操作之間的依賴關(guān)系
注意不要設(shè)置循環(huán)依賴
代碼演練
- (void)demo {
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"login %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download A %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download B %@", [NSThread currentThread]);
}];
[op2 addDependency:op1];
[op3 addDependency:op1];
// 注意不要添加循環(huán)依賴
//? ? [op1 addDependency:op2];
[_queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
NSLog(@"come here");
}
注意不要設(shè)置循環(huán)依賴
與 GCD 的對(duì)比
GCD
將任務(wù)(block)添加到隊(duì)列(串行/并發(fā)/主隊(duì)列),并且指定任務(wù)執(zhí)行的函數(shù)(同步/異步)
GCD 是底層的 C 語(yǔ)言構(gòu)成的 API
iOS 4.0 推出的,針對(duì)多核處理器的并發(fā)技術(shù)
在隊(duì)列中執(zhí)行的是由 block 構(gòu)成的任務(wù),這是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu)
要停止已經(jīng)加入 queue 的 block 需要寫(xiě)復(fù)雜的代碼
需要通過(guò) Barrier 或者同步任務(wù)設(shè)置任務(wù)之間的依賴關(guān)系
只能設(shè)置隊(duì)列的優(yōu)先級(jí)
高級(jí)功能:
一次性 once
延遲操作 after
調(diào)度組
NSOperation
核心概念:把操作(異步)添加到隊(duì)列(并發(fā)隊(duì)列)
OC 框架,更加面向?qū)ο螅菍?duì) GCD 的封裝
iOS 2.0 推出的,蘋(píng)果推出 GCD 之后,對(duì) NSOperation 的底層全部重寫(xiě)
Operation作為一個(gè)對(duì)象,為我們提供了更多的選擇
可以隨時(shí)取消已經(jīng)設(shè)定要準(zhǔn)備執(zhí)行的操作,已經(jīng)執(zhí)行的除外
可以跨隊(duì)列設(shè)置操作的依賴關(guān)系
可以設(shè)置隊(duì)列中每一個(gè)操作的優(yōu)先級(jí)
高級(jí)功能:
最大操作并發(fā)數(shù)(GCD不好做)
繼續(xù)/暫停/全部取消
跨隊(duì)列設(shè)置操作的依賴關(guān)系