iOS-多線程

iOS開發中常用的幾種多線程方案,簡單做個小結,方便日后查閱。

  • NSThead
  • GCD
  • NSOperation & NSOpeartionQueue
  • Pthreads

這種方式不用介紹(我也不太會使用),一般ios開發里也用不上,這是在很多操作系統中都通用的。使用方法大概如下:
#import
創建線程并執行任務:

void *run(void *data){    
for (int i = 0; i<1000; i++) {        
NSLog(@"touchesBegan:%d-----%@", i, [NSThread currentThread]);    }    
return NULL;}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    // 創建線程    pthread_t myRestrict;   
 pthread_create(&myRestrict, NULL, run, NULL);
}

通過log可以看到:

2016-03-08 18:27:04.936 testPthread[4859:1637201] touchesBegan:0-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:1-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:2-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:3-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:4-----{number = 3, name = (null)}

這種方式雖然創建了一個線程,但并沒有銷毀,我們需要手動管理線程的生命周期。隨意感受一下就好了。。。
NSThead
優點:NSThread 比其他幾個輕量級。
缺點:需要自己管理線程的生命周期,線程同步。線程同步對數據的加鎖會有一定的系統開銷。
創建和啟動線程的3種方式:
1.先創建后啟動:

// 創建線程NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(download:) object:@"http://b.png"];
thread.name=@"下載線程";
// 啟動線程(調用self的download方法)
[thread start];

selector :線程執行的方法,這個selector只能有一個參數,而且不能有返回值。

target :selector消息發送的對象

argument:傳輸給target的唯一參數,也可以是nil

2.創建并自動啟動線程:

[NSThreaddetachNewThreadSelector:@selector(download:) toTarget:selfwithObject:@"http://a.jpg"];
3.使用NSObject方法創建線程并自動啟動:
[selfperformSelectorInBackground:@selector(download:)withObject:@"http://c.gif"];

其他常見方法:

//獲得當前線程+(NSThread*)currentThread;
//獲得主線程+(NSThread*)mainThread;
//睡眠(暫停)線程+(void)sleepUntilDate:(NSDate*)date;    +(void)sleepForTimeInterval:(NSTimeInterval)ti;
//設置和獲取線程名稱-(void)setName:(NSString*)n;   
-(NSString*)name;
GCD

Grand Central Dispatch 簡稱(GCD)是蘋果公司開發的技術,以優化的應用程序支持多核心處理器和其他的對稱多處理系統的系統。這建立在任務并行執行的線程池模式的基礎上的。它首次發布在Mac OS X 10.6 ,iOS 4及以上也可用。GCD的工作原理是:讓程序平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創建線程、調度任務、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。
任務和隊列
任務:
需要執行什么操作,一個任務可以是一個函數(function)或者是一個block。任務有兩種執行方式:同步執行和異步執行,它們區別在于是否會阻塞當前線程,直到任務執行完。如果是 同步(sync) 操作,它會阻塞當前線程并等待 Block 中的任務執行完畢,然后當前線程才會繼續往下運行。如果是 異步(async)操作,當前線程會直接往下執行,它不會阻塞當前線程。
創建任務方式:
同步任務:會阻塞當前線程,并且不具備開啟新線程的能力。
dispatch_sync(<#queue#>, ^{//code here});
異步任務:不會阻塞當前線程,并且具有開啟新線程的能力。
dispatch_async(<#queue#>, ^{//code here});
隊列:
存放任務。有串行隊列和并行隊列兩種。
串行隊列:GCD中的FIFO隊列稱為dispatch queue,它可以保證先進來的任務先得到執行,然后再取下一個,一個接著一個執行。
并行隊列:放到并行隊列的任務可以并發執行。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。
創建隊列方式
主隊列:這是一個特殊的串行隊列,主要用于刷新UI界面,處理UI控件的事件。如下:
dispatch_queue_t queue = dispatch_get_main_queue();
自建創建的隊列:自己可以創建串行隊列,也可以創建 并行隊列。它有兩個參數:
第一個參數:標識符,用于debug的時候標識唯一的隊列。
第二個參數:用來表示創建的隊列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL或NULL表示創建串行隊列。傳入DISPATCH_QUEUE_CONCURRENT表示創建并行隊列。如下:

//串行隊列dispatch_queue_tqueue=dispatch_queue_create("serial.testQueue",NULL);
//并發隊列dispatch_queue_tqueue= dispatch_queue_create("serial.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊列dispatch_queue_tqueue= dispatch_queue_create("concurrent.testQueue", DISPATCH_QUEUE_CONCURRENT);```

**全局并發隊列:**可以讓任務并發執行,只要是并行任務一般都加入到這個隊列中。
`dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);`
下面的例子能更好的理解同步和異步和各種隊列的使用:
例1:
```objc
//sync -- 主隊列(不能用---會卡死)
-(void)testSyncMainQueue{   
 NSLog(@"download之前----%@",[NSThreadcurrentThread]);    
// 1.主隊列(添加到主隊列中的任務,都會自動放到主線程中去執行)    
dispatch_queue_tqueue = dispatch_get_main_queue();    
// 2.異步執行   
 dispatch_sync(queue, ^
{NSLog(@"-----download1---%@", [NSThreadcurrentThread]);        });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]);    });
NSLog(@"download之后------");
}```
打印結果:
>2016-03-09 10:37:47.450 testGCD[5725:2260551] download之前----{number = 1, name = main}
只打印了第一句后主線程就被卡死了,什么界面操作都做不了。這是因為dispatch_sync同步任務會阻塞當前的主線程,然后把block中的任務放到主隊列queue當中,可是添加到主隊列中的任務,都會自動放到主線程中去執行,但是主線程已被阻塞,所以block中download image無法執行,dispatch_sync就會一直阻塞主線程,造成死鎖。

例2:
```objc
- (void)testAsyncSerialQueue{
// 1.創建一個串行隊列
dispatch_queue_tqueue = dispatch_queue_create("testAsync.SerialQueue",NULL);
NSLog(@"download之前----%@",[NSThreadcurrentThread]);
// 2.異步執行dispatch_async(queue, ^{NSLog(@"sync download之前----%@",[NSThreadcurrentThread]);dispatch_sync(queue, ^{NSLog(@"sync download ----%@",[NSThreadcurrentThread]);
        });
NSLog(@"sync download 之后----%@",[NSThreadcurrentThread]);      });
dispatch_async(queue, ^{NSLog(@"async download2----%@",[NSThreadcurrentThread]);    });
dispatch_async(queue, ^{NSLog(@"async download3----%@",[NSThreadcurrentThread]);    });
NSLog(@"async download 之后----%@",[NSThreadcurrentThread]);}```

>打印結果:
2016-03-09 11:00:27.419 testGCD[5876:2284715] download之前----{number = 1, name = main}
2016-03-09 11:00:27.420 testGCD[5876:2284748] sync download之前----{number = 2, name = (null)}
2016-03-09 11:00:27.420 testGCD[5876:2284715] async download 之后----{number = 1, name = main}

可以看到sync download----和sync download之后----還有async download2----和async download3----這幾個沒有被打印出來。這是為啥?
分析:
首先必須明白,dispatch_queue_create創建一個新隊列queue, 參數DISPATCH_QUEUE_SERIAL或NULL表示創建的這是一個串行隊列。
接著打印download之前----正常;
dispatch_async異步執行,當前線程不會被阻塞,且具備開啟新線程能力,所以同時有了兩條線程。一條當前線程打印出了async download 之后----,正常。另外一條線程執行block里的任務,打印sync download image之前----,也是正常。因為這兩條線程是并行,所以并不會相互影響,順序前后也不一定。
之后dispatch_sync同步執行,同步任務會阻塞當前所在的線程,直到sync里的任務執行完畢后才會繼續往下。然后與例1相似,sync把block中的任務放到隊列queue當中,可是queue是一個串行隊列,串行隊列是任務一個接著一個執行,一次只能執行一個任務,所以sycn的block里的任務就必須等到前一個任務執行完畢,可是,前一個正在執行的任務正是被sync阻塞的那個。于是這里又出現了死鎖現象。sycn所在的線程就被卡死了。sync download----和sync download之后----這兩句代碼也就不會被打印出來了。明白不?后面的兩個dispatch_async任務同樣道理。
例3:
```objc
-(void)testAsyncGlobalQueue{
// 并發隊列
dispatch_queue_tqueue      =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//異步 執行
dispatch_async(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]);        });
dispatch_async(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]);      });
dispatch_async(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]);      });
dispatch_async(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]);      });
dispatch_async(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]);    });}```
>打印結果:
2016-03-09 11:49:44.439 testGCD[6039:2341953] -----download5---{number = 6, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341800] -----download4---{number = 5, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341755] -----download2---{number = 2, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341756] -----download1---{number = 3, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341799] -----download3---{number = 4, name = (null)}

從這個結果可以看出,異步執行任務,一般同時開啟多條線程,且彼此之間不會相互影響,并發執行,哪條線程先后執行都不一定。
例4:
```objc
-(void)testSyncGlobalQueue{
// 全局并發隊列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 同步執行dispatch_sync(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]);    });
dispatch_sync(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]);      });}```

>打印結果:
2016-03-09 12:07:51.493 testGCD[6150:2359827] -----download1---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download2---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download3---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download4---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download5---{number = 1, name = main}

可以看出,同步任務不會創建新的線程,串行執行一次只能執行一個任務,一個接著一個,按順序執行。并且這個全局并發隊列失去了并發功能。
例5:
```objc
-(void)testSyncSerialQueue{
//串行隊列
dispatch_queue_tqueue = dispatch_queue_create("testSync.SerialQueue",NULL);
//同步執行
dispatch_sync(queue, ^{
NSLog(@"-----download1---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download2---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download3---%@", [NSThreadcurrentThread]);      });
dispatch_sync(queue, ^{
NSLog(@"-----download4---%@", [NSThreadcurrentThread]);        });
dispatch_sync(queue, ^{
NSLog(@"-----download5---%@", [NSThreadcurrentThread]);     }); }```
>打印結果:
2016-03-09 12:20:13.188 testGCD[6210:2373033] -----download1---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download2---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download3---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download4---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download5---{number = 1, name = main}

可以看出,同步任務不會創建新的線程,串行隊列執行一次只能執行一個任務,一個接著一個,按順序執行。
隊列組
dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作。這個方法很有用,比如你執行兩個下載任務,當兩個任務都下載完成后你才通知界面說完成的了。
```objc
-(void)testGCDQueueGroup{      {
// 1.隊列組
dispatch_group_t group = dispatch_group_create();
// 創建隊列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 2.使用隊列組的異步方法,下載庫里的圖片
__blockUIImage*image1 =nil;   
dispatch_group_async(group, queue, ^{NSURL*url1 = [NSURLURLWithString:@"http://img5.imgtn.bdimg.com/it/u=824940461,4099510846&fm=21&gp=0.jpg"];
NSData*data1 = [NSDatadataWithContentsOfURL:url1];   
image1 = [UIImageimageWithData:data1];        });
// 3.使用隊列組的異步方法,下載圖片
__blockUIImage*image2 =nil;       
dispatch_group_async(group, queue, ^{NSURL*url2 = [NSURLURLWithString:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
NSData*data2 = [NSDatadataWithContentsOfURL:url2]; 
image2 = [UIImageimageWithData:data2];    });
// 4.合并圖片dispatch_group_notify(group, queue, ^{
// 開啟一個位圖上下文UIGraphicsBeginImageContextWithOptions(image1.size,NO,0.0);
// 繪制第1張圖片CGFloatimage1W = image1.size.width;
CGFloatimage1H = image1.size.height;  
 [image1 drawInRect:CGRectMake(0,0, image1W, image1H)];
// 繪制第2張圖片CGFloatimage2W = image2.size.width*0.5;
CGFloatimage2H = image2.size.height*0.5;CGFloatimage2Y = image1H - image2H; 
[image2 drawInRect:CGRectMake(0, image2Y, image2W, image2H)];
// 得到上下文中的圖片
UIImage*fullImage =UIGraphicsGetImageFromCurrentImageContext();
// 結束上下文
UIGraphicsEndImageContext();
// 5.回到主線程顯示圖片
dispatch_async(dispatch_get_main_queue(), ^{self.imageView.image= fullImage;          
      }); 
  });  
}}```

這個例子就是一個簡單的使用方法,在隊列里分別下載兩張圖片,等兩張圖片下載完成后,合并成一張圖片。保證執行完組里面的所有任務之后,再執行notify函數里面的block。最后再回到主線程,更新界面并顯示出來。
還有幾點從其他文章看到,貼一下:
dispatch_barrier_async(queue, ^{ });這個方法重點是你傳入的 queue,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數自己創建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執行完成后才會開始執行自己,自己執行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務繼續執行。如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了。 例子代碼如下:

```objc
-(void)testGCDBarrierAsync{
NSLog(@"begin ---%@",[NSThreadcurrentThread]);
dispatch_queue_tqueue = dispatch_queue_create("testGCD.BarrierAsync", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{   
 [NSThreadsleepForTimeInterval:2];
NSLog(@"dispatch_async1");});
dispatch_async(queue, ^{   
 [NSThreadsleepForTimeInterval:5];
NSLog(@"dispatch_async2");});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");  
[NSThreadsleepForTimeInterval:5];});
dispatch_async(queue, ^{   
 [NSThreadsleepForTimeInterval:1];
NSLog(@"dispatch_async3");  });}```

打印結果:
>2016-03-09 14:42:14.239 testGCD[7320:2534975] begin ---{number = 1, name = main}
2016-03-09 14:42:16.244 testGCD[7320:2535015] dispatch_async1
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_async2
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_barrier_async
2016-03-09 14:42:25.247 testGCD[7320:2535016] dispatch_async3

請注意執行的時間,可以看到dispatch_barrier_async是在前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行。
dispatch_barrier_sync(queue, ^{ });這個方法的使用和上一個一樣,傳入 自定義的并發隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程。如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。例子如下:

```objc
-(void)testGCDBarrierSync{
NSLog(@"begin ---%@",[NSThreadcurrentThread]);
dispatch_queue_tqueue = dispatch_queue_create("testGCD.BarrierSync", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{   
 [NSThreadsleepForTimeInterval:2];
NSLog(@"dispatch_Sync1");});
dispatch_sync(queue, ^{  
 [NSThreadsleepForTimeInterval:5];
NSLog(@"dispatch_Sync2");});
dispatch_barrier_sync(queue, ^{
NSLog(@"dispatch_barrier_Sync");   
 [NSThreadsleepForTimeInterval:5];});
dispatch_sync(queue, ^{   
 [NSThreadsleepForTimeInterval:3];
NSLog(@"dispatch_Sync3");});}
>打印結果:
2016-03-09 14:58:10.146 testGCD[7449:2557946] begin ---{number = 1, name = main}
2016-03-09 14:58:12.151 testGCD[7449:2557946] dispatch_Sync1
2016-03-09 14:58:17.153 testGCD[7449:2557946] dispatch_Sync2
2016-03-09 14:58:17.154 testGCD[7449:2557946] dispatch_barrier_Sync
2016-03-09 14:58:25.157 testGCD[7449:2557946] dispatch_Sync3

從這個打印結果看不出什么,囧。。。但是此時如果UI上有什么交互的話,就不行了。自己可以另行實驗。

`dispatch_apply(times, queue, ^(size_t index) { });`
表現得就像一個 for 循環,但它能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。 簡單例子如下:

```objc
-(void)testGCDForApply{
dispatch_queue_tqueue=  
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);        
dispatch_apply(5,queue, ^(size_tindex) {
// 執行5次NSLog(@"testGCDForApply");    
  });   
 }```
>打印出來的結果如下:
2016-03-09 15:15:01.340 testGCD[7593:2586389] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586391] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2584865] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586252] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586390] testGCDForApply

其他用法
**延遲執行:**所謂延遲執行就是延時一段時間再執行某段代碼。下面是一些常用方法:
```objc
1.[NSThread sleepForTimeInterval:3];
雖然這種方法能起到延遲執行的作用,但別用,因為會卡住當前線程。
2.[self performSelector:@selector(download:) withObject:@"http://abc.jpg" afterDelay:5];
5秒后自動調用download:方法,并且傳遞參數。
3.NSTimer: 也能延遲執行。`
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(download:) userInfo:@"http://abc.jpg" repeats:NO];`
4.GCD中的dispatch_after
dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);doubledelay =3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),queue, ^{
NSLog(@"------download------%@", [NSThread currentThread]);
});```

**線程安全**:為了防止多個線程搶奪同一個資源造成的數據安全問題。線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。也有兩種實現方法:
互斥鎖:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。
//()小括號里面放的是鎖對象@synchronized(self) {//code here}
2.同步執行:把多個線程需要執行的代碼添加到同一個串行隊列中,由于串行隊列一次只能執行一個任務,所以也能實現線程同步的作用。
```objc
- (void)saleTicket2{
while(1) {
dispatch_sync(ticketQueue, ^{
NSIntegercount =self.leftTicketCount;if(count >0)
 {  
[NSThreadsleepForTimeInterval:0.05];
self.leftTicketCount=self.leftTicketCount= count -1;
NSLog(@"線程名:%@,售出1,剩余票數是:%ld,",
[[NSThreadcurrentThread] name],(long)self.leftTicketCount);
}
else
{return; 
} 
});
}}```

GCD還有一些其他用法,比如創建單例:
**單例模式:**關于單例,有三件事是你必須要記住的:
單例必須是唯一的,所以它才被稱為單例。在一個應用程序的生命周期里,有且只有一個實例存在。單例的存在給我們提供了一個唯一的全局狀態。比如我們熟悉的NSNotification,UIApplication和NSUserDefaults都是單例。
為了保持一個單例的唯一性,單例的構造器必須是私有的。這防止其他對象也能創建出單例類的實例。感謝所有幫我指出這點的人。
為了確保單例在應用程序的整個生命周期是唯一的,它就必須是線程安全的。當你一想到并發肯定一陣惡心,簡單來說,如果你寫單例的方式是錯誤的,就有可能會有兩個線程嘗試在同一時間初始化同一個單例,這樣你就有潛在的風險得到兩個不同的單例。這就意味著我們需要用GCD的dispatch_once來確保初始化單例的代碼在運行時只執行一次。
```objc
@interfaceLogin:NSObject
+(instancetype)sharedLogin;
@end
@implementationLoginstaticid_instance;
+(instancetype)sharedLogin {
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{ 
 _instance = [[Login alloc] init];
});
return_instance;
}
@end```
關于GCD目前就想到這些,寫到這。
NSOperation 和 NSOperationQueue
NSOperation
iOS并發編程中,把每個并發任務定義為一個Operation,對應的類名是NSOperation。對應GCD中的任務。NSOperation是一個抽象類,無法直接使用,它只定義了Operation的一些基本方法。我們需要創建一個繼承于它的子類或者使用系統預定義的子類。目前系統預定義了兩個子類:NSInvocationOperation和NSBlockOperation。創建一個Operation后需要調用start來啟動任務,它會默認在當前隊列中同步執行。
NSInvocationOperation
NSInvocationOperation 是一個基于對象和selector的operation,使用這個只需要指定對象以及任務的selector,還可以設定傳遞的參數對象。
```objc
// 創建操作
NSInvocationOperation*operation = [[NSInvocationOperationalloc] initWithTarget:selfselector:@selector(download) object:nil];
// 開始執行
operation直接調用start
是同步執行(在當前線程執行操作)
[operation start];
同時當這個operation執行完成后,還可以獲取operation中Invocation執行后返回的結果對象:
idresult = [operation result];
NSBlockOperation```

**運行一個Operation**
在一個Block中執行一個任務,這時我們就需要用到NSBlockOperation。可以通過blockOperationWithBlock:方法來方便地創建一個NSBlockOperation:
//創建NSBlockOperation對象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{
NSLog(@"---download---%@", [NSThreadcurrentThread]);  }];
//開始執行任務[operation start];
start方法用來啟動一個Operation任務,這個operation默認會在當前線程中執行。
NSBlockOperation還有一個方法:
addExecutionBlock,通過這個方法可以給operation添加多個執行block。這樣 Operation 中的任務會**并發執行**,它會**在主線程和其它的多個線程**執行這些任務:
//創建NSBlockOperation對象
NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]);  }];
//添加多個Block
for(NSIntegeri =0; i <6; i++) {      [operation addExecutionBlock:^{NSLog(@"---download%ld:%@", i, [NSThreadcurrentThread]);      }];  }
//開始執行任務
[operation start];
>注意這打印結果:
2016-03-10 12:13:17.269 TestOperation[9900:3589128] ---download1:{number = 5, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589136] ---download4:{number = 7, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589091] operation---{number = 1, name = main}
2016-03-10 12:13:17.269 TestOperation[9900:3589129] ---download0:{number = 2, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589137] ---download3:{number = 4, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589135] ---download5:{number = 6, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589132] ---download2:{number = 3, name = (null)}

**取消Operation**
要取消一個Operation,要向Operation對象發送cancel消息:
[operation cancel];
當向一個Operation對象發送cancel消息后,并不保證這個Operation對象一定能立刻取消,這取決于你的main中對cancel的處理。如果你在main方法中沒有對cancel進行任何處理的話,發送cancel消息是沒有任何效果的。為了讓Operation響應cancel消息,那么你就要在main方法中一些適當的地方手動的判斷isCancelled屬性,如果返回YES的話,應釋放相關資源并立刻停止繼續執行。
自定義Operation
自定義Operation 需要繼承NSOperation,并實現Operation提供的main方法,你的所有任務都應該在main中進行處理。默認的start方法中會先做出一些異常判斷然后直接調用main方法。如果需要自定義一個NSOperation必須重載main方法來執行你所想要執行的任務。
@implementationCustomOperation
-(void)main {@try{// Do some work.}@catch(...) {// Exception handle.}  }
@end
NSOperationQueue
NSOperationQueue是一個Operation執行隊列,你可以將任何你想要執行的Operation添加到Operation Queue中,以在隊列中執行。Operation Queue一共有兩種類型:主隊列和其他隊列,只要添加到隊列中,會自動調用start()方法執行任務,無需再手動添加。
主隊列
創建主隊列:
// 主隊列NSOperationQueue*queue = [NSOperationQueuemainQueue];
其他隊列
其他隊列的任務會在其他線程中并行執行。
//創建NSBlockOperation對象NSBlockOperation*operation = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"operation---%@", [NSThreadcurrentThread]);}];
//添加多個Blockfor(NSIntegeri =0; i <6; i++) {  [operation addExecutionBlock:^{NSLog(@"---download%ld:%@", i, [NSThreadcurrentThread]);  }];}
//開始任務,用NSOperationQueue無需手動start方法
// [operation start];// 創建隊列NSOperationQueue*queue = [[NSOperationQueuealloc] init];
// 添加任務到隊列中(自動異步執行)[queue addOperation:operation];
>打印結果:
2016-03-10 14:06:03.208 TestOperation[10144:3718109] ---download1:{number = 8, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718203] ---download0:{number = 7, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718107] operation---{number = 2, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718240] ---download4:{number = 5, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718239] ---download3:{number = 4, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718241] ---download5:{number = 6, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718204] ---download2:{number = 3, name = (null)}

NSOperationQueue還有一個添加任務的方法addOperationWithBlock
NSOperationQueue*queue = [[NSOperationQueuealloc] init];[queue addOperationWithBlock:^{
// 異步下載圖片NSURL*url = [NSURLURLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];NSData*data = [NSDatadataWithContentsOfURL:url];UIImage*image = [UIImageimageWithData:data];// 回到主線程,顯示圖片[[NSOperationQueuemainQueue] addOperationWithBlock:^{self.imageView.image= image;    }];}];
**最大并發Operation數目**
在一個Operation Queue中是可以同時執行多個Operation的,Operation Queue會動態的創建多個線程來完成相應Operation。具體的線程數是由Operation Queue來優化配置的,這一般取決與系統CPU的性能,比如CPU的核心數,和CPU的負載。但我們還是可以設置一個最大并發數的,那么Operation Queue就不會創建超過最大并發數量的線程。當設置最大并發數位1時,隊列中每次只能執行一個任務,這就是一個串行執行隊列了。
NSOperationQueue *queue= [[NSOperationQueue alloc] init];queue.maxConcurrentOperationCount =1;
**Operation的依賴關系**
有時候我們對任務的執行順序有要求,一個任務必須在另一個任務執行之前完成,這就需要用到Operation的依賴(Dependency)屬性。我們可以為每個Operation設定一些依賴的另外一些Operation,那么如果依賴的Operation沒有全部執行完畢,這個Operation就不會被執行。比如有個操作C依賴操作B,操作b又依賴操作A:
// 1.創建一個隊列(非主隊列)NSOperationQueue*queue = [[NSOperationQueuealloc] init];// 2.創建3個操作NSBlockOperation*operationC = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作C---%@", [NSThreadcurrentThread]);}];NSBlockOperation*operationA = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作A---%@", [NSThreadcurrentThread]);}];NSBlockOperation*operationB = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"---操作B---%@", [NSThreadcurrentThread]);}];// 設置依賴[operationB addDependency:operationA];[operationC addDependency:operationB];// 3.添加操作到隊列中(自動異步執行任務)[queue addOperation:operationA];[queue addOperation:operationB];[queue addOperation:operationC];
打印輸出:
2016-03-10 14:52:01.518 TestOperation[10812:3782938] ---操作A---{number = 2, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作B---{number = 3, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作C---{number = 3, name = (null)}
如果將這些operation和它所依賴的operation加入某個隊列中,那么這些operation只有在它所依賴的operation都執行完畢后才可以被執行。注意:
不能相互添加依賴,比如operationA依賴operationB,operationB 又依賴operationA,這會造成死鎖。
可以在不同隊列之間添加依賴。
可以使用removeDependency解除依賴。
**Operation在隊列中執行的優先級**
Operation在隊列中默認是按FIFO(First In First Out)順序執行的。同時我們可以為單個的Operation設置一個執行的優先級queuePriority,打亂這個順序。當Queue有空閑資源執行新的Operation時,會優先執行當前隊列中優先級最高的待執行Operation。
我從網上copy了一小段代碼來展示一下隊列優先級效果:
NSBlockOperation*operation1 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation1 begin------");    sleep(5);NSLog(@"------operation1 end------");}];operation1.queuePriority=NSOperationQueuePriorityHigh;NSBlockOperation*operation2 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation2 begin------");    sleep(1);NSLog(@"------operation2 end------");}];NSBlockOperation*operation3 = [NSBlockOperationblockOperationWithBlock:^{NSLog(@"------operation3 begin------");    sleep(2);NSLog(@"------operation3 end------");}];operation2.completionBlock= ^{NSLog(@"------operation2 finished in completionBlock------");};NSOperationQueue*queue = [[NSOperationQueuealloc] init];queue.maxConcurrentOperationCount=1;[queue addOperation:operation2];[queue addOperation:operation3];[queue addOperation:operation1];[queue waitUntilAllOperationsAreFinished];
設置最大并發數為1,以便任務能一個一個的執行。打印輸出:
2016-03-10 15:26:11.790 TestOperation[11068:3829921] -----test-----{number = 1, name = main}
2016-03-10 15:26:11.791 TestOperation[11068:3829995] ------operation2 begin------
2016-03-10 15:26:12.864 TestOperation[11068:3829995] ------operation2 end------
2016-03-10 15:26:12.865 TestOperation[11068:3830132] ------operation2 finished in completionBlock------
2016-03-10 15:26:12.865 TestOperation[11068:3829996] ------operation1 begin------
2016-03-10 15:26:17.934 TestOperation[11068:3829996] ------operation1 end------
2016-03-10 15:26:17.934 TestOperation[11068:3830132] ------operation3 begin------
2016-03-10 15:26:20.006 TestOperation[11068:3830132] ------operation3 end------
從這個輸出結果可以看出,operation2執行完畢后會執行operation1,而不是operation3,這是因為operation1的優先級是NSOperationQueuePriorityHigh。這里還要注意點一就是,**第一個加入到Operation Queue中的Operation,無論它的優先級有多么低,總是會第一個執行**。
**設置Operation的completionBlock**
每個Operation都可以設置一個completionBlock,在Operation執行完成時自動執行這個Block。我們可以在此進行一些完成的處理。completionBlock實現原理是對Operation的isFinnshed字段進行KVO(Key-Value Observing),當監聽到isFinnished變成YES時,就執行completionBlock。上面的小段代碼可以看出operation2的block執行完畢后立刻執行completionBlock。
其他方法
**設置Operation的線程優先級**:我們可以為Operation設置一個線程優先級,即threadPriority。那么執行main的時候,線程優先級就會調整到所設置的線程優先級。這個默認值是0.5,我們可以在Operation執行前修改它。
operation.threadPriority = 0.1;
注意:如果你重載的start方法,那么你需要自己來配置main執行時的線程優先級和threadPriority字段保持一致。
NSOperation
//判斷任務是否正在執行BOOLexecuting;//判斷任務是否完成BOOLfinished;//取消任務-(void)cancel;//阻塞當前線程直到此任務執行結束-(void)waitUntilFinished//是否異步執行任務BOOLasynchronous
NSOperationQueue
//獲取隊列任務數NSUIntegeroperationCount//暫停或是取消任務BOOLsuspended;//取消所有任務- (void)cancelAllOperations;//阻塞當前線程直到隊列中的所有任務執行結束- (void)waitUntilAllOperationsAreFinished;
在最后再看看有哪些方式可以**從其他線程回到主線程:**
NSThead
[selfperformSelectorOnMainThread:@selector(run) withObject:nilwaitUntilDone:NO];
GCD
dispatch_async(dispatch_get_main_queue(), ^{});
NSOperationQueue
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
}];
關于多線程編程,還有其他很多功能,這里所寫的只是自己平時比較常用的。具體可以查看[官方文檔](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/doc/uid/TP40008079),或是其他[資料](http://www.cocoachina.com/industry/20140515/8433.html)。本篇GCD的代碼例子可以到[github](https://github.com/acqiang/TestGCD)上查看,NSOperation的代碼在[這里](https://github.com/acqiang/TestOperation)
最后聲明:
本文所記載的東西都是來自網上資料或是官方文檔,如有侵權,請及時告訴我,但所有代碼我都親自測試了一遍,知識水平有限,如果有錯可隨時指證,謝謝!!!希望站在巨人的肩膀上看得更遠一些。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,065評論 6 18
  • 在這篇文章中,我將為你整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案...
    張戰威ican閱讀 611評論 0 0
  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 393評論 0 0
  • 一、前言 本篇博文介紹的是iOS中常用的幾個多線程技術: NSThread GCD NSOperation 由于a...
    和玨貓閱讀 584評論 0 1
  • 前言 設計模式是軟件工程中一些問題的統一解決方案的模型,它的出現是為了解決一些普遍存在的,卻不能被語言特性直接解決...
    java部落閱讀 466評論 0 1