更新
下面評論的好友“@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 框架