0 Replies
      Latest reply on Sep 4, 2019 7:12 AM by eskimo
      eskimo Apple Staff Apple Staff (11,835 points)

        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:

        • NULL, indicates a failure (A) — In this case shouldFreeInfo will always be true.

        • non-NULL, with shouldFreeInfo false (B) — Here the routine has created a new CF object and thus has retained a reference to the context.info.

        • non-NULL, with shouldFreeInfo true (C) — Here the routine has returned an existing CF object, which has already retained its context.info parameter, and thus your context.info parameter went unused.  You should, as the name suggests, free it.

        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 context.info parameter has complex state, such that you need to call a method to tidy up that state, then you’ll need to look at shouldFreeInfo to determine whether that’s necessary.  For example, imagine MyObject 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:

          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 shouldFreeInfo in all cases because the value is set even if the routine returns 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 …
            }
        }