Search code examples
callbackobjective-c-blocksfseventstoll-free-bridging

OS X and FSEvents: How do you release a callback pointer supplied to an FSEventStreamRef?


I'm using FSEvents to monitor a directory, and whenever the directory changes I call a block which I originally passed into the FSEventStreamContext of the FSEventStreamRef. How do I release the block when it is time to stop monitoring the directory? Code below for reference.

void fsevents_callback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) {
    void (^block)() = (__bridge void (^)())(clientCallBackInfo);
    block();
}

- (FSEventStreamRef)startObserving:(NSString *)path block:(void(^)())block {
    void *ptr = (void *)CFBridgingRetain(block);  // NOTE: the block is retained
    FSEventStreamContext context = { 0, ptr, NULL, NULL, NULL };
    FSEventStreamRef stream = FSEventStreamCreate(NULL, fsevents_callback, &context, (__bridge CFArrayRef)@[path], kFSEventStreamEventIdSinceNow, 10, kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagIgnoreSelf);
    FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    FSEventStreamStart(stream);
    return stream;
}

- (void)stopObserving:(FSEventStreamRef)stream {
    // HELP: the block should be released here. can I get it through FSEvents?
    FSEventStreamStop(stream);
    FSEventStreamInvalidate(stream);
    FSEventStreamRelease(stream);
}

Solution

  • FSEventStreamContext has member variables for functions to retain and release the info pointer, which in your example is your void * block pointer.

    Via Apple's FSEvents reference:

    retain
      The callback used retain the info pointer. This can be NULL.
    
    release
      The callback used release a retain on the info pointer. This can be NULL.
    

    First, retain. Since you need to cast the block to void * for FSEventStreamContext anyway, I think it's fine to continue using CFBridgingRetain() in your startObserving: method. No retain callback function is needed.

    For release, try this callback function:

    void release_callback(const void *info) {
        CFBridgingRelease(info);
    }
    

    Then try changing your FSEventStreamContext declaration to:

        FSEventStreamContext context = { 0, ptr, NULL, release_callback, NULL };
    

    That should release your block when stopObserving: is called.