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