Alternative à performer dans Swift?

La famille de méthodes performSelector n’est pas disponible dans Swift . Alors, comment pouvez-vous appeler une méthode sur un object @objc , où la méthode à appeler est choisie lors de l’exécution et non connue au moment de la compilation? NSInvocation n’est apparemment pas non plus disponible dans Swift.

Je sais que dans Swift, vous pouvez envoyer n’importe quelle méthode (pour laquelle une déclaration de méthode @objc visible) au type AnyObject , similaire à id dans Objective-C. Cependant, vous devez toujours coder en dur le nom de la méthode à la compilation. Existe-t-il un moyen de le choisir dynamicment à l’exécution?

Utiliser des fermetures

 class A { var selectorClosure: (() -> Void)? func invoke() { self.selectorClosure?() } } var a = A() a.selectorClosure = { println("Selector called") } a.invoke() 

Notez que ceci n’est pas nouveau, même dans Obj-C, les nouvelles API préfèrent utiliser des blocs sur performSelector (comparez UIAlertView qui utilise UIAlertView respondsToSelector: et performSelector: pour appeler des méthodes déléguées, avec le nouveau UIAlertController ).

Utiliser performSelector: est toujours dangereux et ne joue pas bien avec ARC (d’où les avertissements ARC pour performSelector: .

À partir de Xcode 7, la famille complète des méthodes performSelector est disponible dans Swift, notamment performSelectorOnMainThread() et performSelectorInBackground() . Prendre plaisir!

Approche A

Utilisez NSThread.detachNewThreadSelector , une bonne chose à propos de cette approche est que nous pouvons attacher un object au message. Exemple de code dans 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)") } 

Journal de la console:

monde de salutations

object attaché: soleil

Approche B

Cette alternative a été découverte plus tôt, j’ai également testé sur appareil et simulateur. L’idée est d’utiliser la méthode suivante de UIControl :

 func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!) 

Exemple de code dans 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") } 

Journal de la console:

monde de salutations

Approche C

NSTimer

 class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval, target target: AnyObject!, selector aSelector: Selector, userInfo userInfo: AnyObject!, repeats repeats: Bool) -> NSTimer! 

Swift 3

perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])

Comme pour @JTerry, répondez “Vous n’avez pas besoin de sélecteurs dans Swift”, vous pouvez affecter des méthodes réelles aux variables. Ma solution était la suivante (j’avais besoin d’un paramètre dans la méthode):

 class SettingsMenuItem: NSObject { ... var tapFunction: ((sender: AnyObject?) -> ())? } 

Et puis en vue contrôleur j’ai déclaré, assigné et exécuté la fonction de cette manière:

 class SettingsViewController: UITableViewController { func editProfile(sender: AnyObject?) { ... } ... menuItem.tapFunction = editProfile ... if let tapFunction = menuItem.tapFunction { tapFunction(sender: self) } } 

Vous pouvez l’utiliser dans Swift

 var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false) func someSelector() { // Something after a delay } 

par ceci vous pouvez faire ce qui est fait par performSelector dans Objective-C

Je me débattais avec ça aussi. J’ai finalement réalisé que je n’avais pas besoin d’utiliser de cibles ou de sélecteurs. Pour moi, la solution consistait à atsortingbuer le func à une variable et à appeler cette variable. Cela fonctionne même si vous l’appelez d’autres classes. Voici un exemple rapide:

 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(); 

Swift 3.1
Pour les projets standards Swift, les fermetures sont une solution élégante déjà couverte par la réponse de Sulthan . Invoquer des méthodes de manière dynamic à l’aide de noms de chaînes de sélecteurs est logique si l’on dépend d’anciennes bibliothèques / codes Objective-C.

Seules les sous-classes NSObject peuvent recevoir des messages, toute tentative d’en envoyer une à une classe Swift pure entraînera un blocage.

#selector(mySelectorName) peut résoudre les noms de sélecteur typés dans son fichier source de classe uniquement.
En sacrifiant la vérification de type, un sélecteur peut être récupéré à l’aide de NSSelectorFromSsortingng(...)
( Ce n’est pas plus sûr que Selector("selectorName:arg:") il ne se produit pas un avertissement ).

Appel de la méthode d’instance de sous-classe NSObject

 let instance : NSObject = fooReturningObjectInstance() as! NSObject instance.perform(#selector(NSSelectorFromSsortingng("selector")) instance.perform(#selector(NSSelectorFromSsortingng("selectorArg:"), with: arg) instance.perform(#selector(NSSelectorFromSsortingng("selectorArg:Arg2:"), with: arg, with: arg2) 

également avec la variante principale de fil:

 instance.performSelector(onMainThread: NSSelectorFromSsortingng("selectorArg:"), with: arg, waitUntilDone: false) 

Comme noté par iOS_MIB dans https://stackoverflow.com/a/48644264/5329717, cela n’est pas équivalent à

 DispatchQueue.main.async { //perform selector } 

et variante de thread d’arrière-plan:

 instance.performSelector(inBackground: NSSelectorFromSsortingng("selectorArg:"), with: arg) 

Il y a cependant des limitations:

  • Il ne peut prendre que 0-2 arguments
  • les arguments de type valeur comme les entiers et les sélecteurs ne fonctionnent pas
  • ne peut pas gérer les types de valeur retournés
  • renvoie des objects comme non Unmanaged

Cette approche à faible effort est donc pratique lorsque les arguments de retour et de type de valeur ne sont pas nécessaires.

NSObject méthode d’exécution NSObject IMP permet de créer un appel typé avec des arguments et un type de retour appropriés. @convention(c)(types)->type permet de convertir le résultat IMP en fonction de fermeture Swift compatible.

Dans @convention(c) tous les types ne sont pas autorisés

  • Pour les classes, utilisez Any ou AnyClass
  • Pour les objects, utilisez un type de classe Any ou exact si son symbole est disponible
  • Pour les types de valeur, utilisez le type approprié
  • Pour void *, utilisez OpaquePointer

Ceci est par définition dangereux et si cela est fait de manière incorrecte, cela se traduira par des accidents et des effets secondaires.

Chaque méthode Objective-C au niveau C contient deux arguments cachés pour se conformer à objc_msgSend(id self, SEL op, ...) qui doivent être inclus dans le type de fonction comme @convention(c)(Any?,Selector, ... )

 let instance : NSObject = fooReturningObjectInstance() as! NSObject let selector : Selector = NSSelectorFromSsortingng("selectorArg:") let methodIMP : IMP! = instance.method(for: selector) unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?)->Void).self)(instance,selector,arg) 

Ce sont des équivalents statiques de perform(...)

 NSObject.perform(NSSelectorFromSsortingng("selector")) NSObject.perform(NSSelectorFromSsortingng("selectorArg:"), with: arg) NSObject.perform(NSSelectorFromSsortingng("selectorArg:Arg2:"), with: arg, with: arg2) NSObject.performSelector(onMainThread: NSSelectorFromSsortingng("selectorArg:"), with: arg, waitUntilDone: false) NSObject.performSelector(inBackground: NSSelectorFromSsortingng("selectorArg:"), with: arg) 

Limites:

  • Tous les problèmes de type mentionnés précédemment
  • La classe de récepteur doit avoir un symbole défini

Récupération de la méthode statique d’exécution et des types de traitement, @convention(c) s’applique

 let receiverClass = NSClassFromSsortingng("MyClass") let selector : Selector = NSSelectorFromSsortingng("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 

Il n’y a aucune raison pratique de le faire, mais objc_msgSend peut être utilisé dynamicment.

 let instance : NSObject = fooReturningObjectInstance() as! NSObject let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW) let selector : Selector = NSSelectorFromSsortingng("selectorArg:") unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(instance,selector,arg) dlclose(handle) 

Même chose pour NSInvocation (ce n’est qu’un exercice amusant, ne le faites pas )

 class Test : NSObject { var name : Ssortingng? { didSet { NSLog("didSetCalled") } } func invocationTest() { let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromSsortingng("NSInvocation"), NSSelectorFromSsortingng("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromSsortingng("NSInvocation"),NSSelectorFromSsortingng("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromSsortingng("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromSsortingng("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject unsafeBitCast(class_getMethodImplementation(NSClassFromSsortingng("NSInvocation"), NSSelectorFromSsortingng("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromSsortingng("setSelector:"),#selector(setter:name)) var localName = name withUnsafePointer(to: &localName) { unsafeBitCast(class_getMethodImplementation(NSClassFromSsortingng("NSInvocation"), NSSelectorFromSsortingng("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromSsortingng("setArgument:atIndex:"), OpaquePointer($0),2) } invocation.perform(NSSelectorFromSsortingng("invokeWithTarget:"), with: self) } } 

Huh, nous pouvons utiliser swizzling pour dévoiler les méthodes souhaitées!

Ajoutez simplement cette extension et 🚀 tous les appels avec le symbole 🚀 .

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

la syntaxe réelle de la queue d’envoi est la suivante.

 dispatch_after(1, dispatch_get_main_queue()) { () -> Void in self.loadData() // call your method. } 

Je ne sais pas exactement depuis quand, mais Apple a ramené performSelector dans Xcode 7.1.1 (du moins, c’est la version que j’utilise).

Dans mon application que je suis en train de construire, j’appelle diverses fonctions avec des noms de fonctions similaires dans un UIView généré à partir de CoreAnimator (super app, BTW), donc performSelector est très pratique. Voici comment je l’utilise:

 //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 = NSSelectorFromSsortingng(AnimMethod) //calls the said function in UIView named "meter" meter.performSelector(selector) 

Parfois (en particulier si vous utilisez target/action modèle target/action ), vous devrez peut-être utiliser la méthode -[UIApplication sendAction:to:from:forEvent:] (pour iOS). Dans Swift, cela peut être quelque chose comme ceci:

 UIApplication.sharedApplication() .sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent) 

J’utilise la solution suivante:

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

J’ai une situation où le sélecteur est construit avec un littéral de chaîne qui vient du fichier plist. Donc, le moyen le plus rapide d’effectuer un sélecteur dans swift a été résolu avec le code suivant

 var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someSsortingng), userInfo: nil, repeats: false) timer.fire() timer.invalidate() 

Un exemple concret dans la rapidité du commentaire de “Matej Ukmar” à la réponse de “J Terry”:

 class Button { var title:Ssortingng = "The big button" var selector: ((sender: AnyObject?, type:Ssortingng) -> ())?/*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:Ssortingng) { switch type{ case "click": Swift.print("View.handleSelector() sender: " + Ssortingng(sender!.dynamicType) + ", title: " + Ssortingng((sender as! Button).title) + ", type: " + type) case "hover": Swift.print("View.handleSelector() sender: " + Ssortingng(sender!.dynamicType) + ", title: " + Ssortingng((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 

juste une autre entrée pour ce sujet.

De temps en temps, je devais appeler des fonctions / méthodes “indirectement”. Exemple: appel de fonctions individuelles pour des cellules spécifiques. J’utilise souvent des tableaux de structures pour définir le comportement de tabelView.

J’ai déjà utilisé PerformSelector , mais cela semble toujours “étrange” dans un programme rapide, j’ai donc fait des recherches et depuis lors, j’ai utilisé les appels de fonction indirects.

Ceci est un exemple rapide de mon terrain de jeu pour tester la syntaxe et le comportement … (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: Ssortingng // a ssortingng 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: Ssortingng, _ 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) } 

donne la sortie:

 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 

fait amusant: l'impression de la chaîne (quoi) est plus lente que l'appel de la fonction avec une impression ... Ce qui est aussi un avertissement: ne faites pas confiance à la séquence avec cette tactique