多線程的概念在各個操做系統上都會接觸到,windows、Linux、mac os等等這些經常使用的操做系統,都支持多線程的概念。java
固然ios中也不例外,可是線程的運行節點多是咱們日常不太注意的。ios
例如:程序員
1 - (void)viewDidLoad 2 { 3 [super viewDidLoad]; 4 for(int i = 0 ; i < 100 ; i++) 5 { 6 NSLog(@"===%@===%d" , [NSThread currentThread].name , i); 7 if(i == 20) 8 { 9 // 建立線程對象 10 NSThread *thread = [[NSThread alloc]initWithTarget:self 11 selector:@selector(run) object:nil]; 12 // 啓動新線程 13 [thread start]; 14 // // 建立並啓動新線程 15 // [NSThread detachNewThreadSelector:@selector(run) toTarget:self 16 // withObject:nil]; 17 } 18 } 19 } 20 - (void)run 21 { 22 for(int i = 0 ; i < 100 ; i++) 23 { 24 NSLog(@"-----%@----%d" , [NSThread currentThread].name, i); 25 } 26 }
上面打印的內容每一次都是不一樣的,什麼意思呢?編程
當咱們建立了4個線程後,加上UI主線程一共5個線程。windows
新的線程在執行start方法以後,並不會當即執行。他們會被cpu隨機的執行,只是間隔很是短,以致於咱們感受上是多個線程在同時執行。網絡
因此線程有這麼一個特色:執行的隨機性。多線程
可是咱們能夠設置線程的優先級,讓優先級更高的線程得到更多的執行機會。併發
那麼何時要使用多線程編程呢?app
相信有過開發經驗的程序員都知道,當咱們把代碼寫完後,程序是一行一行逐行執行代碼的,當其中一行代碼須要執行較長時間(例如select一個教複雜的語句或者較多的數據時),那麼程序就會出現卡頓的現象,不會相應用戶的操做。異步
由於開啓程序後會默認開啓一個主線程,即UI線程。當處於剛纔那種狀況時,好比一個windows程序,就會出現程序暫時無響應的提示,好像電腦卡主的感受,這是很是很差的一種感覺。。。。
當咱們要避免這種狀況的時候,最好的方式就是多線程,開啓一個新的線程,用來執行一個耗時的操做,執行完成後再讓主線程來修改ui頁面(若是須要的話)。
介紹完了線程的一些知識,那麼下面來具體看ios中多線程的幾種實現方式,主要有一下三種:
一、NSThread :就是剛剛例子中使用的方式,可是使用上比較繁瑣,並且須要控制好數據的同步和異步問題
二、NSOperation 和 NSOperationQueue : 這種方式代碼比較簡潔,可讀性強,並且使用隊列的形式管理多個任務,本人比較喜歡
三、使用GCD( Grand Central Dispatch ) :相較於NSThread使用簡單,使用隊列管理任務
1、首先來介紹NSThread
一、建立NSThread的兩種方式
-(id) initWithTarget:(id) target selector:(SEL) selector object:(id) arg:
+(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:
第二種方式,建立NSThread後會自動啓動
二、NSThread的經常使用方法
+currentThread : 返回當前正在執行的線程對象
三、線程的狀態
一開始的例子中提了一下,線程建立後,執行了start方法並非當即就執行了。可能ui線程執行了幾毫秒後,cpu才執行它,執行幾毫秒後再執行ui線程,但這個過程是隨機發生的。
若是想讓線程當即執行,那麼可讓ui線程sleep 1毫秒,這樣cpu就會執行其餘可執行的線程,能夠達到當即執行的效果
1 [NSThread sleepForTimeInterval:0.001];//讓當前運行的線程睡眠1毫秒
線程正在執行時,調用isExecuting方法返回 YES ,線程執行完成後調用 isFinished 方法就會返回 YES
四、終止子線程
線程會以一下3種方式之一結束,結束後就處於死亡狀態
1)線程執行的方法體執行完成,線程正常結束
2)執行過程當中出現了錯誤
3)調用NSThread 類的 exit 方法來終止當前線程
在UI 線程中 ,NSThread 並無提供方法來結束其餘的子線程。可是咱們能夠利用 NSThread 的cancel 方法,執行該方法後, 該線程的狀態爲 isCancelled = YES,但並不會結束線程。
1 NSThread* thread; 2 - (void)viewDidLoad 3 { 4 [super viewDidLoad]; 5 // 建立新線程對象 6 thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) 7 object:nil]; 8 // 啓動新線程 9 [thread start]; 10 } 11 - (void)run 12 { 13 for(int i = 0 ; i < 100 ; i++) 14 { 15 if([NSThread currentThread].isCancelled) 16 { 17 // 終止當前正在執行的線程 18 [NSThread exit]; 19 } 20 NSLog(@"-----%@----%d" , [NSThread currentThread].name, i); 21 // 每執行一次,線程暫停0.5秒 22 [NSThread sleepForTimeInterval:0.5]; 23 } 24 } 25 - (IBAction)cancelThread:(id)sender 26 { 27 // 取消thread線程,調用該方法後,thread的isCancelled方法將會返回NO 28 [thread cancel]; 29 }
利用例子中代碼的形式,咱們就能夠達到在UI線程中結束其餘子線程的目的了。
五、線程睡眠
要讓線程進入阻塞狀態或者睡眠狀態,能夠執行sleepXXX格式的方法:
+(void) sleepUntilDate:(NSDate *) aDate : 讓線程睡眠,知道aDate那個時間點再醒過來
-(void)sleepForTimeInterval :讓線程睡眠多少秒
六、改變線程優先級
NSThread 提供了以下幾個方法來獲取和設置線程的優先級
+threadPriority: 獲取當前正在執行的線程的優先級
-threadPriority:獲取線程實例的優先級
+setThreadPriority :(double) priority : 設置當前正在執行的線程的優先級
-setThreadPriority :(double) priority : 設置線程實例的優先級
(double) priority的 取值範圍是0.0~1.0;優先級越高的線程得到的執行機會越多
1 - (void)viewDidLoad 2 { 3 [super viewDidLoad]; 4 NSLog(@"UI線程的優先級爲:%g" , [NSThread threadPriority]); 5 // 建立第一個線程對象 6 NSThread* thread1 = [[NSThread alloc] 7 initWithTarget:self selector:@selector(run) object:nil]; 8 // 設置第一個線程對象的名字 9 thread1.name = @"線程A"; 10 NSLog(@"線程A的優先級爲:%g" , thread1.threadPriority); 11 // 設置使用最低優先級 12 thread1.threadPriority = 0.0; 13 // 建立第二個線程對象 14 NSThread* thread2 = [[NSThread alloc] 15 initWithTarget:self selector:@selector(run) object:nil]; 16 // 設置第二個線程對象的名字 17 thread2.name = @"線程B"; 18 NSLog(@"線程B的優先級爲:%g" , thread2.threadPriority); 19 // 設置使用最高優先級 20 thread2.threadPriority = 1.0; 21 // 啓動2個線程 22 [thread1 start]; 23 [thread2 start]; 24 } 25 - (void)run 26 { 27 for(int i = 0 ; i < 100 ; i++) 28 { 29 NSLog(@"-----%@----%d" , [NSThread currentThread].name, i); 30 } 31 }
2、使用GCD實現多線程
GCD簡化了多線程的實現,主要有兩個核心概念:
一、隊列:隊列負責管理開發者提交的任務,以先進先出的方式來處理任務。
1)串行隊列:每次只執行一個任務,當前一個任務執行完成後才執行下一個任務
2)並行隊列:多個任務併發執行,因此先執行的任務可能最後才完成(由於具體的執行過程致使)
二、任務:任務就是開發者提供給隊列的工做單元,這些任務將會提交給隊列底層維護的線程池,所以這些任務將會以多線程的方式執行。
三、建立隊列
1)獲取系統默認的全局併發隊列:
1 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2) 獲取系統主線程關聯的穿行隊列
1 dispatch_queue_t queue = dispatch_get_main_queue();
若是將任務提交給主線程關聯的串行隊列,那麼就至關於在程序主線程中去執行該任務。
3)建立穿行隊列
1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);
4)建立併發隊列
1 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
5)獲取當前執行代碼所在隊列
dispatch_get_current_queue,返回一個dispatch_queue_t類型的值
四、提交任務
使用下面的方法將任務以同步或者異步的方式提交到隊列
1 //將代碼塊以異步的方式提交給指定隊列 2 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); 3 4 //將函數以異步的方式提交給指定隊列,通常執行函數的方法與執行代碼塊的方法比,方法名多了一個_f的後綴 5 void dispatch_async_f(dispatch_queue_t queue, void* context, dispatch_function_t work); 6 7 //將代碼塊以同步的方式提交給指定隊列 8 void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); 9 10 //將函數以同步的方式提交給指定隊列,通常執行函數的方法與執行代碼塊的方法比,方法名多了一個_f的後綴 11 void dispatch_sync_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
1 //將代碼塊以異步的方式提交給指定隊列,隊列的線程池負責在指定時間點 when 以後執行 2 void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); 3 4 //將函數以異步的方式提交給指定隊列,隊列的線程池負責在指定時間點 when 以後執行 5 void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void* context, dispatch_function_t work); 6 7 //將代碼塊以異步的方式提交給指定隊列,隊列的線程池將會重複屢次執行該任務 8 void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t)); 9 10 //將函數以異步的方式提交給指定隊列,隊列的線程池將會重複屢次執行該任務 11 void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void* context, void(*work)(void*, size_t)); 12 13 //將代碼塊提交給指定隊列,在應用的某個生命週期內金執行一次 14 void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
下面給出一個以異步方式向串行隊列、併發隊列添加任務的實例
1 // 定義2個隊列 2 dispatch_queue_t serialQueue; 3 dispatch_queue_t concurrentQueue; 4 - (void)viewDidLoad 5 { 6 [super viewDidLoad]; 7 // 建立串行隊列 8 serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL); 9 // 建立併發隊列 10 concurrentQueue = dispatch_queue_create("fkjava.queue" 11 , DISPATCH_QUEUE_CONCURRENT); 12 } 13 - (IBAction)serial:(id)sender 14 { 15 // 依次將2個代碼塊提交給串行隊列 16 // 必須等到第1個代碼塊完成後,才能執行第2個代碼塊。 17 dispatch_async(serialQueue, ^(void) 18 { 19 for (int i = 0 ; i < 100; i ++) 20 { 21 NSLog(@"%@=====%d" , [NSThread currentThread] , i); 22 } 23 }); 24 dispatch_async(serialQueue, ^(void) 25 { 26 for (int i = 0 ; i < 100; i ++) 27 { 28 NSLog(@"%@------%d" , [NSThread currentThread] , i); 29 } 30 }); 31 } 32 - (IBAction)concurrent:(id)sender 33 { 34 // 依次將2個代碼塊提交給併發隊列 35 // 兩個代碼塊能夠併發執行 36 dispatch_async(concurrentQueue, ^(void) 37 { 38 for (int i = 0 ; i < 100; i ++) 39 { 40 NSLog(@"%@=====%d" , [NSThread currentThread] , i); 41 } 42 }); 43 dispatch_async(concurrentQueue, ^(void) 44 { 45 for (int i = 0 ; i < 100; i ++) 46 { 47 NSLog(@"%@------%d" , [NSThread currentThread] , i); 48 } 49 }); 50 }
提交同步任務:
1 - (void)viewDidLoad 2 { 3 [super viewDidLoad]; 4 } 5 - (IBAction)clicked:(id)sender 6 { 7 // 以同步方式前後提交2個代碼塊 8 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 9 , ^(void){ 10 for (int i = 0 ; i < 100; i ++) 11 { 12 NSLog(@"%@=====%d" , [NSThread currentThread] , i); 13 [NSThread sleepForTimeInterval:0.1]; 14 } 15 }); 16 // 必須等第一次提交的代碼塊執行完成後,dispatch_sync()函數纔會返回, 17 // 程序纔會執行到這裏,才能提交第二個代碼塊。 18 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 19 , ^(void){ 20 for (int i = 0 ; i < 100; i ++) 21 { 22 NSLog(@"%@-----%d" , [NSThread currentThread] , i); 23 [NSThread sleepForTimeInterval:0.1]; 24 } 25 }); 26 }
屢次執行的任務:
1 - (void)viewDidLoad 2 { 3 [super viewDidLoad]; 4 } 5 - (IBAction)clicked:(id)sender 6 { 7 // 控制代碼塊執行5次 8 dispatch_apply(5 9 , dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 10 // time形參表明當前正在執行第幾回 11 , ^(size_t time) 12 { 13 NSLog(@"===執行【%lu】次===%@" , time 14 , [NSThread currentThread]); 15 }); 16 }
只執行一次的任務
1 @implementation FKViewController 2 - (void)viewDidLoad 3 { 4 [super viewDidLoad]; 5 } 6 - (IBAction)clicked:(id)sender 7 { 8 static dispatch_once_t onceToken; 9 dispatch_once(&onceToken, ^{ 10 NSLog(@"==執行代碼塊=="); 11 // 線程暫停3秒 12 [NSThread sleepForTimeInterval:3]; 13 }); 14 }
3、使用NSOperation 和 NSOPerationQueue 實現多線程
和GCD差很少,也是有隊列和任務的概念
NSOperationQueue:表明一個先進先出的隊列,負責管理系統提交的多個NSOperation。底層維護一個線程池,會按順序啓動線程來執行提交給隊列的NSOperation
NSOperation:表明多線程任務。通常不直接使用NSOperation,而是使用NSOperation的子類。或者使用NSInvocationOperation和NSBlockOperation(這兩個類繼承自NSOperation);
一、NSOperation的使用
NSOperation 的使用相較於GCD是面向對象的,OC實現的,而GCD應該是C實現的(看函數的定義和使用)。
使用NSOperation 只需兩步:
1)建立 NSOperationQueue 隊列,並未該隊列設置相關屬性
2)建立 NSOperation 子類對象,並將該對象提交給 NSOperationQueue 隊列,該隊列將會按順序依次啓動每一個 NSOperation。
二、NSOperationQueue的經常使用方法:
1 +currentQueue //類方法,返回執行當前NSOperation的NSOperationQueue隊列 2 3 +mainQueue //返回系統主線程的NSOperationQueue隊列 4 5 -(void) addOperation:(NSOperation *) operation //將operation添加到NSOperationQueue隊列中 6 7 -(void) addOperations:(NSArray *) ops waitUnitlFinished:(BOLL) wait //將NSArray中包含的全部NSOperation添加到NSOperationQueue。若是第二個參數指定爲YES,將會阻塞當前線程,直到提交的全部NSOperation執行完成。若是第二個參數爲NO,該方法當即返回,NSArray包含的NSOperation將以異步方式執行,不會阻塞當前線程。 8 9 - operations //只讀屬性,返回該NSOperationQueue管理的全部NSOperation 10 -operationCount //只讀屬性,返回該NSOperationQueue管理的全部NSOperation數量 11 12 -cancelAllOperations: //取消NSOperationQueue隊列中全部正在排隊和執行的NSOperation 13 14 -waitUntilAllOperationsAreFinished://阻塞當前線程,直到該NSOperationQueue中全部排隊和執行的NSOperation執行完成再接觸阻塞 15 16 -(NSInteger) maxConcurrentOperationCount://返回該隊列最大支持多少個併發線程 17 18 -setMaxConcurrentOperationCount:(NSInteger) count //設置該隊列最大支持多少個併發線程 19 20 -setSuspended:(BOOL) suspend: //設置NSOperationQueue是否已經暫停調度正在排隊的NSOperation 21 22 -(BOLL) isSuspended: //返回NSOperationQueue是否已經暫停調度正在排隊的NSOperation
三、使用NSInvocationOperation 和 NSBlockOperation
NSInvocationOperation 和 NSBlockOperation 繼承自 NSOperation,因此能夠直接使用,用於封裝須要異步執行的任務。
使用它們實現圖片異步下載:
1 NSOperationQueue* queue; 2 - (void)viewDidLoad 3 { 4 [super viewDidLoad]; 5 queue = [[NSOperationQueue alloc]init]; 6 // 設置該隊列最多支持10條併發線程 7 queue.maxConcurrentOperationCount = 10; 8 } 9 - (IBAction)clicked:(id)sender 10 { 11 NSString* url = @"http://www.......jpg"; 12 // 以傳入的代碼塊做爲執行體,建立NSOperation 13 NSBlockOperation* operation = [NSBlockOperation 14 blockOperationWithBlock:^{ 15 // 從網絡獲取數據 16 NSData *data = [[NSData alloc] 17 initWithContentsOfURL:[NSURL URLWithString:url]]; 18 // 將網絡數據初始化爲UIImage對象 19 UIImage *image = [[UIImage alloc]initWithData:data]; 20 if(image != nil) 21 { 22 // 在主線程中執行updateUI:方法 23 [self performSelectorOnMainThread:@selector(updateUI:) 24 withObject:image waitUntilDone:YES]; 25 } 26 else 27 { 28 NSLog(@"---下載圖片出現錯誤---"); 29 } 30 }]; 31 // 將NSOperation添加給NSOperationQueue 32 [queue addOperation:operation]; 33 } 34 -(void)updateUI:(UIImage*) image 35 { 36 self.iv.image = image; 37 }
1 NSOperationQueue* queue; 2 - (void)viewDidLoad 3 { 4 [super viewDidLoad]; 5 queue = [[NSOperationQueue alloc]init]; 6 // 設置該隊列最多支持10條併發線程 7 queue.maxConcurrentOperationCount = 10; 8 } 9 - (IBAction)clicked:(id)sender 10 { 11 NSString* url = @"http://www.......jpg"; 12 // 以self的downloadImageFromURL:方法做爲執行體,建立NSOperation 13 NSInvocationOperation* operation = [[NSInvocationOperation alloc] 14 initWithTarget:self selector:@selector(downloadImageFromURL:) 15 object:url]; 16 // 將NSOperation添加給NSOperationQueue 17 [queue addOperation:operation]; 18 } 19 20 // 定義一個方法做爲線程執行體。 21 -(void)downloadImageFromURL:(NSString *) url 22 { 23 // 從網絡獲取數據 24 NSData *data = [[NSData alloc] 25 initWithContentsOfURL:[NSURL URLWithString:url]]; 26 // 將網絡數據初始化爲UIImage對象 27 UIImage *image = [[UIImage alloc]initWithData:data]; 28 if(image != nil) 29 { 30 // 在主線程中執行updateUI:方法 31 [self performSelectorOnMainThread:@selector(updateUI:) 32 withObject:image waitUntilDone:YES]; 33 } 34 else 35 { 36 NSLog(@"---下載圖片出現錯誤---"); 37 } 38 } 39 -(void)updateUI:(UIImage*) image 40 { 41 self.iv.image = image; 42 }
四、自定義NSOperation 的子類
建立 NSOperation 的子類,須要重寫一個方法:-(void) main,該方法的方法體將做爲 NSOperationQueue 完成的任務
下面自定義一個NSOperation 子類來實現下載圖片的功能
1 @interface MyDownImageOperation : NSOperation 2 @property (nonatomic , strong) NSURL* url; 3 @property (nonatomic , weak) UIImageView* imageView; 4 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv; 5 @end
1 @implementation MyDownImageOperation 2 - (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv 3 { 4 self = [super init]; 5 if (self) { 6 _imageView = iv; 7 _url = url; 8 } 9 return self; 10 } 11 // 重寫main方法,該方法將做爲線程執行體 12 - (void)main 13 { 14 // 從網絡獲取數據 15 NSData *data = [[NSData alloc] 16 initWithContentsOfURL:self.url]; 17 // 將網絡數據初始化爲UIImage對象 18 UIImage *image = [[UIImage alloc]initWithData:data]; 19 if(image != nil) 20 { 21 // 在主線程中執行updateUI:方法 22 [self performSelectorOnMainThread:@selector(updateUI:) 23 withObject:image waitUntilDone:YES]; // ① 24 } 25 else 26 { 27 NSLog(@"---下載圖片出現錯誤---"); 28 } 29 } 30 -(void)updateUI:(UIImage*) image 31 { 32 self.imageView.image = image; 33 }
viewController代碼:
1 NSOperationQueue* queue; 2 - (void)viewDidLoad 3 { 4 [super viewDidLoad]; 5 queue = [[NSOperationQueue alloc]init]; 6 // 設置該隊列最多支持10條併發線程 7 queue.maxConcurrentOperationCount = 10; 8 } 9 - (IBAction)clicked:(id)sender 10 { 11 // 定義要加載的圖片的URL 12 NSURL* url = [NSURL URLWithString:@"http://www.crazyit.org/logo.jpg"]; 13 // 建立FKDownImageOperation對象 14 MyDownImageOperation* operation = [[MyDownImageOperation alloc] 15 initWithURL:url imageView:self.iv]; 16 // 將NSOperation的子類的實例提交給NSOperationQueue 17 [queue addOperation:operation]; 18 }