Swift 3:便利初始化程序扩展基础 'Timer' 挂起

Swift 3: Convenience Initializer Extending Foundation's 'Timer' Hangs

我正在尝试在 Swift 3 中扩展 Foundation 的 Timer class,添加一个方便的初始值设定项。但它对基金会提供的初始化程序的调用从不 returns.

问题在下面的简单演示中得到了说明,可以运行作为游乐场。

import Foundation

extension Timer {
    convenience init(target: Any) {
        print("Next statement never returns")
        self.init(timeInterval: 1.0,
                  target: target,
                  selector: #selector(Target.fire),
                  userInfo: nil,
                  repeats: true)
        print("This never executes")
    }
}

class Target {
    @objc func fire(_ timer: Timer) {
    }
}

let target = Target()
let timer = Timer(target: target)

控制台输出:

Next statement never returns

进一步研究,

• 我编写了扩展 URLProtocol 的类似代码(仅有的其他具有实例初始化器的 classes 之一)。结果:没问题。

• 为了消除 Objective-C 的可能原因,我将包装的初始值设定项更改为 init(timeInterval:repeats:block:) 方法并提供了一个 Swift 闭包。结果:同样的问题。

我实际上并不知道答案,但我从 运行 中看到,在带有调试器的实际应用程序中存在无限递归(因此挂起)。我 怀疑 这是因为您实际上并没有调用 Timer 的指定初始化程序。这个事实并不明显,但是如果您尝试子类化 Timer 并调用 super.init(timeInterval...),编译器会报错,而且 header 中的 super.init(timeInterval...) 上还有一个奇怪的 "not inherited" 标记。

我可以通过调用 self.init(fireAt:...) 来解决这个问题:

extension Timer {
    convenience init(target: Any) {
        print("Next statement never returns") // but it does
        self.init(fireAt: Date(), interval: 1, target: target, 
            selector: #selector(Target.fire), userInfo: nil, repeats: true)
        print("This never executes") // but it does
    }
}

随心所欲...

我看到了与 matt 描述的无限递归相同的问题。 -[NSCFTimer release] 在同一个对象上被一遍又一遍地调用。通过从实例初始化程序中调用 class 初始化程序,可以在纯 Objective-C 中重现此行为。

@implementation NSTimer (Foo)

- (instancetype)initWithTarget:(id)t {
    return [NSTimer timerWithTimeInterval:1 target:t selector:@selector(description) userInfo:nil repeats:NO];
}

@end

编译器抱怨没有调用指定的初始化器,这似乎与解决问题有关,但没有解释递归调用。

warning: convenience initializer missing a 'self' call to another initializer

@matt 的回答有效。

是的,我在我的应用程序中也看到了无限递归 – CFReleases。 Swift 书中明确指出 便利初始化器必须调用指定初始化器 。不过,它没有说明惩罚是什么。无限递归虽然令人惊讶,但却是合理的。

但是,请查看这两个声明,您可以通过单击选项或命令单击 Xcode 中的其中一种方法来查看它们:

init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

convenience init(fire date: Date, interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

我觉得那里有问题。 @matt 建议的函数,带有额外的 ("fire") 参数,为我解决了这个问题,标记为 convenience。我使用的函数 not 标记为 convenience,因此我认为(作为 Swift 新手)是 指定。但是它少了一个参数。嗯?

我想我应该提交一个错误,说明 Apple 以某种方式在错误的函数 上获得了 convenience 关键字。这可能是因为 Swift 真的没有头文件,对吗?所以我想知道当我们选择单击 Foundation 函数时我们会看到什么。可能他们的工作流程中有一个容易出现人为错误的步骤?