Как устранить ошибку "неоднозначного использования" с помощью синтаксиса Swift #selector?

[ПРИМЕЧАНИЕ. Этот вопрос был изначально сформулирован в Swift 2.2. Он был пересмотрен для Swift 4 и включает два важных языковых изменения: первый параметр метода external больше не подавляется автоматически, а селектор должен быть явно представлен Objective-C.]

Допустим, у меня есть два метода в моем классе:

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}

Теперь я хочу использовать новый Swift 2.2 #selector синтаксис, чтобы сделать селектор, соответствующий первому из этих методов, func test(), Как мне это сделать? Когда я пытаюсь это:

let selector = #selector(test) // error

... я получаю сообщение об ошибке "Неоднозначное использование test()"Но если я скажу это:

let selector = #selector(test(_:)) // ok, but...

... ошибка исчезла, но сейчас я имею в виду неправильный методс параметром. Я хочу сослаться на тот, без каких-либо параметров. Как мне это сделать?

[Примечание: пример не является искусственным. NSObject имеет оба Objective-C copy а также copy: методы экземпляра, Swift copy() а также copy(sender:AnyObject?); поэтому проблема может легко возникнуть в реальной жизни.]

1 ответ

Решение

[ПРИМЕЧАНИЕ. Этот ответ был изначально сформулирован в Swift 2.2. Он был пересмотрен для Swift 4 и включает два важных языковых изменения: первый параметр метода external больше не подавляется автоматически, а селектор должен быть явно представлен Objective-C.]

Вы можете обойти эту проблему, приведя ссылку на функцию к правильной сигнатуре метода:

let selector = #selector(test as () -> Void)

(Однако, по моему мнению, вам не нужно этого делать. Я рассматриваю эту ситуацию как ошибку, показывая, что синтаксис Swift для ссылки на функции неадекватен. Я подал отчет об ошибке, но безрезультатно.)


Просто чтобы подвести итог нового #selector синтаксис:

Целью этого синтаксиса является предотвращение слишком распространенных сбоев во время выполнения (обычно "нераспознанный селектор"), которые могут возникнуть при предоставлении селектора в виде литеральной строки. #selector() принимает ссылку нафункцию, и компилятор проверит, что функция действительно существует, и разрешит ссылку на селектор Objective-C для вас. Таким образом, вы не можете легко ошибиться.

(РЕДАКТИРОВАТЬ: Хорошо, да, вы можете. Вы можете быть полным ланчем и установить цель для экземпляра, который не реализует сообщение действия, указанное#selector, Компилятор не остановит вас, и вы потерпите крах, как в старые добрые времена. Вздох...)

Ссылка на функцию может появляться в любой из трех форм:

  • Самоназвание функции. Этого достаточно, если функция однозначна. Так, например:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    

    Здесь только один test метод, так что это #selector относится к нему, даже если он принимает параметр и#selectorне упоминает параметр. Разрешенный закулисный селектор Objective-C по-прежнему будет правильно"test:"(с двоеточием, указывающим параметр).

  • Имя функции вместе с остальнойее подписью. Например:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    

    У нас есть два testметоды, поэтому мы должны различать; обозначениеtest(_:) разрешается ковторому, с параметром.

  • Имя функции с или без остальной ее подписи, а также приведение, чтобы показатьтипы параметров. Таким образом:

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
        let selector1 = #selector(test as (Int) -> Void)
        // or:
        let selector2 = #selector(test(_:) as (Int) -> Void)
    }
    

    Здесь мыперегруженыtest(_:), Перегрузка не может быть выставлена ​​Objective C, потому что Objective C не позволяет перегрузку, таким образом, только одна из них выставлена, и мы можем сформировать селектор только для той, которая выставлена, потому что селекторы - функция Objective C, Но мы должны все же устранить неоднозначность в том, что касается Свифта, и актерский состав делает это.

    (Именно эта лингвистическая особенность используется, по моему мнению, неправильно) в качестве основы для ответа выше.)

Также вам, возможно, придется помочь Swift разрешить ссылку на функцию, сообщив ей, в каком классе находится функция:

  • Если класс такой же, как этот, или выше по цепочке суперклассов, то дальнейшее разрешение обычно не требуется (как показано в примерах выше); при желании можно сказать selfс точечной нотацией (например, #selector(self.test)и в некоторых ситуациях вам, возможно, придется это сделать.

  • В противном случае вы используете либо ссылку на экземпляр, для которого реализован метод, с точечной нотацией, как в этом реальном примере (self.mp является MPMusicPlayerController):

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
        target: self.mp, action: #selector(self.mp.pause))
    

    ... или вы можете использовать имя класса с точечной нотацией:

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    

    (Это кажется любопытным обозначением, потому что похоже, что вы говорите test является методом класса, а не методом экземпляра, но, тем не менее, он будет правильно преобразован в селектор, и это все, что имеет значение.)

Я хочу добавить недостающее значение: доступ к методу экземпляра извне класса.

class Foo {
    @objc func test() {}
    @objc func test(_ sender: AnyObject?) {}
}

С точки зрения класса полная подпись test() метод (Foo) -> () -> Void, который вам нужно будет указать, чтобы получить Selector.

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))

В качестве альтернативы вы можете обратиться к экземпляру Selectors, как показано в исходном ответе.

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))

В моем случае (Xcode 11.3.1) ошибка была только при использовании lldb во время отладки. При запуске работает исправно.

Другие вопросы по тегам