Estoy intentando extender la clase de la Fundación Timer en Swift 3, agregando un inicializador de conveniencia. Pero su llamada al inicializador proporcionado por la Fundación nunca regresa.

El problema se ilustra en la siguiente demostración trivializada, que se puede ejecutar como 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)

Salida de consola:

Next statement never returns

Para estudiar más a fondo,

• Escribí un código similar extendiendo URLProtocol (una de las únicas otras clases de Foundation con un inicializador de instancia). Resultado: no hay problema.

• Para eliminar el material de Objective-C como una posible causa, cambié el inicializador envuelto al método init(timeInterval:repeats:block:) y proporcioné un cierre Swift. Resultado: mismo problema.

3
Jerry Krinock 24 dic. 2016 a las 22:48

3 respuestas

La mejor respuesta

Realmente no sé la respuesta, pero veo al ejecutar esto en una aplicación real con un depurador que hay una recursión infinita (de ahí el bloqueo). sospecho que esto se debe a que, de hecho, no está llamando a un inicializador designado de Timer. Este hecho no es obvio, pero si intenta subclasificar Timer y llamar a super.init(timeInterval...), el compilador se queja, y también hay una marca "no heredada" extraña en super.init(timeInterval...) en el encabezado.

Pude solucionar el problema llamando a self.init(fireAt:...) en su lugar:

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

Haz de eso lo que quieras ...

4
matt 24 dic. 2016 a las 22:18

Veo el mismo problema descrito por Matt con recursión infinita. -[NSCFTimer release] se llama una y otra vez en el mismo objeto. Este comportamiento se puede reproducir en Objective-C puro llamando a un inicializador de clase desde un inicializador de instancia.

@implementation NSTimer (Foo)

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

@end

El compilador se queja de que no se llama a un inicializador designado, lo que parece estar muy relacionado con la solución del problema, pero no explica las llamadas recursivas.

warning: convenience initializer missing a 'self' call to another initializer
2
Anurag 24 dic. 2016 a las 23:04

La respuesta de @matt funciona.

Sí, también vi esa recursión infinita en mi aplicación: CFReleases. El libro de Swift es claro que los inicializadores de conveniencia deben llamar a los inicializadores designados . Sin embargo, no dice cuál es la pena. Una recursión infinita, aunque sorprendente, es plausible.

Sin embargo, mire estas dos declaraciones que puede ver haciendo clic en la opción o haciendo clic en uno de estos métodos en Xcode:

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

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

Creo que algo anda mal allí. La función @matt sugerida, con el parámetro adicional ("fuego"), que resuelve el problema para mí, está marcada conveniencia . Supongo que la función que utilicé, que no está marcada como conveniencia (como novato de Swift), está designada . Pero tiene un parámetro menos. ¿Eh?

Creo que presentaré un error que indica que Apple de alguna manera obtuvo la palabra clave conveniencia en la función incorrecta . Esto puede ser posible porque Swift realmente no tiene archivos de encabezado, ¿correcto? Así que me pregunto qué estamos viendo cuando hacemos clic en una función de Foundation. ¿Posiblemente hay un paso en su flujo de trabajo que es susceptible al error humano?

0
Jerry Krinock 25 dic. 2016 a las 07:46