1.iOS 開發(fā)中多線程出現(xiàn)的本質妇拯?
- 原因:一個iOS程序運行后,默認會開啟1條線程洗鸵,稱為“主線程”或“UI線程”來處理任務越锈。當任務量變得很大時,主線程疲于處理當前耗時操作膘滨,對于其他需要處理的任務就會響應不過來甘凭,宏觀體現(xiàn) —— 好卡。 為了解決這種“卡”的用戶體驗火邓,出現(xiàn)了多線程處理任務策略丹弱。
- 注意:多線程并發(fā)執(zhí)行任務本質是cpu的快速切換。
- 技巧:iOS中模擬耗時操作铲咨,只需寫一個很大的for循環(huán)
2.iOS中多線程的實現(xiàn)方案(4種)
①pthread
- 使用步驟:
- 導入頭文件 #import <pthread.h>
- 調用創(chuàng)建線程函數
#import "ViewController.h"
#import <pthread.h>
@interface ViewController ()
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 創(chuàng)建線程
/*
第一個參數:線程對象地址
第二個參數:屬性
第三個參數:要調用的方法指針
第四個參數:要傳遞給函數的參數
*/
pthread_t thread = nil;
pthread_create(&thread, NULL, run, NULL);
}
//(*) =>函數名稱
void *run(void *str)
{
for (NSInteger i =0 ; i<300; i++) {
NSLog(@"%zd--%@",i,[NSThread currentThread]); //均為子線程
}
return NULL;
}
@end
②NSThread
使用步驟:直接創(chuàng)建線程躲胳,不需要導入頭文件
-
創(chuàng)建線程方式:
<方式1>
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ios"];
[thread start];
優(yōu)點:能拿到線程對象 缺點:需要手動的啟動線程<方式2>
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
優(yōu)點:自動啟動線程 缺點:不能拿到線程對象<方式3>
[self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];
優(yōu)點:自動啟動線程 缺點:不能拿到線程對象<方式4>自定義,重寫main方法封裝任務
NewThread *threadB = [[NewThread alloc]init];
[threadB start];
優(yōu)點:能拿到線程對象 缺點:需要手動的啟動線程
=> 線程封裝任務是在main方法里面纤勒。 注意:
生命周期:線程當任務執(zhí)行完畢的時候自動銷毀
線程對象先執(zhí)行完任務的先銷毀
線程優(yōu)先級只在任務相對較多時更好的體現(xiàn)坯苹,第一次執(zhí)行還是按照線程開始的先后順序執(zhí)行。(線程優(yōu)先級threadPriority摇天,范圍:0~1.0 默認是0.5)
③GCD
1.使用步驟:任務 + 隊列
//1 獲得隊列
第一個參數:C語言的字符串 對隊列的名稱(com.520it.www.DownloadQueue)
第二個參數:隊列的類型
DISPATCH_QUEUE_SERIAL 串行隊列
DISPATCH_QUEUE_CONCURRENT 并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("com.520ios.www.DownloadQueue”,DISPATCH_QUEUE_SERIAL);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2 封裝任務并把任務添加到隊列
dispatch_async(queue, ^{
NSLog(@"download1---%@",[NSThread currentThread]);
});
拓展:GCD常用函數
1)一次性代碼
- 特點:
- 整個程序運行過程中只會執(zhí)行一次
- 線程安全
- 程序每次啟動都會執(zhí)行一次
- 一次性代碼不能放在懶加載中:因為創(chuàng)建多個對象時粹湃,也只會執(zhí)行一次。單例中才可以泉坐。
-(void)once
{
static dispatch_once_t onceToken; //typedef long dispatch_once_t;
//內部的實現(xiàn)原理:最開始的時候onceToken == 0 如果onceToken == 0 那么就執(zhí)行一次,執(zhí)行一次之后onceToken = -1
dispatch_once(&onceToken, ^{
NSLog(@"once");
});
}
應用場景:單例(注意:單例中使用一次性代碼或同步鎖均可为鳄,都是線程安全)
2)延遲執(zhí)行
- 特點:
- dispatch_after本身是一個異步函數
- 該函數的隊列參數選項,除了隊列設置的為“主隊列”則延時執(zhí)行的任務在主線程中執(zhí)行外坚冀,其他隊列做參數济赎,均在子線程中執(zhí)行延時任務,包括參數隊列設置為自己創(chuàng)建的串行隊列记某。
- 延遲執(zhí)行的單位:納秒 司训,精度高。
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_SERIAL);
// 延遲2秒,然后再把任務提交到隊列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
NSLog(@"GCD----%@",[NSThread currentThread]); //子線程=> 異步函數
});
-
拓展總結:延遲執(zhí)行方法
//延遲方法一(NSTimer)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:NO]; //默認添加進“當前RunLoop”液南,且是默認模式
[NSTimer timerWithTimeInterval:2.0 repeats:YES block:nil]; //此種方法要將定時器手動添加進RunLoop//延遲方法二(NSRunloop) [self performSelector:@selector(task) withObject:nil afterDelay:2.0]; //延遲方法三:(GCD中的延遲執(zhí)行/GCD定時器) dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_SERIAL); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{ NSLog(@"GCD----%@",[NSThread currentThread]); //子線程 });
3)快速迭代
- 特點:
- 快速迭代屬于:同步函數 (在主隊列中會發(fā)生死鎖 + 打印【顯示它沒開子線程?遣隆!】共同推導出的)
- 但注意:在并發(fā)隊列中還是會同時開啟"主線程"和"子線程"一起并發(fā)執(zhí)行任務(主流)
- 在自己創(chuàng)建的串行隊列中會在主線程中串行執(zhí)行滑凉,不開子線程故無意義统扳,因為開子線程還要耗費性能喘帚!性能更低。
- 快速迭代:本質就是遍歷執(zhí)行任務咒钟,只不過對比for循環(huán)吹由,多了開啟子線程的能力而已,故效率更高朱嘴。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);// 開啟子線程協(xié)助主線程完成任務
// dispatch_queue_t queue = dispatch_queue_create("good", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_main_queue();//死鎖 => 同步函數 + 主隊列
// dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, queue, ^(size_t i) {
NSLog(@"%zd---%@",i,[NSThread currentThread]); //內部執(zhí)行順序是并發(fā)的倾鲫,外部卻認為它沒開子線程!萍嬉!
});
應用場景 :遍歷并剪切文件
-(void)moveFile
{
//01 得到上層文件夾的路徑
NSString *fromPath = @"/Users/wuyuanping/Desktop/from";
NSString *toPath = @"/Users/wuyuanping/Desktop/to";
//02 得到上層文件夾中所有的文件
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:fromPath];
//03 遍歷并剪切文件
dispatch_apply(subPaths.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSString *fileName = subPaths[index];
NSString *fromFullPath = [fromPath stringByAppendingPathComponent:fileName];
NSString *toFullPath = [toPath stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
NSLog(@"%@--%@--%@",fromFullPath,toFullPath,[NSThread currentThread]); //子線程和主線程都有
});
}
4)柵欄函數
- 特點:
- 攔截柵欄后面的任務乌昔,必須等前面的任務執(zhí)行完才執(zhí)行柵欄當前的block,且必須等柵欄的block執(zhí)行完才執(zhí)行柵欄后面任務
- dispatch_barrier_async 柵欄Block創(chuàng)建了子線程則在子線程中執(zhí)行任務(因為“主隊列”只能在主線程中執(zhí)行任務);dispatch_barrier_sync 主線程執(zhí)行柵欄任務
- 子線程中可以出現(xiàn):同步函數 + 主隊列 不會發(fā)生死鎖 壤追,不建議子線程中開子線程,系統(tǒng)空閑就可以開磕道,否則不能
- 柵欄函數在使用中不能使用系統(tǒng)提供的全局并發(fā)隊列(會喪失攔截的功能),只能用自己創(chuàng)建的并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
NSLog(@"+++++%@",[NSThread currentThread]);
});
應用場景:需求是哪些子線程任務先執(zhí)行
5)隊列組
特點:
當隊列組中所有的任務都執(zhí)行完畢,那么就會執(zhí)行dispatch_group_notify的block
dispatch_group_notify該函數本身是異步的
隊列組的使用:
方式一:
// 添加任務進組函數
//dispatch_group_async =>異步函數封裝任務|提交到隊列|監(jiān)聽任務是否執(zhí)行完畢
//dispatch__async =>異步函數封裝任務|提交到隊列
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
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]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"4----%@",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{ //組任務都執(zhí)行完畢之后會調用此方法
NSLog(@"---end---%@",[NSThread currentThread]); //子線程
});
方式二:
//使用函數對來監(jiān)聽任務(dispatch_group_enter|dispatch_group_leave)
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
//在該函數后面的異步任務會被group監(jiān)聽
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);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"3----%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{ //組任務都執(zhí)行完畢之后會調用此方法
NSLog(@"---end---%@",[NSThread currentThread]);
});
拓展:GCD定時器
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//1 創(chuàng)建定時器對象(技巧:輸入dispatch_source 再選GCD的會一次性出來四個函數)
/*
第一個參數:創(chuàng)建的source的類型 DISPATCH_SOURCE_TYPE_TIMER 定時器事件
第二個參數:描述信息
第三個參數:更詳細的描述信息
第四個參數:隊列(線程)[主隊列那么回調就在主線程中執(zhí)行行冰,否則就在子線程中執(zhí)行]
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL));
//2 設置定時器對象
/*
第一個參數:定時器對象
第二個參數:開始時間(第一次執(zhí)行的時間)
第三個參數:間隔時間 GCD的時間單位是納秒
第四個參數:精準度(允許的誤差溺蕉,一般取0)
*/
dispatch_time_t timeT = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);//tips:取消一開始就執(zhí)行一次,即達到也是延時兩秒執(zhí)行
dispatch_source_set_timer(timer, timeT, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3 設置定時器的事件
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD---%@",[NSThread currentThread]); //非主隊列悼做,則為子線程
});
//4 執(zhí)行定時器對象
dispatch_resume(timer);
self.timer = timer;//強引用焙贷,防止定時器一出作用域就銷毀
}
GCD總結
- 具體是否創(chuàng)建子線程?
- 異步函數 + 并發(fā)隊列: => 能夠開啟多條子線程“并發(fā)”的執(zhí)行隊列中的任務(驗證“線程間”及“各個子線程中任務”并發(fā)執(zhí)行贿堰?打印)
- 異步函數 + 串行隊列: => 一個串行隊列對應只能開啟一條子線程串行執(zhí)行任務辙芍,即嚴格按照任務代碼塊從上往下"串行"執(zhí)行任務。
注意:多個串行隊列羹与,就相當于開啟了多條子線程故硅,線程間則是異步執(zhí)行任務的。同一條子線程則串行 - 同步函數 + 并發(fā)隊列/串行隊列: => 不會開啟子線程,所有的任務在當前線程中串行執(zhí)行
- 異步函數 + 主隊列:=> 不會開子線程,所有的任務均在主線程中的串行執(zhí)行
(異步函數可以并發(fā)執(zhí)行纵搁,不會一直等待前面線程執(zhí)行完才輪到自己執(zhí)行吃衅,故不會發(fā)生死鎖) - 同步函數 + 主隊列: => 代碼寫在主線程中會發(fā)生死鎖(會一直傻等主線程來執(zhí)行故發(fā)生死鎖) 但代碼寫在子線程中就不會發(fā)生死鎖。故可以用在線程間通信(子線程跳到主線程)
- “并發(fā)”的本質是:取出上一個任務之后腾誉,無論是否執(zhí)行完直接接著取下一個任務徘层,多個Blcok塊各有很多任務時,按一開始執(zhí)行子線程的順序你一下我一下并肩完成利职。
- 開子線程的數量并不是僅僅由任務的數量決定的趣效,要看系統(tǒng)是否空閑。出于性能考慮iOS中一般只能開6條子線程
- 一個Block任務代碼塊中只能開啟一個子線程
- 當系統(tǒng)空閑時猪贪,子線程執(zhí)行任務的順序等于創(chuàng)建子線程的先后順序跷敬,當任務變多,順序就不確定热押。
- 只要不涉及到創(chuàng)建子線程西傀,代碼執(zhí)行的順序就是老老實實的從上往下串行執(zhí)行斤寇。(因為均在主線程,默認主隊列)
- 創(chuàng)建子線程本質原因:“并發(fā)”從而避免主線程忙不過來(卡)拥褂,或者說充分利用cpu性能娘锁。
④NSOperation
- 步驟: 操作 + 隊列
- 注意:
- NSOperation是抽象類不能直接使用,可以使用它的子類(系統(tǒng)提供了NSInvocationOperation饺鹃,NSBlockOperation和自定義的)
【自定義繼承自NSOperation :重寫內部的Main方法致盟,說明start方法內部也是調用的main方法 ,優(yōu)點:有利于代碼的封裝和復用】 - 單獨封裝操作并執(zhí)行時并不會開啟子線程尤慰,只會在主線程中執(zhí)行,除非配合非主隊列一起使用時才會生成子線程并發(fā)執(zhí)行任務
- 單獨封裝操作并執(zhí)行時雷蹂,也可以通過添加額外任務addExecutionBlock,即操作對象中封裝的任務數量>1也會開子線程執(zhí)行任務
但注意:額外操作才會開子線程去執(zhí)行伟端,之前的操作 仍然在主線程中執(zhí)行,這是與操作直接添加進非主隊列的區(qū)別所在匪煌。 - GCD和NSOperation隊列對比:
GCD:
并發(fā):自己創(chuàng)建|全局并發(fā)
串行:自己創(chuàng)建|主隊列
NSOperation:
并發(fā):自己創(chuàng)建[[NSOperationQueue alloc]init]默認是并發(fā)隊列
串行: 自己創(chuàng)建[[NSOperationQueue alloc]init]設置最大并發(fā)數等于1 | 主隊列
[NSOperationQueue MainQueue]
注意:通過設置最大并發(fā)數為1而達到串行執(zhí)行任務目的 與 主隊列 的串行 是有區(qū)別的责蝠。前者還是會生成子線程,只不過是控制子線程間只能串行執(zhí)行任務萎庭,具體生成子線程個數不確定霜医。 順序就為添加進隊列(開始)順序執(zhí)行
后者不會生成子線程,在主線程中串行執(zhí)行任務驳规。
maxConcurrentOperationCount 最大并發(fā)數:允許并發(fā)執(zhí)行的子線程數
非主流開發(fā)演示:
//1.封裝操作
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@",[NSThread currentThread]); //主線程
}];
//NSBlockOperation如果操作對象中封裝的操作數量>1那么添加的代碼塊中就會開子線程和當前線程一起執(zhí)行任務
[op3 addExecutionBlock:^{
NSLog(@"+++++++4++++%@",[NSThread currentThread]); //子線程
}];
[op3 addExecutionBlock:^{
NSLog(@"+++++++5++++%@",[NSThread currentThread]);//子線程
}];
//2 執(zhí)行操作
[op3 start];
主流開發(fā)代碼演示:
-(void)BlockOperationWithQueue
{ //操作 + 隊列:只要不是主隊列肴敛,就會開子線程并發(fā)執(zhí)行任務
//1.封裝操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
//NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];
//2.把操作添加到隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:op1]; //addOperation 內部調用start方法,故你可以不寫start
//該方法內部先封裝操作,然后把操作添加到隊列中(簡便寫法:直接操作添加到隊列)
[queue addOperationWithBlock:^{
NSLog(@"3---%@",[NSThread currentThread]); //子線程
}];
}
拓展:操作隊列的其他用法
- 設置最大并發(fā)數量 (maxConcurrentOperationCount)
- 設置操作依賴(addDependency)和監(jiān)聽(completionBlock)
- 操作隊列的暫停(setSuspended)和取消(cancel/cancelAllOperations)
注意: - 操作的狀態(tài): 正在執(zhí)行|等待執(zhí)行|執(zhí)行完畢
- 暫停 只能暫停下一個操作,當前正處于執(zhí)行狀態(tài)的操作是不能暫停的
- 取消隊列中所有的任務 當前正在執(zhí)行的操作不能馬上取消,需要等當前操作執(zhí)行完畢才能取消后面的操作
- 如果自定義NSOperation蘋果官方的建議:每執(zhí)行完一段耗時操作之后就檢查當前操作是否被取消
自定義NSOperation:
#import “YPOperation.h"
@implementation YPOperation
-(void)main
{
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"1---%zd--%@",i,[NSThread currentThread]);
//if(self.isCancelled)
return;
}
//每執(zhí)行完一段耗時操作之后就判斷當前操作是否被取消,如果被取消了那么就直接退出
if(self.isCancelled) return;
NSLog(@"++++++++++++++");
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"2---%zd--%@",i,[NSThread currentThread]);
//if(self.isCancelled) return;
}
if(self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
if(self.isCancelled) return;
NSLog(@"3---%zd--%@",i,[NSThread currentThread]);
}
}
@end
例子:設置監(jiān)聽和操作依賴
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"4-下載電影-%@",[NSThread currentThread]);
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"+++++++5+%@+++",[NSThread currentThread]);
}];
//設置監(jiān)聽
op4.completionBlock = ^{
NSLog(@"我已經被下載完了,快點來看我吧--%@",[NSThread currentThread]);
};
//設置依賴(不能設置循環(huán)依賴)
[op3 addDependency:op4];
[op4 addDependency:op5];
注意:因為開啟子線程自定義隊列默認異步執(zhí)行,故只能保證op4任務結束會執(zhí)行他的監(jiān)聽Block吗购,即block在op4后面執(zhí)行医男,
但無法保證一定緊接其后,要看具體情況)
綜合案例: 多圖下載(利用上面所學的多線程技術)
-
總結:
- 基本數據展示
問題:UI不流暢 => 在子線程中下載圖片
注意:cell的復用造成數據錯亂捻勉,通過設置占位圖片解決該問題 - 性能優(yōu)化
問題:重復下載圖片
①已經下載好了圖片的 => 二級緩存(內存緩存磁盤緩存)
②已經開始執(zhí)行下載任務但還沒有下載完 => 緩存下載任務(操作)
- 基本數據展示
注意點
①圖片下載完成之后把操作從緩存中移除
②圖片下載完成后镀梭,對獲得的圖片數據做容錯處理
③發(fā)生內存警告后的處理:1)移除內存緩存中的所有元素 2)取消下載圖片隊列中所有的操作拓展:磁盤(沙盒)緩存的目錄結構:
①Documents :該目錄下面的數據在連接手機時會備份,蘋果官方不允許把下載的數據存放于該目錄下
②Libriary
1)caches
2)perference 該目錄用來存放偏好設置如登錄名密碼等等③Temp :會被隨機刪除
3.線程狀態(tài)
新建 -> 就緒 <-> 運行 -> 死亡
|
阻塞
- 注意:
- “運行”狀態(tài)可以直接切換為“阻塞”狀態(tài)踱启,阻塞狀態(tài)不能直接切換為運行报账,必須先切換為“就緒”狀態(tài),再從就緒狀態(tài)切換為“運行”狀態(tài)埠偿。
- 線程死亡之后不能重新開啟,得重新創(chuàng)建透罢。
- 阻塞線程的方式:
[NSThread sleepForTimeInterval:3.0];
[NSThread sleepUntilDate: [NSDatedateWithTimeIntervalSinceNow:2.0]]; - 線程死亡的方式:
①任務執(zhí)行完畢(默認)
②強制退出線程 + (void)exit;
③提前讓任務結束 break和return
4.線程安全
- 問題產生的原因:多個線程訪問同一塊資源會發(fā)生數據安全問題
- 解決方式: 線程同步(同一時間只有一個線程訪問同一塊資源)
@synchronized(鎖對象){
要鎖住的代碼
}
鎖對象:要求是全局唯一的屬性,一般用self - 注意點:
- 要注意加鎖的位置
- 加鎖需要耗費性能,因此需要注意加鎖的條件(多線程訪問同一塊資源冠蒋,不得已才去加鎖)
- 原子和非原子屬性:
atomic會對setter方法加鎖 (OSX應用琐凭,常用atomic,電腦性能好浊服,更多的是關注安全)
noatomic不會對Setter方法加鎖(性能高统屈,安全性低常用于iOS 應用)
應用:售票
-(void)saleTicket
{
while (1) { //讓線程一直執(zhí)行
@synchronized (self) {
//檢查余票
NSInteger count = self.totalCount;
if (count >0) {
//賣出去一張
self.totalCount = count - 1;
NSLog(@"%@賣出去了一張票還剩下%zd張票",[NSThread currentThread].name,self.totalCount);
}else
{
NSLog(@"%@發(fā)現(xiàn)票已經賣完了",[NSThread currentThread].name);
break; //讓線程死亡
}
}
}
}
5.線程間通信
①利用NSThread方法(2種)
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];
[self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
//直接調用本質方法
[self.imageView performSelector:@selector(setImage:) onThread:
[NSThread mainThread] withObject:image waitUntilDone:YES];
②利用GCD方法
//同步函數+主隊列(異步函數+ 主隊列更主流)
dispatch_sync(dispatch_get_main_queue(), ^{// 注意:當“同步函數+主隊列”是寫在子線程當中時是不會發(fā)生死鎖的
self.imageView.image = image;
NSLog(@"UI---%@",[NSThread currentThread]);//主線程
});
③利用NSOperation方法
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
NSLog(@"UI----%@",[NSThread currentThread]);
}];
6.RunLoop
基本作用
①保持程序的持續(xù)運行(ios程序為什么能一直活著不會死,如果沒有Runloop,那么程序一啟動就會退出)
②處理app中的各種事件(比如觸摸事件胚吁、定時器事件、selector事件)
③節(jié)省CPU資源愁憔,提高程序性能腕扶,有事情就做事情,沒事情就休息注意:
1>如果有了Runloop吨掌,那么相當于在內部有一個死循環(huán)
2>main函數中的Runloop :在UIApplicationMain函數內部就啟動了一個Runloop,這個默認啟動的Runloop是跟主線程相關聯(lián)的-
RunLoop使用
(在iOS開發(fā)中有兩套api來訪問Runloop半抱,它們是等價的,可以互相轉換)
①Foundation框架 :(OC)
[NSRunLoop mainRunLoop]膜宋;獲得主線程對應的Runloop
[NSRunLoop currentRunLoop]窿侈;獲得執(zhí)行當前方法的線程對應的Runloop②CoreFoundation框架 :(C) CFRunLoopGetMain();獲得主線程對應的Runloop CFRunLoopGetCurrent()秋茫;獲得執(zhí)行當前方法的線程對應的Runloop 相互轉換: CFRunLoopRef runloop = mainRunLoop.getCFRunLoop史简; NSRunLoop和CFRunLoopRef都代表著RunLoop對象
Runloop與線程關系
①一個Runloop對應著一條唯一的線程
②主線程Runloop已經創(chuàng)建好了,子線程的runloop需要手動創(chuàng)建并開啟(模式不能為空肛著,得有任務干Runloop才不會退出)
③Runloop在第一次獲取時創(chuàng)建圆兵,在線程結束時銷毀RunLoop相關5個類
①CFRunloopRef(Runloop對象)
②CFRunloopModeRef(Runloop的運行模式)
③CFRunloopSourceRef(Runloop要處理的事件源)
④CFRunloopTimerRef(Timer事件)
⑤CFRunloopObserverRef(Runloop的觀察者(監(jiān)聽者))RunLoop使用總結:
-
<1>CFRunloopModeRef (代表著Runloop的運行模式5種)
①kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
②UITrackingRunLoopMode:界面跟蹤 Mode枢贿,用于 ScrollView 追蹤觸摸滑動
③UIInitializationRunLoopMode: 在剛啟動 App 時進入的第一個 Mode殉农,啟動完成后就不再使用
④GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內部 Mode,通常用不到
⑤kCFRunLoopCommonModes: 這是一個占位用的Mode局荚,不是一種真正的Mode超凳,標記mode
(kCFRunLoopCommonModes == UITrackingRunLoopMode +kCFRunLoopDefaultMode)- 注意:
1. NSTimer在各種模式下運行的效果:
①scheduledTimerWithTimeInterval方法:創(chuàng)建定時器并默認添加到當前線程的Runloop中指定默認運行模式
②timerWithTimeInterval:創(chuàng)建定時器,如果該定時器要工作還需要添加到runloop中并指定相應的運行模式
2.GCD中的定時器在各種模式下運行的效果:
①GCD的定時器不會受到Runloop運行模式的影響(因為source不包括這個定時器)
②GCD定時器對象需要添加強引用耀态,防止被銷毀
- 注意:
<2>CFRunloopSourceRef(Runloop要處理的事件源)
按照蘋果官方文檔進行劃分的:
①Port-Based Sources (基于端口)
②Custom Input Sources (自定義輸入源)
③Cocoa Perform Selector Sources (Perform Selector 輸入源)
函數調用棧劃分:
①Source0:非基于Port的
②Source1:基于Port的<3>CFRunLoopObserverRef
作用:監(jiān)聽指定模式下運行循環(huán)的狀態(tài)
如何監(jiān)聽?
1.創(chuàng)建觀察者對象
2.給RunLoop添加觀察者
3.注意對象的釋放
-(void)observer
{
//1 創(chuàng)建觀察者對象
//block調用:當監(jiān)聽者發(fā)現(xiàn)runloop狀態(tài)改變的時候會調用block
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"進入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理time事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop被喚醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
});
//2 給RunLoop添加觀察者
/*
NSDefaultRunLoopMode = kCFRunLoopDefaultMode
NSRunLoopCommonModes = kCFRunLoopCommonModes //追蹤模式下的RunLoop也可以追蹤
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//3.釋放Observer
CFRelease(observer);
}
RunLoop總結
說明:
一個Runloop中可以有多個mode,一個mode里面又可以有多個source\observer\timer等等
每次Runloop啟動的時候聪建,只能指定其中一個mode,這個mode被稱為該Runloop的當前mode
如果需要切換mode,只能先退出當前Runloop,再重新指定一個mode進入當前Runloop,目的:為了分割不同組的定時器等茫陆,讓他們相互之間不受影響
runloop要想持續(xù)運行金麸,必須選中的mode不為空(即mode里面source或Timer至少有一個)
子線程的RunLoop必須手動創(chuàng)建并開啟,要想持續(xù)運行簿盅,mode不能為空挥下,得要有任務處理才不會退出。
線程與RunLoop一一對應桨醋,不過子線程的Runloop需要手動創(chuàng)建并開啟棚瘟,否則執(zhí)行完一次任務就退出。
線程執(zhí)行完任務就會死亡喜最,故保持后臺常駐線程 — RunLoop一直有活干偎蘸,一般加定時器,或端口(source三種)
Runloop應用
1.ImageView顯示(利用PerformSelector 可以設置運行模式)
2.常駐線程
3.自動釋放池
第一次創(chuàng)建:Runloop啟動的時候創(chuàng)建
最后一次銷毀:Runloop退出的時候銷毀
其它時候的創(chuàng)建和銷毀:當Runloop即將進入休眠狀態(tài)的時候會把當前的自動釋放池釋放并創(chuàng)建一個新的