Каков быстрый эквивалент настройки свойств на `id`?

Интересно, что эквивалент Swift при вызове метода на id, в котором определяется доступность метода во время выполнения. В частности, я хочу сделать этот шаблон в Swift:

-(IBAction) handleEvent:(id) sender {
    BOOL didDisable = NO;
    if([sender respondsToSelector:@selector(setEnabled:)]) {
        [sender setEnabled:NO];
        didDisable = YES;
    }
    [self doSomethingAsyncWithCompletionHandler:^{
        if(didDisable) {
            [sender setEnabled:YES];
        }
    }];
}

Самая большая проблема заключается в том, что setEnabled: импортируется в Swift как свойство (например, UIBarItem), и ни одна из следующих конструкций не компилирует

func handleEvent(sender: AnyObject) {
    // Error: AnyObject does not have a member named "enabled"
    sender.enabled? = false

    // Error: (BooleanLiteralCompatible) -> _ is not identical to Bool
    sender.setEnabled?(false)
}

Ответ 1

В Swift 2.0 beta 4 ваши молитвы отвечают; этот код становится законным:

@IBAction
func handleEvent(sender: AnyObject) {
    if sender.respondsToSelector("setHidden:") {
        sender.performSelector("setHidden:", withObject: true)
    }
}

Ответ 2

Фактически вы можете сделать это точно так же, как и раньше: звоните respondsToSelector:. В самом деле, это именно то, что делает ваше предлагаемое выражение:

sender.setEnabled?(false)

Это выражение на самом деле является сокращением - сначала он вызывает respondsToSelector:, а затем вызывает setEnabled:, только если проходит тест respondsToSelector:. К сожалению, как вы говорите, вы не можете получить этот код для компиляции. Это, однако, просто причуда Свифта, известного репертуара доступных методов. Дело в том, что, хотя немного сложно сделать это для компиляции, это можно сделать - и как только вы его скомпилируете, он ведет себя так, как вы ожидали.

Однако я не буду объяснять, как это сделать, потому что я не хочу поощрять подобные трюки. Такой тип динамических сообщений не рекомендуется в Swift. В общем, в Swift не нужны динамические сообщения, такие как кодирование с ключом, интроспекция и т.д., И не согласуются с подходом Swift с сильной типизацией. Было бы лучше сделать что-то быстро, добавив опцию к чему-то, что у вас есть основания полагать, что это может быть и что имеет свойство enabled. Например:

@IBAction func doButton(sender: AnyObject) {
    switch sender {
    case let c as UIControl: c.enabled = false
    case let b as UIBarItem: b.enabled = false
    default:break
    }
}

Или:

@IBAction func doButton(sender: AnyObject) {
    (sender as? UIControl)?.enabled = false
    (sender as? UIBarItem)?.enabled = false
}

Ответ 3

Если вы хотите избежать использования метода respondsToSelector:, вы можете определить протокол вместо этого. Затем расширьте классы, которые вы хотите использовать, которые уже соответствуют определению этого протокола (включено) и определяют функцию с общей переменной, соответствующей вашему протоколу.

protocol Enablable{
    var enabled:Bool { get set }
}

extension UIButton        : Enablable {}
extension UIBarButtonItem : Enablable {}

//....

func handleEvent<T:Enablable>(var sender: T) {
    sender.enabled = false
}

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

@IBAction func handleEventPressed(sender:AnyObject){
    handleEvent(sender);
}

Нам также нужна соответствующая общая функция без соответствия Enablable, так что мы можем вызвать handleEvent, не зная, что кто-либо, или не отправитель, является Enablable. К счастью, компилятор достаточно умен, чтобы выяснить, какую из двух общих функций использовать.

func handleEvent<T>(var sender: T) {
    //Do Nothing case if T does not conform to Enablable
}

Ответ 4

В качестве обходного пути/альтернативы вы можете использовать кодирование с ключом:

@IBAction func handler(sender: AnyObject) {

    if sender.respondsToSelector("setEnabled:") {
        sender.setValue(false, forKey:"enabled")
    }
}

Это работает как с Swift 1.2 (Xcode 6.4), так и с Swift 2.0 (Xcode 7 beta).