• 为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权-备


    下面评论的好友“@Jim”给了种新的思路,就是在清除context的函数里面,用“_bridge_transfer”转换context,把context的内存管理权限重新交给ARC,这样,就不用显式调用“CFRelease”了。如下:

    1
    2
    3
    4
    5
    6
    7
    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

    先看看这两个函数的原型:

    1
    2
    3
    4
    //设置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,看看如下简短例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //定义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)runTest {
    //创建队列
    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;
    });
    }

    上面的代码运行后如下:

    1
    2
    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类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @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”崩溃!

    正确的用法

    重写上面的例子,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //定义队列的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内存。

    运行结果

    1
    2
    3
    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)关键字转换对象的内存管理权,让我们自己控制对象的生命周期。

  • 相关阅读:
    软件工程--团队作业2
    软件工程——团队作业1
    软件工程第二次作业——四则运算结对编程3.0版本(最终版本)
    软件工程第一次作业补充
    软件工程第一次作业
    实验一
    Qt-关于QTreeView的一些设置
    Qt-QTreeView绘制单元格
    NX二次开发-获取集成环境下打开的part名
    NX二次开发-NX是否处于集成环境下
  • 原文地址:https://www.cnblogs.com/isItOk/p/5300712.html
Copyright © 2020-2023  润新知