How to make sure a RunLoopObserver callback finishes before calling CFRunLoopRemoveObserver?

I have a task that uses a CFRunLoopObserver like the following.

class Task {
    CFRunLoopObserverRef fObserver;
public:
    Task() {
        CFRunLoopObserverContext context = {0, this, NULL, NULL, NULL};
        fObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                            kCFRunLoopBeforeWaiting,
                                            true, 0, &MainRunLoopObserverCallback, &context);
        ::CFRunLoopAddObserver(CFRunLoopGetMain(), fObserver, kCFRunLoopCommonModes);
    }

    static void MainRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {

        Task* task = reinterpret_cast<Task*>(info);
        // task->member causes a crash if task deleted
    }

    ~Task() {
        if (fObserver)
        {
            ::CFRunLoopRemoveObserver(CFRunLoopGetMain(), fObserver, kCFRunLoopCommonModes);
            ::CFRelease(fObserver);
        }
    }
};

I have noticed that the app crashes sometimes when the task object gets deleted before the CFRunLoopObserverCallBack is completed.

Would it be possible to make sure that the observer callback is complete before the observer is removed from the runloop?

Answered by DTS Engineer in 698577022

The observers are always added an removed from the same thread.

That’s good.

Just for context, manipulating the run loop of thread A from thread B is supposed to work but my experience is that it opens you up to a bunch of problems, both in the OS and in your own code.


Coming back to your original issue, the key problem is this:

CFRunLoopObserverContext context = {0, this, NULL, NULL, NULL};

Your first two NULL values are meant to be retain and release callbacks. The run loop uses those to ensure that the object referenced by the info pointer (in your case that’s the C++ this) sticks around until the run loop is done with it. By passing in NULL you’re taking on the responsibility of ensuring that the object stays around until the run loop is done with it. That’s quite tricky.

The solution here is to use a reference-counted object and supply retain and release callbacks. This is pretty straightforward in Objective-C and Swift. I’m not familiar enough with reference counting in C++ to offer you advice on that front.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Maybe for the destructor call instead of using an if probably a while testing the function below.

func CFRunLoopContainsObserver(_ rl: CFRunLoop!, 
                             _ observer: CFRunLoopObserver!, 
                             _ mode: CFRunLoopMode!) -> Bool

https://developer.apple.com/documentation/corefoundation/1542815-cfrunloopcontainsobserver

Are you adding and removing observers from a different thread?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

No. The observers are always added an removed from the same thread.

Accepted Answer

The observers are always added an removed from the same thread.

That’s good.

Just for context, manipulating the run loop of thread A from thread B is supposed to work but my experience is that it opens you up to a bunch of problems, both in the OS and in your own code.


Coming back to your original issue, the key problem is this:

CFRunLoopObserverContext context = {0, this, NULL, NULL, NULL};

Your first two NULL values are meant to be retain and release callbacks. The run loop uses those to ensure that the object referenced by the info pointer (in your case that’s the C++ this) sticks around until the run loop is done with it. By passing in NULL you’re taking on the responsibility of ensuring that the object stays around until the run loop is done with it. That’s quite tricky.

The solution here is to use a reference-counted object and supply retain and release callbacks. This is pretty straightforward in Objective-C and Swift. I’m not familiar enough with reference counting in C++ to offer you advice on that front.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

How to make sure a RunLoopObserver callback finishes before calling CFRunLoopRemoveObserver?
 
 
Q