Как устранить ошибку "неоднозначного использования" с помощью синтаксиса 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(_:))
В качестве альтернативы вы можете обратиться к экземпляру Selector
s, как показано в исходном ответе.
let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))
В моем случае (Xcode 11.3.1) ошибка была только при использовании lldb во время отладки. При запуске работает исправно.