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

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

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

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

Теперь я хочу использовать новый синтаксис #selector Swift 2.2, чтобы сделать селектор, соответствующий первому из этих методов, 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:

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

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

Ответ 2

скороговорка

#selector(methodName as (parameterType1, parameterType2) -> returnType)

Например, давайте посмотрим на класс JSONSerialization от Foundation

open class JSONSerialization : NSObject {
    open class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
    open class func jsonObject(with stream: InputStream, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
    ...
}

Результат может быть

#selector(JSONSerialization.jsonObject as (Data, JSONSerialization.ReadingOptions) throws -> Any )
#selector(JSONSerialization.jsonObject as (InputStream, JSONSerialization.ReadingOptions) throws -> Any )

В этом случае Xcode без проблем jumps to definition

Ответ 3

Спасибо @matt за ответ.

Основываясь на этой информации и некотором опыте

скороговорка

#selector(methodName as (parameterType1, parameterType2) -> returnType)

Например, давайте посмотрим на класс JSONSerialization от Foundation

open class JSONSerialization : NSObject {
    open class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
    open class func jsonObject(with stream: InputStream, options opt: JSONSerialization.ReadingOptions = []) throws -> Any
    ...
}

Результат будет

#selector(JSONSerialization.jsonObject as (Data, JSONSerialization.ReadingOptions) throws -> Any )
#selector(JSONSerialization.jsonObject as (InputStream, JSONSerialization.ReadingOptions) throws -> Any )

В этом случае Xcode без проблем jumps to definition