Zoeken…


Opmerkingen

Wanneer u methode swizzling in Swift gebruikt, zijn er twee vereisten waaraan uw klassen / methoden moeten voldoen:

  • Je klas moet NSObject uitbreiden
  • De functies die u wilt swizzle moeten het dynamic kenmerk hebben

Voor een volledige uitleg waarom dit nodig is, bekijk Swift met Cocoa en Objective-C :

Dynamische verzending vereist

Hoewel het kenmerk @objc uw Swift API blootstelt aan de runtime van Objective-C, garandeert dit geen dynamische verzending van een eigenschap, methode, subscript of initialisatie. De Swift-compiler kan lidrechten nog steeds devirtualiseren of inline-toegang verlenen om de prestaties van uw code te optimaliseren, waarbij de Objective-C runtime wordt omzeild . Wanneer u een ledenverklaring markeert met de dynamic modificator, wordt de toegang tot dat lid altijd dynamisch verzonden. Omdat declaraties gemarkeerd met de dynamic modifier worden verzonden met behulp van de Objective-C runtime, worden ze impliciet gemarkeerd met het attribuut @objc .

Het vereisen van dynamische verzending is zelden noodzakelijk. U moet echter de dynamic modifier gebruiken als u weet dat de implementatie van een API tijdens runtime wordt vervangen . U kunt bijvoorbeeld de functie method_exchangeImplementations in de Objective-C runtime gebruiken om de implementatie van een methode te verwisselen terwijl een app actief is. Als de Swift-compiler de implementatie van de methode zou onderstrepen of de toegang daartoe zou devirtualiseren, zou de nieuwe implementatie niet worden gebruikt .

Objective-C Runtime Reference

Methode Swizzling op NSHipster

UIViewController en Swizzling viewDidLoad uitbreiden

In Objective-C is methode swizzling het proces van het wijzigen van de implementatie van een bestaande selector. Dit is mogelijk vanwege de manier waarop selectors worden toegewezen aan een verzendtabel of een tabel met verwijzingen naar functies of methoden.

Pure Swift-methoden worden niet dynamisch verzonden door de Objective-C runtime, maar we kunnen nog steeds profiteren van deze trucs voor elke klasse die van NSObject erft.

Hier zullen we UIViewController en swizzle viewDidLoad uitbreiden om wat aangepaste logging toe te voegen:

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

Basisprincipes van Swift Swizzling

Laten we de implementatie van methodOne() en methodTwo() in onze klasse 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())

Basisprincipes van Swizzling - Objective-C

Doelstelling-C voorbeeld van swizzling UIView's initWithFrame: methode

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow