[swift-users] Callback in Swift

Quinn "The Eskimo!" eskimo1 at apple.com
Wed Jan 25 03:46:35 CST 2017


On 25 Jan 2017, at 07:49, John Brownie via swift-users <swift-users at swift.org> wrote:

> My macOS app shows a representation of the contents of various folders, so using FSEvents to track modifications from outside the app seemed to be the way to go. I am running into difficulty with writing the code with a callback, all in Swift.

Yep, that can be a bit tricky.  There’s two parts to this, one of which is relatively straightforward and the other is not something I’ve dealt with before:

A. Setting the `info` pointer in the context — The standard approach for this is as follows:

1. Set the `info` pointer like this:

context.info = Unmanaged.passRetained(self).toOpaque()

2. Convert from the `info` pointer like this:

let obj = Unmanaged<Observer>.fromOpaque(info!).takeUnretainedValue()

IMPORTANT: After this the `info` pointer is holding a reference to your self object.  If you shut down the stream, you need to release that pointer.  Let me know if that’s relevant to you (a lot of folks just start a stream and leave it running).

B. Dealing with FSEventStreamCallback parameters — FSEvents is weird in that the callback can take either an array of C strings or a CFArray of CFStrings depending on how you configure it.  The latter is easier, so like you I set `kFSEventStreamCreateFlagUseCFTypes`.

My code is pasted in below.

Share and Enjoy
--
Quinn "The Eskimo!"                    <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

---------------------------------------------------------------------------
import Foundation

class Watcher {

    var stream: FSEventStreamRef? = nil

    func start() {
        var context = FSEventStreamContext()
        context.version = 0
        context.info = Unmanaged.passRetained(self).toOpaque()
        let sinceWhen = FSEventStreamEventId(kFSEventStreamEventIdSinceNow)
        let flags = FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes)
        guard let stream = FSEventStreamCreate(nil, { (_, info, pathCount, rawPaths, flagsBase, eventIDsBase) in
            let obj = Unmanaged<Watcher>.fromOpaque(info!).takeUnretainedValue()
            
            let paths = Unmanaged<CFArray>.fromOpaque(rawPaths).takeUnretainedValue() as! [String]
            let flags = UnsafeBufferPointer(start: flagsBase, count: pathCount)
            let eventIDs = UnsafeBufferPointer(start: eventIDsBase, count: pathCount)
            obj.printBatch(paths: paths, flags: Array(flags), eventIDs: Array(eventIDs))
        }, &context, ["/"] as NSArray, sinceWhen, 1.0, flags) else {
            fatalError()
        }
        self.stream = stream
        
        FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
        
        let success = FSEventStreamStart(stream)
        assert(success)
    }
    
    func printBatch(paths: [String], flags: [FSEventStreamEventFlags], eventIDs: [FSEventStreamEventId]) {
        precondition(paths.count == flags.count)
        precondition(paths.count == eventIDs.count)
        NSLog("batch:")
        for i in 0..<paths.count {
            NSLog("  %08x %016llx %@", flags[i], eventIDs[i], paths[i] as NSString)
        }
    }
}

func main() {
    let watcher = Watcher()
    watcher.start()
    RunLoop.current.run()
}

main()
---------------------------------------------------------------------------



More information about the swift-users mailing list