Reputation: 6942
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
?
Upvotes: 11
Views: 6546
Reputation: 6942
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
Upvotes: 13
Reputation: 8718
[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
}
}
}
Upvotes: 10