異步實(shí)現(xiàn)的記錄總結(jié)

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];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容