web-dev-qa-db-fra.com

Fonction rapide swizzling/runtime

Avant Swift, dans Objective-C, je passais en revue ou accrochais des méthodes dans une classe à l'aide de <objc/runtime.h>.

Si quelqu'un a des informations sur le sujet de la modification des fonctions d'exécution et de raccordement de Swift telles que CydiaSubstrate et d'autres bibliothèques qui ont aidé dans ce domaine, veuillez m'en informer.

19
atomikpanda

J'ai réussi avec la méthode Swizzling à Swift. Cet exemple montre comment accrocher la méthode de description sur NSDictionary

Ma mise en oeuvre:

extension NSDictionary {
     func myDescription() -> String!{
        println("Description hooked")
        return "Hooooked " + myDescription();
    }
}

Code Swizzling: 

func swizzleEmAll() {
        var dict:NSDictionary = ["SuperSecret": kSecValueRef]
        var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description"))

        println(dict.description) // Check original description

        var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription"))
        method_exchangeImplementations(method, swizzledMethod)

        println(dict.description) //Check that swizzling works
    }

Edited: Ce code fonctionnera pour toute classe Swift personnalisée qui héritera de NSObject (mais ne fonctionnera pas pour les classes qui n'en possèdent pas.) Autres exemples - https: // github. com/mbazaliy/MBSwizzler

36
mbazaliy

Vous seriez probablement capable de balayer les classes générées par Swift qui héritent des classes Objective-C sans aucun problème, car elles semblent utiliser en tout temps la répartition dynamique des méthodes. Vous serez peut-être en mesure d’analyser les méthodes de classes définies par Swift qui existent dans le runtime d’Objective-C en vertu du fait qu’elles sont passées sur le pont, mais les méthodes côté Objective-C risquent d’être simplement des mandataires de retour sur le pont menant à Swift- côté exécution, donc il n'est pas clair qu'il serait particulièrement utile de les balayer.

Les appels "purs" à la méthode Swift ne semblent pas être distribués dynamiquement via objc_msgSend et il semble (à partir de brèves expériences) que la sécurité de type de Swift est implémentée à la compilation, et qu'une grande partie des informations de type réelles sont absentes (c'est-à-dire disparues). ) au moment de l'exécution pour les types n'appartenant pas à la classe (les deux contribuant probablement aux avantages supposés de Swift en matière de vitesse.) 

Pour ces raisons, je m'attends à ce que les méthodes Swift-swimmling significativement swizzling soient beaucoup plus difficiles que les méthodes swizzling Objective-C, et ressembleront probablement beaucoup plus à mach_override qu'à la méthode Objective-C swizzling.

19
ipmcc

Je réponds à cette question plus d'un an plus tard, car aucune des autres réponses ne fournit l'ensemble définitif d'exigences en matière de méthode pour tous les types de cours.

Ce qui est décrit par d’autres, s’il fonctionnera parfaitement pour les extensions de classes foundation/uikit (comme NSDictionary), ne fonctionnera tout simplement jamais pour vos propres classes Swift.

Comme décrit ici , il existe une exigence supplémentaire pour la méthode swizzling autre que l'extension de NSObject dans votre classe personnalisée. 

La méthode Swift que vous voulez balayer doit être marquéedynamic.

Si vous ne le marquez pas, le moteur d'exécution continuera simplement à appeler la méthode d'origine au lieu de la méthode utilisée, même si les pointeurs de méthode semblent avoir été permutés correctement.

Mettre à jour:

J'ai développé cette réponse dans un article de blog .

5
Umberto Raimondi

J'avais un projet iOS Xcode 7 écrit dans Swift 2, en utilisant des Cocoapods. Dans un Cocoapod spécifique, avec une source Objective-C, je voulais remplacer une méthode courte, sans bifurquer. Ecrire une extension Swift ne fonctionnerait pas dans mon cas.

Pour utiliser la méthode swizzling, j'ai créé une nouvelle classe Objective-C dans mon lot principal avec la méthode que je voulais remplacer/injecter dans le cocoapod. (Ajout également l'en-tête de pontage) 

En utilisant la solution de mbazaliy sur stackflow , je mets un code similaire à celui-ci dans la variable didFinishLaunchingWithOptions de mon Appdelegate:

    let mySelector: Selector = "nameOfMethodToReplace"
    let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector)
    let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector)
    method_exchangeImplementations(method, swizzledMethod)

Cela a fonctionné parfaitement. La différence entre le code de @mbazaliy est que je n'avais pas besoin de créer d'abord une instance de la classe SomeClassInAPod, ce qui aurait été impossible dans mon cas.

Remarque: je mets le code dans Appdelegate car, une fois sur deux, le code est exécuté et la méthode est échangée contre la méthode d'origine. Il ne doit être exécuté qu'une seule fois.

J'avais également besoin de copier certains actifs référencés dans le paquet du pod dans le paquet principal.

2
Nate Flink

Je voudrais prolonger l'excellente réponse fournie par mbazaliy .

Une autre façon de fonctionner dans Swift consiste à implémenter un bloc Objective-C.

par exemple. pour remplacer descriptionmethod sur la classe NSString, nous pouvons écrire:

let originalMethod = class_getInstanceMethod(NSString.self, "description")

let impBlock : @objc_block () -> NSString =
        { () in return "Bit of a hack job!" }

let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self))

method_setImplementation(originalMethod, newMethodImp)

Cela fonctionne à partir de Swift 1.1.

0
Matteo Pacini

Je ne le ferais pas de cette façon, je pense que les fermetures fournissent les réponses (car elles vous permettent d'intercepter, d'évaluer et de transmettre l'invocation de la fonction. De plus, il sera facile d'étendre quand et si nous avons une réflexion. 

http://www.Swift-studies.com/blog/2014/7/13/method-swizzling-in-Swift

0
rougeExciter