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


多線程的概念在各個操作系統上都會接觸到,windows、Linux、mac os等等這些常用的操作系統,都支持多線程的概念。

當然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個線程。

新的線程在執行start方法之后,並不會立即執行。他們會被cpu隨機的執行,只是間隔非常短,以至於我們感覺上是多個線程在同時執行。

所以線程有這么一個特點:執行的隨機性

但是我們可以設置線程的優先級,讓優先級更高的線程獲得更多的執行機會。

 

那么什么時候要使用多線程編程呢?

相信有過開發經驗的程序員都知道,當我們把代碼寫完后,程序是一行一行逐行執行代碼的,當其中一行代碼需要執行較長時間(例如select一個教復雜的語句或者較多的數據時),那么程序就會出現卡頓的現象,不會相應用戶的操作。

因為開啟程序后會默認開啟一個主線程,即UI線程。當處於剛才那種情況時,比如一個windows程序,就會出現程序暫時無響應的提示,好像電腦卡主的感覺,這是非常不好的一種感受。。。。

當我們要避免這種情況的時候,最好的方式就是多線程,開啟一個新的線程,用來執行一個耗時的操作,執行完成后再讓主線程來修改ui頁面(如果需要的話)。

 

介紹完了線程的一些知識,那么下面來具體看ios中多線程的幾種實現方式,主要有一下三種:

1、NSThread :就是剛剛例子中使用的方式,但是使用上比較繁瑣,而且需要控制好數據的同步和異步問題

2、NSOperation 和 NSOperationQueue : 這種方式代碼比較簡潔,可讀性強,而且使用隊列的形式管理多個任務,本人比較喜歡

3、使用GCD( Grand Central Dispatch ) :相較於NSThread使用簡單,使用隊列管理任務

 

一、首先來介紹NSThread

1、創建NSThread的兩種方式

-(id) initWithTarget:(id) target selector:(SEL) selector object:(id) arg:

+(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:

第二種方式,創建NSThread后會自動啟動

 

2、NSThread的常用方法

+currentThread : 返回當前正在執行的線程對象

 

3、線程的狀態

一開始的例子中提了一下,線程創建后,執行了start方法並不是立即就執行了。可能ui線程執行了幾毫秒后,cpu才執行它,執行幾毫秒后再執行ui線程,但這個過程是隨機發生的。

如果想讓線程立即執行,那么可以讓ui線程sleep 1毫秒,這樣cpu就會執行其他可執行的線程,可以達到立即執行的效果

1 [NSThread sleepForTimeInterval:0.001];//讓當前運行的線程睡眠1毫秒

線程正在執行時,調用isExecuting方法返回 YES ,線程執行完成后調用 isFinished 方法就會返回 YES

 

4、終止子線程

線程會以一下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線程中結束其他子線程的目的了。

 

5、線程睡眠

要讓線程進入阻塞狀態或者睡眠狀態,可以執行sleepXXX格式的方法:

+(void) sleepUntilDate:(NSDate *) aDate  : 讓線程睡眠,知道aDate那個時間點再醒過來

-(void)sleepForTimeInterval :讓線程睡眠多少秒

 

6、改變線程優先級

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 }

 

二、使用GCD實現多線程

GCD簡化了多線程的實現,主要有兩個核心概念:

1、隊列:隊列負責管理開發者提交的任務,以先進先出的方式來處理任務。

1)串行隊列:每次只執行一個任務,當前一個任務執行完成后才執行下一個任務

2)並行隊列:多個任務並發執行,所以先執行的任務可能最后才完成(因為具體的執行過程導致)

2、任務:任務就是開發者提供給隊列的工作單元,這些任務將會提交給隊列底層維護的線程池,因此這些任務將會以多線程的方式執行。

 

3、創建隊列

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類型的值

 

4、提交任務

使用下面的方法將任務以同步或者異步的方式提交到隊列

 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 }

三、使用NSOperation 和 NSOPerationQueue 實現多線程

和GCD差不多,也是有隊列和任務的概念

NSOperationQueue:代表一個先進先出的隊列,負責管理系統提交的多個NSOperation。底層維護一個線程池,會按順序啟動線程來執行提交給隊列的NSOperation

NSOperation:代表多線程任務。一般不直接使用NSOperation,而是使用NSOperation的子類。或者使用NSInvocationOperation和NSBlockOperation(這兩個類繼承自NSOperation);

1、NSOperation的使用

NSOperation 的使用相較於GCD是面向對象的,OC實現的,而GCD應該是C實現的(看函數的定義和使用)。

使用NSOperation 只需兩步:

1)創建 NSOperationQueue 隊列,並未該隊列設置相關屬性

2)創建 NSOperation 子類對象,並將該對象提交給 NSOperationQueue 隊列,該隊列將會按順序依次啟動每個 NSOperation。

 

2、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

3、使用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 }

 

4、自定義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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM