Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am attempting to extend Foundation's Timer class in Swift 3, adding a convenience initializer. But its call to the Foundation-provided initializer never returns.

The problem is illustrated in the following trivialized demo, which can be run as a Playground.

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)

Console output:

Next statement never returns

To study further,

• I wrote similar code extending URLProtocol (one of the only other Foundation classes with an instance initializer). Result: No problem.

• To eliminate the Objective-C stuff as a possible cause, I changed the wrapped initializer to init(timeInterval:repeats:block:) method and provided a Swift closure. Result: Same problem.

like image 517
Jerry Krinock Avatar asked Apr 17 '26 04:04

Jerry Krinock


2 Answers

I don't actually know the answer, but I see from running this in an actual app with a debugger that there's an infinite recursion (hence the hang). I suspect that this is because you're not in fact calling a designated initializer of Timer. This fact is not obvious, but if you try to subclass Timer and call super.init(timeInterval...) the compiler complains, and also there is an odd "not inherited" marking on super.init(timeInterval...) in the header.

I was able to work around the issue by calling self.init(fireAt:...) instead:

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

Make of that what you will...

like image 181
matt Avatar answered Apr 20 '26 13:04

matt


I see the same issue as described by matt with infinite recursion. -[NSCFTimer release] is called over and over on the same object. This behavior can be reproduced in pure Objective-C by calling a class initializer from within an instance initializer.

@implementation NSTimer (Foo)

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

@end

The compiler complains that a designated initializer isn't being called which very well seems related to fixing the issue but doesn't explain the recursive calls.

warning: convenience initializer missing a 'self' call to another initializer
like image 45
Anurag Avatar answered Apr 20 '26 14:04

Anurag



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!