Swift CFRunLoopTimerCreate - 如何在计时器回调中获取 "self"

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

如何访问 class "self" 实例以调用 class 实例方法,代码如下。如果我尝试 self.callSomeClassIntance(),如图所示,我会从编译器中得到一个 "A C function pointer cannot be formed fro a closure that captures context" 错误。我尝试 info.callSomeClassInstance(),但这会给出 "no member callSomeClassInstance" 错误。如果一行代码 xxxx.callSomeClassIntance() 被删除,代码将正确触发时间。

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

    }
}

我们不需要捕获 self 因为我们已经在传递它了。

当您为计时器创建上下文时,您将 self 放入允许 C 代码处理它的格式,空指针:

unsafeBitCast(self, UnsafeMutablePointer<Void>.self)

此代码 returns 指向 self 的空指针。这就是您在创建上下文时为 info 参数传递的内容。

无论您在创建上下文时为 info 参数传递什么,都是用于为 CFRunLoopTimerCallback 函数的 info 参数传递的内容。因此,我们需要对 info 参数应用逆运算 (unsafeBitCast(info, Foo.self)):

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

How to use instance method as callback for function which takes only func or literal closure 一样,CFRunLoopTimerCallBack 必须是全局函数或不捕获上下文的闭包。 特别是,闭包不能捕获 self 因此必须 将上下文中 info 的 void 指针转换回实例 指针。

您不一定需要 cfRunloopTimerCallback() 功能, 闭包可以直接作为参数传递:

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

这里我使用了Unmanaged来进行实例指针和void指针的转换。它看起来更复杂,但强调 未保留的引用 被传递。 unsafeBitCast() 如 @nhgrif 的回答可以交替使用。

你也可以定义类似于Objective-C__bridge的函数, 比较 .