Recherche…


Remarques

Lorsque vous utilisez la méthode swizzling dans Swift, vos classes / méthodes doivent satisfaire à deux exigences:

  • Votre classe doit étendre NSObject
  • Les fonctions que vous souhaitez modifier doivent avoir l'attribut dynamic

Pour une explication complète de la nécessité de cette opération, consultez la rubrique Utilisation de Swift avec Cocoa et Objective-C :

Exiger un envoi dynamique

Bien que l'attribut @objc expose votre API Swift à l' @objc exécution Objective-C, il ne garantit pas la distribution dynamique d'une propriété, d'une méthode, d'un indice ou d'un initialiseur. Le compilateur Swift peut toujours déstirualiser ou mettre en ligne l'accès des membres pour optimiser les performances de votre code, en contournant le runtime Objective-C . Lorsque vous marquez une déclaration de membre avec le modificateur dynamic , l'accès à ce membre est toujours distribué dynamiquement. Les déclarations marquées avec le modificateur dynamic étant distribuées à l'aide de l' @objc exécution Objective-C, elles sont implicitement marquées avec l'attribut @objc .

Exiger une répartition dynamique est rarement nécessaire. Cependant, vous devez utiliser le modificateur dynamic lorsque vous savez que l'implémentation d'une API est remplacée lors de l'exécution . Par exemple, vous pouvez utiliser la fonction method_exchangeImplementations dans l' method_exchangeImplementations exécution Objective-C pour remplacer l'implémentation d'une méthode pendant l'exécution d'une application. Si le compilateur Swift mettait en œuvre l'implémentation de la méthode ou y accédait, la nouvelle implémentation ne serait pas utilisée .

Liens

Référence à l'exécution d'Objective-C

Méthode Swizzling sur NSHipster

Extension de UIViewController et Swizzling viewDidLoad

En Objective-C, la méthode swizzling est le processus de modification de l'implémentation d'un sélecteur existant. Cela est possible grâce à la façon dont les sélecteurs sont mappés sur une table de distribution ou une table de pointeurs vers des fonctions ou des méthodes.

Les méthodes Pure Swift ne sont pas distribuées dynamiquement par le moteur d'exécution Objective-C, mais nous pouvons toujours tirer parti de ces astuces pour toute classe qui hérite de NSObject .

Ici, nous étendrons UIViewController et swizzle viewDidLoad pour ajouter de la journalisation personnalisée:

extension UIViewController {
    
    // We cannot override load like we could in Objective-C, so override initialize instead
    public override static func initialize() {
        
        // Make a static struct for our dispatch token so only one exists in memory
        struct Static {
            static var token: dispatch_once_t = 0
        }
        
        // Wrap this in a dispatch_once block so it is only run once
        dispatch_once(&Static.token) {
            // Get the original selectors and method implementations, and swap them with our new method
            let originalSelector = #selector(UIViewController.viewDidLoad)
            let swizzledSelector = #selector(UIViewController.myViewDidLoad)
            
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            
            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
            
            // class_addMethod can fail if used incorrectly or with invalid pointers, so check to make sure we were able to add the method to the lookup table successfully
            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
    }
    
    // Our new viewDidLoad function
    // In this example, we are just logging the name of the function, but this can be used to run any custom code
    func myViewDidLoad() {
        // This is not recursive since we swapped the Selectors in initialize().
        // We cannot call super in an extension.
        self.myViewDidLoad()
        print(#function) // logs myViewDidLoad()
    }
}

Bases de Swift Swizzling

methodOne() l'implémentation de methodOne() et de methodTwo() dans notre classe TestSwizzling :

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {
    
    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {

        struct Inner {
            static let i: () = {

                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
        let _ = Inner.i
    }
    
    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

Bases de Swizzling - Objective-C

Exemple d'Objective-C de la méthode initWithFrame de initWithFrame:

static IMP original_initWithFrame;

+ (void)swizzleMethods {
    static BOOL swizzled = NO;
    if (!swizzled) {
        swizzled = YES;

        Method initWithFrameMethod =
            class_getInstanceMethod([UIView class], @selector(initWithFrame:));
        original_initWithFrame = method_setImplementation(
            initWithFrameMethod, (IMP)replacement_initWithFrame);
    }
}

static id replacement_initWithFrame(id self, SEL _cmd, CGRect rect) {
    
    // This will be called instead of the original initWithFrame method on UIView
    // Do here whatever you need... 

    // Bonus: This is how you would call the original initWithFrame method
    UIView *view =
        ((id (*)(id, SEL, CGRect))original_initWithFrame)(self, _cmd, rect);

    return view;
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow