Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method swizzling for property in swift

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?

like image 301
brigadir Avatar asked Aug 04 '16 22:08

brigadir


People also ask

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.

What is swizzle iOS?

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.

How do I disable method swizzling?

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).


2 Answers

[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
        }
    }
}
like image 199
BaseZen Avatar answered Oct 22 '22 02:10

BaseZen


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

like image 39
brigadir Avatar answered Oct 22 '22 00:10

brigadir