@selector () in Swift?


660

Sto cercando di creare un NSTimerin Swiftma ho qualche problema.

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

test() è una funzione nella stessa classe.


Viene visualizzato un errore nell'editor:

Impossibile trovare un sovraccarico per "init" che accetta gli argomenti forniti

Quando cambio selector: test()per selector: nille scompare errore.

Ho provato:

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

Ma nulla funziona e non riesco a trovare una soluzione nei riferimenti.


11
selector: test()chiamerebbe teste passerebbe il suo valore di ritorno selectorall'argomento.
Michael Dorst,

Risposte:


930

Swift stesso non utilizza i selettori: diversi modelli di design che in Objective-C utilizzano i selettori funzionano in modo diverso in Swift. (Ad esempio, utilizzare il concatenamento facoltativo su tipi di protocollo o is/ astest anziché respondsToSelector:e utilizzare chiusure ovunque possibile anziché performSelector:per una migliore sicurezza di tipo / memoria.)

Esistono tuttavia alcune importanti API basate su ObjC che utilizzano selettori, inclusi i timer e il modello di destinazione / azione. Swift fornisce il Selectortipo per lavorare con questi. (Swift lo utilizza automaticamente al posto del SELtipo di ObjC .)

In Swift 2.2 (Xcode 7.3) e versioni successive (inclusi Swift 3 / Xcode 8 e Swift 4 / Xcode 9):

Puoi costruire un Selectorda un tipo di funzione Swift usando l' #selectorespressione.

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)

La cosa grandiosa di questo approccio? Il compilatore Swift verifica il riferimento di una funzione, pertanto è possibile utilizzare l' #selectorespressione solo con coppie classe / metodo effettivamente esistenti e idonee all'uso come selettori (vedere "Disponibilità del selettore" di seguito). Sei anche libero di rendere il riferimento alla tua funzione solo specifico di cui hai bisogno, secondo le regole di Swift 2.2+ per la denominazione del tipo di funzione .

(Questo è in realtà un miglioramento rispetto alla @selector()direttiva di ObjC , perché il -Wundeclared-selectorcontrollo del compilatore verifica solo l'esistenza del selettore denominato. Il riferimento alla funzione Swift che passi per #selectorverificare l'esistenza, l'appartenenza a una classe e la firma del tipo.)

Ci sono un paio di avvertenze extra per i riferimenti di funzione che passi #selectorall'espressione:

  • Più funzioni con lo stesso nome di base possono essere differenziate dalle loro etichette dei parametri utilizzando la sintassi di cui sopra per i riferimenti di funzione (ad es. insertSubview(_:at:)Vs insertSubview(_:aboveSubview:)). Ma se una funzione non ha parametri, l'unico modo per chiarire l'ambiguità è usare un ascast con la firma del tipo di funzione (ad es. foo as () -> ()Vs foo(_:)).
  • Esiste una sintassi speciale per le coppie getter / setter di proprietà in Swift 3.0+. Ad esempio, dato un var foo: Int, puoi usare #selector(getter: MyClass.foo)o #selector(setter: MyClass.foo).

Note generali:

Casi in cui #selectornon funziona e denominazione: a volte non si dispone di un riferimento di funzione con cui creare un selettore (ad esempio, con metodi registrati dinamicamente nel runtime di ObjC). In tal caso, puoi costruire un Selectorda una stringa: ad esempio Selector("dynamicMethod:"), anche se perdi il controllo di validità del compilatore. Quando lo fai, devi seguire le regole di denominazione di ObjC, inclusi i due punti ( :) per ciascun parametro.

Disponibilità del selettore: il metodo a cui fa riferimento il selettore deve essere esposto al runtime ObjC. In Swift 4, ogni metodo esposto a ObjC deve avere la sua dichiarazione preceduta dall'attributo @objc. (Nelle versioni precedenti hai ottenuto l'attributo gratuitamente in alcuni casi, ma ora devi dichiararlo esplicitamente.)

Ricorda che anche i privatesimboli non sono esposti al runtime: il tuo metodo deve avere almeno internalvisibilità.

Percorsi chiave: sono correlati ma non uguali ai selettori. C'è una sintassi speciale anche per questi in Swift 3: ad es chris.valueForKeyPath(#keyPath(Person.friends.firstName)). Vedere SE-0062 per i dettagli. E ancora più contenuti KeyPathin Swift 4 , quindi assicurati di utilizzare l'API basata su KeyPath corretta anziché i selettori, se appropriato.

Puoi leggere ulteriori informazioni sui selettori in Interazione con le API di Objective-C in Uso di Swift con Cocoa e Objective-C .

Nota: prima di Swift 2.2, Selectorconforme StringLiteralConvertible, quindi potresti trovare il vecchio codice in cui le stringhe nude vengono passate alle API che accettano selettori. Ti consigliamo di eseguire "Converti in sintassi Swift corrente" in Xcode per ottenere quelli che utilizzano #selector.


8
Inserendo una stringa con il nome della funzione funzionante, funziona anche NSSelectorFromString ().
Arbitur,

7
Vorrei menzionare che mentre "Interagire con le API Objective-C" è sul sito Web, NON è presente nel libro "Il linguaggio di programmazione rapido".

10
Questo dovrebbe probabilmente menzionare che il selettore ha bisogno di un ":" alla fine se accetta un argomento. (Ad esempio test () -> "test" e test (this: String) -> "test:")
Daniel Schlaug,

2
Va inoltre sottolineato che i framework Cocoa prevedono un nome di metodo in stile Objective-C. Se il tuo metodo accetta un argomento, avrai bisogno di un ':' se accetta 2 argomenti size:andShape:, se il primo argomento è chiamato potresti aver bisogno di un With, cioè initWithData:perfunc init(Data data: NSData)
JMFR,

6
Esiste un modo per aggiungere la convalida al passaggio del "selettore" come stringa? Il compilatore di IE ci avvisa quando si
scrive

86

Ecco un breve esempio su come utilizzare la Selectorclasse su 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   
}

Nota che se il metodo passato come stringa non funziona, non funzionerà in fase di esecuzione, non durante la compilazione e causerà l'arresto anomalo dell'app. Stai attento


13
che è orribile ... esiste un tipo di cosa "NSStringFromSelector"?
Lena Bru,

11
non riesco a credere che abbiano progettato per selettori non controllati poiché objc ha avuto questo
malhal

4
@malcomhall: @selectorè utile, ma non è applicato formalmente come potresti pensare. "Selettore non dichiarato" è semplicemente un avvertimento del compilatore, poiché è sempre possibile introdurre nuovi selettori in fase di esecuzione. Tuttavia, i riferimenti ai selettori verificabili / refactorable in Swift sarebbero una buona richiesta di funzionalità .
rickster

2
Questa risposta è utile ma la risposta di seguito con @objc è più appropriata.
cynistersix,

Quando si passa la stringa di selezione come variabile o parametro, è necessario far sapere al compilatore che è un selettore usando la funzione Selector (). grazie
prelievo

46

Inoltre, se la tua classe (Swift) non discende da una classe Objective-C, allora devi avere i due punti alla fine della stringa del nome del metodo target e devi usare la proprietà @objc con il tuo metodo target, ad es.

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

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

in caso contrario verrà visualizzato un errore "Selettore non riconosciuto" in fase di esecuzione.


3
1. i selettori con i due punti devono accettare un argomento 2. in base ai documenti Apple, le azioni del timer dovrebbero accettare l'argomento NSTimer 3. la Selectorparola chiave non è obbligatoria. Quindi in questo caso la firma deve essere@objc func method(timer: NSTimer) {/*code*/}
Yevhen Dubinin,

@objcha funzionato per me. Non avevo bisogno di includere timer: NSTimernella mia firma del metodo per essere chiamato.
Mike Taverne,

32

Swift 2.2+ e Swift 3 Update

Utilizzare la nuova #selectorespressione, che elimina la necessità di utilizzare letterali di stringa rendendo l'utilizzo meno soggetto a errori. Per riferimento:

Selector("keyboardDidHide:")

diventa

#selector(keyboardDidHide(_:))

Vedi anche: proposta Swift Evolution

Nota (Swift 4.0):

Se si utilizza, #selectorè necessario contrassegnare la funzione come@objc

Esempio:

@objc func something(_ sender: UIButton)


26

Swift 4.0

crei il Selettore come sotto.

1.aggiungere l'evento a un pulsante come:

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

e la funzione sarà come di seguito:

@objc func clickedButton(sender: AnyObject) {

}

4
Hai dimenticato di mettere @objcprima funcche è richiesto in Swift 4.
Vahid Amiri

24

Per i futuri lettori, ho scoperto di aver riscontrato un problema e di aver riscontrato un unrecognised selector sent to instanceerrore causato contrassegnando l'obiettivo funccome privato.

Il func DEVE essere pubblicamente visibili di essere chiamato da un oggetto con un riferimento a un selettore.


13
non deve essere pubblico, puoi comunque mantenere il metodo privato ma aggiungere objcprima della sua dichiarazione. Es: @objc private func foo() { ...allora puoi usare "foo"come selettore quanto vuoi
apouche il

Può anche essere internal, quindi non specificando alcun modificatore di accesso. Uso spesso questo schema://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
Sajjon,

19

Nel caso in cui qualcun altro abbia lo stesso problema che ho avuto con NSTimer in cui nessuna delle altre risposte ha risolto il problema, è davvero importante menzionare che, se si utilizza una classe che non eredita da NSObject direttamente o in profondità nella gerarchia ( ad es. file rapidi creati manualmente), nessuna delle altre risposte funzionerà anche se specificato come segue:

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

Senza cambiare nient'altro che il fatto di ereditare la classe da NSObject, ho smesso di ottenere l'errore "Selettore non riconosciuto" e ho fatto funzionare la mia logica come previsto.


Il problema con questa alternativa è che non è possibile modificare una classe (diciamo ViewController) per ereditarla da NSObject, dato che è necessaria l'implementazione della classe ViewController (ad esempio viewDidLoad ()). Qualche idea su come chiamare una funzione Swift all'interno di un ViewController usando NSTimer? ... e
eharo2

1
UIViewController eredita già da NSObject, la maggior parte delle classi esposte dall'SDK lo fanno, questo esempio è per le tue classi create che richiedono la funzionalità NSTimer ...
Martin Cazares

14

Se si desidera passare un parametro alla funzione da NSTimer, ecco la soluzione:

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

Includi i due punti nel testo del selettore (tester :) e i tuoi parametri vanno in userInfo.

La tua funzione dovrebbe prendere NSTimer come parametro. Quindi estrarre userInfo per ottenere il parametro passato.


3
Stavo usando NSTimer (0.01, target: self, ...) che NON funzionava, mentre usando NSTimer.scheduledTimerWithTimeInterval (0.01, ..) DID work !? Strano ma grazie @Scooter per la tua risposta!
iOS-Coder

1
@ iOS-Coder semplicemente creando un timer con l'inizializzatore non lo aggiunge a un runloop, mentre scheduledTimerWith...lo aggiunge automaticamente al runloop corrente - quindi qui non c'è alcun comportamento strano;)
David Ganster,

1
@ David grazie per il tuo suggerimento. Immagino che il mio malinteso debba appartenere alla categoria STFW o RTFA (Leggi l'API F ... ing)?
iOS-Coder

1
Non ti preoccupare, nessuno si può aspettare di leggere la documentazione su ogni singolo metodo in ogni API;)
David Ganster,

10

I selettori sono una rappresentazione interna di un nome di metodo in Objective-C. In Objective-C "@selector (methodName)" converte un metodo di codice sorgente in un tipo di dati di SEL. Dal momento che non è possibile utilizzare la sintassi @selector in Swift (il rickster è sul punto lì), è necessario specificare manualmente il nome del metodo come oggetto String o passando un oggetto String al tipo Selettore. Ecco un esempio:

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

o

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

8

Swift 4.1
Con un esempio di gesto del tocco

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

Consulta il documento di Apple per maggiori dettagli su: Selector Expression


Per favore, smetti di farlo. Aiuta nessuno. In che modo differisce da Swift 3.1? E perché hai ritenuto necessario aggiungere un'altra risposta a questa quando ha già circa 20 risposte?
Fogmeister,

il selettore di chiamata è diverso in swift 4. Prova queste risposte in swift 4 e vedi. Nessuno di questi funzionerà senza modifiche. Ti preghiamo di non contrassegnare alcuna dichiarazione come spam senza assicurarne l'impotenza
Krunal

Quindi c'è qualche motivo per cui non è possibile modificare la risposta esistente accettata? Lo renderebbe effettivamente utile piuttosto che aggiungere alla fine di un lungo elenco di risposte. Il pulsante "Modifica" è lì per un motivo.
Fogmeister,

Inoltre, quale parte di questo è diversa da Swift 3?
Fogmeister,

2
Devi aggiungere il tag objc a tutti i selettori per Swift 4. Questa è la risposta corretta. E non dovresti modificare le risposte degli altri per cambiarne il significato. @Krunal ha perfettamente ragione.
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
}

sarebbe stato più bello ed educativo se tu avessi un esempio prendendo due o tre argomenti anche per Swift3 o Swift4. Grazie.
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)

// Aggiorna metodo di controllo

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

}

5

Dal momento che Swift 3.0 è stato pubblicato, è persino un po 'più sottile dichiarare appropriata un'azione target

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

Quando si usa performSelector()

/addtarget()/NStimer.scheduledTimerWithInterval() metodi il metodo (corrispondente al selettore) deve essere contrassegnato come

@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 () {.
}

Per Swift 2.2, è necessario scrivere '#selector ()' invece del nome della stringa e del selettore in modo che le possibilità di errori di ortografia e crash dovute a questo non siano più presenti. Di seguito è riportato un esempio

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

3

crei il Selettore come sotto.
1.

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

2.

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

Si noti che la sintassi @selector è scomparsa e sostituita con una semplice stringa che nomina il metodo da chiamare. C'è un'area in cui possiamo essere tutti d'accordo sul fatto che la verbosità abbia ostacolato. Naturalmente, se dichiarassimo che esiste un metodo target chiamato flatButtonPressed: è meglio scriverne uno:

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

imposta il timer:

    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

Per essere completo, ecco flatButtonPressed

func flatButtonPressed(timer: NSTimer) {
}

Hai qualche fonte per " Prendi nota che la sintassi @selector è sparita "?
winklerrr,

3

Ho trovato molte di queste risposte utili, ma non era chiaro come farlo con qualcosa che non era un pulsante. Stavo aggiungendo un riconoscitore di gesti a un UILabel in modo rapido e faticoso, quindi ecco quello che ho trovato ha funzionato per me dopo aver letto tutto sopra:

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

Dove il "Selettore" è stato dichiarato come:

func labelTapped(sender: UILabel) { }

Nota che è pubblico e che non sto usando la sintassi Selector () ma è possibile farlo anche.

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

3

L'utilizzo di #selector verificherà il codice al momento della compilazione per assicurarsi che il metodo che desideri chiamare esista effettivamente. Ancora meglio, se il metodo non esiste, otterrai un errore di compilazione: Xcode rifiuterà di costruire la tua app, bandendo così l'oblio di un'altra possibile fonte di bug.

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

Può essere utile annotare dove si imposta il controllo che innesca l'azione.

Ad esempio, ho scoperto che durante l'impostazione di UIBarButtonItem, dovevo creare il pulsante in viewDidLoad, altrimenti avrei ottenuto un'eccezione del selettore non riconosciuta.

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è una parola dal Objective-Cmondo e sei in grado di usarlo da Swiftper avere la possibilità di chiamare Objective-Cda Swift Ti permette di eseguire del codice in fase di esecuzione

Prima che Swift 2.2la sintassi sia:

Selector("foo:")

Poiché un nome di funzione viene passato Selectorcome Stringparametro ("pippo"), non è possibile controllare un nome in fase di compilazione . Di conseguenza è possibile ottenere un errore di runtime:

unrecognized selector sent to instance

Dopo Swift 2.2+la sintassi è:

#selector(foo(_:))

Il completamento automatico di Xcode ti aiuta a chiamare il metodo giusto


1

Cambia come una semplice denominazione di stringa nel metodo che richiede la sintassi del selettore

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

Successivamente, digita func test ().


0

Per Swift 3

// Codice di esempio per creare un timer

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.

0

Selettore in Swift 4:

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

-1

Per rapido 3

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

Dichiarazione di funzione Nella stessa classe:

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

Se il bersaglio è sé, non è necessario averlo selfnel selettore. Questo dovrebbe essere sufficiente:let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
Mithra Singam il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.