How to implement method swizzling swift 3.0?
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
orclass
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)")
}
}
UIView method swizzling swift 3
The problem is that awakeFromNib
is a method of NSObject
and
not of UIView
. Your code swizzles the NSObject
method with a
method of UIView
, and calling the original method crashes
when the swizzled method is called on UINavigationController
(or any other subclass of NSObject
which is not a subclass of UIView
).
The solution is to try to add the swizzled method with the
original name first (as described in http://nshipster.com/method-swizzling/):
private let swizzling: (UIView.Type) -> () = { view in
let originalSelector = #selector(view.awakeFromNib)
let swizzledSelector = #selector(view.swizzled_localization_awakeFromNib)
let originalMethod = class_getInstanceMethod(view, originalSelector)
let swizzledMethod = class_getInstanceMethod(view, swizzledSelector)
let didAddMethod = class_addMethod(view, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(view, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
Swift - method swizzling
Extend your class:
extension YourClassName {
static let classInit: () -> () = {
let originalSelector = #selector(originalFunction)
let swizzledSelector = #selector(swizzledFunction)
swizzle(YourClassName.self, originalSelector, swizzledSelector)
}
@objc func swizzledFunction() {
//Your new implementation
}
}
Your class (YourClassName
) should inherit from NSObject
and originalSelector
should be a dynamic
method.
swizzle
is a closure that exchanges the implementations:
private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in
guard
let originalMethod = class_getInstanceMethod(fromClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
You could define swizzle
in the class where you are going to do the swizzling. For example the AppDelegate
class definition. And then do the swizzling in AppDelegate.init()
:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
YourClassName.classInit()
}
}
Example
Here is a concrete example of swizzling, it exchanges the implementation of two methods that take one argument:
class Example: NSObject {
@objc dynamic func sayHi(to name: String) {
print("Hi", name)
}
}
extension Example {
public class func swizzleMethod() {
guard
let originalMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHi(to:))),
let swizzledMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHello(to:)))
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
@objc func sayHello(to name: String) {
print("Hello", name)
}
}
Example.swizzleMethod()
let a = Example()
a.sayHi(to: "Nitish") //Hello Nitish
Method swizzling in Swift
Objective-C, which uses dynamic dispatch supports the following:
Class method swizzling:
The kind you're using above. All instances of a class will have their method replaced with the new implementation. The new implementation can optionally wrap the old.
Isa-pointer swizzling:
An instance of a class is set to a new run-time generated sub-class. This instance's methods will be replaced with a new method, which can optionally wrap the existing method.
Message forwarding:
A class acts as a proxy to another class, by performing some work, before forwarding the message to another handler.
These are all variations on the powerful intercept pattern, which many of Cocoa's best features rely on.
Enter Swift:
Swift continues the tradition of ARC, in that the compiler will do powerful optimizations on your behalf. It will attempt to inline your methods or use static dispatch or vtable dispatch. While faster, these all prevent method interception (in the absence of a virtual machine). However you can indicate to Swift that you'd like dynamic binding (and therefore method interception) by complying with the following:
- By extending NSObject or using the @objc directive.
- By adding the dynamic attribute to a function, eg
public dynamic func foobar() -> AnyObject
In the example you provide above, these requirements are being met. Your class is derived transitively from NSObject
via UIView
and UIResponder
, however there is something odd about that category:
- The category is overriding the load method, which will normally be called once for a class. Doing this from a category probably isn't wise, and I'm guessing that while it might have worked before, in the case of Swift it doesn't.
Try instead to move the Swizzling code into your AppDelegate's did finish launching:
//Put this instead in AppDelegate
method_exchangeImplementations(
class_getInstanceMethod(UINavigationBar.self, "sizeThatFits:"),
class_getInstanceMethod(UINavigationBar.self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
Method Swizzling with Swift 5.5
That's because
@objc internal func log_initWithString(string URLString: String)
is exposed to Objective-C as log_initWithStringWithString:
and not as log_initWithString:
.
Obvious fix is:
...
let swizzled = Selector("log_initWithStringWithString:")
...
To have better compile time checks on that you can use this syntax:
let original = #selector(NSURL.init(string:))
let swizzled = #selector(NSURL.log_initWithString(string:))
This will compile, but there is at least one thing left to fix - swizzled method return value. In your example:
@objc internal func log_initWithString(string URLString: String) {
NSLog("Hello from initWithString")
return log_initWithString(string: URLString)
}
returns nothing, while NSURL
's init is supposed to return NSURL
, so the fix is:
@objc internal func log_initWithString(string URLString: String) -> NSURL {
...
Related Topics
Closure Cannot Implicitly Capture a Mutating Self Parameter
Sharing Data in Between Apps in Ios
Disable Gesture to Pull Down Form/Page Sheet Modal Presentation
Uitableview Cell Selected Color
Iphone Smooth Sketch Drawing Algorithm
Xcode iOS 8 Keyboard Types Not Supported
Unique Values of Array in Swift
How to Increase the Height of Navigation Bar in Xcode
Modal View Controllers - How to Display and Dismiss
How to Build For Armv6 and Armv7 Architectures With iOS 5
Displaying a Stock iOS Notification Banner When Your App Is Open and in the Foreground
How to Create Percentage of Total Width Using Autolayout
Detecting Sheet Was Dismissed on iOS 13
Nsurlsession With Nsblockoperation and Queues