@selector () у Swift?


659

Я намагаюся створити 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/ asтести 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+ є спеціальний синтаксис для пар власників / сеттер. Наприклад, давши a var foo: Int, ви можете використовувати #selector(getter: MyClass.foo)або #selector(setter: MyClass.foo).

Загальні примітки:

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

Доступність селектора: Метод, на який посилається селектор, повинен піддаватися виконанню 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 .

Примітка: Перед Swift 2.2, Selectorвідповідно до StringLiteralConvertible, таким чином, ви можете знайти старий код, де голі рядки передаються API, що приймають селектори. Вам потрібно запустити "Перетворити на поточний синтаксис Swift" у Xcode, щоб отримати тих, хто використовує #selector.


8
Помістивши рядок із назвою функції, працює також NSSelectorFromString ().
Арбітур

7
Я хотів би зазначити, що хоч "Взаємодія з API-кодами Objective-C" знаходиться на веб-сайті, це НЕ в книзі "Швидка мова програмування".

10
Це, мабуть, має зазначати, що селектору потрібне ":" наприкінці, якщо він бере аргумент. (Eg test () -> "test" & test (this: String) -> "test:")
Daniel Schlaug

2
Слід також зазначити, що в рамках какао очікується назва методу стилю Objective-C. Якщо ваш метод бере аргумент, вам знадобиться ':', якщо він займає 2 аргументи, size:andShape:якщо перший аргумент названий вам може знадобитися a 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

4
@malcomhall: @selectorце зручно, але воно не застосовується настільки формально, як ви могли б подумати. "Недекларований селектор" - це лише попередження від компілятора, тому що нові селектори завжди можуть бути введені під час виконання. Хоча було б корисним зробити запит на вибір, який можна перевірити / відремонтувати в секторі Swift .
рикстер

2
Ця відповідь корисна, але відповідь нижче з @objc є більш доречною.
cynistersix

Коли ви передаєте рядок селектора у вигляді змінної чи параметра, вам потрібно буде повідомити компілятору про його селектор за допомогою функції Selector (). спасибі
levous

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 Update

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

Selector("keyboardDidHide:")

стає

#selector(keyboardDidHide(_:))

Дивіться також: Пропозиція щодо швидкої еволюції

Примітка (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.
Vahid Amiri

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 безпосередньо чи глибоко в ієрархії ( наприклад, створені вручну швидкі файли), жоден з інших відповідей не буде працювати, навіть якщо вказано наступним чином:

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)
}

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

Ваша функція повинна приймати NSTimer як параметр. Потім просто витягніть userInfo, щоб отримати параметр, який пройшов.


3
Я використовував NSTimer (0,01, ціль: self, ...), який НЕ працював, тоді як використовував NSTimer.scheduledTimerWithTimeInterval (0,01, ..) DID !? Дивно, але дякую @Scooter за вашу відповідь!
iOS-Coder

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

1
@ Дякуємо, дякую за вашу пропозицію. Я думаю, моє непорозуміння повинно належати або до категорії STFW, або до RTFA (читай F ... ing API)?
iOS-Coder

1
Не хвилюйтеся з цього приводу, ні від кого не можна буде читати документацію про кожен метод у кожному API;)
David Ganster

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

Швидкий 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

Перемикач викликів різний у швидкості 4. Спробуйте ці відповіді у швидкому 4 та подивіться. Жодне це не буде працювати без редагування. Будь ласка, не позначайте жодне твердження як спам, не гарантуючи його
імпотенції

Так чи є причина, що ви не змогли редагувати існуючу прийняту відповідь? Це зробило б насправді корисним, а не додаванням у кінці довгого списку відповідей. Кнопка «Редагувати» є з якоїсь причини.
Фогмайстер

Також, яка частина цього відрізняється від Swift 3?
Фогмайстер

2
Ви повинні додати тег objc до будь-яких селекторів для Swift 4. Це правильна відповідь. І ви не повинні редагувати відповіді інших людей, щоб змінити їх значення. @Krunal абсолютно прав.
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 відсутній і замінений простим String іменуванням методу виклику. Є одна сфера, де ми можемо погодитись, що багатослів’я заважають. Звичайно, якщо ми заявили, що існує цільовий метод, який називається flatButtonPress: краще написати один:

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

Для того, щоб бути завершеною, ось ось плоска кнопка

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")
}

1

Зміна як найменування простого рядка в методі, що викликає синтаксис селектора

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

Після цього наберіть функцію test ().


1

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 допоможе вам викликати правильний метод


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в селекторі немає необхідності . Цього має бути достатньо: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.