1、主線程(UI線程)
——顧名思義就是:與UI有關,處理UI事件的線程胸遇。
1.1盆均、主線程的主要作用:
顯示\刷新UI界面、處理UI事件(比如點擊事件,滾動事件,拖拽事件)
使用注意:不能將比較耗時的操作放到主線程中,會有卡的感覺拴曲。耗時操作要放到子線程(后臺線程,非主線程)操作
單條線程是串行凛忿,多條線程同時(來回快速切換)工作是并行
耗時操作放在子線程-后臺線程執(zhí)行
2澈灼、開辟線程:
2.1、線程分類
1店溢、 pthread--POSIX threads 跨平臺(就會與OS有關) 純C語言 但是線程的生命周期由程序員自己管理 幾乎不用
(POSIX表示可移植操作系統(tǒng)接口(Portable Operating System Interface 叁熔,縮寫為 POSIX)
2、 NSThread OC的 可直接操作線程對象 但是線程的生命周期由程序員自己管理 偶爾使用
3逞怨、GCD 替代NSThread等線程技術 充分利用設備的核 C語言 線程的生命周期自動管理 經(jīng)常使用
4者疤、NSOperation 基于GCD 比GCD多了一些更簡單實用的功能 更加面向對象 OC語言 線程的生命周期自動管理 經(jīng)常使用
一、pthread生命周期程序員管理
// 1叠赦、pthread
pthread_t pthreadID;
pthread_create(&pthreadID, NULL, run, NULL);
void *run(void *org){
NSThread *t = [NSThread currentThread];
for (int i = 0; i< 10000; i++) {
NSLog(@"子線程-- %@",t);
}
return NULL;
}
二驹马、NSThread生命周期程序員管理
創(chuàng)建NSThread線程的三種方法:
1 NSThread
1- NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
2- [t start];
start之后革砸,就執(zhí)行self的run方法,之后的object可以是run中傳入的方法
有優(yōu)先級的設置和獲得糯累、有獲得主線程/當前線程的方法算利,設置/獲得線程的名稱
2 創(chuàng)建線程自動啟動 這個沒有返回值 detach派遣分離
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
3 隱式創(chuàng)建子線程(InBackground在后臺執(zhí)行的線程就是子線程)自動啟動線程
performSelectorInBackground是NSObject的對象方法,誰都可以調(diào)用
[self performSelectorInBackground:@selector(run) withObject:nil];
pthread與NSThread需要程序員自己管理線程的生命周期泳姐,以下介紹生命周期的常識:
<pre>新建new —(start)— 就緒runnable(在調(diào)度池中) —— 運行running
—— 阻塞blocked(sleep或者等待同步鎖效拭,在內(nèi)存中,不在調(diào)度池中胖秒,就緒的狀態(tài)都不是)
—— 死亡deaded(死亡只是狀態(tài) 可是線程對象還在內(nèi)存中缎患,不在調(diào)度池中)release之后線程對象才會從內(nèi)存中消失。</pre>
只有就緒和運行狀態(tài)時候阎肝,線程才會在調(diào)度池中挤渔。
注意:一旦線程停止(死亡)了,就不能再次開啟任務风题。
2.2判导、線程安全隱患:
資源共享
比如訪問同一塊內(nèi)存,對象 變量 文件讀寫等等 比如銀行的存錢與取錢 還有火車站賣票問題沛硅。
方法一:加互斥鎖
多條線程只能有一條線程去訪問同一塊資源眼刃。
// 任何對象都可以為鎖對象,鎖只能有一把,每次線程想訪問資源的時候摇肌,都會問下鎖對象是否可以訪問資源擂红,所以要用公用的鎖比較好,鎖對象多個的話就不曉得問哪一把鎖了围小,問出的結果就會特別亂篮条,導致錯誤
@synchronized (self) {
self.leftTicketCount -- ;
NSLog(@"賣票之后剩余%d張-%@", self.leftTicketCount, [NSThread currentThread].name);
}
線程的優(yōu)先級問題:
self.thread01.threadPriority = 0.5; // 0.0~1.0 值越大,優(yōu)先級越高吩抓,被調(diào)度的頻率越高
[self.thread01 setThreadPriority:0.5];
[NSThread setThreadPriority:0.5];
[NSThread threadPriority]; // readonly屬性
互斥鎖的優(yōu)缺點---一般加鎖之后性能會比較差:
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點:需要消耗大量CPU資源(其他線程的等待以及每時每刻記錄著鎖的狀態(tài))
注意:只有存在多線程才會加鎖
線程同步:多條線程按順序的執(zhí)行任務--串行執(zhí)行「昂蓿互斥鎖就是運用了線程同步技術
方法二:非原子屬性
原子和非原子屬性:
atomic:OC中屬性默認是原子屬性疹娶,也就是默認在setter方法中加了鎖,性能會比較差伦连,線程安全雨饺,需要消耗大量的資源
nonatomic:非原子屬性,非線程安全惑淳,適合內(nèi)存小的移動設備
(最優(yōu))iOS開發(fā)建議:
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同一塊資源
盡量將加鎖额港、資源搶奪的業(yè)務邏輯交給服務器處理,減小移動客戶端的壓力
2.3歧焦、線程間通訊:
線程之間數(shù)據(jù)傳遞 以及 線程之間的切換(比如從子線程跳到主線程)
\- (void) performSelectorOnMainThread:(SEL)aSelector......; // 跳轉到主線程
\- (void) performSelector:(SEL)aSelector onThread:(NSThread *)thr...... // 跳轉到特定的線程上
例子:圖片的下載
/// waitUntilDone YES 等待主線程完成后再子線程繼續(xù)執(zhí)行以后代碼一般設置為NO
/// withObject可以傳入?yún)?shù)
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]; // “self” 調(diào)用后面的“方法”
三移斩、GCD
兩步驟:
- 1定制任務
- 2將任務添加到隊列中
GCD會自動將隊列中的任務取出,放到對應的線程中執(zhí)行(主隊列 對應 主線程,其他隊列 對應 子線程)
-
兩個方法可以將任務添加到隊列
(是否具備開啟新線程的能力分了兩種方法):dispatch_sync(queue,block) // 同步-----不具備開啟線程的能力
dispatch_async(queue, block) // 異步——具備開線程的能力
隊列的分類(決定了任務的執(zhí)行方式):
并發(fā)隊列---多個任務并行執(zhí)行向瓷,說明會開多條線程執(zhí)行任務肠套,只能是async函數(shù)。
串行隊列---多個任務一個挨著一個的執(zhí)行
MRC(MannulReference Counting)
并發(fā)隊列:
GCD已經(jīng)提供給我們?nèi)值牟l(fā)隊列猖任,供整個應用使用你稚,不需要手動創(chuàng)建
dispatch_get_global_queue(優(yōu)先級枚舉,0);
優(yōu)先級枚舉值:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高的
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認的 ---- 常用
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) 低的
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 最低的
// 異步執(zhí)行--- 有開線程的能力
// 創(chuàng)建隊列(全局隊列默認就是并發(fā)隊列)
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]);
});
#結果:開兩條線程
串行隊列:
-
1 手動創(chuàng)建 參數(shù):隊列名稱(出現(xiàn)在調(diào)試界面)與傳入?yún)?shù)NULL
dispatch_queue_t queue = dispatch_queue_create("串行隊列create", NULL);
// 添加任務到隊列dispatch_async(queue, ^{ NSLog(@"下載圖片1---%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"下載圖片2---%@",[NSThread currentThread]); }); # 結果:開一條線程
2 主隊列 (與主線程相關的隊列)
dispatch_queue_t queue = dispatch_get_main_queue();
同步執(zhí)行sync:在當前線程執(zhí)行
并行隊列(全局隊列): 不開線程
串行隊列: 不開線程
主隊列(也是串行隊列):放在主線程時候 相互等待卡死狀態(tài)
例外:下載圖片------不開線程朱躺,會把任務放到主隊列的最后刁赖,出現(xiàn)了永遠不能執(zhí)行而是互相等待的狀態(tài)~~~~比如:在B塊代碼在A行之后,把下載圖片sync方式C代碼塊添加到A行上长搀,之后下載圖片的C代碼塊在內(nèi)存的位置是放到了B代碼塊的后面宇弛。由于是在主線程順序執(zhí)行代碼的規(guī)則,在程序代碼先后順序上想執(zhí)行B必須先執(zhí)行完C代碼(B等C)盈滴,在主隊列中先執(zhí)行完B才能執(zhí)行C(C等B)涯肩,所以,互相等待中...
異步執(zhí)行async:開線程
并行隊列(全局隊列): 能開多條線程巢钓,由任務個數(shù)決定
串行隊列: 只開啟一條線程
主隊列(也是串行隊列):block內(nèi)任務可以緩一緩
總結:只要是同步是指病苗,等待對應的任務執(zhí)行完畢后,才繼續(xù)向下執(zhí)行
只要是異步是指症汹,不是串行執(zhí)行硫朦,可以緩一緩再執(zhí)行任務
任務就是放在隊列中的,一個block是一個任務1痴颉Rд埂!B髡丁破婆!
GCD其他函數(shù):
1、延時函數(shù):dispatch_after(dispatch_time(xx,xx), 隊列胸囱, block); // 其中隊列可以是主隊列 也可以是全局隊列!!!!!!!!之前不曉得~
類似:[self performSelectior:@selector(run) withObject:nil afterDelay:2.0];
2祷舀、程序運行過程中,只執(zhí)行一次:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 代碼
});
隊列組:
需求:首先烹笔,分別異步執(zhí)行2個耗時的操作
其次裳扯,等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
===========================代碼==========================
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 下載圖片1和2
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block UIImage *image01 = nil;
dispatch_group_async(group, queue, ^{
// 下載第一張圖片
NSURL *url = [NSURL URLWithString:@"http://preview.quanjing.com/fod_liv002/fo-11171537.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSLog(@"data01-----------%@",imageData);
image01 = [UIImage imageWithData:imageData];
});
__block UIImage *image02 = nil;
dispatch_group_async(group, queue, ^{
// 下載第二張圖片
NSURL *url = [NSURL URLWithString:@"http://scimg.jb51.net/allimg/160716/105-160G61F250436.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
image02 = [UIImage imageWithData:imageData];
});
// 完成后的通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 顯示圖片
self.imageView01.image = image01;
self.imageView02.image = image02;
// 合并圖片 開啟圖片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
[image01 drawInRect:CGRectMake(0, 0, 100, 100)];
[image02 drawInRect:CGRectMake(100, 0, 100, 100)];
self.imageViewBig.image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉圖片上下文
UIGraphicsEndImageContext();
});
=======================end=======================================
四谤职、NSOperation:
類似于GCD GCD中任務類似NSOperation
隊列類似NSOperationQueue
一個operation對象對應一個任務饰豺。
NSOperatinon是個抽象類
代碼實現(xiàn)1:----沒有創(chuàng)建operation隊列
invocation是“回調(diào)”的意思。
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationSomething) object:nil];
[invocation start]; // 沒有添加到隊列的話允蜈,就是主線程
NSBlockOperation *blockOperation = [NSBlockOperation
blockOperationWithBlock:^{
NSLog(@"NSBlockOperation---%@", [NSThread currentThread]);
}];
[blockOperation start]; // 沒有添加到隊列的話 就是主線程
- 默認情況下冤吨,調(diào)用了start方法后蒿柳,并不會開一條新線程去執(zhí)行操作,而是在當前線程(并不一定總是主線程)同步執(zhí)行操作锅很。
只有將操作NSInvocationOperation放到隊列NSOperationQueue中其馏,才會開啟線程異步執(zhí)行操作。
但是對于NSBlockOperation只要操作數(shù)大于2就會開啟新線程爆安,如果還是1就在當前線程執(zhí)行:
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"--blockOperation1 %@--", [NSThread currentThread]);
}];
// 添加更多操作
[blockOperation addExecutionBlock:^{
NSLog(@"--blockExecutionOperation2 %@--", [NSThread currentThread]);
}];
[blockOperation start]; //如果任務個數(shù)大于1個叛复,比如n(n>1)個,就會開新線程n-1條扔仓,不會有GCD中的異步同步函數(shù)
-
需求:不管操作數(shù)是一個還是兩個都要開啟新線程褐奥,那么就要放到隊列中,并且自動執(zhí)行操作翘簇,自動開啟線程撬码。
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 調(diào)用[queue addOperation:xxx];
或者:
[queue addOperationWithBlock:^{
NSLog(@"匿名操作--%@--",[NSThread currentThread]);
}];
生成主隊列 [NSOperationQueue mainQueue];
最大并發(fā)數(shù):
(queue的屬性)同時執(zhí)行的任務數(shù)。2~3為宜版保。如果是2呜笑,線程num = 2,num = 3,num = 4...由整的操作數(shù)決定彻犁。在打印的統(tǒng)一時間上的執(zhí)行操作數(shù)一般都是設置的最大并發(fā)數(shù)叫胁。 maxConcurrentOperationCount屬性。
隊列的取消:
調(diào)用cancelAllOperations取消隊列的所有操作 或者汞幢,操作調(diào)用cancel方法取消單個操作驼鹅。
隊列的暫停:
setSuspended:(BOOL)b; // YES暫停隊列 NO恢復隊列
比如:icon異步下載并且顯示,當tableView上下滾動的時候森篷,顯得有些卡頓(主線程與多個子線程來回
切換的原因)输钩,所以,當滾動的時候仲智,下載操作暫停买乃,tableView停止?jié)L動的時候,下載操作再恢復就可以了钓辆。
操作依賴----面試題
操作的對象方法:[operationA addDependency:operationB];
operationA 依賴于 operationB;
這句代碼要寫在添加隊列之前为牍。否則沒有效果,添加的時候代碼執(zhí)行自動取出操作開始執(zhí)行岩馍。
注意:可以在不同queue之間產(chǎn)生操作依賴,就是不能相互依賴抖韩。
操作監(jiān)聽:operation.completionBlock = ^{
// 寫上操作完成后的所做的事情蛀恩。所在線程是操作的當前線程。也可以寫在操作代碼的后面茂浮,也算是操作監(jiān)聽双谆。操作完成之后在執(zhí)行此段代碼壳咕。
};
例子:自定義operation
在tableViewcell上顯示下載圖片,并且滾動的時候用戶體驗好一些:
1 寫個類繼承自NSOperation
2 定義-(void)main函數(shù)
3 下載操作寫在main函數(shù)內(nèi)顽馋,而且添加自動釋放池谓厘,且需要判斷是否操作被取消,如果被取消了就不再繼續(xù)執(zhí)行以下代碼
4 傳image時候寸谜,使用代理在主線程展示圖片到視圖上
最后保存到沙盒的時候竟稳,使用壓縮圖片
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:@“” atomically:YES];