Sök…


Anmärkningar

När du använder metodswizzling i Swift finns det två krav som dina klasser / metoder måste uppfylla:

  • Din klass måste utöka NSObject
  • Funktionerna du vill swizzla måste ha det dynamic attributet

För en fullständig förklaring av varför detta krävs, kolla in Använda Swift med kakao och Objekt-C :

Kräver dynamisk avsändning

Medan @objc attributet exponerar ditt Swift API för Objekt-C-runtime, garanterar det inte dynamisk skick av en egenskap, metod, subscript eller initialiserare. Swift-kompilatorn kan fortfarande avinstallera eller ansluta medlemstillträde för att optimera prestandan för din kod genom att kringgå Objekt-C-körtiden . När du markerar en medlemsdeklaration med den dynamic modifieraren skickas alltid åtkomst till den medlemmen dynamiskt. Eftersom deklarationer markerade med den dynamic modifieraren skickas med Objekt-C-körtiden är de implicit markerade med @objc attributet.

Det är sällan nödvändigt att kräva dynamisk avsändning. Du måste dock använda den dynamic modifieraren när du vet att implementeringen av en API ersätts vid körning . Till exempel kan du använda method_exchangeImplementations funktionen i objektiv-C-runtime för att byta ut implementeringen av en metod medan en app körs. Om Swift-kompilatorn anvisade implementeringen av metoden eller devirtualiserade åtkomst till den, skulle den nya implementeringen inte användas .

länkar

Objekt-C Runtime Reference

Metod Swizzling på NSHipster

Utöka UIViewController och Swizzling viewDidLoad

I Objekt-C är metodswizzling processen att ändra implementeringen av en befintlig väljare. Detta är möjligt på grund av hur väljare mappas på ett sändningstabell eller en tabell med pekare till funktioner eller metoder.

Pure Swift-metoder skickas inte dynamiskt med Objekt-C-runtime, men vi kan fortfarande dra nytta av dessa trick på alla klasser som ärver från NSObject .

Här kommer vi att utöka UIViewController och swizzle viewDidLoad att lägga till lite anpassad loggning:

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

Grunderna i Swift Swizzling

Låt oss byta implementering av methodOne() och methodTwo() i vår TestSwizzling klass:

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

Grunderna i Swizzling - Objekt-C

Objektivt-C-exempel på svarrande UIViews initWithFrame: -metod

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow