GCD
Grand Central Dispatch(GCD)是異步執(zhí)行任務(wù)的技術(shù)之一。一般講應(yīng)用程序中記述的線程管理用的代碼在系統(tǒng)級中實(shí)現(xiàn),也就是基于iOS的UNX內(nèi)核。多線程編程會導(dǎo)致很多問題,比如 資源競爭、死鎖和太多線程導(dǎo)致消耗大量內(nèi)存 等。
開發(fā)者要做的只是定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue中
Dispatch Queue
介紹
“Dispatch Queue”就是執(zhí)行處理的等待隊(duì)列,他會按照追加的順序(先進(jìn)先出 FIFO)執(zhí)行處理。
兩種Dispatch Queue
- Serial Dispatch Queue 等待現(xiàn)在執(zhí)行中處理結(jié)束。但是,如果生成多個Serial Dispatch Queue時,各個Serial Dispatch Queue將并行執(zhí)行。
- Concurrent Dispatch Queue 不等待現(xiàn)在執(zhí)行中處理結(jié)束??梢圆⑿袌?zhí)行多個處理,但執(zhí)行的數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài),即iOS和OS X基于Dispatch Queue中的處理數(shù)、CPU核數(shù)、CPU負(fù)載等當(dāng)前的系統(tǒng)狀態(tài)來決定并行執(zhí)行的處理數(shù)。
根據(jù)是否有數(shù)據(jù)競爭來判定使用哪種類型。
得到Dispatch Queue
第一種方法
使用dispatch_queue_create來得到,自己的。
第一個參數(shù)指定Dispatch Queue的名稱,推薦使用逆序全程域名??梢栽O(shè)置為
NULL,但是最好不要,該名稱會在Xcode和Instrument的調(diào)試器中作為Dispatch Queue名稱出現(xiàn),也會出現(xiàn)在應(yīng)用程序崩潰時所產(chǎn)生的CrashLog中。-
第二個參數(shù)執(zhí)行生成Dispatch Queue的類型。生成Serial Dispatch Queue時,一般設(shè)置為
NULL就行了,也可以設(shè)置為DISPATCH_QUEUE_SERIAL。生成Concurrent Dispatch Queue時,設(shè)置為DISPATCH_QUEUE_CONCURRENT。dispatch_queue_t queueSerial = dispatch_queue_create("com.jiayoufang.gcdbenchmark.serialqueue", NULL); dispatch_queue_t queueConcurrent = dispatch_queue_create("com.jiayoufang.gcdbenchmark.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
關(guān)于內(nèi)存:會自動處理
在dispatch_async函數(shù)中追加Block到Dispatch Queue后,Block會通過dispatch_retain 函數(shù)持有Dispatch Queue,所以即使Dispatch Queue被立即釋放,他也不會被銷毀。
第二種方法
獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue
-
Main Dispatch Queue 是在主線程中執(zhí)行的Dispatch Queue,因?yàn)橹骶€程只有一個,所以Main Dispatch Queue自然就是Serial Dispatch Queue。追加到它上面的處理在主線程的Runloop中執(zhí)行,可以處理用戶的界面更新等操作。
dispatch_queue_t queueMain = dispatch_get_main_queue(); -
Global Dispatch Queue 是所有應(yīng)用程序都可使用的Concurrent Dispatch Queue。他有4個執(zhí)行優(yōu)先級,分別是高優(yōu)先級(High Priority)、默認(rèn)優(yōu)先級(Default Priority)、低優(yōu)先級(Low Priority)和后臺優(yōu)先級(Background Priority)。通過XNU內(nèi)核管理的Global Dispatch Queue的線程,將各自使用Global Dispatch Queue的執(zhí)行優(yōu)先級作為線程的執(zhí)行優(yōu)先級,但并不能保證線程的實(shí)時性,因此執(zhí)行優(yōu)先級只是大致判斷。
//獲取默認(rèn)優(yōu)先級的Global Dispatch Queue dispatch_queue_t queueGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
變更生成的Dispatch Queue的執(zhí)行優(yōu)先級
dispatch_queue_create生成的 Dispatch Queue,不管是Serial Dispatch Queue還是 Concurrent Dispatch Queue,都使用與默認(rèn)優(yōu)先級Global Dispatch Queue相同執(zhí)行優(yōu)先級的線程。變更優(yōu)先級使用dispatch_set_target_queue函數(shù)。
例如
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.jiayoufang.queue", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialDispatchQueue, globalQueue);
- 第一個參數(shù)指定要變更優(yōu)先級的Dispatch Queue。不可指定系統(tǒng)提供的 Main Dispatch Queue 和 Global Dispatch Queue。
- 第二個參數(shù)指定與要使用的執(zhí)行優(yōu)先級相同優(yōu)先級的Global Dispatch Queue。
指定時間后執(zhí)行處理
使用dispatch_after來實(shí)現(xiàn)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
});
需要注意的是:dispatch_after并不是在指定時間后執(zhí)行處理,而是在指定時間追加處理到Dispatch Queue。
dispatch_after的底層其實(shí)是用dispatch_source實(shí)現(xiàn)的,不依賴runloop。
dispatch_time_t的創(chuàng)建
-
指定相對時間
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
“ull”是C語言的數(shù)值字面量,是顯式表明類型時使用的字符創(chuàng)(表示“unsigned long long)。
-
指定絕對時間
dispatch_time_t time1 = getDispatchTimeByDate([NSDate dateWithTimeIntervalSinceNow:3]); dispatch_time_t getDispatchTimeByDate(NSDate *date){ NSTimeInterval timeInterval; double second,subsecond; struct timespec time; dispatch_time_t timestone; timeInterval = [date timeIntervalSince1970]; subsecond = modf(timeInterval, &second); time.tv_sec = second; time.tv_nsec = subsecond * NSEC_PER_SEC; timestone = dispatch_walltime(&time, 0); return timestone; }
Dispatch Group
在追加到Dispatch Queue的多個處理全部結(jié)束后想執(zhí)行結(jié)束處理。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"Block0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"Block2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"Done");
});
如果僅僅只是驗(yàn)證是否結(jié)束,也可以使用dispatch_group_wait。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第二個參數(shù)表示等待的時間(超時時間)。他屬于dispatch_time_t的值。dispatch_group_wait是有返回值的,如果返回值不為0,說明雖然經(jīng)過了指定的時間,但是屬于Dispatch Queue的某一個處理還在進(jìn)行中。如果返回值為0,說明全部處理執(zhí)行結(jié)束。
時間指定為DISPATCH_TIME_FOREVER,返回值肯定為0。
指定為DISPATCH_TIME_NOW,不用等待,直接判定屬于Dispatch Group的處理是否執(zhí)行結(jié)束。
需要注意的是,dispatch_group_wait,他會阻塞當(dāng)前線程,如有必要,可以使用dispatch_async將整個方法放入后臺隊(duì)列以避免線程阻塞。
或者使用dispatch_group_notify來觀察Dispatch Group中的任務(wù)是否執(zhí)行完。
多個異步請求判斷都結(jié)束了
參考了葉大神的在這里
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
int i = 0;
while (i < 1000) {
NSLog(@"Group1 : %d",i++);
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
int i = 0;
while (i < 1000) {
NSLog(@"AAAAAA Group : %d",i++);
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"Done");
});
dispatch_barrier_async
可以達(dá)到 等待追加到Concurrent Dispatch Queue上的并行執(zhí)行的處理全部結(jié)束之后,再將指定的處理追加到Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢之后,Concurrent Dispatch Queue再恢復(fù)一般的動作。
dispatch_queue_t queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"A");
});
dispatch_async(queue, ^{
NSLog(@"B");
});
dispatch_async(queue, ^{
NSLog(@"C");
});
dispatch_barrier_async(queue, ^{
NSLog(@"A、B、C 先執(zhí)行完");
});
dispatch_async(queue, ^{
NSLog(@"D");
});
dispatch_async(queue, ^{
NSLog(@"E");
});
dispatch_barrier_async(queue, ^{
NSLog(@"D、E 先執(zhí)行完");
});
dispatch_async(queue, ^{
NSLog(@"F");
});
dispatch_async(queue, ^{
NSLog(@"G");
});
dispatch_apply
該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并會等待全部處理執(zhí)行結(jié)束。
dispatch_queue_t queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index : %zd",index);
});
例如對NSArray類對象的所有元素執(zhí)行處理,不必一個一個編寫for循環(huán)部分。
Note:由于dispatch_apply會和dispatch_sync函數(shù)相同,會等待處理結(jié)束。因此,推薦在dispatch_async函數(shù)中非同步的執(zhí)行dispatch_apply函數(shù)。
線程掛起和恢復(fù)
掛起指定的Dispatch Queue
dispatch_suspend(queue);
恢復(fù)指定的Dispatch Queue
dispatch_resume(queue);
這些函數(shù)對已經(jīng)執(zhí)行過的處理沒有影響。掛起后,尚未執(zhí)行的處理會停止執(zhí)行,恢復(fù)之后這些處理能夠繼續(xù)執(zhí)行。
dispatch_semaphore_t
創(chuàng)建一個信號量。參數(shù)指定信號量的起始值,這個數(shù)字是可以訪問的信號量。需要注意的是,如果初始化為0,那么在使用信號量時必然會被阻塞,也是使用這個方式來解決測試用例時的異步代碼問題。
加鎖的實(shí)現(xiàn)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t sempaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc]init];
for (NSInteger i = 0 ; i < 10000 ; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(sempaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInteger:i]];
dispatch_semaphore_signal(sempaphore);
});
}
在使用Xcode中的 Product/Test 運(yùn)行測試時,測試是在主線程運(yùn)行的,所以可以假設(shè)所有的測試都是串行發(fā)生的。在一個給定的測試方法運(yùn)行完成,XCTest方法將考慮此次測試已結(jié)束,并進(jìn)入下一個測試,這就意味著任何來自目前一個測試的異步代碼會在下一個測試運(yùn)行時繼續(xù)發(fā)生。解決這個問題:
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("com.ivan.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"執(zhí)行了測試");
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 888; i++) {
NSLog(@"********* : %zd",i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, 1);
if(dispatch_semaphore_wait(semaphore, timeoutTime)){
XCTFail("Time out");
}
}
Dispatch Source
GCD 除了主要的Dispatch Queue外,還有Dispatch Source。
實(shí)現(xiàn)一個簡單的定時器,說明用法
- (void)test7{
//實(shí)現(xiàn)一個定時器的例子
__block NSInteger count = 0;
//指定 DISPATCH_SOURCE_TYPE_TIMER,作成 Dispatch Source。在定時器經(jīng)過指定時間時設(shè)定Main Dispatch 為追殲處理的Dispatch Queue。
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//將定時器設(shè)置為2秒后,允許延遲1秒
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
//指定定時器指定時間內(nèi)執(zhí)行的處理
dispatch_source_set_event_handler(timer, ^{
NSLog(@"執(zhí)行");
if (count >= 4) {
NSLog(@"Count >= 4");
//執(zhí)行完,之后,取消Dispatch Source
dispatch_source_cancel(timer);
}else{
NSLog(@"Count < 4");
count++;
}
});
//指定取消Dispatch Source時的處理
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"cancel");
//釋放自身
// dispatch_release(timer);
});
//啟動Dispatch Source
dispatch_resume(timer);
}
另外,dispatch_source_t的本質(zhì)其實(shí)是一個OC對象,不要被騙了,可以打印驗(yàn)證(這是個方法,要記住)
//打印出來的內(nèi)容是類名+內(nèi)存地址
NSLog(@"%@",timer);
如果聲明全局變量,需要強(qiáng)引用
@property(nonatomic,strong) dispatch_source_t timer;
NSOperation
NSOperation是基于GCD實(shí)現(xiàn)的,由于是面向?qū)ο蟮模赡芸粗鴷娣恍?/p>
實(shí)現(xiàn)步驟
- 將要執(zhí)行的操作封裝到一個
NSOperation對象中 - 將
NSOperation對象添加到NSOperationQueue中 - 系統(tǒng)會自動將
NSOperationQueue中的NSOperation取出來
NSOperation說明
NSOperation是一個抽象類,并不具備封裝操作的能力,必須使用它的子類
- NSInvocationOperation
- NSBlockOperation
- 自定義子類繼承
NSOperation來實(shí)現(xiàn)內(nèi)部響應(yīng)的方法
Note:默認(rèn)情況下,調(diào)用了start方法之后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作。只有當(dāng)NSOperation添加到一個NSOperationQueue中,才會異步執(zhí)行操作。但NSBlockOperation只要封裝數(shù)大于1,就會異步執(zhí)行操作。
使用NSInvocationOperation
- (void)testInvocationOperation
{
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation) object:nil];
[op start];
}
- (void)operation{
NSLog(@"---%@",[NSThread currentThread]);
}
最后的打印結(jié)果是: ---<NSThread: 0x7fb5e9707b20>{number = 1, name = main}
說明這樣操作的話他并沒有開啟新的線程。
使用NSBlockOperation
如果只是添加一個block的話,也是不會開啟新的線程來進(jìn)行操作的
- (void)testBlockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
[op start];
}
但是如果添加多個block則會開啟新的線程,但不是一定開啟
- (void)testBlockOperation
{
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"2---%@",[NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"3---%@",[NSThread currentThread]);
}];
[op start];
}
NSOperationQueue
類型:
- 主隊(duì)列
- [NSOperationQueue mainQueue]
- 凡是添加到主隊(duì)列中的任務(wù)(NSOperation),都會放到主線程中執(zhí)行。
- 非主隊(duì)列
- [[NSOperationQueu alloc]init]
- 同時包含了:串行、并發(fā)功能
- 添加到這種隊(duì)列中的任務(wù),就會自動放到子線程中執(zhí)行。
使用suspend屬性來暫停,已經(jīng)啟動的線程是無法停止的,只能停止接下來要執(zhí)行的操作。
但是使用cancelAllOperaion也不會停止,所以在自定義NSOperation的時候,我們可以采取一種方法來增強(qiáng)體驗(yàn),就是沒執(zhí)行完一個長時間的操作,就判定一下是否取消。
#import "CustomOperation.h"
@implementation CustomOperation
- (void)main
{
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"操作1 %zd",i);
}
//就是這樣
if (self.isCancelled) {
return;
}
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"操作2 %zd",i);
}
if (self.isCancelled) {
return;
}
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"操作3 %zd",i);
}
}
@end
依賴和監(jiān)聽
依賴就是執(zhí)行其中一個之后才可以執(zhí)行。
Note : 實(shí)現(xiàn)很簡單,主要是要理解是什么意思,還有就是千萬不要循環(huán)了。但是可以在不同隊(duì)列之間設(shè)置依賴,還是比較牛逼的
監(jiān)聽需要注意的是不一定是在同一條線程中執(zhí)行,但是是在子線程中的
- (void)testDependency
{
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation1");
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation2");
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"operation3");
}];
operation3.completionBlock = ^(){
NSLog(@"監(jiān)聽執(zhí)行完成");
};
[operation3 addDependency:operation1];
[operation2 addDependency:operation3];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
}