@selector () en Swift?

660

Estoy intentando crear una NSTimeren Swiftpero estoy teniendo algunos problemas.

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

test() es una función en la misma clase.


Me sale un error en el editor:

No se pudo encontrar una sobrecarga para 'init' que acepte los argumentos proporcionados

Cuando cambio selector: test()a selector: nilel error desaparece.

He intentado:

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

Pero nada funciona y no puedo encontrar una solución en las referencias.

Arbitur
fuente
11
selector: test()llamaría testy pasaría su valor de retorno al selectorargumento.
Michael Dorst

Respuestas:

930

Swift en no usa selectores: varios patrones de diseño que en Objective-C hacen uso de selectores funcionan de manera diferente en Swift. (Por ejemplo, el uso opcional en el encadenamiento de los tipos de protocolos o is/ aspruebas en lugar de respondsToSelector:, y cierres de uso siempre que sea posible en lugar de performSelector:para un mejor tipo / seguridad de la memoria).

Pero todavía hay una serie de API importantes basadas en ObjC que usan selectores, incluidos los temporizadores y el patrón de objetivo / acción. Swift proporciona el Selectortipo para trabajar con estos. (Swift usa esto automáticamente en lugar del SELtipo de ObjC ).

En Swift 2.2 (Xcode 7.3) y versiones posteriores (incluidos Swift 3 / Xcode 8 y Swift 4 / Xcode 9):

Puede construir un Selectortipo de función Swift utilizando la #selectorexpresión.

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)

¿Lo bueno de este enfoque? El compilador Swift verifica una referencia de función, por lo que puede usar la #selectorexpresión solo con pares de clase / método que realmente existen y son elegibles para usar como selectores (consulte "Disponibilidad de selectores" a continuación). También es libre de hacer que su referencia de función sea tan específica como lo necesite, según las reglas de Swift 2.2+ para nombres de tipo de función .

(Esto es realmente una mejora con respecto a la @selector()directiva de ObjC , porque la -Wundeclared-selectorverificación del compilador solo verifica que el selector con nombre existe. La referencia de la función Swift que pasa a la #selectorexistencia de cheques, membresía en una clase y firma de tipo).

Hay un par de advertencias adicionales para las referencias de funciones que pasa a la #selectorexpresión:

  • Las funciones múltiples con el mismo nombre base se pueden diferenciar por sus etiquetas de parámetros utilizando la sintaxis mencionada anteriormente para las referencias de funciones (por ejemplo, insertSubview(_:at:)vs insertSubview(_:aboveSubview:)). Pero si una función no tiene parámetros, la única forma de desambiguarla es usar una asconversión con la firma de tipo de la función (por ejemplo, foo as () -> ()vs foo(_:)).
  • Hay una sintaxis especial para los pares getter / setter de propiedad en Swift 3.0+. Por ejemplo, dado a var foo: Int, puede usar #selector(getter: MyClass.foo)o #selector(setter: MyClass.foo).

Notas generales:

Casos en los #selectorque no funciona y nombres: a veces no tiene una referencia de función para hacer un selector (por ejemplo, con métodos registrados dinámicamente en el tiempo de ejecución de ObjC). En ese caso, puede construir un a Selectorpartir de una cadena: por ejemplo Selector("dynamicMethod:"), aunque pierde la comprobación de validez del compilador. Cuando haga eso, debe seguir las reglas de nomenclatura de ObjC, incluidos los dos puntos ( :) para cada parámetro.

Disponibilidad del selector: el método al que hace referencia el selector debe estar expuesto al tiempo de ejecución ObjC. En Swift 4, cada método expuesto a ObjC debe tener su declaración precedida por el @objcatributo. (En versiones anteriores obtuviste ese atributo gratis en algunos casos, pero ahora tienes que declararlo explícitamente).

Recuerde que los privatesímbolos tampoco están expuestos al tiempo de ejecución: su método debe tener al menos internalvisibilidad.

Rutas clave: están relacionadas pero no son exactamente iguales a los selectores. También hay una sintaxis especial para estos en Swift 3: por ejemplo chris.valueForKeyPath(#keyPath(Person.friends.firstName)). Ver SE-0062 para más detalles. Y aún más KeyPathcosas en Swift 4 , así que asegúrate de estar usando la API correcta basada en KeyPath en lugar de selectores si es apropiado.

Puede leer más sobre los selectores en Interactuar con las API de Objective-C en Uso de Swift con Cocoa y Objective-C .

Nota: Antes de Swift 2.2, Selectorconforme a StringLiteralConvertible, por lo que puede encontrar código antiguo donde se pasan cadenas desnudas a las API que toman selectores. Deberá ejecutar "Convertir a sintaxis Swift actual" en Xcode para que los usen #selector.

rickster
fuente
8
Al poner una cadena con el nombre de la función funcionó, NSSelectorFromString () también funciona.
Arbitur
77
Me gustaría mencionar que si bien "Interactuar con las API de Objective-C" está en el sitio web, NO está en el libro 'El lenguaje de programación rápido'.
10
Esto probablemente debería mencionar que el selector necesita un ":" al final si toma un argumento. (Por ejemplo, test () -> "test" & test (this: String) -> "test:")
Daniel Schlaug el
2
También debe señalarse que los marcos de Cocoa esperan un nombre de método de estilo Objective-C. Si su método toma un argumento, necesitará un ':' si toma 2 argumentos size:andShape:, si se nombra el primer argumento puede necesitar un With, es decir, initWithData:parafunc init(Data data: NSData)
JMFR
66
¿Hay alguna forma de agregar validación al pasar el "selector" como una cadena? IE nos advierten compilador cuando escriben mal, etc.
yo.ian.g
86

Aquí hay un ejemplo rápido sobre cómo usar la Selectorclase en 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   
}

Tenga en cuenta que si el método pasado como una cadena no funciona, fallará en tiempo de ejecución, no en tiempo de compilación, y bloqueará su aplicación. Ten cuidado

Oscar Swanros
fuente
13
lo cual es horrible ... ¿hay un tipo de cosas "NSStringFromSelector"?
Lena Bru
11
No puedo creer que diseñó para los selectores sin marcar, ya que tenía esta objc
malhal
44
@malcomhall: @selectores útil, pero no se aplica tan formalmente como podría pensar. El "selector no declarado" es simplemente una advertencia del compilador, porque siempre se pueden introducir nuevos selectores en tiempo de ejecución. Sin embargo, las referencias de selector verificables / refactorables en Swift serían una buena solicitud de función .
rickster
2
Esta respuesta es útil, pero la respuesta a continuación con @objc es más apropiada.
cynistersix
Cuando pase la cadena del selector como variable o parámetro, deberá informar al compilador que es un selector utilizando la función Selector (). gracias
levous
46

Además, si su clase (Swift) no desciende de una clase Objective-C, entonces debe tener dos puntos al final de la cadena de nombre del método de destino y debe usar la propiedad @objc con su método de destino, por ejemplo

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

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

de lo contrario, recibirá un error "Selector no reconocido" en tiempo de ejecución.

usuario3771857
fuente
3
1. los selectores con dos puntos deben tomar un argumento 2. según las acciones del temporizador de documentos de Apple deben tomar el argumento NSTimer 3. la Selectorpalabra clave no es obligatoria. Entonces, en este caso, la firma debe ser@objc func method(timer: NSTimer) {/*code*/}
Yevhen Dubinin, el
@objctrabajó para mi. No necesitaba incluir timer: NSTimeren mi firma de método para que se llamara.
Mike Taverne
32

Swift 2.2+ y Swift 3 Update

Use la nueva #selectorexpresión, que elimina la necesidad de usar literales de cadena haciendo que el uso sea menos propenso a errores. Para referencia:

Selector("keyboardDidHide:")

se convierte

#selector(keyboardDidHide(_:))

Ver también: Propuesta de evolución rápida

Nota (Swift 4.0):

Si lo usa #selector, deberá marcar la función como@objc

Ejemplo:

@objc func something(_ sender: UIButton)

Kyle Clegg
fuente
26

Swift 4.0

creas el Selector como a continuación.

1.Agregue el evento a un botón como:

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

y la función será la siguiente:

@objc func clickedButton(sender: AnyObject) {

}
pravin salame
fuente
44
Olvidó poner @objcantes de lo funcque se requiere en Swift 4.
Vahid Amiri
24

Para los futuros lectores, descubrí que tuve un problema y recibí un unrecognised selector sent to instanceerror causado al marcar el objetivo funccomo privado.

El func DEBE ser visible públicamente para ser llamado por un objeto con referencia a un selector.

Rob Sanders
fuente
13
no tiene que ser público, aún puede mantener el método privado pero agregar objcantes de su declaración Ej: @objc private func foo() { ...entonces puedes usar "foo"como selector todo lo que quieras
apouche
También puede ser internal, por lo tanto, no especifica ningún modificador de acceso. A menudo uso este patrón://MARK: - Selector Methods\n extension MyController {\n func buttonPressed(_ button: UIButton) {
Sajjon
19

En caso de que alguien más tenga el mismo problema que tuve con NSTimer donde ninguna de las otras respuestas solucionó el problema, es realmente importante mencionar que, si está utilizando una clase que no hereda de NSObject, ya sea directamente o en lo profundo de la jerarquía ( por ejemplo, archivos rápidos creados manualmente), ninguna de las otras respuestas funcionará incluso cuando se especifica de la siguiente manera:

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

Sin cambiar nada más que hacer que la clase herede de NSObject, dejé de recibir el error "Selector no reconocido" y conseguí que mi lógica funcionara como se esperaba.

Martín Cazares
fuente
El problema con esta alternativa es que no puede cambiar una clase (digamos ViewController) para heredar de NSObject, dado que necesita cosas implementadas en la clase ViewController (por ejemplo, viewDidLoad ()). ¿Alguna idea de cómo llamar a una función Swift dentro de un ViewController usando NSTimer? ... e
eharo2
1
UIViewController ya hereda de NSObject, la mayoría de las clases expuestas por el SDK sí, este ejemplo es para sus propias clases creadas que requieren la funcionalidad NSTimer ...
Martin Cazares
14

Si desea pasar un parámetro a la función desde el NSTimer, aquí está su solución:

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

Incluya los dos puntos en el texto del selector (probador :), y sus parámetros irán en userInfo.

Su función debe tomar NSTimer como parámetro. Luego solo extraiga userInfo para obtener el parámetro que pasó.

Scooter
fuente
3
Estaba usando NSTimer (0.01, target: self, ...) que NO funcionó, mientras que usando NSTimer.scheduledTimerWithTimeInterval (0.01, ..) ¿FUNCIONÓ? Extraño pero gracias @Scooter por tu respuesta!
iOS-Coder
1
@ iOS-Coder simplemente crea un temporizador con el inicializador no lo agrega a un runloop, mientras que scheduledTimerWith...automáticamente lo agrega al runloop actual, por lo que no hay ningún comportamiento extraño aquí;)
David Ganster
1
@David gracias por tu sugerencia. ¿Supongo que mi malentendido debería pertenecer a la categoría STFW o RTFA (Leer la API de F ... ing)?
iOS-Coder
1
No se preocupe, nadie puede leer la documentación sobre cada método en cada API;)
David Ganster
10

Los selectores son una representación interna de un nombre de método en Objective-C. En Objective-C, "@selector (methodName)" convertiría un método de código fuente en un tipo de datos de SEL. Como no puede usar la sintaxis @selector en Swift (el rickster está en el punto allí), debe especificar manualmente el nombre del método como un objeto String directamente, o pasando un objeto String al tipo Selector. Aquí hay un ejemplo:

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")
)
Jon Tsiros
fuente
8

Swift 4.1
Con muestra de gesto de toque

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

Consulte el documento de Apple para obtener más detalles sobre: Expresión de selector

Krunal
fuente
Por favor deja de hacer esto. No ayuda a nadie. ¿En qué se diferencia esto de Swift 3.1? ¿Y por qué pensaste que era necesario agregar otra respuesta a esto cuando ya tiene alrededor de 20 respuestas?
Fogmeister
el selector de llamadas es diferente en swift 4. Pruebe estas respuestas en swift 4 y vea. Ninguno de estos funcionará sin editar. No marque ninguna declaración como spam sin garantizar su impotencia
Krunal
Entonces, ¿hay alguna razón por la que no pueda editar la respuesta aceptada existente? Lo haría realmente útil en lugar de agregar al final de una larga lista de respuestas. El botón "Editar" está ahí por una razón.
Fogmeister
Además, ¿qué parte de esto es diferente de Swift 3?
Fogmeister
2
Debe agregar la etiqueta objc a cualquier selector para Swift 4. Esta es la respuesta correcta. Y no debes editar las respuestas de otras personas para cambiar su significado. @ Krunal tiene toda la razón.
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
}
John Lee
fuente
Hubiera sido mejor y más educativo si tuvieras un ejemplo tomando dos o tres argumentos para Swift3 o Swift4 también. Gracias.
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)

// Actualizar método de control

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

}
Renish Dadhaniya
fuente
5

Dado que se publica Swift 3.0, es incluso un poco más sutil declarar un objetivo Acción apropiada

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")
        }
    }
}
LukeSideWalker
fuente
4

Cuando usas performSelector()

/addtarget()/NStimer.scheduledTimerWithInterval() métodos su método (que coincida con el selector) debe marcarse como

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

Para Swift 2.2, debe escribir '#selector ()' en lugar de la cadena y el nombre del selector para que las posibilidades de error de ortografía y bloqueo debido a eso ya no estén allí. A continuación se muestra un ejemplo

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

creas el Selector como a continuación.
1)

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

2)

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

Tenga en cuenta que la sintaxis @selector desapareció y se reemplazó con una cadena simple que nombra el método a llamar. Hay un área donde todos podemos estar de acuerdo en que la verbosidad se interpuso en el camino. Por supuesto, si declaramos que hay un método de destino llamado flatButtonPressed: mejor escribimos uno:

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

configurar el temporizador:

    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

Para completar, aquí está el flatButtonPressed

func flatButtonPressed(timer: NSTimer) {
}
Daxesh Nagar
fuente
¿Tiene alguna fuente para " Tomar nota de que la sintaxis @selector ha desaparecido "?
winklerrr
3

Encontré muchas de estas respuestas útiles, pero no estaba claro cómo hacer esto con algo que no era un botón. Estaba agregando un reconocedor de gestos a un UILabel rápidamente y luché, así que esto es lo que encontré que me funcionó después de leer todo lo anterior:

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

Donde el "Selector" fue declarado como:

func labelTapped(sender: UILabel) { }

Tenga en cuenta que es público y que no estoy usando la sintaxis Selector (), pero también es posible hacerlo.

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

El uso de #selector verificará su código en tiempo de compilación para asegurarse de que el método al que desea llamar realmente exista. Aún mejor, si el método no existe, obtendrá un error de compilación: Xcode se negará a construir su aplicación, por lo que se olvidará de otra posible fuente de errores.

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!")
    }
Desarrollador Swift
fuente
2

Puede ser útil tener en cuenta dónde configura el control que activa la acción.

Por ejemplo, descubrí que al configurar un UIBarButtonItem, tenía que crear el botón dentro de viewDidLoad o de lo contrario obtendría una excepción de selector no reconocida.

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")
}
Michael Peterson
fuente
2

selectores una palabra del Objective-Cmundo y puedes usarla Swiftpara tener la posibilidad de llamar Objective-Cdesde Swift Te permite ejecutar algún código en tiempo de ejecución

Antes de Swift 2.2la sintaxis es:

Selector("foo:")

Dado que el nombre de una función se pasa Selectorcomo Stringparámetro ("foo") no es posible verificar un nombre en tiempo de compilación . Como resultado, puede obtener un error de tiempo de ejecución:

unrecognized selector sent to instance

Después de Swift 2.2+la sintaxis es:

#selector(foo(_:))

El autocompletado de Xcode lo ayuda a llamar al método correcto

yoAlex5
fuente
1

Cambie como una simple cadena de nombres en el método que llama a la sintaxis del selector

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

Después de eso, escriba func test ().

Thar Htet
fuente
0

Para Swift 3

// Código de muestra para crear temporizador

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.
CrazyPro007
fuente
0

Selector en Swift 4:

button.addTarget(self, action: #selector(buttonTapped(sender:)), for: UIControlEvents.touchUpInside)
Mahesh Chaudhari
fuente
-1

Para Swift 3

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

Declaración de función en la misma clase:

@objc func test()
{
    // my function
} 
Mustajab Haider
fuente
Si el objetivo es propio, entonces no es necesario tenerlo selfen el selector. Esto debería ser suficiente:let timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(test), userInfo: nil, repeats: true)
Mithra Singam