博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
__bridge 显式转换 id和void *
阅读量:4296 次
发布时间:2019-05-27

本文共 7485 字,大约阅读时间需要 24 分钟。

最近在做项目优化工作,搭建基于CoreText的富文本引擎,遇到了不少问题,将它们记录下来。后续整理。

写法1:

CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)imageInfoDict);

写法2:

    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge_retained void *)imageInfoDict);

imageInfoDict = nil;

写法1是大家通用的常规写法,imageInfoDict创建后,未再做其他操作,但有时,会存在delegate回调 CGFloat widthCallback(void *ref) {}等方法时,ref 已被release 造成野指针问题。

这里采用了写法2 在桥接转换时,做一次retain操作。无需做release操作。

项目紧张,暂时写这些,下面是我整理的相关资料。作为原理性扩展学习。

在ARC 无效时,像以下代码这样将id 变量强制转换void * 变量并不会出问题。

/* ARC 无效 */
id obj = [[NSObject alloc] init];
void *p = obj;

更进一步,将该void * 变量赋值给id 变量中,调用其实例方法,运行时也不会有问题。

/* ARC 无效 */
id o = p;
[o release];

但是在ARC 有效时这便会引起编译错误。

error: implicit conversion of an Objective-C pointer
    to 'void *' is disallowed with ARC
    void *p = obj;
              ^
error: implicit conversion of a non-Objective-C pointer
    type 'void *' to 'id' is disallowed with ARC
    id o = p;
            ^

id 型或对象型变量赋值给void  * 或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,则可以使用“_ _ bridge 转换”。

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

像这样,通过“_ _ bridge 转换”,id 和void * 就能够相互转换。

但是转换为void  * 的_ _ bridge 转换,其安全性与赋值给_ _ unsafe_unretained 修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

_ _ bridge 转换中还有另外两种转换,分别是“_ _ bridge_retained 转换”和 “_ _ bridge_transfer转换”
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

_ _ bridge_retained 转换可使要转换赋值的变量也持有所赋值的对象。下面我们来看ARC 无效时的源代码是如何编写的。

/* ARC 无效 */
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];

_ _ bridge_retained 转换变为了retain。变量obj 和变量p 同时持有对象。再来看几个其他的例子。

void *p = 0;
{
    id obj = [[NSObject alloc] init];
    p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);

变量作用域结束时,虽然随着持有强引用的变量obj 失效,对象随之释放,但由于_ _ bridge_retained 转换使变量p 看上去处于持有该对象的状态,因此该对象不会被废弃。下面我们比较一下ARC 无效时的代码是怎样的。

/* ARC 无效 */
void *p = 0;
{
    id obj = [[NSObject alloc] init];
    /* [obj retainCount] -> 1 */
    p = [obj retain];
    /* [obj retainCount] -> 2 */
    [obj release];
    /* [obj retainCount] -> 1 */
}
/*
 * [(id)p retainCount] -> 1
 * 即
 * [obj retainCount] -> 1
 * 对象仍存在
 */
NSLog(@"class=%@", [(__bridge id)p class]);

_ _ bridge_transfer 转换提供与此相反的动作,被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放。

id obj = (__bridge_transfer id)p;

该源代码在ARC 无效时又如何表述呢?

/* ARC 无效 */
id obj = (id)p;
[obj retain];
[(id)p release];

同_ _ bridge_retained 转换与retain 类似,_ _ bridge_transfer 转换与release 相似。在给id  obj 赋值时retain 即相当于_ _ strong 修饰符的变量。

如果使用以上两种转换,那么不使用id 型或对象型变量也可以生成、持有以及释放对象。

虽然可以这样做,但在ARC 中并不推荐这种方法。使用时还请注意。

void *p = (__bridge_retained void *)[[NSObject alloc] init];
NSLog(@"class=%@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p;

该源代码与ARC 无效时的下列源代码相同。

/* ARC 无效 */
id p = [[NSObject alloc] init];
NSLog(@"class=%@", [p class]);
[p release];

这些转换多数使用在Objective-C 对象与Core Foundation 对象之间的相互变换中。

专栏Objective-C 对象与Core Foundation对象

Core Foundation对象主要使用在用C语言编写的CoreFoundation框架中,并使用引用计数的对象。在ARC无效时,CoreFoundation框架中的retain/release分别是CFRetain/CFRelease。

Core Foundation对象与Objective -C对象的区别很小,不同之处只在于是由哪一个框架(Foundation框架还是CoreFoundation框架)所生成的。无论是由哪种框架生成的对象,一旦生成之后,便能在不同的框架中使用。Foundation框架的API生成并持有的对象可以用CoreFoundation框架的API释放。当然,反过来也是可以的。

因为Core Foundation对象与Objective -C对象没有区别,所以在ARC无效时,只用简单的C语言的转换也能实现互换。另外这种转换不需要使用额外的CPU资源,因此也被称为“免费桥”(Toll-FreeBridge)。

Toll-FreeBridge类一览可参考以下文档。

 To l l - F r e e   B r i d g e d   Ty p e s     h t t p : / / d e v e l o p e r. a p p l e . c o m / l i b r a r y / m a c / d o c u m e n t a t i o n /
CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html以下函数可用于Objective-C 对象与Core  Foundation 对象之间的相互变换,即Toll-Free Bridge 转换。
CFTypeRef CFBridgingRetain(id X) {
    return (__bridge_retained CFTypeRef)X;
}
id CFBridgingRelease(CFTypeRef X) {
    return (__bridge_transfer id)X;
}

我们来看看到底是如何使用的。以下将生成并持有的NSMutableArray 对象作为Core Foundation 对象来处理。

CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init];
    cfObject = CFBridgingRetain(obj);
    CFShow(cfObject);
    printf("retain count = %d\n", CFGetRetainCount(cfObject));
}
printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);

该源代码正常运行后,会输出以下结果。()表示空的数组。

(
)
retain count = 2
retain count after the scope = 1

由此可知,Foundation 框架的API 生成并持有的Objective-C 对象能够作为Core  Foundation对象来使用。也可以通过CFRelease 来释放。当然,也可以使用_ _ bridge_retained 转换来替代CFBridgingRetain。大家可选用自己更熟悉的方法。

CFMutableArrayRef cfObject = (__bridge_retained CFMutableArrayRef)obj;

以下基于CFGetRetainCount 的值来确认对象的所有状况。

CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init];
    /*
     * 变量obj 持有对生成并持有对象的强引用。
     */
    cfObject = CFBridgingRetain(obj);
    /*
     * 通过CFBridgingRetain,
     * 将对象CFRetain,
     * 赋值给变量cfObject。
     */
    CFShow(cfObject);
    printf("retain count = %d\n",CFGetRetainCount(cfObject));
    /*
     * 通过变量obj 的强引用和
     * 通过CFBridgingRetain,
     * 引用计数为2。
     */
}   /*
     * 因为变量obj 超出其作用域,所以其强引用失效,
     * 引用计数为1。
     */
printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);
/*
 * 因为将对象CFRelease,所以其引用计数为0,
 * 故该对象被废弃。
 */

使用_ _ b r i d g e 转换来替代C F B r i d g i n g R e t a i n 或_ _ b r i d g e _ r e t a i n e d 转换时,源代码会变成什么样呢?

CFMutableArrayRef cfObject = NULL;
{
    id obj = [[NSMutableArray alloc] init];
    /*
     * 变量obj 持有对生成并持有对象的强引用。
     */
    cfObject = (__bridge CFMutableArrayRef)obj;
    CFShow(cfObject);
    printf("retain count = %d\n",CFGetRetainCount(cfObject));
    /*
     * 因为__bridge 转换不改变对象的持有状况,
     * 所以只有通过变量obj 的强引用,
     * 引用计数为1。
     */
}   /*
     * 因为变量obj 超出其作用域,
     * 所以其强引用失效,对象得到释放,
     * 无持有者的对象被废弃。
     */
/*
 * 此后对对象的访问出错!(悬垂指针)
 */
printf("retain count after the scope = %d\n", CFGetRetainCount(cfObject));
CFRelease(cfObject);

由此可知,CFBridgingRetain 或者_ _ bridge_retained 转换是不可或缺的。

这次反过来,将使用Core  Foundation 的API 生成并持有对象,将该对象作为SMutableArray

对象来处理。
{
    CFMutableArrayRef cfObject =
        CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    printf("retain count = %d\n", CFGetRetainCount(cfObject));
    id obj = CFBridgingRelease(cfObject);
    printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject));
    NSLog(@"class=%@", obj);
}

由此可知,与之前相反的由Core  Foundation 框架的API 生成并持有的Core  Foundation 对象也能够作为Objective-C 对象来使用。其运行结果如下:

retain count = 1
retain count after the cast = 1

当然也可使用_ _ bridge_transfer 转换替代CFBridgingRelease。

id obj = (__bridge_transfer id)cfObject;

此处也要基于CFGetRetainCount 的值来确认对象的持有状况。

{
    CFMutableArrayRef cfObject =
       CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    printf("retain count = %d\n", CFGetRetainCount(cfObject));
    /*
     * Core Foundation 框架的API 生成并持有对象
     * 之后的对象引用计数为“1”。
     */
    id obj = CFBridgingRelease(cfObject);
    /*
     * 通过CFBridgingRelease 赋值,
     * 变量obj 持有对象强引用的同时
     * 对象通过CFRelease 释放。
     */
    printf("retain count after the cast = %d\n", C FGetRetainCount(cfObject));
    /*
     * 因为只有变量obj
     * 持有对生成并持有对象的强引用,
     * 故引用计数为“1”。
     *
     * 另外,因为经由CFBridgingRelease 转换后,
     * 赋值给变量cfObject 中的指针
     * 也指向仍然存在的对象,
     * 所以可以正常使用。
     */
    NSLog(@"class=%@", obj);
}   /*
     * 因为变量obj 超出其作用域,
     * 所以其强引用失效,对象得到释放,
     * 无所有者的对象随之被废弃。
     */

以下为用_ _ bridge 转换替代CFBridgingRelease 或_ _ bridge_transfer 转换的情形。

{
    CFMutableArrayRef cfObject =
       CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    printf("retain count = %d\n", CFGetRetainCount(cfObject));
    /*
     * Core Foundation 框架生成并持有对象
     * 之后的对象引用计数为“1”。
     */
    id obj = (__bridge id)cfObject;
    /*
     * 因为赋值给附有__strong 修饰符的变量中,
     * 所以发生强引用。
     */
    printf("retain count after the cast = %d\n", CFGetRetainCount(cfObject));
    /*
     * 因为变量obj 持有对象强引用且
     * 对象没有进行CFRelease,
     * 所以引用计数为“2”。
     */
    NSLog(@"class=%@", obj);
}   /*
     * 因为变量obj 超出其作用域,
     * 所以其强引用失效,对象得以释放。
     */
/*
 * 因为引用计数为“1”,所以对象仍然存在。
 * 发生内存泄漏!
 */

因此,必须恰当使用CFBridgingRetain/CFBridgingRelease 或者_ _ bridge_retained/_ _ bridge_transfer 转换。在将Objective-C 变量赋值给C 语言变量,即没有附加所有权修饰符的void  * 等指针型变量时,伴随着一定的风险。在实现代码时要高度重视。

转载地址:http://iudws.baihongyu.com/

你可能感兴趣的文章
MSP430中断原理分析
查看>>
display几点解释
查看>>
详解SPI中的极性CPOL和相位CPHA
查看>>
各类总线传输速率
查看>>
有关分散加载文件scatter的理解 关键是加载域 加载地址与执行域地址的 处理
查看>>
RO,RW,ZI 和scatter file详解(转载)
查看>>
Coap协议学习笔记
查看>>
谈"http get和post的区别"
查看>>
Java虚拟机学习笔记(一)——JVM运行时数据区和常见内存错误
查看>>
Java浅拷贝和深拷贝
查看>>
HTTP协议基础(HTTP 1.1)
查看>>
从字节码看Java中for-each循环(增强for循环)实现原理
查看>>
Java时间类记录
查看>>
[Java] CountDownLatch和ExecutorService的简单使用
查看>>
ArcToolbox的运行结果发布为GP服务
查看>>
Java图片处理方法和工具
查看>>
Java-POI使用
查看>>
Java-IO类记录
查看>>
Oracle设置密码永不过期
查看>>
[Windows-bat] Jeecg-boot一键启动或重启
查看>>