FB13398940: Removing a CMIOObjectPropertyListenerBlock ...doesn't do anything?

I've added a listener block for camera notifications. This works as expected: the listener block is invoked then the camera is activated/deactivated.

However, when I call CMIOObjectRemovePropertyListenerBlock to remove the listener block, though the call succeeds, camera notifications are still delivered to the listener block.

Since in the header file it states this function "Unregisters the given CMIOObjectPropertyListenerBlock from receiving notifications when the given properties change." I'd assume that once called, no more notifications would be delivered?

Sample code:

#import <Foundation/Foundation.h>
#import <CoreMediaIO/CMIOHardware.h>
#import <AVFoundation/AVCaptureDevice.h>

int main(int argc, const char * argv[]) {
        
    AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    OSStatus status = -1;
    CMIOObjectID deviceID = 0;
    
    CMIOObjectPropertyAddress propertyStruct = {0};
    
    propertyStruct.mSelector = kAudioDevicePropertyDeviceIsRunningSomewhere;
    propertyStruct.mScope = kAudioObjectPropertyScopeGlobal;
    propertyStruct.mElement = kAudioObjectPropertyElementMain;
    
    deviceID = (UInt32)[camera performSelector:NSSelectorFromString(@"connectionID") withObject:nil];
    
    CMIOObjectPropertyListenerBlock listenerBlock = ^(UInt32 inNumberAddresses, const CMIOObjectPropertyAddress addresses[]) {
        NSLog(@"Callback: CMIOObjectPropertyListenerBlock invoked");
    };
    
    status = CMIOObjectAddPropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), listenerBlock);
    if(noErr != status) {
        NSLog(@"ERROR: CMIOObjectAddPropertyListenerBlock() failed with %d", status);
        return -1;
    }
    
    NSLog(@"Monitoring %@ (uuid: %@ / %x)", camera.localizedName, camera.uniqueID, deviceID);
    
    sleep(10);
    
    status = CMIOObjectRemovePropertyListenerBlock(deviceID, &propertyStruct, dispatch_get_main_queue(), listenerBlock);
    if(noErr != status) {
        NSLog(@"ERROR: 'AudioObjectRemovePropertyListenerBlock' failed with %d", status);
        return -1;
    }
    
    NSLog(@"Stopped monitoring %@ (uuid: %@ / %x)", camera.localizedName, camera.uniqueID, deviceID);
    
    sleep(10);
        
    
    return 0;
}

Compiling and running this code outputs:

Monitoring FaceTime HD Camera (uuid: 3F45E80A-0176-46F7-B185-BB9E2C0E436A / 21)
Callback: CMIOObjectPropertyListenerBlock invoked
Callback: CMIOObjectPropertyListenerBlock invoked
Stopped monitoring FaceTime HD Camera (uuid: 3F45E80A-0176-46F7-B185-BB9E2C0E436A / 21)
Callback: CMIOObjectPropertyListenerBlock invoked
Callback: CMIOObjectPropertyListenerBlock invoked

Note the last two log messages showing that the CMIOObjectPropertyListenerBlock is still invoked ...even though CMIOObjectRemovePropertyListenerBlock has successfully been invoked.

Am I just doing something wrong here? Or is the API broken?

I think this isn't doing what you expect because it is a command line program without a running run loop. You could put your code into an regular application, or you could try calling

CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, false);

instead of your sleep(10) calls

I looked at my own code, but I found I never uninstall my listener blocks so I've never tripped over this problem. I do have a command line program with an explicit call to CFRunLoopRunInMode though...

Although weirdly I was rewriting that code in Swift today and it is now also not successfully removing the listener, despite the Remove call returning success. My callback is still getting called.

https://github.com/Hammerspoon/hammerspoon/blob/a20d2d9fb6818e569fcc88791a40d5a6b26a405f/Hammertime/Camera.swift#L207

FB13398940: Removing a CMIOObjectPropertyListenerBlock ...doesn't do anything?
 
 
Q