首页 > 代码库 > id 与void *类型的转换(转)

id 与void *类型的转换(转)

在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  * 等指针型变量时,伴随着一定的风险。在实现代码时要高度重视。