3

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

    }
}
nhgrif
  • 61,578
  • 25
  • 134
  • 173
MEB
  • 53
  • 6
  • See also http://stackoverflow.com/questions/30786883/swift-2-unsafemutablepointervoid-to-object. – Martin R Mar 29 '16 at 01:44
  • I think this is a different problem. He wants to be able to pass the self into the callback function. Which can be done by passing Foo! to the cfRunloopTimerCallback function and using the passed parameter to call callSomeClassInstance. – Christian Abella Mar 29 '16 at 03:00
  • func cfRunloopTimerCallback( selfPtr : Foo!) -> CFRunLoopTimerCallBack { return { (cfRunloopTimer, info) -> Void in print("Fire timer...") // need self context here to call class instance methods selfPtr.callSomeClassIntance() } } } – Christian Abella Mar 29 '16 at 03:01
  • @ChristianAbella: Passing `self` as a void pointer to the C callback and converting it back to an instance pointer (in order to call a `Foo` method) is exactly what is solved in the referenced Q&A's. I am pretty sure that this is a duplicate – Martin R Mar 29 '16 at 08:41
  • @Martin: Albiet what you marked as a duplicate works in the situation as described in that post. The CFRunLoopTimerCreate method signature requires a CFRunLoopTimerCallBack which that solution does not support. – MEB Mar 29 '16 at 13:03
  • I am kind of torn here. On the one hand, I see where @MartinR is coming from with the duplicate mark. The problem here doesn't really have so much to do with `CFRunLoopTimerCallBack` and more to do with converting Swift types to & from void pointers. On the other hand, it's not necessarily clear simply by reading the answers to the duplicate question that the thing we converted to a void pointer here is actually passed into the C closure we're creating. – nhgrif Mar 29 '16 at 13:13
  • @nhgrif: When in doubt, reopen :) – I have added an answer instead which demonstrates that the same approach as in the linked-to thread works here. Of course your answer is correct as well. – Martin R Mar 29 '16 at 17:08

2 Answers2

3

As in How to use instance method as callback for function which takes only func or literal closure, the CFRunLoopTimerCallBack must be a global function or a closure which does not capture context. In particular, that closure cannot capture self and therefore must convert the void pointer from info in the context back to an instance pointer.

You don't necessarily need the cfRunloopTimerCallback() function, a closure can be passed directly as an argument:

class Foo {
    func callSomeClassIntance() {}

    func start() {
        let runLoop = CFRunLoopGetCurrent();
        var context = CFRunLoopTimerContext()
        context.info = UnsafeMutablePointer<Void>(Unmanaged.passUnretained(self).toOpaque())

        let timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 3.0, 0, 0, {
            (cfRunloopTimer, info) -> Void in

            let mySelf = Unmanaged<Foo>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
            mySelf.callSomeClassIntance()
        }, &context);

        CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
        CFRunLoopRun()
    }
}

Here I have used Unmanaged for the conversions between instance pointer and void pointer. It looks more complicated but emphasizes that unretained references are passed around. unsafeBitCast() as in @nhgrif's answer can be used alternatively.

You can also define functions similar to the Objective-C __bridge, compare How to cast self to UnsafeMutablePointer<Void> type in swift.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
2

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()
    }
}
nhgrif
  • 61,578
  • 25
  • 134
  • 173