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


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

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

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

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

let selector = #selector(test) // error

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

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

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

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

1 60

1 ответ:

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

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

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

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


просто подведем итоги нового #selector синтаксис:

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

(EDIT: хорошо, да, вы можете. Вы можете быть полным тупицей и установить цель на экземпляр, который не реализует сообщение действия, указанное #selector. Компилятор не остановит вас, и вы рухнете так же, как в старые добрые времена. Вздох...)

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

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

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

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

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

    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 это метод класса, а не метод экземпляра, но он будет правильно разрешен для селектора, тем не менее, это все, что имеет значение.)