你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 為GCD隊列綁定NSObject類型上下文數據

為GCD隊列綁定NSObject類型上下文數據

編輯:IOS開發基礎

1.jpg

更新

下面評論的好友“@Jim”給了種新的思路,就是在清除context的函數裡面,用“_bridge_transfer”轉換context,把context的內存管理權限重新交給ARC,這樣,就不用顯式調用“CFRelease”了。如下:

void cleanStaff(void *context) {
    //這裡用_bridge_transfer轉換,將內存管理權限交還給ARC
    Data *data = (_bridge_transfer Data *)(context);
    NSLog(@"In clean, context number: %d", data.number);
    //不用顯式釋放context的內存!
}

前言

看過GCD(Grand Central Dispatch)的Apple官方文檔的朋友一定見過“dispatch_set_context”和“dispatch_get_context”這兩個函數,那麼這兩個函數該怎麼用呢??

我們都知道,GCD的接口參數都是“C語言類型“的,那麼,我們如何將NSObject類型(Foundation框架)的數據,傳入GCD的接口呢?(即:Core Foundation和Foundation對象的轉換)

本文關鍵字

  • GCD:dispatch_set_context,dispatch_get_context

  • __bridge,__bridge_retained,__bridge_transfer

  • Core Foundation, NSObject

dispatch_set(get)_context

先看看這兩個函數的原型:

//設置context
void dispatch_set_context ( dispatch_object_t object, void *context );
//獲取context
void * dispatch_get_context ( dispatch_object_t object );

這裡的object一般指的就是通過dispatch_queue_create創建的隊列。?

所以,這兩個函數分別完成了將context“綁定”到特定GCD隊列和從GCD隊列獲取對應context的任務。

什麼是context

在上述函數原型中,context是一個“void類型指針”,學過C語言的朋友應該都知道,void型指針可以指向任意類型,就是說,context在這裡可以是任意類型的指針。

從這裡可以得知,我們可以為隊列“set”任意類型的數據,並在合適的時候取出來用。

用malloc創建context並綁定到隊列上

參考Apple官方的例子,我們先用傳統的malloc創建context,看看如下簡短例子:

//定義context,即一個結構體typedef struct _Data {
    int number;
} Data;//定義隊列的finalizer函數,用於釋放context內存void cleanStaff(void *context) {
    NSLog(@"In clean, context number: %d", ((Data *)context)->number);    //釋放,如果是new出來的對象,就要用delete
    free(context);
}- (void)testBody {    //創建隊列
    dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);    //創建Data類型context數據並初始化
    Data *myData = malloc(sizeof(Data));
    myData->number = 10;    //綁定context
    dispatch_set_context(queue, myData);    //設置finalizer函數,用於在隊列執行完成後釋放對應context內存
    dispatch_set_finalizer_f(queue, cleanStaff);

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

上面的代碼運行後如下:

2015-03-29 20:28:16.854 GCDTest[37787:1443423] 1: context number: 10
2015-03-29 20:28:16.855 GCDTest[37787:1443423] In clean, context number: 20

看,通過為隊列設置context,我們就能為隊列綁定自定義的數據,然後在合適的時候取出來用。

NSObject類型的context

在Mac、iOS的開發過程中,我們大部分用的都是Foundation框架下的類,就是如NSString、NSDictionary這些NSObject類型的類。

但是上面的dispatch_set(get)_context接受的context參數是C語言類型的,即Core Foundation類型的,我們如何轉換呢?

由於ARC不能管理Core Foundation Object的生命周期,所以我們必須先轉換context的“類型”,以便轉換內存管理權。

__bridge

Apple已經為我們提供了用於轉換的關鍵字,如下:

  • __bridge: 只做了類型轉換,不修改內存管理權;

  • __bridge_retained(即CFBridgingRetain)轉換類型,同時將內存管理權從ARC中移除,後面需要使用CFRelease來釋放對象;

  • __bridge_transfer(即CFBridgingRelease)將Core Foundation的對象轉換為Objective-C的對象,同時將內存管理權交給ARC。

重新定義context

為了方便下面的說明,我們先定義context類。

@interface Data : NSObject
@property(assign, nonatomic) int number;
@end

@implementation Data

//繼承dealloc方法,便於觀察對象何時被釋放
- (void)dealloc {
    NSLog(@"Data dealloc...");
}

@end

看,我們繼承了dealloc方法,這樣就能知道Data類型對象什麼時候被釋放。

需要注意的點

__bridge的轉換是沒有轉移內存管理權的,這點要特別注意。?

如果在傳context對象時,用的是__bridge轉換,那麼context對象的內存管理權還在ARC手裡,一旦當前作用域執行完,context就會被釋放,而如果隊列的任務用了context對象,就會造成“EXC_BAD_ACCESS”崩潰!

正確的用法

重寫上面的例子,如下:

//定義隊列的finalizer函數,用於釋放context內存
void cleanStaff(void *context) {
    //這裡用__bridge轉換,不改變內存管理權
    Data *data = (__bridge Data *)(context);
    NSLog(@"In clean, context number: %d", data.number);

    //釋放context的內存!
    CFRelease(context);
}

- (void)testBody {
    //創建隊列
    dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

    //創建Data類型context數據並初始化
    Data *myData = [Data new];
    myData.number = 10;

    //綁定context
    //這裡用__bridge_retained轉換,將context的內存管理權從ARC移除,交由我們自己手動釋放!
    dispatch_set_context(queue, (__bridge_retained void *)(myData));

    //設置finalizer函數,用於在隊列執行完成後釋放對應context內存
    dispatch_set_finalizer_f(queue, cleanStaff);

    dispatch_async(queue, ^{
        //獲取隊列的context數據
        //這裡用__bridge轉換,不改變內存管理權
        Data *data = (__bridge Data *)(dispatch_get_context(queue));
        //打印
        NSLog(@"1: context number: %d", data.number);
        //修改context保存的數據
        data.number = 20;
    });
}

解釋

  • 在dispatch_set_context的時候用__bridge_retained轉換,將context的內存管理權從ARC移除,交給我們自己管理。

  • 在隊列任務中,用dispatch_get_context獲取context的時候,用__bridge轉換,維持context的內存管理權不變,防止出了作用域context被釋放。

  • 最後用CFRelease釋放context內存。

運行結果

2015-03-29 21:12:41.631 GCDTest[38131:1465900] 1: context number: 10
2015-03-29 21:12:41.632 GCDTest[38131:1465900] In clean, context number: 20
2015-03-29 21:12:41.632 GCDTest[38131:1465900] Data dealloc...

由結果可知,我們的context對象在最後顯式調用CFRelease才被釋放。

總結

總的來說,就是合理運用__bridge_retained(transfer)關鍵字轉換對象的內存管理權,讓我們自己控制對象的生命周期。

參考

  • Grand Central Dispatch (GCD) Reference

  • Concurrency Programming Guide

  • Toll-Free Bridged Types

  • Core Foundation 框架

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved