模擬耗時操作
- 耗時操作對UI的影響 : 會卡死UI / 界面 / 主線程
- 如何解決耗時操作卡死主線程?
- 使用多線程技術,把耗時的操作放進子線程中執(zhí)行,使得主線程有資源處理UI
- 多線程的核心思想 : 把耗時操作放到子線程異步執(zhí)行
多線程基本概念
同步和異步
- 同步和異步是任務 / 代碼 執(zhí)行的兩種方式
同步
- 多個任務按順序依次執(zhí)行,就是同步執(zhí)行
異步
- 多個任務同時執(zhí)行,就是異步執(zhí)行
- 如何保證多個任務同時執(zhí)行?
- 開線程,開多個線程,就可以保證多個任務同時執(zhí)行
- 提示 : 凡是遇到異步 / 多線程 / 耗時操作 第一反應就是需要開啟新的子線程
- 學習多線程就是為了如何讓任務在子線程異步執(zhí)行
進程和線程
進程
- 系統(tǒng)中
正在運行
的應用程序叫做進程 - 進程可以類必成公司
線程 / 多線程
- 程序一啟動就會默認開啟一個線程,稱之為主線程
- 線程是進程最基本的執(zhí)行單元,進程里面所有的任務都在線程中執(zhí)行的
- 一個進程,可以開啟多個線程,稱之為多線程
多線程執(zhí)行原理
- CPU在多個線程之間,快速來回的切換,調度線程執(zhí)行任務,如果切換的速度足夠快,就造成多個任務
同時
執(zhí)行的假象
多線程優(yōu)缺點
優(yōu)點
- 可以
適當
提高程序執(zhí)行的效率 (開啟多個線程下載視頻)
缺點
- 前提 : 當線程非常多的時候,就暴露缺點
- 會消耗大量的CPU資源
- 時間開銷 / 空間開銷
- 多線程的使用原則 : 能不用就不用,如果非要用,就簡單的使用,少用
主線程
- 作用 : 刷新UI / 處理UI事件
- 使用時的注意點 : 不要把耗時操作放到主線程中執(zhí)行
pthread
- 學習pthread的目的 : 就是為了復習C語言相關的知識點
- 在C語言中,一般帶
_t
/_ref
標識數(shù)據(jù)類型 - NULL : 表示空地址,一般在C語言使用;
- nil : 表示空對象,一般在OC使用;
- 其實,NULLh和nil本質上沒有半點兒區(qū)別
-
void *(*)(void *)
: 表示指向函數(shù)的指針,即函數(shù)名;函數(shù)名就是表示函數(shù)地址; - 數(shù)組地址就是數(shù)組名或者數(shù)組第0個角標元素的地址
-
void *
表示可以指向任何地址的指針,代表任意數(shù)據(jù)類型;類似于OC的id;
返回值 函數(shù)名 函數(shù)參數(shù)
void * (*) (void *)
橋接
- 使用場景 : 在C語言和OC語言混合開發(fā)時,需要做數(shù)據(jù)類型轉換,有時候需要使用橋接;
- 橋接作用 : 在做數(shù)據(jù)類型轉換時,告訴編譯器如何管理C語言的內存
- 提示 : 在ARC環(huán)境下,編譯器在編譯時,不會自動管理C語言申請的內存空間
- 提示: 在ARC環(huán)境下,加上__bridge 表示告訴編譯器C語言申請的內存也是自動管理的,因為大環(huán)境是ARC的
- 提問 : MRC環(huán)境下,需要使用__bridge 嗎? 不需要,因為本來就是手動管理的
NSThread創(chuàng)建線程三種方式
構造方法
- 可以拿到線程對象
- 需要自己啟動線程
// 創(chuàng)建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
// 啟動線程
[thread start];
類方法
- 不可以拿到線程對象
- 不需要自己啟動線程
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
NSObject分類方法
- 不可以拿到線程對象
- 不需要自己啟動線程
[self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
target和selector的關系
- 執(zhí)行哪個對象的哪個方法
- 需求 : 執(zhí)行Person對象的run方法,run方法需要在子線程執(zhí)行
// 創(chuàng)建線程對象
NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"person"];
// 啟動線程
[thread start];
線程生命周期 / 線程狀態(tài)
- 新建狀態(tài)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
- 就緒狀態(tài)
[thread start];
- 運行狀態(tài) : 程序員無法干預
- 阻塞狀態(tài) : 調用sleep方法 / 添加互斥鎖(同步鎖)
- 死亡狀態(tài)
- 正常死亡 : 任務執(zhí)行結束
- 異常死亡 : exit
線程屬性
- name : 標識唯一的線程對象,方便定位線程對象
- threadPriority : 決定了線程有更多的機會被CPU調度執(zhí)行;等同于qualityOfService;實際開發(fā)中千萬不要隨意修改
- stackSize : 線程對象占用內存空間大小.主線程 / 子線程 512KB
多線程訪問共享資源 (會造成線程安全問題)
- 當多個線程同時操作共享資源,就會出現(xiàn)線程安全問題
- 解決辦法 : 加鎖 (互斥鎖 / 同步鎖)
- 互斥鎖 / 同步鎖 : 使用了線程同步技術
- 特點 : 可以保證被鎖定的代碼,同一時間只有一個線程可以訪問
- self : 表示互斥鎖的參數(shù);互斥鎖的參數(shù),又叫做鎖對象;
- 鎖對象 : 任何繼承自NSObject的對象,都可以作為互斥鎖的參數(shù);內部有把鎖,默認是開啟的
- 鎖對象必須是全局的對象;self是最方便獲取的全局的鎖對象
- 局部鎖對象是鎖不住的,因為每次線程進來之前會新建一把鎖
- 提示 : 加鎖的事情,不是再客戶端操作的;是服務器加鎖的,多線程資源共享絕大多數(shù)是在服務器發(fā)生;
- 提示 : 加鎖是犧牲了性能,保證安全.客戶端的性能不能輕易犧牲
異步下載網絡圖片
在 iOS 開發(fā)中,使用多線程只有一個目的:將耗時操作放在后臺工作舷蟀,待工作完成后汹押,通知主線程更新 UI
耗時的下載操作放在子線程
- (void)viewDidLoad {
[super viewDidLoad];
// [self loadImageData];
// 在子線程執(zhí)行耗時操作
[self performSelectorInBackground:@selector(loadImageData) withObject:nil];
}
// 下載圖片的主方法
- (void)loadImageData
{
// URL
NSURL *URL = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1476696781&di=f721c3cba572282b9d4b135866894858&src=http://www.hn.xinhuanet.com/2016-08/31/1119483302_14726048769591n.jpg"];
// 發(fā)送網絡請求,獲取圖片二進制數(shù)據(jù),是個耗時操作
NSData *data = [NSData dataWithContentsOfURL:URL];
// image : 就是子線程執(zhí)行的結果,需要傳遞到主線程
UIImage *image = [UIImage imageWithData:data];
// 下載完成之后,通知主線程刷新UI
// waitUntilDone : 是否等待updateUI執(zhí)行完,再執(zhí)行后面的代碼,一般傳入NO
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:NO];
NSLog(@"后面的代碼");
}
- 更新UI的操作在主線程
// 回到主線程更新UI
- (void)updateUI:(UIImage *)image
{
self.imgView.image = image;
[self.imgView sizeToFit];
self.scrollView.contentSize = image.size;
}
- 在子線程下載圖片,在主線程更新UI,是線程間通信的一種;
- 線程間通信 : 一個線程把他執(zhí)行的結果,傳遞到另外的一個線程