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?
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:
).
À partir de Xcode 7, la famille complète de méthodes performSelector est disponible dans Swift, notamment performSelectorOnMainThread()
et performSelectorInBackground()
. Prendre plaisir!
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
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
NSTimer
class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
target target: AnyObject!,
selector aSelector: Selector,
userInfo userInfo: AnyObject!,
repeats repeats: Bool) -> NSTimer!
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)
}
}
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
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();
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:
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
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:
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)
}
}
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)
}
}
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.
}
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)
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)
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
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
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()
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()
})