@selector () в Swift?


660

Я пытаюсь создать NSTimerин , Swiftно у меня возникают некоторые проблемы.

NSTimer(timeInterval: 1, target: self, selector: test(), userInfo: nil, repeats: true)

test() это функция в том же классе.


Я получаю ошибку в редакторе:

Не удалось найти перегрузку для 'init', который принимает предоставленные аргументы

Когда я изменяю selector: test()к selector: nilпо ошибке исчезает.

Я пробовал:

  • selector: test()
  • selector: test
  • selector: Selector(test())

Но ничего не работает, и я не могу найти решение в ссылках.


11
selector: test()будет вызывать testи передавать это возвращаемое значение в selectorаргумент.
Майкл Дорст

Ответы:


930

Сам Swift не использует селекторы - несколько шаблонов проектирования, которые в Objective-C используют селекторы, работают по-разному в Swift. (Например, используйте дополнительные цепочки для типов протоколов или is/ astests вместо respondsToSelector:, и используйте замыкания везде, где вы можете вместо performSelector:лучшей безопасности типов / памяти.)

Но есть еще ряд важных API на основе ObjC, которые используют селекторы, включая таймеры и шаблон назначения / действия. Swift предоставляет Selectorтип для работы с ними. (Swift автоматически использует это вместо типа ObjC SEL.)

В Swift 2.2 (Xcode 7.3) и более поздних версиях (включая Swift 3 / Xcode 8 и Swift 4 / Xcode 9):

Вы можете создать Selectorфункцию из типа Swift, используя #selectorвыражение.

let timer = Timer(timeInterval: 1, target: object,
                  selector: #selector(MyClass.test),
                  userInfo: nil, repeats: false)
button.addTarget(object, action: #selector(MyClass.buttonTapped),
                 for: .touchUpInside)
view.perform(#selector(UIView.insertSubview(_:aboveSubview:)),
             with: button, with: otherButton)

Что хорошего в этом подходе? Ссылка на функцию проверяется компилятором Swift, поэтому вы можете использовать #selectorвыражение только с теми парами класс / метод, которые действительно существуют и могут использоваться в качестве селекторов (см. «Доступность селектора» ниже). Вы также можете свободно ссылаться на свои функции только в соответствии с вашими требованиями, согласно правилам Swift 2.2+ для именования типов функций .

(На самом деле это улучшение по сравнению с @selector()директивой ObjC , поскольку -Wundeclared-selectorпроверка компилятором проверяет только существование именованного селектора. Ссылка на функцию Swift, которую вы передаете для #selectorпроверки существования, членства в классе и подписи типа.)

Есть несколько дополнительных предостережений для ссылок на функции, которые вы передаете #selectorвыражению:

  • Несколько функций с одинаковым базовым именем могут различаться по меткам параметров с использованием вышеупомянутого синтаксиса для ссылок на функции (например, insertSubview(_:at:)vs insertSubview(_:aboveSubview:)). Но если функция не имеет параметров, единственный способ устранить ее неоднозначность - это использовать asприведение с сигнатурой типа функции (например, foo as () -> ()vs foo(_:)).
  • В Swift 3.0+ есть специальный синтаксис для пар методов получения / установки свойств. Например, учитывая var foo: Int, вы можете использовать #selector(getter: MyClass.foo)или #selector(setter: MyClass.foo).

Главные примечания:

Случаи, когда #selectorне работает, и именование: Иногда у вас нет ссылки на функцию, с которой можно сделать селектор (например, с методами, динамически зарегистрированными во время выполнения ObjC). В этом случае вы можете создать a Selectorиз строки: например, Selector("dynamicMethod:")- хотя вы потеряете проверку корректности компилятора. Когда вы делаете это, вы должны следовать правилам именования ObjC, включая colons ( :) для каждого параметра.

Доступность селектора: метод, на который ссылается селектор, должен быть открыт для среды выполнения ObjC. В Swift 4 у каждого метода, представленного в ObjC, должно быть объявление перед @objcатрибутом. (В предыдущих версиях вы в некоторых случаях получали этот атрибут бесплатно, но теперь вы должны явно объявить его.)

Помните, что privateсимволы также не отображаются во время выполнения - ваш метод должен иметь как минимум internalвидимость.

Ключевые пути: они связаны, но не совсем с селекторами. В Swift 3 также есть специальный синтаксис: например chris.valueForKeyPath(#keyPath(Person.friends.firstName)). См. SE-0062 для деталей. И еще больше KeyPathвещей в Swift 4 , поэтому убедитесь, что вы используете правильный API на основе KeyPath вместо селекторов, если это необходимо.

Подробнее о селекторах читайте в разделе « Взаимодействие с API-интерфейсами Objective C» в разделе « Использование Swift с какао и Objective-C» .

Примечание. До Selectorверсии Swift 2.2 это соответствовало требованиям StringLiteralConvertible, поэтому вы можете найти старый код, в котором голые строки передаются в API, которые принимают селекторы. Вы захотите запустить «Преобразовать в текущий синтаксис Swift» в XCode, чтобы получить их использующие #selector.


8
Помещение строки с именем функции сработало, NSSelectorFromString () также работает.
Арбитур

7
Хотелось бы отметить, что хотя «Взаимодействие с API-интерфейсами Objective-C» есть на сайте, его НЕ в книге «Язык программирования Swift».

10
Вероятно, следует упомянуть, что селектору в конце нужен символ «:», если он принимает аргумент. (Например, test () -> «test» & test (this: String) -> «test:»)
Даниэль Шлауг

2
Следует также отметить, что платформы Какао ожидают имя метода в стиле Objective-C. Если ваш метод принимает аргумент, вам понадобится ':', если он принимает 2 аргумента, size:andShape:если первый аргумент назван, вам может понадобиться With, то есть initWithData:дляfunc init(Data data: NSData)
JMFR

6
Есть ли способ добавить проверку передачи селектора в виде строки? IE компилятор предупреждает нас, когда мы
ошибаемся

86

Вот краткий пример использования Selectorкласса в Swift:

override func viewDidLoad() {
    super.viewDidLoad()

    var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))
    self.navigationItem.rightBarButtonItem = rightButton
}

func method() {
    // Something cool here   
}

Обратите внимание, что если метод, переданный в виде строки, не работает, он потерпит неудачу во время выполнения, а не во время компиляции и приведет к сбою приложения. Быть осторожен


13
что ужасно ... есть что-то типа "NSStringFromSelector"?
Лена Брю

11
не могу поверить, что они предназначены для неконтролируемых селекторов, так как у objc это было
malhal

4
@malcomhall: @selectorудобно, но не так строго, как вы думаете. «Необъявленный селектор» - это просто предупреждение от компилятора, потому что новые селекторы всегда могут быть введены во время выполнения. Впрочем, ссылки на проверяемые / рефакторируемые селекторы в Swift могут быть полезны .
Рикстер

2
Этот ответ полезен, но ответ ниже с @objc более уместен.
Cynistersix

Когда вы передаете строку селектора в качестве переменной или параметра, вам нужно сообщить компилятору о своем селекторе с помощью функции Selector (). спасибо
левый

46

Кроме того, если ваш (Swift) класс не происходит от класса Objective-C, то у вас должно быть двоеточие в конце строки имени целевого метода, и вы должны использовать свойство @objc с вашим целевым методом, например

var rightButton = UIBarButtonItem(title: "Title", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("method"))

@objc func method() {
    // Something cool here   
} 

в противном случае вы получите ошибку «Нераспознанный селектор» во время выполнения.


3
1. селекторы с двоеточием должны принимать аргумент 2. в соответствии с документами Apple таймер должен принимать аргумент NSTimer 3. Selectorключевое слово не обязательно. Так что в этом случае подпись должна быть@objc func method(timer: NSTimer) {/*code*/}
Евгений Дубинин

@objcработал на меня. Мне не нужно было включать timer: NSTimerв мой метод подпись для его вызова.
Майк Таверн

32

Обновление Swift 2.2+ и Swift 3

Используйте новое #selectorвыражение, которое избавляет от необходимости использовать строковые литералы, делая использование менее подверженным ошибкам. Для справки:

Selector("keyboardDidHide:")

становится

#selector(keyboardDidHide(_:))

Смотрите также: Предложение Swift Evolution

Примечание (Swift 4.0):

При использовании #selectorвам нужно пометить функцию как@objc

Пример:

@objc func something(_ sender: UIButton)


26

Swift 4.0

Вы создаете селектор, как показано ниже.

1. добавить событие к кнопке, как:

button.addTarget(self, action: #selector(clickedButton(sender:)), for: UIControlEvents.touchUpInside)

и функция будет как ниже:

@objc func clickedButton(sender: AnyObject) {

}

4
Вы забыли поставить @objcперед тем, funcчто требуется в Swift 4.
Вахид Амири

24

Для будущих читателей я обнаружил, что у меня возникла проблема, и я получил unrecognised selector sent to instanceошибку, вызванную пометкой цели funcкак частной.

func ДОЛЖНО быть общедоступным , чтобы назвать объект со ссылкой на селектор.


13
он не должен быть публичным, вы все равно можете хранить метод закрытым, но добавить objcего до объявления. Пример: @objc private func foo() { ...тогда вы можете использовать "foo"в качестве селектора столько, сколько вам нравится
apouche

Это также может быть internal, таким образом, не указывая какой-либо модификатор доступа. Я часто использую этот шаблон://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
Саджон

19

Просто в случае, если у кого-то еще есть та же проблема, что и у меня с NSTimer, где ни один из других ответов не устранил проблему, очень важно отметить, что, если вы используете класс, который не наследует от NSObject ни напрямую, ни глубоко в иерархии ( например, созданные вручную файлы swift), ни один из других ответов не будет работать, даже если указано следующее:

let timer = NSTimer(timeInterval: 1, target: self, selector: "test", 
                    userInfo: nil, repeats: false)
func test () {}

Не изменяя ничего, кроме простого наследования класса от NSObject, я перестал получать сообщение об ошибке «Нераспознанный селектор» и заставил мою логику работать так, как ожидалось.


Проблема с этой альтернативой заключается в том, что вы не можете изменить класс (скажем, ViewController) для наследования от NSObject, учитывая, что вам нужны вещи, реализованные в классе ViewController (например, viewDidLoad ()). Любая идея, как вызвать функцию Swift в ViewController с помощью NSTimer? ... e
eharo2

1
UIViewController уже наследуется от NSObject, большинство классов, предоставляемых SDK, делают этот пример для ваших собственных созданных классов, которым требуется функциональность NSTimer ...
Martin Cazares,

14

Если вы хотите передать параметр в функцию из NSTimer, вот ваше решение:

var somethingToPass = "It worked"

let timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "tester:", userInfo: somethingToPass, repeats: false)

func tester(timer: NSTimer)
{
    let theStringToPrint = timer.userInfo as String
    println(theStringToPrint)
}

Включите двоеточие в текст селектора (tester :), а ваши параметры перейдите в userInfo.

Ваша функция должна принимать NSTimer в качестве параметра. Затем просто извлеките userInfo, чтобы получить переданный параметр.


3
Я использовал NSTimer (0.01, target: self, ...), который НЕ работал, в то время как NSTimer.scheduledTimerWithTimeInterval (0.01, ..) DID работал !? Странно, но спасибо @Scooter за ответ!
iOS-кодер

1
@ iOS-кодер, просто создающий таймер с инициализатором, не добавляет его в цикл запуска, тогда как scheduledTimerWith...автоматически добавляет его в текущий цикл запуска - так что здесь вообще нет странного поведения;)
Дэвид Ганстер,

1
@ Дэвид спасибо за ваше предложение. Полагаю, мое недоразумение должно относиться либо к STFW, либо к RTFA (Read The F ... ing API)?
iOS-кодер

1
Не беспокойтесь, никто не может прочитать документацию о каждом методе в каждом API;)
Дэвид Гэнстер,

10

Селекторы являются внутренним представлением имени метода в Objective-C. В Objective-C "@selector (methodName)" будет преобразовывать метод исходного кода в тип данных SEL. Поскольку вы не можете использовать синтаксис @selector в Swift (здесь есть рикстер), вы должны вручную указать имя метода как объект String напрямую или передать объект String типу Selector. Вот пример:

var rightBarButton = UIBarButtonItem(
    title: "Logout", 
    style: UIBarButtonItemStyle.Plain, 
    target: self, 
    action:"logout"
)

или

var rightBarButton = UIBarButtonItem(
    title: "Logout", 
    style: UIBarButtonItemStyle.Plain, 
    target: self, 
    action:Selector("logout")
)

8

Swift 4.1
с образцом жеста касания

let gestureRecognizer = UITapGestureRecognizer()
self.view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.addTarget(self, action: #selector(self.dismiss(completion:)))

// Use destination 'Class Name' directly, if you selector (function) is not in same class.
//gestureRecognizer.addTarget(self, action: #selector(DestinationClass.dismiss(completion:)))


@objc func dismiss(completion: (() -> Void)?) {
      self.dismiss(animated: true, completion: completion)
}

См. Документ Apple для более подробной информации о: выражении селектора


Пожалуйста, прекратите делать это. Это никому не помогает. Чем это отличается от Swift 3.1? И почему вы считаете необходимым добавить к этому еще один ответ, когда у него уже есть около 20 ответов?
Fogmeister

Селектор вызовов отличается в Swift 4. Попробуйте эти ответы в Swift 4 и посмотрите. Ни один из них не будет работать без редактирования. Пожалуйста, не помечайте любое заявление как спам, не гарантируя его важность
Крунал

Так есть ли причина, по которой вы не можете редактировать существующий принятый ответ? Это сделало бы его действительно полезным, а не добавляя в конце длинный список ответов. Кнопка «Редактировать» существует по причине.
Fogmeister

Кроме того, какая часть этого отличается от Swift 3?
Fogmeister

2
Вы должны добавить тег objc к любым селекторам для Swift 4. Это правильный ответ. И вы не должны редактировать ответы других людей, чтобы изменить их значение. @ Крунал абсолютно прав.
Unome

6
// for swift 2.2
// version 1
buttton.addTarget(self, action: #selector(ViewController.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(ViewController.tappedButton2(_:)), forControlEvents: .TouchUpInside)

// version 2
buttton.addTarget(self, action: #selector(self.tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(self.tappedButton2(_:)), forControlEvents: .TouchUpInside)

// version 3
buttton.addTarget(self, action: #selector(tappedButton), forControlEvents: .TouchUpInside)
buttton.addTarget(self, action: #selector(tappedButton2(_:)), forControlEvents: .TouchUpInside)

func tappedButton() {
  print("tapped")
}

func tappedButton2(sender: UIButton) {
  print("tapped 2")
}

// swift 3.x
button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)

func tappedButton(_ sender: UIButton) {
  // tapped
}

button.addTarget(self, action: #selector(tappedButton(_:_:)), for: .touchUpInside)

func tappedButton(_ sender: UIButton, _ event: UIEvent) {
  // tapped
}

было бы лучше и поучительнее, если бы у вас был пример, содержащий два или три аргумента для Swift3 или Swift4. Спасибо.
nyxee

5
Create Refresh control using Selector method.   
    var refreshCntrl : UIRefreshControl!
    refreshCntrl = UIRefreshControl()
    refreshCntrl.tintColor = UIColor.whiteColor()
    refreshCntrl.attributedTitle = NSAttributedString(string: "Please Wait...")
    refreshCntrl.addTarget(self, action:"refreshControlValueChanged", forControlEvents: UIControlEvents.ValueChanged)
    atableView.addSubview(refreshCntrl)

// Обновить метод управления

func refreshControlValueChanged(){
    atableView.reloadData()
    refreshCntrl.endRefreshing()

}

5

Поскольку Swift 3.0 опубликован, даже немного сложнее объявить подходящим targetAction

class MyCustomView : UIView {

    func addTapGestureRecognizer() {

        // the "_" is important
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MyCustomView.handleTapGesture(_:)))
        tapGestureRecognizer.numberOfTapsRequired = 1
        addGestureRecognizer(tapGestureRecognizer)
    }

    // since Swift 3.0 this "_" in the method implementation is very important to 
    // let the selector understand the targetAction
    func handleTapGesture(_ tapGesture : UITapGestureRecognizer) {

        if tapGesture.state == .ended {
            print("TapGesture detected")
        }
    }
}

4

Когда используешь performSelector()

/addtarget()/NStimer.scheduledTimerWithInterval() Методы вашего метода (соответствующие селектору) должны быть помечены как

@objc
For Swift 2.0:
    {  
        //...
        self.performSelector(“performMethod”, withObject: nil , afterDelay: 0.5)
        //...


    //...
    btnHome.addTarget(self, action: “buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
    //...

    //...
     NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector : “timerMethod”, userInfo: nil, repeats: false)
    //...

}

@objc private func performMethod() {}
@objc private func buttonPressed(sender:UIButton){.
}
@objc private func timerMethod () {.
}

Для Swift 2.2 вам нужно написать «#selector ()» вместо строки и имени селектора, чтобы больше не было возможности орфографической ошибки и сбоя из-за этого. Ниже приведен пример

self.performSelector(#selector(MyClass.performMethod), withObject: nil , afterDelay: 0.5)

3

Вы создаете селектор, как показано ниже.
1.

UIBarButtonItem(
    title: "Some Title",
    style: UIBarButtonItemStyle.Done,
    target: self,
    action: "flatButtonPressed"
)

2.

flatButton.addTarget(self, action: "flatButtonPressed:", forControlEvents: UIControlEvents.TouchUpInside)

Обратите внимание, что синтаксис @selector пропал и заменен простой строкой с именем вызываемого метода. Есть одна область, в которой мы все можем согласиться с многословием, возникшим на пути. Конечно, если мы объявили, что существует целевой метод с именем flatButtonPressed: лучше написать один:

func flatButtonPressed(sender: AnyObject) {
  NSLog("flatButtonPressed")
}

установить таймер:

    var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, 
                    target: self, 
                    selector: Selector("flatButtonPressed"), 
                    userInfo: userInfo, 
                    repeats: true)
    let mainLoop = NSRunLoop.mainRunLoop()  //1
    mainLoop.addTimer(timer, forMode: NSDefaultRunLoopMode) //2 this two line is optinal

Чтобы быть полным, вот flatButtonPressed

func flatButtonPressed(timer: NSTimer) {
}

Есть ли у вас источник для « Обратите внимание, что синтаксис @selector пропал »?
winklerrr

3

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

let tapRecognizer = UITapGestureRecognizer(
            target: self,
            action: "labelTapped:")

Где «Селектор» был объявлен как:

func labelTapped(sender: UILabel) { }

Обратите внимание, что он общедоступен, и что я не использую синтаксис Selector (), но это также возможно.

let tapRecognizer = UITapGestureRecognizer(
            target: self,
            action: Selector("labelTapped:"))

3

Использование #selector проверит ваш код во время компиляции, чтобы убедиться, что метод, который вы хотите вызвать, действительно существует. Еще лучше, если метод не существует, вы получите ошибку компиляции: Xcode откажется создавать ваше приложение, тем самым изгнав забвение другого возможного источника ошибок.

override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.rightBarButtonItem =
            UIBarButtonItem(barButtonSystemItem: .Add, target: self,
                            action: #selector(addNewFireflyRefernce))
    }

    func addNewFireflyReference() {
        gratuitousReferences.append("Curse your sudden but inevitable betrayal!")
    }

2

Может быть полезно отметить, где вы устанавливаете элемент управления, запускающий действие.

Например, я обнаружил, что при настройке UIBarButtonItem мне нужно было создать кнопку внутри viewDidLoad, иначе я бы получил нераспознанное исключение селектора.

override func viewDidLoad() {
    super.viewDidLoad() 

    // add button
    let addButton = UIBarButtonItem(image: UIImage(named: "746-plus-circle.png"), style: UIBarButtonItemStyle.Plain, target: self, action: Selector("addAction:"))
    self.navigationItem.rightBarButtonItem = addButton
}

func addAction(send: AnyObject?) {     
    NSLog("addAction")
}

2

selectorэто слово из Objective-Cмира, и вы можете использовать его, Swiftчтобы иметь возможность позвонить Objective-Cиз Swift него позволяет выполнять некоторый код во время выполнения

Перед Swift 2.2синтаксисом это:

Selector("foo:")

Поскольку имя функции передается Selectorкак Stringпараметр ("foo"), невозможно проверить имя во время компиляции . В результате вы можете получить ошибку во время выполнения:

unrecognized selector sent to instance

После Swift 2.2+синтаксиса это:

#selector(foo(_:))

Автозаполнение XCode поможет вам вызвать правильный метод


1

Изменить как простое именование строк в методе, вызывающем синтаксис селектора

var timer1 : NSTimer? = nil
timer1= NSTimer(timeInterval: 0.1, target: self, selector: Selector("test"), userInfo: nil, repeats: true)

После этого введите func test ().


0

Для Swift 3

// Пример кода для создания таймера

Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)

WHERE
timeInterval:- Interval in which timer should fire like 1s, 10s, 100s etc. [Its value is in secs]
target:- function which pointed to class. So here I am pointing to current class.
selector:- function that will execute when timer fires.

func updateTimer(){
    //Implemetation 
} 

repeats:- true/false specifies that timer should call again n again.


-1

Для быстрой 3

let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.test), userInfo: nil, repeats: true)

Объявление функции в том же классе:

@objc func test()
{
    // my function
} 

Если целью является self, то нет необходимости selfв селекторе. Этого должно быть достаточно:let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
Митра
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.