8

I am using a third-party library that requires a C callback in an iOS program. The callback provides a void* userdata parameter that I can use as I wish. In this callback I want to create an NSData object that is returned to the code that invokes the API that eventually calls the callback. For example:

// C callback:
static void callback(int x, void* userdata)
{
    if (userdata)
    {
        // This gives an error:
        // Pointer to non-const type 'NSData*' with no explicit ownership
        NSData** p = (NSData**)userdata;

        *p = [NSData init:...];
    }
}

// main code:
NSData* d = ...;  // sometimes valid, sometimes nil
library_api(callback, &d);  // have the callback initialize or replace d
if (d)
{
    [d doSomthing:...];
}

I am using ARC, which I think is at the root of the issues. I thought of using a struct to hold the NSData*, but ARC forbids Objective-C objects in structs or unions. There is probably some variant of __bridge that might help, but I am not sure which. I have looked at a lot of SO questions and read the Apple docs, but it is not clear to me how to code this up.

Ideally, if d were not nil at the time the callback is invoked, its current value would be released and a new NSData object would replace it. In other words, when library_api completes, d always holds the NSData object created by the callback, and any previously held value would have been released properly.

I suppose I could keep the Objective-C code out of the callback and just malloc() a buffer to store the data which would then be copied to an NSData object in the main code, but I am hoping that I can avoid that extra step.

4

2 回答 2

4

您不能将对象所有权转移到void*,但可以将对象所有权转移到 CoreFoundation 类型,例如CFDataRefCFTypeRef。然后,您可以随意传递 CF 指针。

例如:

NSData* data = ...

CFTypeRef cfData = (__bridge CFTypeRef)data; // If you want to pass in data as well.

library_api(callback, &cfData);

if (cfData)
{
    data = (__bridge_transfer NSData*)cfData; // Transfer ownership to ARC.  No need to call CFRelease(cfData).
    NSLog@("Data: %@", data);
}

在回调中:

static void callback(int x, void* userdata)
{
    if (userdata)
    {
        CFTypeRef* cfDataPtr = userdata;

        CFTypeRef cfInData = *cfDataPtr;
        NSData* inData = (__bridge NSData*)cfInData;
        NSLog(@"In Data: %@", inData);

        NSData* outData = [[NSData alloc] init];
        CFTypeRef cfOutData = (__bridge_retained CFTypeRef)outData; // Transfer ownership out of ARC.
        *cfDataPtr = cfOutData;
    }
}
于 2013-09-16T22:50:23.137 回答
1

您可以让 struct 包含 ObjC 对象,它们只需__unretained_unsafe在 .mm 文件中为 OR。

有几个选项:

1 使用结构将 ObjC 对象保存在 .m 文件中

struct MyStruct
{
    __unretained_unsafe id obj;
}

2 使用结构将 ObjC 对象保存在.mm文件 (Objective-C++) 中,并使用 C++new/delete作为结构。您可以这样做,因为编译器将在结构的构造函数/析构函数中生成保留/释放。

struct MyStruct
{
    id obj; // no need to have __unretained_unsafe
}

3 更改回调函数的签名并强制转换(我之前没有尝试过)

4 只需使用-fno-objc-arc编译器标志将此文件设为非 ARC

于 2013-09-16T22:23:17.590 回答