iOS多線程知識點總結
每一個iOS應用程序會有刷新UI界面、處理用戶的觸摸事件、解析網絡下載的數據等操作,為了防止界面假死,我們不能將一些太耗時的操作(比如網絡下載解析數據等)放在主線程中執行,而是要開啟另一個線程去執行耗時操作,增加運行效率。
一、應用場景
-
異步下載數據,這是多線程技術的一個比較常見的應用場景
-
還有一些比較耗時的操作或者功能(客戶端與服務端的交互;從數據庫中一次性讀取大量數據等),需要在主線程之外,單獨的開辟一個新的線程(子線程/工作線程)來執行。
二、iOS支持的多線程編程方法
-
NSThread
-
NSOperation & NSOperationQueue
-
GCD
四、線程的創建
-
創建后臺線程,自動的開啟線程
//performSelectorInBackground內部會創建一個線程 專門 執行調用 -thread1Click: [self performSelectorInBackground:@selector(thread1Click:) withObject:@"線程1"];
2.先創建,自己手動啟動線程
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"]; //設置名字 thread2.name = @"thread2"; //啟動線程 [thread2 start]; //這種方式創建線程 需要手動啟動
3.線程一旦創建,會立即執行
[NSThread detachNewThreadSelector:@selector(thread3Click:) toTarget:self withObject:@"線程3"];
五、界面假死
在開發中常常需要將耗時操作交給子線程/工作線程,而主線程去刷新UI界面
- (void)viewDidLoad {
[super viewDidLoad];
#if 0
[self func1];
[self func2];
[self func3];
/*上面的寫法是把耗時操作都交給UI主線程來完成,這樣主線程只有把上面三個函數執行完成 才會繼續向下執行
這樣的界面會假死,影響用戶體驗。
我們應該把耗時的操作交給子線程來完成,異步完成
*/
#else
//創建三個子線程 來完成 func1 func2 func3
[NSThread detachNewThreadSelector:@selector(func1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(func2) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(func3) toTarget:self withObject:nil];
#endif
}
- (void)func1 {
for (NSInteger i = 0; i < 20; i ++) {
NSLog(@"func1:i->%ld",i);
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"func1即將結束");
}
- (void)func2 {
for (NSInteger i = 0; i < 20; i ++) {
NSLog(@"func2:i->%ld",i);
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"func2即將結束");
}
- (void)func3 {
for (NSInteger i = 0; i < 20; i ++) {
NSLog(@"func3:i->%ld",i);
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"func3即將結束");
} 六、監聽線程結束
可以建立觀察者,監聽一個線程是否結束
- (void)viewDidLoad {
[super viewDidLoad];
//要先注冊觀察者
//注冊觀察者的時候內部會專門創建一個線程監聽其他線程有沒有結束,
//當線程結束時,一般都會發送一個結束通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(threadWillEnd:) name:NSThreadWillExitNotification object:nil];
//子線程 一旦創建啟動 就會和主線程 同時異步執行
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"線程1"];
thread1.name = @"thread1";
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"];
thread2.name = @"thread2";
[thread2 start];
}
- (void)thread1Click:(id)obj {
//打印當前線程和主線程
NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]);
NSLog(@"%s",__func__);
for (NSInteger i = 0; i < 10; i ++) {
NSLog(@"func1_i:%ld",i);
[NSThread sleepForTimeInterval:1];
}
NSLog(@"thread1即將結束");
}
- (void)thread2Click:(id)obj {
NSLog(@"thread:%@ isMain:%d",[NSThread currentThread],[NSThread isMainThread]);
NSLog(@"%s",__func__);
for (NSInteger i = 0; i < 10; i ++) {
NSLog(@"func2_i:%ld",i);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"thread2即將結束");
}
//當監聽到線程結束的時候調用這個函數
//上面的兩個線程結束都會調用這個函數
- (void)threadWillEnd:(NSNotification *)nf {
NSLog(@"thread:%@ isMain:%d_func:%s",[NSThread currentThread],[NSThread isMainThread],__func__);
NSLog(@"obj:%@",nf.object);//誰發的通知
}
- (void)dealloc {
//最后要移除觀察者
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:nil];
} 七、多個線程之間的通信
- (void)viewDidLoad {
[super viewDidLoad];
//創建3個線程
_thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Click:) object:@"線程1"];
[_thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Click:) object:@"線程2"];
[thread2 start];
NSThread *thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(thread3Click:) object:@"線程3"];
[thread3 start];
}
- (void)thread1Click:(id) obj {
NSLog(@"%s",__func__);
while (1) {
if ([[NSThread currentThread] isCancelled]) {
//要判斷 當前這個線程 是否 被取消過(是否發送過取消信號)
//如果被取消過,那么我們可以讓當前函數結束那么這個線程也就結束了
//break;//結束循環
NSLog(@"子線程1即將結束");
//return;//返回 函數
[NSThread exit];//線程退出
}
NSLog(@"thread1");
[NSThread sleepForTimeInterval:0.5];
}
NSLog(@"子線程1即將結束");
}
- (void)thread2Click:(id) obj {
NSLog(@"%s",__func__);
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.2];
NSLog(@"thread2:_i:%ld",i);
}
NSLog(@"子線程2即將結束");
//當 thread2 即將結束之后 通知 thread1結束
[_thread1 cancel];
// cancel 在這里 只是給_thread1 發送了一個cancel 信號,最終thread1的取消,取決于 thread1的,如果要取消thread1,那么需要在thread1執行調用的函數內部 進行判斷
}
- (void)thread3Click:(id) obj {
NSLog(@"%s",__func__);
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.2];
NSLog(@"thread3:_i:%ld",i);
}
NSLog(@"子線程3即將結束");
} 八、線程鎖
當多個線程同時操作同一個資源的時候,這時如果不處理,那么這個資源有可能就會紊亂,達不到我們想要的效果,所以如果我們要保證同時訪問的重要數據不紊亂,我們需要添加線程鎖,阻塞線程,使線程同步。排隊訪問
- (void)viewDidLoad {
[super viewDidLoad];
//必須要先創建鎖
_lock = [[NSLock alloc] init];
//創建兩個線程 操作同一個資源變量
[NSThread detachNewThreadSelector:@selector(thread1Click:) toTarget:self withObject:@"線程1"];
[NSThread detachNewThreadSelector:@selector(thread2Click:) toTarget:self withObject:@"線程2"];
}
- (void)thread1Click:(id)obj {
#if 0
[_lock lock];//加鎖
NSLog(@"thread1開始");
for (NSInteger i = 0 ; i < 10; i++) {
_cnt += 2;//想讓 _cnt 連續+2
NSLog(@"thread1_cnt:%ld",_cnt);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"thread1即將結束");
[_lock unlock];//解鎖
//訪問資源結束解鎖
#else
// @synchronized (self){} 類似于 加鎖和解鎖過程
@synchronized(self) {
//使線程對當前對象進行操作時,同步進行,阻塞線程
//跟加鎖原理是一樣的,執行 @synchronized(self)會判斷有沒有加鎖,加過鎖那么阻塞,沒有加鎖就繼續執行
NSLog(@"thread1開始");
for (NSInteger i = 0 ; i < 10; i++) {
_cnt += 2;//想讓 _cnt 連續+2
NSLog(@"thread1_cnt:%ld",_cnt);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"thread1即將結束");
}
#endif
}
- (void)thread2Click:(id)obj {
[_lock lock];
NSLog(@"thread2開始");
for (NSInteger i = 0 ; i < 10; i++) {
_cnt -= 5;//讓 _cnt連續-5
NSLog(@"thread2_cnt:%ld",_cnt);
[NSThread sleepForTimeInterval:0.2];
}
NSLog(@"thread2即將結束");
[_lock unlock];
} 九、任務隊列
NSThread操作線程是最基本的類,NSOperation是一個輕量級的線程,任務隊列得到的子線程的效率要高于NSTread。
NSOperation是以任務為導向的管理線程機制,將操作(任務)放入到線程池里,會自動執行,弱化線程的概念(任務:可以簡單的理解為線程)
- (void)viewDidLoad {
[super viewDidLoad];
//創建一個線程池
_queue = [[NSOperationQueue alloc] init];
//設置 一個隊列中 允許 最大 任務的并發 個數
_queue.maxConcurrentOperationCount = 2;
//如果寫成1 表示 線程池中的任務 一個一個 串行執行
[self createInvocationOperation];
[self createBlockOperation];
}
/*
NSOperation 是一個抽象類 NSOperation 方法 需要有子類自己實現
//創建任務對象 都是 NSOperation 的子類對象
//NSBlockOperation NSInvocationOperation
任務 要和 任務隊列/線程池 結合使用
*/
#pragma mark - block任務
- (void)createBlockOperation {
//block代碼塊就是一個 任務
NSBlockOperation *blockOp1 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"block任務:%ld",i);
[NSThread sleepForTimeInterval:0.3];
}
}];
[blockOp1 setCompletionBlock:^{
//block 任務完成之后 會回調 這個block
NSLog(@"block任務完成");
}];
//放在線程池中
[_queue addOperation:blockOp1];
}
#pragma mark - 任務
//第一種任務
- (void)createInvocationOperation {
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1:) object:@"任務1"];
//[op1 start]; 任務 默認start 相對于主線程是同步
//把任務 放入 線程池
[_queue addOperation:op1];//一旦放入 這個 任務 就會異步啟動
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2:) object:@"任務2"];
//把任務 放入 線程池
[_queue addOperation:op2];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation3:) object:@"任務3"];
//把任務 放入 線程池
[_queue addOperation:op3];
}
- (void)operation3:(id)obj {
NSLog(@"obj:%@",obj);
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"op3:i->%ld",i);
}
NSLog(@"任務3即將結束");
}
- (void)operation2:(id)obj {
NSLog(@"obj:%@",obj);
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"op2:i->%ld",i);
}
NSLog(@"任務2即將結束");
}
- (void)operation1:(id)obj {
NSLog(@"obj:%@",obj);
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"op1:i->%ld",i);
}
NSLog(@"任務1即將結束");
} 十、GCD
GCD 全稱Grand Central Dispatch(隊列調度),是一套低層API,提供了?種新的方法來進?并發程序編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程序將任務切分為多個單一任務,然后提交?至?工作隊列來并發地或者串?行地執?行。
GCD是C實現,?NSOpertionQueue更底層更高效,并且它不是Cocoa框架的一部分,并發任務會像NSOperationQueue那樣基于系統負載來合適地并發進?,串?行隊列同一時間只執行單一任務
GCD的API很大程度上基于block
- (void)viewDidLoad {
[super viewDidLoad];
//[self createMainQueue];
//[self createPrivateQueue];
[self createGlobalQueue];
}
#pragma mark - 全局隊列
/*
3.全局隊列
// 并行隊列(全局)不需要我們創建,通過dispatch_get_global_queue()方法獲得
// 三個可用隊列
// 第一個參數是選取按個全局隊列,一般采用DEFAULT,默認優先級隊列
// 第二個參數是保留標志,目前的版本沒有任何用處(不代表以后版本),直接設置為0就可以了
// DISPATCH_QUEUE_PRIORITY_HIGH
// DISPATCH_QUEUE_PRIORITY_DEFAULT
// DISPATCH_QUEUE_PRIORITY_LOW
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
*/
- (void)createGlobalQueue {
//全局隊列 內部任務 異步/并行 執行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"全局隊列任務1:%ld",i);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"全局隊列任務2:%ld",i);
}
});
}
#pragma mark - 私有隊列
/*
2.創建私有隊列 用戶隊列/串行隊列
// C接口,創建一個私有隊列 ,隊列名是一個C字符串,沒有特別的要求,Apple建議用倒裝的標識符來表示(這個名字,更多用于調試)
私有隊列內部也是串行操作
*/
- (void)createPrivateQueue {
//創建一個私有隊列
//私有隊列 相對于主線程 異步
//私有隊列內部的任務 是 串行執行
//下面函數的第一個參數 就是一個標簽字符串 標識隊列
dispatch_queue_t queue = dispatch_queue_create("com.1507", NULL);
//增加任務
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"私有隊列任務1");
}
});
//增加任務 這兩個任務 是 ?
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"私有隊列任務2");
}
});
//私有隊列 相當于 NSOperationQueue 隊列 內部最大并發個數是1
}
#pragma mark - 主線程隊列
- (void)createMainQueue {
//主線程隊列 只需要獲取 --》一般都是在子線程獲取 在子線程獲取才有意義
//1.獲取主線程隊列 -->主線程隊列內部 都是 串行
dispatch_queue_t queue = dispatch_get_main_queue();
//dispatch_async 相對于 當前線程 異步給 queue 增加一個任務
dispatch_async(queue, ^{
//給主線程 增加一個任務
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"主線程任務1_i:%ld",i);
[NSThread sleepForTimeInterval:0.5];
}
});
//增加任務 是異步 的 主線程 隊列內部 是串行執行任務的
dispatch_async(dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"主線程任務2_i:%ld",i);
[NSThread sleepForTimeInterval:0.5];
}
});
} 來自:http://my.oschina.net/u/2429584/blog/491771