//:如有需要代碼demo 在評(píng)論留下郵箱 即刻回復(fù)
一 : 科普一分鐘
上一期簡(jiǎn)單普及了一下有多線程的知識(shí).
如何創(chuàng)建子線程,對(duì)于線程的控制,如何對(duì)UI 線程的操作.
在實(shí)際開發(fā)應(yīng)用中,我們用到子線程,進(jìn)行耗時(shí)任務(wù)操作,并發(fā)任務(wù)之間是如何依賴的,這一期將詳細(xì)講解.
我們?cè)谫I火車票的時(shí)候?yàn)槭裁床粫?huì)購(gòu)買重復(fù)嗎,對(duì)于線程安全也會(huì)有詳細(xì)講解.
二 : 詳細(xì)講解iOS中多線程方案
1. pthread
- 簡(jiǎn)單了解即可,在開發(fā)應(yīng)用中非常少. 首先導(dǎo)入庫(kù)
#import <pthread.h>
- 線程對(duì)象的創(chuàng)建
//1.pthread 創(chuàng)建線程對(duì)象
pthread_t thread;
pthread_t thread1 = NULL;
- 創(chuàng)建線程
/**
第一個(gè)參數(shù):線程對(duì)象傳遞地址
第二個(gè)參數(shù):線程的屬性 NULL
第三個(gè)參數(shù):指向函數(shù)的指針
第四個(gè)參數(shù):函數(shù)需要接受的參數(shù)
*/
pthread_create(&thread, NULL,TZtest, NULL);
void *TZtest (void *parm){
NSLog(@"--%@",[NSThread mainThread]);
return NULL;
}
- 判斷兩個(gè)條線程是否相等
pthread_equal(thread, thread1);
- 應(yīng)用
記得之前在 viewDidLoad
寫過一個(gè)耗時(shí)操作
//耗時(shí)操作
for (int i = 0; i < 100000; i++) {
NSLog(@" i = %d ---currentThread = %@",i,[NSThread mainThread]);
}
現(xiàn)在我們把這個(gè)方法放入我們的創(chuàng)建的線程方法里
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t thread;
pthread_create(&thread, NULL,test, NULL);
}
void *test (void *parm){
for (int i = 0; i < 100000; i++) {
NSLog(@" i = %d ---currentThread = %@",i,[NSThread mainThread]);
}
NSLog(@"--%@",[NSThread mainThread]);
return NULL;
}
2. NSThread
- 創(chuàng)建線程的的方式
1.第一種方式 手動(dòng)啟動(dòng)線程
參數(shù)1 : 目標(biāo)對(duì)象
參數(shù)2 : 方法選擇器
參數(shù)3 : 前面調(diào)用方法需要傳遞的參數(shù) 沒有時(shí)傳nil
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
//啟動(dòng)線程
[thread start];
-(void)test:(NSString *)str{
NSLog(@"--%@",[NSThread mainThread]);
}
2.第二種方式 類方法 自動(dòng)啟動(dòng)
//創(chuàng)建線程的第二種方法 自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"TZ"];
3.第三種方法
//開啟一條后臺(tái)線程
[self performSelectorInBackground:@selector(test:) withObject:@"TZ"];
- 屬性 屬性在啟動(dòng)線程之前調(diào)用
name
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
//設(shè)置屬性在啟動(dòng)線程之前設(shè)置
thread.name = @"A";
threadPriority
TZThread *thread = [[TZThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
//設(shè)置屬性在啟動(dòng)線程之前設(shè)置
//線程的優(yōu)先級(jí) 取值范圍 0.0 - 1.0之間 默認(rèn)優(yōu)先級(jí) 0.5
thread.threadPriority = 1.0 ;
線程的周期
生命周期,當(dāng)任務(wù)執(zhí)行完畢后被釋放掉線程的狀態(tài)
當(dāng)線程創(chuàng)建好后 執(zhí)行start
方法后 --->線程進(jìn)入就緒狀態(tài)
-runable
--> 當(dāng)CPU 調(diào)度線程的時(shí)候 線程進(jìn)入--->運(yùn)行狀態(tài)
-Running
<------當(dāng)CPU 調(diào)度其他線程時(shí)候 狀態(tài)變?yōu)?就緒狀態(tài)
-runable
----->當(dāng)調(diào)用sleep
方法等待同步鎖---->線程進(jìn)入阻塞狀態(tài)
------>當(dāng)線程任務(wù)執(zhí)行完畢/異常退出/強(qiáng)制退出(exit
)---->線程進(jìn)入死亡狀態(tài)-Dead
!注意:
當(dāng)線程執(zhí)行start
后 線程對(duì)象放入 可調(diào)度線程池
當(dāng)線程進(jìn)入阻塞狀態(tài)
時(shí)候 線程對(duì)象
還在內(nèi)存中 只不過暫時(shí)從可調(diào)度線程池
移除
當(dāng)線程進(jìn)入死亡狀態(tài)
時(shí) 線程對(duì)象
從內(nèi)存
中移除
- 阻塞方法
//阻塞線程
[NSThread sleepForTimeInterval:2];
//3秒后
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
- 強(qiáng)制退出方法
[NSThread exit];//退出當(dāng)前線程 強(qiáng)制退出
-
線程之間的通信
1.我們?nèi)ッ嬖嚮蛘呓涣鞯臅r(shí)候總會(huì)被人們問到一個(gè)問題,一個(gè)線程讀取完數(shù)據(jù)怎么再次操作另一個(gè)線程呢...等等
其實(shí)這就是線程之間的通信2.線程通信體現(xiàn)在
1.一個(gè)線程傳遞數(shù)據(jù)給另一個(gè)線程
2.在一個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個(gè)線程繼續(xù)執(zhí)行任務(wù).
3.線程通信的常用方法
回到主線程,第三個(gè)參數(shù)的意思 就是 是否等待執(zhí)行,如果選擇`YES` 則知道回到主線程 參數(shù)的方法執(zhí)行完成后 繼續(xù)執(zhí)行,
否則的話 直接執(zhí)行 該方法下面的函數(shù)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
回到某個(gè)線程
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
4.代碼實(shí)現(xiàn)
我們寫一個(gè)下載圖片的代碼 完成線程間通信
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(downLoad) toTarget:self withObject:nil];
}
-(void)downLoad{
// http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg
NSURL *URL = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
//根據(jù)URL 下載圖片 二進(jìn)制數(shù)據(jù) 到本地
NSData *imageData = [NSData dataWithContentsOfURL:URL];
//3.轉(zhuǎn)換圖片格式
UIImage *image = [UIImage imageWithData:imageData];
//回到主線程顯示UI
// [self performSelectorOnMainThread:@selector(TZshowUI:) withObject:image waitUntilDone:YES];
// [self performSelector:@selector(TZshowUI:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
-(void)TZshowUI:(UIImage*)image{
self.imageView.image = image;
}
3. GCD
概括 : Grand Central DisPatch 中樞調(diào)度器
純C語言,提供了非常多強(qiáng)大的函數(shù)GCD 的優(yōu)勢(shì) : GCD 是蘋果為多核的并行運(yùn)算提出的解決方案
GCD會(huì)自動(dòng)利用更多的CPU 內(nèi)核
GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程,調(diào)度任務(wù),銷毀線程)
程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理的代碼-
CCD 最大的兩個(gè)特性
任務(wù)
和隊(duì)列
任務(wù): 執(zhí)行什么操作
隊(duì)列:用來存放任務(wù)
GCD使用的兩個(gè)步驟
a : 定制任務(wù)
b : 將任務(wù)添加到隊(duì)列中GCD 會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對(duì)應(yīng)的棧中執(zhí)行
任務(wù)的取出時(shí)遵循隊(duì)列的FIFO 原則,先進(jìn)先出,后進(jìn)后 -
常用函數(shù)
a : 同步函數(shù) 參數(shù) : 1.隊(duì)列 2 block 想做的事(任務(wù))dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
b : 異步函數(shù) 參數(shù) : 1.隊(duì)列 2 block 想做的事(任務(wù))
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
-
同步函數(shù)與異步函數(shù)的區(qū)別
同步函數(shù) : 只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步函數(shù) : 可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力 -
隊(duì)列的類型
并發(fā)隊(duì)列 : 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行 ,自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù),并發(fā)功能只有在異步函數(shù)
dispatch_asyn
下才有效串行隊(duì)列 : 讓任務(wù)一個(gè)接一個(gè)地執(zhí)行,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
基礎(chǔ)代碼
a : 創(chuàng)建異步函數(shù)+并發(fā)隊(duì)列 : 會(huì)開啟多條線程,隊(duì)列中的任務(wù)是異步執(zhí)行
-(void)TZasyncConCurrent{
//1.創(chuàng)建隊(duì)列
/**
參數(shù)1 : C語言的字符串,標(biāo)簽
參數(shù)2 : 隊(duì)列的類型
DISPATCH_QUEUE_CONCURRENT 并發(fā)
DISPATCH_QUEUE_SERIAL 串行
*/
// dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
//獲取全局并發(fā)隊(duì)列 從系統(tǒng)中獲得
/**
參數(shù)一 : 優(yōu)先級(jí)
參數(shù)二 : 未來使用
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2 1->封裝任務(wù) 2->添加任務(wù)到隊(duì)列中
/**
參數(shù)1 : 隊(duì)列
參數(shù)2 : 要執(zhí)行的任務(wù)
*/
dispatch_async(queue, ^{
NSLog(@"-TZ1--%@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"-TZ2--%@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"-TZ3--%@",[NSThread mainThread]);
});
}
b : 創(chuàng)建異步函數(shù)+串行隊(duì)列 : 會(huì)開線程,開一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的
//異步函數(shù)+串行隊(duì)列 : 會(huì)開線程,開一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的
-(void)TZasyncSerial{
//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("TZzzzz", DISPATCH_QUEUE_SERIAL);
//2.封裝操作
dispatch_async(queue, ^{
NSLog(@"-TZ1--%@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"-TZ2--%@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"-TZ3--%@",[NSThread mainThread]);
});
}
c : 創(chuàng)建同步函數(shù)+并發(fā)隊(duì)列 : 不會(huì)開線程,任務(wù)是串行執(zhí)行的
-(void)TZsyncConCurrent{
//創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
//封裝任務(wù)
//2.封裝操作
dispatch_sync(queue, ^{
NSLog(@"-TZ1--%@",[NSThread mainThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-TZ2--%@",[NSThread mainThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-TZ3--%@",[NSThread mainThread]);
});
}
d : 創(chuàng)建同步函數(shù)+串行隊(duì)列 : 不會(huì)開線程,任務(wù)是串行執(zhí)行的
-(void)TZsyncSerial{
//創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_SERIAL);
//封裝任務(wù)
//2.封裝操作
dispatch_sync(queue, ^{
NSLog(@"-TZ1--%@",[NSThread mainThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-TZ2--%@",[NSThread mainThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-TZ3--%@",[NSThread mainThread]);
});
}
8.GCD 線程間的通信
我們還是以下載圖片為例子
//創(chuàng)建子線程 下載圖片
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//1.1
NSURL *URL = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
//1.2 下載二進(jìn)制數(shù)據(jù)到本地
NSData *imageData = [NSData dataWithContentsOfURL:URL];
UIImage *image = [UIImage imageWithData:imageData];
//更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
我們開啟了子線程,然后進(jìn)行下載 .回到主線程做事情(刷新UI)
9.GCD 常用函數(shù)
a : 延遲
我們平時(shí)為了在某個(gè)地方停留一會(huì)用的比較多的就是延遲函數(shù)
```
//1. 延遲執(zhí)行的第一種方法
NSLog(@"--start");
[self performSelector:@selector(tztask) withObject:nil afterDelay:2];
//2.延遲執(zhí)行的第二種方法
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(tztask) userInfo:nil repeats:NO];
//3.延遲執(zhí)行的第三種方法
/**
參數(shù)1 : DISPATCH_TIME_NOW 從現(xiàn)在開始計(jì)算時(shí)間
參數(shù)2 : 延遲的時(shí)間 2.0 GCD 時(shí)間單位為納秒
參數(shù)3 : 隊(duì)列
*/
//傳入全局并發(fā)隊(duì)列打印操作在子線程執(zhí)行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), (queue), ^{
NSLog(@"--GCD----%@",[NSThread currentThread]);
});
```
延遲不僅可以在主線程(UI)線程中進(jìn)行,也可以在子線程進(jìn)行,當(dāng)隊(duì)列為并發(fā)隊(duì)列
時(shí)候再子線程執(zhí)行 ,主隊(duì)列
時(shí)候再主線程執(zhí)行. 使用時(shí)請(qǐng)大家注意
b : 一次性代碼-大多數(shù)情況用在單利中
//整個(gè)應(yīng)用程序只調(diào)用一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"===onece");
});
注意:一次性代碼不用放在懶加載里面,因?yàn)橐淮涡源a 整個(gè)app 啟動(dòng)后只會(huì)運(yùn)行一次.
10.GCD 柵欄函數(shù)
他是干什么的呢, 在多個(gè)異步任務(wù),我們?cè)趺匆?guī)范順序呢 這就是柵欄函數(shù)的作用,像圍欄一樣控制你想要的順序
//柵欄函數(shù)不能使用全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("TZdown", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"-TZ1--%@",[NSThread mainThread]);
});
dispatch_async(queue, ^{
NSLog(@"-TZ2--%@",[NSThread mainThread]);
});
//柵欄函數(shù)
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++圍欄++++++++++");
});
dispatch_async(queue, ^{
NSLog(@"-TZ3--%@",[NSThread mainThread]);
});
運(yùn)行結(jié)果 : TZ1
TZ2
打印順序隨意,因?yàn)槭钱惒降?,柵欄在其后,最后執(zhí)行的是TZ3
任務(wù)
柵欄函數(shù)注意點(diǎn):控制并發(fā)隊(duì)列任務(wù)執(zhí)行順序,不能使用全局并發(fā)隊(duì)列
11.GCD 快速迭代
什么是迭代呢,簡(jiǎn)單來說就是循環(huán) ,為什么要使用快速迭代呢,因?yàn)閮?nèi)部能開子線程,執(zhí)行速度快
/**
參數(shù) 1 : 要遍歷的次數(shù)
參數(shù) 2 : 隊(duì)列(只能傳并發(fā)隊(duì)列)
參數(shù) 3 :index 索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"-%zd---%@",index,[NSThread mainThread]);
});
12.GCD隊(duì)列組
隊(duì)列組的作用是會(huì)監(jiān)聽假如隊(duì)列組中任務(wù)的執(zhí)行情況
隊(duì)列組的兩種方式
a :
///1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//3.異步函數(shù)
/**
封裝任務(wù)
把任務(wù)添加到隊(duì)列中
會(huì)監(jiān)聽任務(wù)的執(zhí)行情況,通知group
*/
dispatch_group_async(group, queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3---%@",[NSThread currentThread]);
});
//攔截任務(wù),當(dāng)隊(duì)列組中所有的任務(wù)都執(zhí)行完畢的時(shí)候會(huì)進(jìn)入下面的方法
dispatch_group_notify(group, queue, ^{
NSLog(@"---完成所有任務(wù)---");
});
b :
///1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//3.在該方法后面的異步任務(wù)會(huì)被納入隊(duì)列組的監(jiān)聽范圍,進(jìn)入群組
//dispatch_group_enter|dispatch_group_leave 必須要配對(duì)使用
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
//離開群組
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2---%@",[NSThread currentThread]);
//離開群組
dispatch_group_leave(group);
});
//攔截通知 內(nèi)部本身是異步的 ,不會(huì)堵塞
// dispatch_group_notify(group, queue, ^{
// NSLog(@"----完成任務(wù)----");
// });
//死等 直到隊(duì)列組中所有任務(wù)都執(zhí)行完畢之后才能執(zhí)行
//本身是阻塞的,這一行代碼沒有執(zhí)行完畢后面代碼永遠(yuǎn)不會(huì)被執(zhí)行
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"---over");
13:GCD 隊(duì)列組實(shí)例 ,下載兩張圖片,合并圖片
/**
1.下載圖片1 開子線程
2.下載圖片2 開子線程
3.合成圖片并顯示圖片 開子線程
*/
//獲取一個(gè)隊(duì)列組
dispatch_group_t group = dispatch_group_create();
//獲得并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.下載圖片 1 開啟子線程
dispatch_group_async(group, queue,^{
//確定url
NSURL *url = [NSURL URLWithString:@""];
//下載二進(jìn)制數(shù)據(jù)
NSData *imageData = [NSData dataWithContentsOfURL:url];
//轉(zhuǎn)換圖片
self.image1 = [UIImage imageWithData:imageData];
});
//2.下載圖片2 開啟子線程
dispatch_group_async(group, queue,^{
//確定url
NSURL *url = [NSURL URLWithString:@""];
//下載二進(jìn)制數(shù)據(jù)
NSData *imageData = [NSData dataWithContentsOfURL:url];
//轉(zhuǎn)換圖片
self.image2 = [UIImage imageWithData:imageData];
});
//3.合并圖片
dispatch_group_notify(group, queue, ^{
//1.1 創(chuàng)建圖形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//1.2 畫圖 圖片1
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
self.image1 = nil;
//1.3 畫圖 圖片 2
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
self.image2 = nil;
//1.4根據(jù)上下文得到一張圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//1.5關(guān)閉上下文
UIGraphicsEndImageContext();
//1.6更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
4.NSOperation
NSOperation 主要元素就是 操作和隊(duì)列,其實(shí)操作也就是任務(wù).
NSOperation 是一個(gè)抽象類,沒有封裝,所以我們使用的時(shí)候要使用其自子類,接下來詳細(xì)講解一下 .
- NSInvocationOperation
不會(huì)開線程,在主線程執(zhí)行
1.創(chuàng)建操作封裝任務(wù)
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
2 啟動(dòng)執(zhí)行操作
[op1 start];
2.NSBlockOperation
-(void)blockOperation{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread mainThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread mainThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread mainThread]);
}];
//追加任務(wù),如果一個(gè)操作中的任務(wù)數(shù)量大于1,那么會(huì)開子線程并發(fā)執(zhí)行任務(wù)
[op3 addExecutionBlock:^{
NSLog(@"4----%@",[NSThread mainThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"5----%@",[NSThread mainThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"6----%@",[NSThread mainThread]);
}];
//啟動(dòng)任務(wù)
[op1 start];
[op2 start];
[op3 start];
}
3.NSOperationQueue 如何使用呢,我們要實(shí)現(xiàn)在子線程里面做事情.
首先我們先講一下GCD 中的隊(duì)列 和 NSOperation 中隊(duì)列的區(qū)別
GCD 中的隊(duì)列
串行隊(duì)列 :
a : 創(chuàng)建串行隊(duì)列
b : 主隊(duì)列
并行隊(duì)列 :
a : 創(chuàng)建并行隊(duì)列
b : 全局并發(fā)隊(duì)列
NSOperation中的隊(duì)列
主隊(duì)列 : [NSOperationQueue mainQueue] 和 GCD 主隊(duì)列一樣,串行隊(duì)列
非主隊(duì)列 : [[NSOperationQueue alloc]init];(同時(shí)具備串行和并發(fā)的功能) 默認(rèn)狀態(tài)下非主隊(duì)列是并發(fā)隊(duì)列
NSOperationQueue使用示例
-(void)invocationOperationWithQueue{
//1.創(chuàng)建操作封裝任務(wù)
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
//2.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//3.添加操作到隊(duì)列
[queue addOperation:op1];
[queue addOperation:op2]; // 內(nèi)部已經(jīng)調(diào)用了 start 方法
[queue addOperation:op3];
}
-(void)blockOperationWithQueue{
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread mainThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread mainThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread mainThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"4----%@",[NSThread mainThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"5----%@",[NSThread mainThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"6----%@",[NSThread mainThread]);
}];
//2.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//3.添加操作到隊(duì)列
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
//簡(jiǎn)便方法
//內(nèi)部 : 1創(chuàng)建操作 2.添加操作到隊(duì)列中
[queue addOperationWithBlock:^{
NSLog(@"7----%@",[NSThread mainThread]);
}];
}
4.自定義 NSOperation
子類
系統(tǒng)為我們提供了子類 封裝操作,為什么我還要定義子類呢.
因?yàn)榫拖袷且粋€(gè)自定義View
一樣 我們不喜歡分散的寫在Controller 一樣,假如我們操作代碼十分龐大,方便我復(fù)用等等,我們需要自定義.
通過重寫 main
方法 來實(shí)現(xiàn)我們需要操作的事情
@interface TZOperation : NSOperation
#import "TZOperation.h"
@implementation TZOperation
//告知的是執(zhí)行什么任務(wù)
//有利于代碼隱蔽
//復(fù)用性好
-(void)main{
NSLog(@"---封裝的操作");
}
@end
-(void)TZOperationWithQueue{
//1.封裝操作
TZOperation *op1 = [[TZOperation alloc]init];
TZOperation *op2 = [[TZOperation alloc]init];
//2.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//3.添加操作隊(duì)列
[queue addOperation:op1];
[queue addOperation:op2];
}
5.NSOperation 的依賴和監(jiān)聽
有時(shí)候我們同時(shí)執(zhí)行多個(gè)操作,但是我們要在某一個(gè)操作之后完成后再執(zhí)行另一個(gè)操作,這就用到了 依賴.
如何知道我們的某一操作已經(jīng)做完了-監(jiān)聽
注意點(diǎn) : 我們所添加的依賴不能循環(huán),那樣的話,兩個(gè)操作都不會(huì)執(zhí)行.
依賴可以跨隊(duì)列依賴
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
//2.封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
//操作監(jiān)聽
op3.completionBlock = ^{
NSLog(@"+++操作三完成了 ^_^ ");
};
//添加操作依賴
//注意點(diǎn) : 不能循環(huán)依賴
//可以跨隊(duì)列依賴
[op1 addDependency:op4];
[op4 addDependency:op3];
[op3 addDependency:op2];
//任務(wù)執(zhí)行順序
// op2->op3->op4->op1
//3添加操作
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue2 addOperation:op4];
6.NSOperation 線程間的通信
和GCD 還有NSThread 一樣,我們做完子線程操再回到主線程操作.
我們做兩個(gè)例子,一個(gè)是下載圖片 和 合成圖片,來看看NSOperation 如何實(shí)現(xiàn)線程間的通信
a : 下載圖片
//下載圖片
-(void)TZdown{
//1.開子線程下載圖片 非主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封裝操作
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
NSURL *URL = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:URL];
UIImage *imge = [UIImage imageWithData:imageData];
//更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = imge;
}];
}];
//添加操作到隊(duì)列中
[queue addOperation:download];
}
b : 合成圖片
//1.開子線程下載圖片 非主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
__block UIImage *image1;
__block UIImage *image2;
//2.封裝操作 下載圖片 1
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
NSURL *URL = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:URL];
image1 = [UIImage imageWithData:imageData];
}];
//3.封裝操作,下載圖片2
NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *URL = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:URL];
image2 = [UIImage imageWithData:imageData];
}];
//4合并圖片的操縱
//4.1
NSBlockOperation *com = [NSBlockOperation blockOperationWithBlock:^{
//開啟上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//畫圖1
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
//畫圖2
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
//根據(jù)上下文得到圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉上下文
UIGraphicsEndImageContext();
//7.更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
//5.設(shè)置依賴關(guān)系
[com addDependency:download];
[com addDependency:download2];
//6.添加操作到隊(duì)列里面去
[queue addOperation:download];
[queue addOperation:download2];
[queue addOperation:com];
合成圖片 用到了 依賴 ,合成操作依賴于 兩個(gè)下載操作已經(jīng)完成
三 : 線程安全性
- 原因
一塊資源可能被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問同一塊內(nèi)存資源.
比如多個(gè)線程訪問同一個(gè)對(duì)象,同一個(gè)變量,同一個(gè)文件
當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂,和數(shù)據(jù)安全問題.
蘋果官方訪問統(tǒng)一資源造成了數(shù)據(jù)錯(cuò)亂示意圖
-
解決辦法
- 互斥鎖:
必須是全局唯一的,鎖定一份代碼只能用一把鎖,用多把鎖是無效的
- 互斥鎖:
上圖為加了互斥所 后 線程A 和線程 B 訪問同一塊資源,線程A 在讀取 17
后被鎖上 進(jìn)行 +1
操作 變?yōu)?code>18 直到解鎖寫入 ,線程B 才能方位這個(gè)資源 否則不能訪問,當(dāng) 線程A 解鎖后,線程B 訪問的結(jié)果是18
再 對(duì)其進(jìn)行 +1
操作 最后寫入結(jié)果為19
然后線程B 解鎖
- 代碼
添加互斥所 只需要一行代碼 @synchronized
就可以啦 以下代碼做使用示范
A,B,C 是三個(gè)手機(jī) 共同在網(wǎng)上賣電影票
self.TZCount = 1000;
self.theadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.theadA.name = @"A";
self.threadB.name = @"B";
self.threadC.name = @"C";
//啟動(dòng)線程
[self.theadA start];
[self.threadB start];
[self.threadC start];
-(void)saleTicket{
while (1) {
@synchronized (self) {
NSInteger TZCount = self.TZCount;
if (TZCount > 0) {
[self lastTimeAction];
self.TZCount = TZCount;
}else{
NSLog(@"出售光了");
break;
}
}
}
}
模擬耗時(shí)操作
-(void)lastTimeAction{
for (int i = 0 ; i < 100000; i++) {
}
}
@synchronized (self)
因?yàn)槿治ㄒ?我們通常使用self
-
注意事項(xiàng)
- 注意加鎖的位置
- 注意加鎖的條件:多線程共享同一塊資源
- 注意加鎖是耗費(fèi)性能的
互斥鎖的優(yōu)點(diǎn)
能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題互斥所的缺點(diǎn)
需要消耗大量CPU 資源
加互斥所又叫線程同步,多條線程在同一條線上按順序執(zhí)行
- 原子性和非原子屬性
- atomic : 原子屬性為setter方法加鎖(默認(rèn)就atomic)
- nonatomic : 非原子屬性,不會(huì)為setter方法加鎖
- 原子和非原子的對(duì)比
atomic : 線程安全,需要消耗大量的資源
nonatomic : 非線程安全,適合內(nèi)存較小的移動(dòng)設(shè)備
一般在開發(fā)中我們都是用 nonatomic
屬性
盡量避免多線程搶奪同一塊資源.
四: 總結(jié)
對(duì)于像GCD
,NSOperation
常用控制線程的 API 應(yīng)該熟練掌握
在實(shí)際開發(fā)應(yīng)用中我們大多用到的就是開設(shè)子線程
創(chuàng)建隊(duì)列
創(chuàng)建任務(wù)
任務(wù)放進(jìn)隊(duì)列里
根據(jù)需求 設(shè)置好同步異步任務(wù),和串行并行隊(duì)列
刷新主線程UI 等等操作. 希望大家讀完這一期有所收獲,下期再見 ^ _ ^
//----------希望我們永遠(yuǎn)年輕-----永遠(yuǎn)熱淚盈眶-----