While it is possible to replace setMyProperty:
method in obj-c, I'm wondering how to do it in swift?
For example I want to replace UIScrollView::setContentOffset:
:
let originalSelector: Selector = #selector(UIScrollView.setContentOffset)
let replaceSelector: Selector = #selector(UIScrollView.setContentOffsetHacked)
...
...but after execution originalSelector
contains setContentOffset:animaed
. So, how to pass setter method of property to selector
?
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.
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. viewDidAppear to be informed whenever a view controller is displayed.
Method swizzling in Firebase Cloud Messaging Developers who prefer not to use swizzling can disable it by adding the flag FirebaseAppDelegateProxyEnabled in the app's Info. plist file and setting it to NO (boolean value).
[REWRITTEN after further research]
Here's an elaborate workaround based on the below
http://nshipster.com/swift-objc-runtime/
[WARNING from the authors]
In closing, remember that tinkering with the Objective-C runtime should be much more of a last resort than a place to start. Modifying the frameworks that your code is based upon, as well as any third-party code you run, is a quick way to destabilize the whole stack. Tread softly!
So here it is, all accessors and mutators have to be covered, so it's a lot. Plus, since you need to intercede with the values but must re-use the original stored property since you can't introduce any new storage here, you have some bizarre looking functions that appear to be recursive but aren't because of runtime swizzling. This is the first time the compiler has generated a warning for my code that I know will be wrong at runtime.
Oh well, it is an interesting academic exercise.
extension UIScrollView {
struct StaticVars {
static var token: dispatch_once_t = 0
}
public override class func initialize() {
dispatch_once(&StaticVars.token) {
guard self == UIScrollView.self else {
return
}
// Accessor
method_exchangeImplementations(
class_getInstanceMethod(self, Selector("swizzledContentOffset")),
class_getInstanceMethod(self, Selector("contentOffset"))
)
// Two-param setter
method_exchangeImplementations(
class_getInstanceMethod(self, #selector(UIScrollView.setContentOffset(_:animated:))),
class_getInstanceMethod(self, #selector(UIScrollView.swizzledSetContentOffset(_:animated:)))
)
// One-param setter
method_exchangeImplementations(
class_getInstanceMethod(self, #selector(UIScrollView.swizzledSetContentOffset(_:))),
class_getInstanceMethod(self, Selector("setContentOffset:")))
}
}
func swizzledSetContentOffset(inContentOffset: CGPoint, animated: Bool) {
print("Some interceding code for the swizzled 2-param setter with \(inContentOffset)")
// This is not recursive. The method implementations have been exchanged by runtime. This is the
// original setter that will run.
swizzledSetContentOffset(inContentOffset, animated: animated)
}
func swizzledSetContentOffset(inContentOffset: CGPoint) {
print("Some interceding code for the swizzled 1-param setter with \(inContentOffset)")
swizzledSetContentOffset(inContentOffset) // not recursive
}
var swizzledContentOffset: CGPoint {
get {
print("Some interceding code for the swizzled accessor: \(swizzledContentOffset)") // false warning
return swizzledContentOffset // not recursive, false warning
}
}
}
Starting from Swift 2.3 (XCode 8) it's possible to assign setter and getter to selector variable:
The Objective-C selectors for the getter or setter of a property can now be referenced with #selector. For example:
let sel: Selector = #selector(setter: UIScrollView.contentOffset)
More details here
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