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