Callback in Swift


(John Brownie) #1

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. I must be doing something wrong, as I get a crash in the callback.

Code in my ViewController class:
         let scanTarget = [<the folders I monitor>]
         let scanCallback: FSEventStreamCallback = { (streamRef: ConstFSEventStreamRef, context: UnsafeMutableRawPointer?, count: Int, streamPtr: UnsafeMutableRawPointer, flags: UnsafePointer<FSEventStreamEventFlags>?, eventID: UnsafePointer<FSEventStreamEventId>?) in
             // Convert to appropriate pointer
             let theController = context?.bindMemory(to: ViewController.self, capacity: 1)
             // The following line crashes with a bad address exception
             theController?.pointee.<use the pointer to call a method>
         }
         var mutableSelf = self
         var theContext = FSEventStreamContext(version: 0, info: &mutableSelf, retain: nil, release: nil, copyDescription: nil)
         let scanInterval: CFTimeInterval = 5.0
         eventStream = FSEventStreamCreate(nil, scanCallback, &theContext, scanTarget as CFArray, FSEventStreamEventId(kFSEventStreamEventIdSinceNow), scanInterval, FSEventStreamCreateFlags(kFSEventStreamCreateFlagUseCFTypes))

The pointer I get back (as theController.pointee) is not valid, so I'm obviously doing this incorrectly. I had an idea that the mutableSelf variable should be a class variable, but that didn't really help.

So, how do I get a pointer to self inside the callback?

···

--
John Brownie
In Finland on furlough from SIL Papua New Guinea


(Quinn “The Eskimo!”) #2

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

···

On 25 Jan 2017, at 07:49, John Brownie via swift-users <swift-users@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.

--
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()
---------------------------------------------------------------------------


(John Brownie) #3

Thanks, that does it for me, though I had to qualify the Unmanaged with a type in the first part.

Rien wrote:

For the context I passed in:

UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

And to get the context back inside the callback:

let ourself = Unmanaged< … your type here…>.fromOpaque(arg!).takeUnretainedValue()

Then “ourself” was the object normally obtained as “self”.

A more general question is how to learn these tricks. The worst pain I've had in learning Swift is how to handle interactions with frameworks in other languages, such as this one (Cocoa) or the Expat parser (C). The Swift book doesn't even mention Unmanaged or opaque. The book on using Swift with Objective-C mentions Unmanaged, but not opaque (apart from a couple of references, neither of which mentions toOpaque/fromOpaque. If there were a reference that gathered these things together in one place, it would be easier to learn than simply thrashing around for a while and asking for help.

···

--
John Brownie
In Finland on furlough from SIL Papua New Guinea


(Rien) #4

Thanks, that does it for me, though I had to qualify the Unmanaged with a type in the first part.

Rien wrote:

For the context I passed in:

UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

And to get the context back inside the callback:

let ourself = Unmanaged< … your type here…>.fromOpaque(arg!).takeUnretainedValue()

Then “ourself” was the object normally obtained as “self”.

A more general question is how to learn these tricks. The worst pain I've had in learning Swift is how to handle interactions with frameworks in other languages, such as this one (Cocoa) or the Expat parser (C). The Swift book doesn't even mention Unmanaged or opaque. The book on using Swift with Objective-C mentions Unmanaged, but not opaque (apart from a couple of references, neither of which mentions toOpaque/fromOpaque. If there were a reference that gathered these things together in one place, it would be easier to learn than simply thrashing around for a while and asking for help.

I usually start off with using the debugger. And I like to print out pointer values a lot. Shows you how swift handles them.
After that I usually know what to look for and am able to target my googling.
Subscribing to lists (and scanning posts) like these (and evolution) also makes you more aware of what is going on. In isolation it is difficult to get a good feel for a language.

Rien

···

On 25 Jan 2017, at 10:33, John Brownie <john_brownie@sil.org> wrote:

--
John Brownie
In Finland on furlough from SIL Papua New Guinea