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
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.
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.
First of all dispatch_once_t
is unavailable in Swift 3.0.
You can choose from two alternatives:
Global variable
Static property of struct
, enum
or class
For more details, see that Whither dispatch_once in Swift 3
- Swizzling CocoaTouch class, for example UIViewController;
- Swizzling custom Swift 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)")
}
}
To use method swizzling with your Swift classes there are two requirements that you must comply with (for more details):
NSObject
dynamic
attributeAnd 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)")
}
}
@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")
}
}
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]
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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With