@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.