¿Alternativa para realizarSelector en Swift?
performselector (16)
Enfoque A
Use NSThread.detachNewThreadSelector
, lo bueno de este enfoque es que podemos adjuntar un objeto al mensaje. Código de ejemplo en ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delay = 2.0 * Double(NSEC_PER_SEC)
var time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
NSThread.detachNewThreadSelector(Selector("greetings:"), toTarget:self, withObject: "sunshine")
})
}
func greetings(object: AnyObject?) {
println("greetings world")
println("attached object: /(object)")
}
Registro de consola:
saludos mundo
objeto adjunto: sol
Enfoque B
Esta alternativa fue descubierta anteriormente, también he probado en dispositivos y simuladores. La idea es usar el siguiente método de UIControl :
func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!)
Código de ejemplo en ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var control: UIControl = UIControl()
control.sendAction(Selector("greetings"), to: self, forEvent: nil) // Use dispatch_after to invoke this line as block if delay is intended
}
func greetings() {
println("greetings world")
}
Registro de consola:
saludos mundo
Enfoque C
NSTimer
class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
target target: AnyObject!,
selector aSelector: Selector,
userInfo userInfo: AnyObject!,
repeats repeats: Bool) -> NSTimer!
La familia de métodos performSelector
no está disponible en Swift . Entonces, ¿cómo se puede llamar a un método en un objeto @objc
, donde el método a ser llamado se elige en el tiempo de ejecución, y no se conoce en tiempo de compilación? NSInvocation
aparentemente no está disponible en Swift.
Sé que en Swift, puede enviar cualquier método (para el cual exista una declaración del método @objc
visible) al tipo AnyObject
, similar a id
en Objective-C. Sin embargo, eso todavía requiere que codifique el nombre del método en tiempo de compilación. ¿Hay alguna forma de elegirlo dinámicamente en tiempo de ejecución?
A partir de Xcode 7, la familia completa de métodos performSelector está disponible en Swift, incluyendo performSelectorOnMainThread()
y performSelectorInBackground()
. ¡Disfrutar!
A veces (especialmente si está utilizando target/action
patrón de target/action
) puede que tenga que usar el método -[UIApplication sendAction:to:from:forEvent:]
(para iOS), por lo que en Swift puede haber -[UIApplication sendAction:to:from:forEvent:]
como esta:
UIApplication.sharedApplication()
.sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)
Estoy usando la siguiente solución:
// method will be called after delay
func method1() {
......
}
// to replace performSelector
// delay 100 ms
let time : dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC/(USEC_PER_SEC*10)))
dispatch_after(time, dispatch_get_main_queue(), {
self.method1()
})
Huh, podemos usar Swizzling para revelar los métodos deseados.
Simplemente agregue esta extension
y prefija todas las llamadas con el símbolo 🚀
.
import Foundation
private var dispatchOnceToken: dispatch_once_t = 0
private var selectors: [Selector] = [
"performSelector:",
"performSelector:withObject:",
"performSelector:withObject:withObject:",
"performSelector:withObject:afterDelay:inModes:",
"performSelector:withObject:afterDelay:",
]
private func swizzle() {
dispatch_once(&dispatchOnceToken) {
for selector: Selector in selectors {
let 🚀selector = Selector("🚀/(selector)")
let method = class_getInstanceMethod(NSObject.self, selector)
class_replaceMethod(
NSObject.self,
🚀selector,
method_getImplementation(method),
method_getTypeEncoding(method)
)
}
}
}
extension NSObject {
func 🚀performSelector(selector: Selector) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object)
}
func 🚀performSelector(selector: Selector, withObject object1: AnyObject?, withObject object2: AnyObject?) -> AnyObject? {
swizzle()
return self.🚀performSelector(selector, withObject: object1, withObject: object2)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval, inModes modes: [AnyObject?]?) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay, inModes: modes)
}
func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval) {
swizzle()
self.🚀performSelector(selector, withObject: object, afterDelay: delay)
}
}
No sé exactamente desde cuándo, pero Apple trajo de vuelta performSelector en Xcode 7.1.1 (Al menos esa es la versión que estoy usando).
En mi aplicación que estoy construyendo actualmente, estoy llamando a varias funciones con funciones similares en una UIView generada desde CoreAnimator (gran aplicación, BTW), así que performSelector es muy útil. Así es como lo uso:
//defines the function name dynamically. the variables "stepN" and "dir" are defined elsewhere.
let AnimMethod = "addStep/(stepN)/(dir)Animation"
//prepares the selector with the function name above
let selector: Selector = NSSelectorFromString(AnimMethod)
//calls the said function in UIView named "meter"
meter.performSelector(selector)
Puedes usar esto en Swift
var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
con esto puedes hacer lo que realiza performSelector en Objective-C
Según la respuesta de @JTerry "No necesitas selectores en Swift", puedes asignar métodos reales a las variables. Mi solución fue la siguiente (necesitaba un parámetro en el método):
class SettingsMenuItem: NSObject {
...
var tapFunction: ((sender: AnyObject?) -> ())?
}
Y luego, en el controlador de vista, declaré, asigné y ejecuté la función de esta manera:
class SettingsViewController: UITableViewController {
func editProfile(sender: AnyObject?) {
...
}
...
menuItem.tapFunction = editProfile
...
if let tapFunction = menuItem.tapFunction {
tapFunction(sender: self)
}
}
Tengo una situación, donde selector se construye con string literal que proviene de un archivo plist. Por lo tanto, la forma más rápida de realizar algún selector en forma rápida se resolvió con el siguiente código
var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()
Un ejemplo del mundo real a toda velocidad del comentario de "Matej Ukmar" a la respuesta de "J Terry":
class Button {
var title:String = "The big button"
var selector: ((sender: AnyObject?, type:String) -> ())?/*this holds any method assigned to it that has its type signature*/
func click(){
selector!(sender: self,type: "click")/*call the selector*/
}
func hover(){
selector!(sender: self,type: "hover")/*call the selector*/
}
}
class View {
var button = Button()
init(){
button.selector = handleSelector/*assign a method that will receive a call from the selector*/
}
func handleSelector(sender: AnyObject?,type:String) {
switch type{
case "click": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
case "hover": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
default:break;
}
}
}
let view:View = View()
view.button.click()/*Simulating button click*/
view.button.hover()/*Simulating button hover*/
//Output: View.handleSelector() sender: Button, title: The big button, type: click
//Output: View.handleSelector() sender: Button, title: The big button, type: hover
Usando cierres
class A {
var selectorClosure: (() -> Void)?
func invoke() {
self.selectorClosure?()
}
}
var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
Tenga en cuenta que esto no es nada nuevo, incluso en Obj-C, las nuevas API prefieren usar bloques sobre performSelector
(compare UIAlertView
que usa respondsToSelector:
y performSelector:
para llamar a métodos delegados, con el nuevo UIAlertController
).
Usar performSelector:
siempre es inseguro y no funciona bien con ARC (de ahí las advertencias de ARC para performSelector:
.
Yo estaba luchando con esto también. Finalmente me di cuenta de que no necesitaba usar objetivos o selectores. Para mí, la solución fue asignar el func a una variable y llamar a esa variable. Incluso funciona si lo llamas desde otras clases. Aquí hay un ejemplo rápido:
func Apple() ->Int
{
let b = 45;
return b;
}
func Orange()->Int
{
let i = 5;
return i;
}
func Peach()
{
var a = Apple; // assign the var a the Apple function
var b = Orange; // assisgn the var b to the Orange function
let c = a(); // assign the return value of calling the ''a'' or Apple function to c
let d = b(); // assign the return value of calling the ''b'' or Orange function d
Pear(a, b)
}
func Pear(x:()->Int, y:()->Int)->Int
{
let w = (x()+y()); // call the x function, then the y function and add the return values of each function.
return w; // return the sum
}
Peach();
la sintaxis real para la cola de despacho es la siguiente.
dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
self.loadData() // call your method.
}
solo otra entrada para ese tema.
De vez en cuando tenía que llamar funciones / métodos "indirectamente". Ejemplo: llamar funciones individuales para celdas específicas. A menudo utilizo matrices de estructuras para definir el comportamiento de tabelView.
Utilicé PerformSelector, etc., pero eso siempre parece "extraño" en un programa rápido, así que investigué un poco y, desde entonces, utilicé las llamadas a funciones indirectas.
Este es un ejemplo rápido de mi patio de recreo para probar la sintaxis y el comportamiento ... (xCode 9.4.1)
// Test for indirect function calls
// ------------------------------------------------------------------------
// functions we want to call inderectly
func function1() {
print("function1 active")
}
func function2() {
print("function2 active")
}
func function3() {
print("function3 active")
}
func function4(_ parameter: Int) {
print("function4 use the parameter: /(parameter)")
}
// ------------------------------------------------------------------------
// data structures
// a struct to build array items
struct functionCallTestStruct {
// struct properties
let what: String // a string as an example for other variables
let functionToCall : () // the function as an array element
var functionWithParameter : (Int) -> () // the function as an array element
let parameterForFunction : Int
// Initializer
init(_ what: String,
_ functionToCall: (),
_ functionWithParameter: @escaping (Int) -> (),
_ parameterForFunction: Int) {
self.what = what
self.functionToCall = functionToCall
self.functionWithParameter = functionWithParameter
self.parameterForFunction = parameterForFunction
}
}
// the array which holds the functions we want to call
let functionTestArray : [functionCallTestStruct] = [
functionCallTestStruct("We will call the first function", function1(), function4(_:), 10),
functionCallTestStruct("We will call the second function", function2(), function4(_:), 11),
functionCallTestStruct("We will call the third function", function3(), function4(_:), 12),
]
// ------------------------------------------------------------------------
// Test program
// a loop over the array
for i in 0 ..< functionTestArray.count {
// print explanation (be aware: print is quite lame, .. see the output ;-))
print(functionTestArray[i].what)
// and with this we indirectly call the functions
functionTestArray[i].functionToCall
let myParameter = functionTestArray[i].parameterForFunction
functionTestArray[i].functionWithParameter(myParameter)
}
da la salida:
function1 active
function2 active
function3 active
We will call the first function
function4 use the parameter: 10
We will call the second function
function4 use the parameter: 11
We will call the third function
function4 use the parameter: 12
hecho curioso: la impresión de la cadena (qué) es más lenta que la llamada a la función con una impresión ... que también es una advertencia: no confíe en la secuencia con esta táctica
Swift 3.1
Para los proyectos estándar de Swift, los cierres son una solución elegante que ya está cubierta en la respuesta de Sulthan . Invocar métodos dinámicamente usando nombres de cadenas de selector tiene sentido si uno depende del código / bibliotecas Objective-C heredado.
Solo NSObject
subclases de NSObject
pueden recibir mensajes, e intentar enviar uno a una clase Swift pura dará como resultado un bloqueo.
#selector(mySelectorName)
puede resolver los nombres de los selectores tipeados en su archivo fuente de clase solamente.
Al sacrificar la verificación de tipos, se puede recuperar un selector usando NSSelectorFromString(...)
( no es más seguro en comparación con Selector("selectorName:arg:")
simplemente no genera una advertencia ).
Llamando NSObject
método de instancia de la subclase NSObject
let instance : NSObject = fooReturningObjectInstance() as! NSObject
instance.perform(#selector(NSSelectorFromString("selector"))
instance.perform(#selector(NSSelectorFromString("selectorArg:"), with: arg)
instance.perform(#selector(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
también con la variante de hilo principal:
instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
Como lo señala iOS_MIB en https://.com/a/48644264/5329717 esto no es equivalente a
DispatchQueue.main.async {
//perform selector
}
y variante de hilo de fondo:
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Sin embargo, hay algunas limitaciones:
- Solo puede tomar 0-2 argumentos
- los argumentos de tipo de valor como enteros y selectores no funcionan
- no puede manejar tipos de valores devueltos
- devuelve objetos como
Unmanaged<AnyObject>
Por lo tanto, este enfoque de bajo esfuerzo es conveniente cuando los argumentos de resultado de devolución y tipo de valor no son necesarios.
NSObject
método de tiempo de ejecución NSObject
IMP
permite realizar una llamada tipeada con los argumentos adecuados y el tipo de retorno. @convention(c)(types)->type
permite convertir el resultado de IMP
a la función de cierre de Swift compatible.
En @convention(c)
no se permiten todos los tipos
- Para las clases usa Any or AnyClass
- Para objetos, use Cualquiera o el tipo de clase exacto si su símbolo está disponible
- Para tipos de valor use el tipo relevante
- Para void * use OpaquePointer
Esto es, por definición, inseguro y, si se hace incorrectamente, provocará bloqueos y efectos secundarios.
Cada método de Objective-C en el nivel C contiene dos argumentos ocultos para ajustarse a objc_msgSend(id self, SEL op, ...)
que deben incluirse en el tipo de función como @convention(c)(Any?,Selector, ... )
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = instance.method(for: selector)
unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?)->Void).self)(instance,selector,arg)
Estos son equivalentes estáticos de perform(...)
NSObject.perform(NSSelectorFromString("selector"))
NSObject.perform(NSSelectorFromString("selectorArg:"), with: arg)
NSObject.perform(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
NSObject.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
NSObject.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Limitaciones
- Todos los problemas de tipo mencionados anteriormente
- La clase de receptor debe tener un símbolo definido
Obtención del método estático de tiempo de ejecución IMP
y tipos de manejo, se @convention(c)
let receiverClass = NSClassFromString("MyClass")
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = method_getImplementation(class_getClassMethod(receiverClass, selector))
let result : NSObject = unsafeBitCast(methodIMP,to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(receiverClass,selector,arg) as! NSObject
No hay ninguna razón práctica para hacerlo, pero objc_msgSend
se puede usar de forma dinámica.
let instance : NSObject = fooReturningObjectInstance() as! NSObject
let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW)
let selector : Selector = NSSelectorFromString("selectorArg:")
unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(instance,selector,arg)
dlclose(handle)
Lo mismo para NSInvocation
(esto es solo ejercicio divertido, no lo hagas)
class Test : NSObject
{
var name : String? {
didSet {
NSLog("didSetCalled")
}
}
func invocationTest() {
let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
var localName = name
withUnsafePointer(to: &localName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
Swift 3
perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])