玩轉iOS開發:實戰開發中的GCD Tips小技巧 (二)

文章分享至個人我的技術博客: https://cainluo.github.io/15062229631553.htmlhtml


上一篇, 咱們簡單的講了一些使用GCD的小技巧, 若是沒有看的朋友, 能夠去玩轉iOS開發:實戰開發中的GCD Tips小技巧 (一)看.git

此次, 咱們繼續講解小技巧.github

轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.vim


隊列組的靈活使用

一般咱們使用隊列組執行任務的時候是醬紫的:bash

- (void)queueGroup {

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"執行任務, 當前線程爲:%@", [NSThread currentThread]);
    });
}
複製代碼

打印的結果:微信

2017-09-24 11:34:44.766052+0800 GCD-Tips[59653:3481972] 開始執行
2017-09-24 11:34:44.766606+0800 GCD-Tips[59653:3482075] 執行任務, 當前線程爲:<NSThread: 0x604000464980>{number = 3, name = (null)}
複製代碼

但有時候, 咱們會遇到一種狀況, 就是沒有辦法直接使用隊列組變量, 這個時候, 還有另一種方式, 就是dispatch_group_enterdispatch_group_leave, 注意, 這兩個方法是同時出現的:網絡

- (void)gourpEnterAndLeave {
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);

    [self urlRequestSuccess:^{
        
        NSLog(@"網絡請求成功");
        
        dispatch_group_leave(group);
    } failure:^{
        
        NSLog(@"網絡請求失敗");

        dispatch_group_leave(group);
    }];
}

- (void)urlRequestSuccess:(void(^)())success
                  failure:(void(^)())failure {
    
    success();
// failure();
}
複製代碼

打印的結果:併發

2017-09-24 11:46:16.054410+0800 GCD-Tips[60002:3501228] 開始執行
2017-09-24 11:46:16.054721+0800 GCD-Tips[60002:3501228] 網絡請求成功
複製代碼

這樣子, 咱們就能夠把這個網絡請求給打包起來, 但這裏要注意一下, 不能同時調用兩個dispatch_group_leave, 否則就會掛了.app

若是咱們要添加結束任務的話, 能夠有兩種方式:async

  • dispatch_group_notify
    • 當前的隊列組任務執行完畢以後, 就會調用dispatch_group_notify來通知, 任務已經結束.
  • dispatch_group_wait
    • dispatch_group_notify相似, 只不過是在能夠添加延遲結束的時間, 但這裏須要注意一點, dispatch_group_wait會阻塞當前線程, 因此不要在主線程中調用, 否則會阻塞主線程.

dispatch_barrier_(a)sync使用的注意

咱們都知道dispatch_barrier_(a)sync實際上是一個柵欄方法, 它的做用就是在向某個隊列插入一個block, 等到該block執行完以後, 纔會繼續執行其餘隊列, 有點老大的味道.

- (void)queueBarrier {
    
    dispatch_queue_t queue = dispatch_queue_create("queueBarrier", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        
        NSLog(@"執行一, 當前線程:%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        
        NSLog(@"大佬來了, 當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        
        NSLog(@"執行二, 當前線程:%@", [NSThread currentThread]);
    });
}
複製代碼

打印的結果:

2017-09-24 12:44:36.151126+0800 GCD-Tips[61121:3585236] 開始執行
2017-09-24 12:44:36.151579+0800 GCD-Tips[61121:3585334] 執行一, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.152335+0800 GCD-Tips[61121:3585334] 大佬來了, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
2017-09-24 12:44:36.154241+0800 GCD-Tips[61121:3585334] 執行二, 當前線程:<NSThread: 0x600000462e40>{number = 3, name = (null)}
複製代碼

PS: dispatch_barrier_(a)sync只在本身建立的併發隊列的纔會有效, 若是是在全局併發隊列, 串行隊列, dispatch_(a)sync效果是同樣的, 這樣子的話, 就會容易形成線程死鎖, 因此這裏要注意.


dispatch_set_context與dispatch_set_finalizer_f

這裏要補充兩個東西, dispatch_set_contextdispatch_set_finalizer_f.

  • dispatch_set_context: 能夠爲隊列添加上下文數據
  • dispatch_set_finalizer_f: 轉移內存管理權限

這裏要注意一點dispatch_set_context接受的context參數是爲C語言參數, 因此這裏寫的時候, 要注意一下:

typedef struct _Info {
    int age;
} Info;

void cleanStaff(void *context) {
    
    NSLog(@"In clean, context age: %d", ((Info *)context)->age);
    
    //釋放,若是是new出來的對象,就要用delete
    free(context);
}

- (void)setContext {
    
    dispatch_queue_t queue = dispatch_queue_create("contextQueue", DISPATCH_QUEUE_SERIAL);
    
    // 初始化Data對象, 而且設置初始化值
    Info *myData = malloc(sizeof(Info));
    myData->age = 100;
    
    // 綁定Context
    dispatch_set_context(queue, myData);
    
    // 設置finalizer函數,用於在隊列執行完成後釋放對應context內存
    dispatch_set_finalizer_f(queue, cleanStaff);

    dispatch_async(queue, ^{
        
        //獲取隊列的context數據
        Info *data = dispatch_get_context(queue);
        //打印
        NSLog(@"1: context age: %d", data->age);
        //修改context保存的數據
        data->age = 20;
    });
}
複製代碼

打印一下結果:

2017-09-24 14:24:10.394129+0800 GCD-Tips[61881:3652088] 開始執行
2017-09-24 14:24:10.394547+0800 GCD-Tips[61881:3652274] 1: context age: 100
2017-09-24 14:24:10.394738+0800 GCD-Tips[61881:3652274] In clean, context age: 20
複製代碼

PS: 咱們設置了dispatch_set_context記得必定要釋放掉, 否則就會形成內存泄漏.

除了這個以外, 咱們還能夠對Core Foundation進行操做, 那麼該怎麼作呢?


NSObject與dispatch_set_context

這裏咱們要建立一個Model類, 繼承與NSObject:

@interface GCDModel : NSObject

@property (nonatomic, assign) NSInteger age;

@end

@implementation GCDModel

- (void)dealloc {
    
    NSLog(@"%@ 釋放了", NSStringFromClass([self class]));
}

@end
複製代碼

在這個類裏面, 咱們就簡單操做, 只有一個屬性和一個dealloc方法, 具體操做:

void cleanObjectStaff(void *context) {
    
    GCDModel *model = (__bridge GCDModel *)context;
    
    NSLog(@"In clean, context age: %ld", model.age);
    
    // 釋放內存
    CFRelease(context);
}

- (void)objectAndContext {
    
    dispatch_queue_t queue = dispatch_queue_create("objectQueue", DISPATCH_QUEUE_SERIAL);
    
    // 初始化Data對象, 而且設置初始化值
    GCDModel *model = [[GCDModel alloc] init];
    model.age = 20;
    
    // 綁定Context, 這裏使用__bridge關鍵
    dispatch_set_context(queue, (__bridge_retained void *)(model));
    
    // 設置finalizer函數,用於在隊列執行完成後釋放對應context內存
    dispatch_set_finalizer_f(queue, cleanObjectStaff);
    
    dispatch_async(queue, ^{
        
        //獲取隊列的context數據
        GCDModel *model = (__bridge GCDModel *)(dispatch_get_context(queue));
        //打印
        NSLog(@"1: context age: %ld", model.age);
        //修改context保存的數據
        model.age = 120;
    });
}
複製代碼

打印一下結果:

2017-09-24 14:40:34.024509+0800 GCD-Tips[62448:3676807] 開始執行
2017-09-24 14:40:34.024915+0800 GCD-Tips[62448:3676887] 1: context age: 20
2017-09-24 14:40:34.025236+0800 GCD-Tips[62448:3676887] In clean, context age: 120
2017-09-24 14:40:34.025706+0800 GCD-Tips[62448:3676887] GCDModel 釋放了
複製代碼

這裏咱們要解釋一下__bridge關鍵字:

  • __bridge: 只作類型轉換, 不作內存管理權限修改.
  • __bridge_retained: 內存轉換(CFBridgingRetain), 而且把內存管理權限從ARC裏拿到本身手裏, 最後釋放時要用CFRelease來釋放對象.
  • __bridge_transfer: 將Core Foundation轉換成Objective-C對象(CFBridgingRelease), 而且將內存管理的權限交給ARC.

看到這裏應該會有人問, 爲何要把內存管理拿到本身的手裏, 而不是交給ARC?

其實道理很簡單, 若是是ARC管理的話, 一旦它檢測到做用於完了以後, 你的對象就會釋放了.

那麼你就沒法將這個Context添加到隊列當中, 一旦添加就會給你報一個野指針錯誤, 因此咱們爲了確保不會被ARC給釋放掉, 咱們就須要本身去操做了.

而上面那段代碼的解釋也很簡單:

  • dispatch_set_context時候用__bridge_retained進行轉換, 將Context的內存管理權限拿到咱們本身手上進行管理.
  • 在隊列任務的中, 咱們用dispatch_get_context來獲取context的時候用關鍵字__bridge進行轉換, 這樣子能夠維持context的內存管理權不變, 防止出了做用域Context就會被釋放掉.
  • 最後用CFRelease來釋放掉context, 這樣子就能夠保證內存獲得釋放, 不會形成內存泄漏的問題.

總結

好了, 額外補充的GCD小技巧到這裏就差很少了, 若是之後還有更多的小技巧也會繼續更新, 歡迎各位小夥伴們和我分享其餘的使用技巧~~

這裏推薦幾篇文章:

Grand Central Dispatch (GCD) Reference Concurrency Programming Guide Toll-Free Bridged Types


工程地址

項目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/GCD-Tips/GCD-Tips-Two


最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索