IOS高級編程之三:IOS 多線程編程

多線程的概念在各個操做系統上都會接觸到,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 }
相關文章
相關標籤/搜索