2016-12-24 13 views
3
.

. Я пытаюсь расширить класс Foundation Timer в Swift 3, добавив инициализатор удобства. Но его призыв к инициализатору, предоставленному Фондом, никогда не возвращается.Swift 3: Инициализатор удобства. Расширение «Timer» веток Foundation.

Проблема проиллюстрирована в следующей тривиализованной демонстрации, которая может быть запущена в качестве игровой площадки.

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 (один из единственных других классов Foundation с экземпляром инициализатором). Результат: Нет проблем.

• Чтобы устранить объект Objective-C как возможную причину, я изменил обернутый инициализатор на init(timeInterval:repeats:block:) и предоставил быструю закрытие. Результат: та же проблема.

ответ

4

На самом деле я не знаю ответа, но я вижу, как это работает в реальном приложении с отладчиком, что есть бесконечная рекурсия (отсюда зависание). I подозревает, что это потому, что вы на самом деле не вызываете назначенный инициализатор таймера. Этот факт не очевиден, но если вы пытаетесь подклассировать таймер и вызывать super.init(timeInterval...), компилятор жалуется, а также есть нечетная «не наследуемая» маркировка на super.init(timeInterval...) в заголовке.

я смог обойти эту проблему, вызвав 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 
    } 
} 

Марка о том, что вы ...

+0

«Не наследуемая» маркировка в заголовке означает, что она не может использоваться в качестве инициализатора для подкласса из-за того, что она импортируется из фабричного метода Obj-C ('timerWithTimeInterval: target: selector: userInfo : repeat: '), который возвращает только экземпляры NSTimer (таким образом, не может использоваться для создания экземпляра подкласса). Хотя я не понимаю, почему это должно сделать это бесконечно рекурсивно (не может воспроизвести мой собственный метод фабрики Obj-C), поэтому, вероятно, что-то в реализации NSTimer делает это. Самое своеобразное. – Hamish

2

я вижу один и тот же вопрос, как описано матового с бесконечной рекурсии. -[NSCFTimer release] вызывается снова и снова на одном и том же объекте. Это поведение можно воспроизвести в чистом 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 
0

Ответ на @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 предложила, с дополнительным параметром («огонь»), который решает проблему для меня, отмечается удобство.Функция, которую я использовал, это не с пометкой удобство, я полагаю (как Swift новичок), поэтому обозначен. Но у него есть еще один параметр. А?

Я думаю, что я должен сообщить об ошибке о том, что Apple, какой-то образом получил удобства ключевого слова на неверной функции. Это может быть возможно, потому что у Swift действительно нет файлов заголовков, правильно? Поэтому мне интересно, что мы видим, когда мы выбираем функцию Foundation. Возможно, в их рабочем процессе есть шаг, который восприимчив к человеческой ошибке?

+0

В книге Swift указано: ** Rule2: Инициализатор удобства должен вызывать другой инициализатор из того же класса. ** и ** Rule3: Инициализатор удобства должен в конечном итоге вызвать назначенный инициализатор. ** Правила не запрещают инициализатор удобства, вызывающий другой удобный инициализатор того же класса. А в других классах фактически создается инициализатор удобства, который вызывает другой инициализатор удобства. Итак, вы должны отправить отчет об ошибке с исходной проблемой. «Таймер» - это особый случай. Как отображаются созданные заголовки, это тонкая вещь и игнорируется. – OOPer

+0

Вы правы, @OOPer. Все инициализаторы должны * в конечном итоге * вызывать назначенный инициализатор, поэтому, если мой инициализатор удобства вызывает другой инициализатор, назначенный или нет, он должен работать. –

+2

Я подал заявку на ошибку Apple Bug Reporter ID 29804952 ** на оригинальную версию. В * Дополнительные примечания * Я спросил, как функция X имеет один меньший параметр, чем функция Y, но функция Y является отмеченной * удобностью *. Спасибо вам всем. –