Reputation: 53
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()
}
}
}
Upvotes: 3
Views: 1196
Reputation: 540105
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.
Upvotes: 3
Reputation: 62072
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()
}
}
Upvotes: 2