web-dev-qa-db-fra.com

Alternative à performSelector dans Swift?

La famille performSelector de méthodes n'est pas disponible dans Swift . Alors, comment pouvez-vous appeler une méthode sur un objet @objc, où la méthode à appeler est choisie au moment de l’exécution et inconnue 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 il existe une déclaration de méthode @objc visible) au type AnyObject, similaire à id dans Objective-C. Cependant, cela nécessite toujours de coder en dur le nom de la méthode au moment de la compilation. Existe-t-il un moyen de le choisir dynamiquement au moment de l'exécution?

41
user102008

Utilisation de fermetures

class A {
    var selectorClosure: (() -> Void)?

    func invoke() {
        self.selectorClosure?()
    }
}

var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()

Notez que ce n'est pas nouveau, même dans Obj-C, les nouvelles API préfèrent utiliser des blocs plutôt que performSelector (comparez UIAlertView qui utilise respondsToSelector: et performSelector: pour appeler des méthodes de délégation, avec la nouvelle UIAlertController).

L'utilisation de performSelector: est toujours dangereuse et ne fonctionne pas bien avec ARC (d'où les avertissements ARC pour performSelector:).

17
Sulthan

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

15
FizzBuzz

Approche A

Utilisez NSThread.detachNewThreadSelector, l’avantage de cette approche est que nous pouvons joindre un objet 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

objet 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!
11
vladof81

Selon la réponse de @JTerry "Vous n'avez pas besoin de sélecteurs dans Swift", vous pouvez affecter des méthodes réelles à des 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é, attribué et exécuter la fonction de cette manière:

class SettingsViewController: UITableViewController {

    func editProfile(sender: AnyObject?) {
        ...
    }

    ...

    menuItem.tapFunction = editProfile

    ...

    if let tapFunction = menuItem.tapFunction {
        tapFunction(sender: self)
    }


}
7
Matej Ukmar

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
}

vous pouvez ainsi faire ce que performSelector exécute en Objective-C

6
user3721424

Je me débattais aussi avec ça. J'ai finalement réalisé que je n'avais pas besoin d'utiliser de cibles ni de sélecteurs. Pour moi, la solution a été d’affecter la fonction à une variable et d’appeler cette variable. Cela fonctionne même si vous l'appelez depuis 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();
4
JTerry

Swift 3.1
Pour les projets Swift standard, les fermetures sont une solution élégante déjà couverte dans La réponse de Sulthan . Invoquer des méthodes de manière dynamique à l'aide de noms de chaînes de sélecteur est logique si vous dépendez de l'ancien code/bibliothèques Objective-C ou souhaitez invoquer des méthodes privées. API.

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

#selector(mySelectorName) peut résoudre les noms de sélecteur saisis uniquement dans son fichier source de classe.
En sacrifiant la vérification de type, un sélecteur peut être récupéré avec NSSelectorFromString(...)
(ce n'est pas plus sûr en aucune manière queSelector("selectorName:arg:")il arrive simplement de ne pas générer d'avertissement).

Appel de la méthode d'instance de la sous-classe 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)

aussi avec variante de filetage principal:

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

Comme noté par iOS_MIB inhttps://stackoverflow.com/a/48644264/5329717 Ceci est n'est pas équivalent à

DispatchQueue.main.async {
   //perform selector
}

et variante de fil de fond:

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

Il y a cependant quelques limitations:

  • Il ne peut prendre que 0-2 arguments
  • les arguments de type valeur tels que les entiers et les sélecteurs ne fonctionnent pas 
  • ne peut pas gérer les types de valeur retournés
  • retourne les objets en tant que Unmanaged<AnyObject>

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

Extraire NSObject La méthode d'exécution IMP permet de faire un appel typé avec les arguments appropriés et renvoie le type .@convention(c)(types)->type permet de transtyper 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 objets, utilisez N'importe quel type de classe ou exact si son symbole est disponible
  • Pour les types de valeur, utilisez le type approprié
  • Pour void *, utilisez OpaquePointer

C'est par définition _ unsafe _ ​​et si cela se produirait de manière incorrecte, cela provoquerait des plantages et des effets secondaires.

Chaque méthode Objective-C sur le 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 en tant que @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) 

Ce sont static équivalents 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)

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 IMP et des types de traitement, @convention(c) s'applique

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

Il n'y a aucune raison pratique de le faire, mais objc_msgSend peut être utilisé de manière dynamique.

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)

Idem pour NSInvocation (ceci est seulement un exercice amusant, ne pas le faire)

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)
    }
}
4
Kamil.S

Hein, on peut utiliser swizzling pour dévoiler les méthodes souhaitées!

Ajoutez simplement ceci extension et préfixez 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)
    }

}
3
Valentin Shergin

la syntaxe réelle pour la file d'attente de distribution est la suivante.

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

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 en cours de construction, j'appelle diverses fonctions avec des noms de fonctions similaires dans un UIView généré à partir de CoreAnimator (super application, BTW). PerformSelector est donc très utile. 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 = NSSelectorFromString(AnimMethod)

//calls the said function in UIView named "meter"            
meter.performSelector(selector)
2
LeftySwift

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

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

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 etc. auparavant, mais cela a toujours l'air "étrange" dans un programme Swift, j'ai donc fait quelques 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: 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)
}

donne le résultat:

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 vous fiez pas à la séquence avec cette tactique

0
Hardy_Germany

Un exemple concret dans Swift du commentaire de "Matej Ukmar" à "J Terry" répond: 

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
0
eonist

J'ai une situation, où sélecteur est construit avec un littéral de chaîne qui provient d'un fichier plist . Ainsi, 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(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()
0
Modo Ltunzher

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()
})
0
Bagusflyer