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.
You can't transfer object ownership to a void*
, but you can transfer object ownership to a CoreFoundation type, like CFDataRef
or CFTypeRef
. You're then free to pass around the CF pointer as you wish.
e.g.:
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);
}
And in the callback:
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;
}
}
You can have struct contain ObjC object, they just have to be __unretained_unsafe
OR in .mm file.
There are few options:
1 Use a struct to hold ObjC object in .m file
struct MyStruct
{
__unretained_unsafe id obj;
}
2 Use a struct to hold ObjC object in .mm file (Objective-C++) and use C++ new/delete
for the struct. You can do this because compiler will generate retain/release in the constructor/destructor of the struct.
struct MyStruct
{
id obj; // no need to have __unretained_unsafe
}
3 Change the signature of the callback function and cast it (I didn't try this before)
4 Just make this file non ARC with -fno-objc-arc
compiler flag
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With