swiftcfrunloop

Swift CFRunLoopTimerCreate - how to get "self" in timer callback


How do I get access to the class "self" instance to call class instance methods given the following code. If I try self.callSomeClassIntance(), as shown, I get a "A C function pointer cannot be formed fro a closure that captures context" error from the compiler. I trying info.callSomeClassInstance(), but this will give a "no member callSomeClassInstance" error. Code will fire time correctly if the one line of code xxxx.callSomeClassIntance() is removed.

import Foundation

class Foo {
    func callSomeClassIntance() {}

    func start() {
        let runLoop : CFRunLoopRef = CFRunLoopGetCurrent();
        var context = CFRunLoopTimerContext(version: 0, info: unsafeBitCast(self, UnsafeMutablePointer<Void>.self), retain: nil, release: nil, copyDescription: nil)

        let timer : CFRunLoopTimerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 3.0, 0, 0, cfRunloopTimerCallback(), &context);

        CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

        CFRunLoopRun()
    }

    func cfRunloopTimerCallback() -> CFRunLoopTimerCallBack {

        return { (cfRunloopTimer, info) -> Void in
            print("Fire timer...")
            // need self context here to call class instance methods
            self.callSomeClassIntance()
        }

    }
}

Solution

  • We don't need to capture self because we're already passing it in.

    When you create the context for your timer, you're putting self into a format that allows the C code to deal with it, a void pointer:

    unsafeBitCast(self, UnsafeMutablePointer<Void>.self)
    

    This code returns a void pointer to self. And that's what you're passing in for the info argument when you create your context.

    Whatever you pass for the info argument when you create your context is what is used to pass in for the info argument of the CFRunLoopTimerCallback function. So, we need to apply the inverse operation (unsafeBitCast(info, Foo.self)) to that info argument:

    func cfRunloopTimerCallback() -> CFRunLoopTimerCallBack { 
        return { _, info in
            let grabSelf = unsafeBitCast(info, Foo.self)
            grabSelf.callSomeClassIntance()
        }
    }