There are three Core Foundation routines that have a
shouldFreeInfo
parameter:
These routines are correctly documented but that documentation fails to cover the subtleties involved. The critical point is that there are three distinct return cases:
, indicates a failure (A) — In this caseNULL
will always be true.shouldFreeInfo
non-
, withNULL
false (B) — Here the routine has created a new CF object and thus has retained a reference to theshouldFreeInfo
.context.info
non-
, withNULL
true (C) — Here the routine has returned an existing CF object, which has already retained itsshouldFreeInfo
parameter, and thus yourcontext.info
parameter went unused. You should, as the name suggests, free it.context.info
Note
CFMachPortCreate
can never result in case C. On the one hand, this makes sense because it always allocates a new underlying Mach port. On the other hand, it’s a little weird that this routine has a
shouldFreeInfo
parameter at all!
In most cases you’re calling these routines from Objective-C or Swift, and thus you want to put an object reference into the
context.info
parameter. The easiest way to do this is to use the retain and release callbacks.
MyObject * myObject = …;
CFMessagePortContext context = {
.version = 0,
.info = (__bridge void *) myObject,
.retain = CFRetain,
.release = CFRelease
};
CFMessagePortRef port = CFMessagePortCreateLocal(
NULL,
CFSTR("com.example.myPortName"),
messagePortCallback,
&context,
NULL
);
if (port == NULL) {
… failure …
} else {
… success …
}
Note The Swift version of this code is a little complex because of the required casting (Swift tries to discourage you from doing anything this unsafe). You can find it at the end of this post.
This assumes
MyObject
is defined like so:
@implementation MyObject
- (NSData *)handleMessageOnLocalPort:(CFMessagePortRef)local msgID:(SInt32)msgID data:(NSData *)data {
… message handling code here …
}
static CFDataRef messagePortCallback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void * info) {
MyObject * obj = (__bridge MyObject *) info;
return (__bridge CFDataRef) [obj handleMessageOnLocalPort:local msgID:msgid data:(__bridge NSData *) data];
}
@end
If the CF object holds on to the
context.info
value, it will call the retain routine to do exactly that. Thus, you can deal with success or failure by looking solely at the function result, ignoring
shouldFreeInfo
. In fact, it’s fine to pass
NULL
to that parameter.
However, there are some cases where looking at
shouldFreeInfo
makes sense:
If the object referenced by the
parameter has complex state, such that you need to call a method to tidy up that state, then you’ll need to look atcontext.info
to determine whether that’s necessary. For example, imagineshouldFreeInfo
is responsible for an open file and you need to close that file in order to tidy up. In that case you might have code like this:MyObject
Boolean shouldFreeInfo; CFMessagePortRef port = CFMessagePortCreateLocal( NULL, CFSTR("com.example.myPortName"), messagePortCallback, &context, &shouldFreeInfo ); if (shouldFreeInfo) { [myObject close]; } if (port == NULL) { … failure … } else { … success … }
IMPORTANT It’s safe to reference
in all cases because the value is set even if the routine returnsshouldFreeInfo
.NULL
If, within the context of your program, it only makes sense for one object to be handling this task, you should specifically detect case C and fail.
Boolean shouldFreeInfo; CFMessagePortRef port = CFMessagePortCreateLocal( NULL, CFSTR("com.example.myPortName"), messagePortCallback, &context, &shouldFreeInfo ); if ( (port == NULL) || shouldFreeInfo ) { … failure … } else { … success … }
.
Easy, eh?
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
Here’s the Swift version of the core
CFMessagePortCreateLocal
example:
let myObject: MyObject = …
var context = CFMessagePortContext(
version: 0,
info: Unmanaged.passUnretained(myObject).toOpaque(),
retain: { infoQ in
guard let info = infoQ else { return nil }
let obj = Unmanaged<MyObject>.fromOpaque(info)
let objResult = obj.retain()
return UnsafeRawPointer(objResult.toOpaque())
},
release: { infoQ in
guard let info = infoQ else { return }
Unmanaged<MyObject>.fromOpaque(info).release()
},
copyDescription: nil
)
let portQ = CFMessagePortCreateLocal(
nil,
"com.example.myPortName" as NSString,
{ (localQ, msgid, data, info) -> Unmanaged<CFData>? in
let local = localQ
let data = (data as Data?) ?? Data()
let obj = Unmanaged<MyObject>.fromOpaque(info!).takeUnretainedValue()
guard let result = obj.handleMessage(localPort: local!, msgID: msgid, data: data) else {
return nil
}
return Unmanaged.passRetained(result as NSData)
},
&context,
nil
)
guard let port = portQ else {
… failure …
}
… success …
This looks more complex than it actually is. The main difference between it and the Objective-C version is that
CFRetain
and
CFRelease
are not available in Swift, so you have to implement the retain and release callbacks using
Unmanaged
.
It assumes this definition of
MyObject
:
class MyObject {
func handleMessage(localPort: CFMessagePort, msgID: Int32, data: Data) -> Data? {
… handle the message …
}
}