Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement method swizzling swift 3.0?

How can I implement method swizzling in Swift 3.0 ?

I've read nshipster article about it, but in this code's chunk

struct Static {
    static var token: dispatch_once_t = 0
}

the compiler gives me an error

dispatch_once_t is unavailable in Swift: Use lazily initialized globals instead

like image 954
Tikhonov Aleksandr Avatar asked Sep 18 '16 21:09

Tikhonov Aleksandr


People also ask

What is method swizzling in Swift?

iOS Swift Tips. Swizzling (other languages call this “monkey patching”) is the process of replacing a certain functionality or adding custom code before the original code is called. For example, you could swizzle UIViewController.

How does method swizzling work?

Method swizzling is the process of changing the implementation of an existing selector. It's a technique made possible by the fact that method invocations in Objective-C can be changed at runtime, by changing how selectors are mapped to underlying functions in a class's dispatch table.


Video Answer


4 Answers

First of all dispatch_once_t is unavailable in Swift 3.0. You can choose from two alternatives:

  1. Global variable

  2. Static property of struct, enum or class

For more details, see that Whither dispatch_once in Swift 3

For different purposes you must use different implementation of swizzling

  • Swizzling CocoaTouch class, for example UIViewController;
  • Swizzling custom Swift class;

Swizzling CocoaTouch class

example swizzling viewWillAppear(_:) of UIViewController using global variable

private let swizzling: (UIViewController.Type) -> () = { viewController in

    let originalSelector = #selector(viewController.viewWillAppear(_:))
    let swizzledSelector = #selector(viewController.proj_viewWillAppear(animated:))

    let originalMethod = class_getInstanceMethod(viewController, originalSelector)
    let swizzledMethod = class_getInstanceMethod(viewController, swizzledSelector)

    method_exchangeImplementations(originalMethod, swizzledMethod) }

extension UIViewController {

    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === UIViewController.self else { return }
        swizzling(self)
    }

    // MARK: - Method Swizzling

    func proj_viewWillAppear(animated: Bool) {
        self.proj_viewWillAppear(animated: animated)

        let viewControllerName = NSStringFromClass(type(of: self))
        print("viewWillAppear: \(viewControllerName)")
    } 
 }

Swizzling custom Swift class

To use method swizzling with your Swift classes there are two requirements that you must comply with (for more details):

  • The class containing the methods to be swizzled must extend NSObject
  • The methods you want to swizzle must have the dynamic attribute

And example swizzling method of custom Swift base class Person

class Person: NSObject {
    var name = "Person"
    dynamic func foo(_ bar: Bool) {
        print("Person.foo")
    }
}

class Programmer: Person {
    override func foo(_ bar: Bool) {
        super.foo(bar)
        print("Programmer.foo")
    }
}

private let swizzling: (Person.Type) -> () = { person in

    let originalSelector = #selector(person.foo(_:))
    let swizzledSelector = #selector(person.proj_foo(_:))

    let originalMethod = class_getInstanceMethod(person, originalSelector)
    let swizzledMethod = class_getInstanceMethod(person, swizzledSelector)

    method_exchangeImplementations(originalMethod, swizzledMethod)
}

extension Person {

    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === Person.self else { return }
        swizzling(self)
    }

    // MARK: - Method Swizzling

    func proj_foo(_ bar: Bool) {
        self.proj_foo(bar)

        let className = NSStringFromClass(type(of: self))
        print("class: \(className)")
    }
}
like image 144
Tikhonov Aleksandr Avatar answered Oct 17 '22 05:10

Tikhonov Aleksandr


@TikhonovAlexander: Great answer

I modified the swizzler to take both selectors and made it more generic.

Swift 4 / Swift 5

private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
    guard
        let originalMethod = class_getInstanceMethod(forClass, originalSelector),
        let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
    else { return }
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

extension UIView {
    
    static let classInit: Void = {            
        let originalSelector = #selector(layoutSubviews)
        let swizzledSelector = #selector(swizzled_layoutSubviews)
        swizzling(UIView.self, originalSelector, swizzledSelector)
    }()
    
    @objc func swizzled_layoutSubviews() {
        swizzled_layoutSubviews()
        print("swizzled_layoutSubviews")
    }
    
}

// perform swizzling in AppDelegate.init()

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    override init() {
        super.init()
        UIView.classInit
    }

}

Swift 3

private let swizzling: (AnyClass, Selector, Selector) -> () = { forClass, originalSelector, swizzledSelector in
    let originalMethod = class_getInstanceMethod(forClass, originalSelector)
    let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

// perform swizzling in initialize()

extension UIView {
    
    open override class func initialize() {
        // make sure this isn't a subclass
        guard self === UIView.self else { return }
        
        let originalSelector = #selector(layoutSubviews)
        let swizzledSelector = #selector(swizzled_layoutSubviews)
        swizzling(self, originalSelector, swizzledSelector)
    }
    
    func swizzled_layoutSubviews() {
        swizzled_layoutSubviews()
        print("swizzled_layoutSubviews")
    }
    
}
like image 35
efremidze Avatar answered Oct 17 '22 06:10

efremidze


Swizzling in Playground
Swift 5

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

extension TestSwizzling {
    @objc func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return 2
    }
    //In Objective-C you'd perform the swizzling in load(),
    //but this method is not permitted in Swift
    func swizzle(){
        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!)
            print("swizzled!")
        }
        i()
    }
}

var c = TestSwizzling()
print([c.methodOne(), c.methodTwo()])  // [1, 2]
c.swizzle()                            // swizzled!
print([c.methodOne(), c.methodTwo()])  // [2, 1]
like image 31
Dharay Avatar answered Oct 17 '22 06:10

Dharay


Swift swizzling

@objcMembers
class sA {
    dynamic
    class func sClassFooA() -> String {
        return "class fooA"
    }
    
    dynamic
    func sFooA() -> String {
        return "fooA"
    }
}

@objcMembers
class sB {
    dynamic
    class func sClassFooB() -> String {
        return "class fooB"
    }
    
    dynamic
    func sFooB() -> String {
        return "fooB"
    }
}

Swizzling.swift

import Foundation

@objcMembers
public class Swizzling: NSObject {
    
    public class func sExchangeClass(cls1: AnyClass, sel1: Selector, cls2: AnyClass, sel2: Selector) {
        
        let originalMethod = class_getClassMethod(cls1, sel1)
        let swizzledMethod = class_getClassMethod(cls2, sel2)
        
        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }

    public class func sExchangeInstance(cls1: AnyClass, sel1: Selector, cls2: AnyClass, sel2: Selector) {
        
        let originalMethod = class_getInstanceMethod(cls1, sel1)
        let swizzledMethod = class_getInstanceMethod(cls2, sel2)
        
        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }
}

Using via Swift

func testSExchangeClass() {
    Swizzling.sExchangeClass(cls1: sA.self, sel1: #selector(sA.sClassFooA), cls2: sB.self, sel2: #selector(sB.sClassFooB))
    
    XCTAssertEqual("class fooB", sA.sClassFooA())
}

func testSExchangeInstance() {
    Swizzling.sExchangeInstance(cls1: sA.self, sel1: #selector(sA.sFooA), cls2: sB.self, sel2: #selector(sB.sFooB))
    
    XCTAssertEqual("fooB", sA().sFooA())
}

[Add Objective-C as an consumer]

using via Objective-C

- (void)testSExchangeClass {
    [Swizzling sExchangeClassWithCls1:[cA class] sel1:@selector(cClassFooA) cls2:[cB class] sel2:@selector(cClassFooB)];
    
    XCTAssertEqual(@"class fooB", [cA cClassFooA]);
}

- (void)testSExchangeInstance {
    [Swizzling sExchangeInstanceWithCls1:[cA class] sel1:@selector(cFooA) cls2:[cB class] sel2:@selector(cFooB)];
    
    XCTAssertEqual(@"fooB", [[[cA alloc] init] cFooA]);
}

[Objective-C swizzling]

like image 39
yoAlex5 Avatar answered Oct 17 '22 06:10

yoAlex5