在Swift中进行属性的方法交换

10

虽然在 obj-c 中可以替换 setMyProperty: 方法,但我想知道如何在 swift 中实现?

例如,我想要替换 UIScrollView::setContentOffset: 方法:

let originalSelector: Selector = #selector(UIScrollView.setContentOffset)
let replaceSelector: Selector = #selector(UIScrollView.setContentOffsetHacked)
...

...但在执行后,originalSelector 包含 setContentOffset:animated。那么,如何将属性的setter方法传递给 selector 呢?


这篇文章可能会对你有所帮助:http://nshipster.com/swift-objc-runtime/ - Victor Sigler
1
如果您想要深入了解:https://dev59.com/-18e5IYBdhLWcg3whqvl?rq=1 - BaseZen
@VictorSigler,那篇文章没有涵盖到使用属性设置器的特殊情况。 - brigadir
@BaseZen,你提到的问题和答案都一样。我正在寻找如何处理属性设置器,特别是我想知道为什么从#selector(UIScrollView.setContentOffset)选择了错误的方法。 - brigadir
答案已经重新制作,所以我很好奇现在是否符合您的需求。 - BaseZen
2个回答

13

从Swift 2.3(XCode 8)开始,可以将setter和getter分配给选择器变量:

现在可以使用#selector引用属性的getter或setter的Objective-C选择器。例如:

let sel: Selector = #selector(setter: UIScrollView.contentOffset)

更多细节在这里


10

[进一步研究后重写]

以下是一个详细的解决方法

http://nshipster.com/swift-objc-runtime/

[作者的警告]

总之,记住修改 Objective-C runtime 应该是最后的手段而不是首要选择。 修改您的代码所基于的框架以及运行的任何第三方代码都是破坏整个栈的快速方法。小心谨慎!

因此,所有访问器和修改器都必须被覆盖,这样就很多了。 此外,由于您需要介入值但必须重用原始存储属性,因此您有一些看起来像递归但实际上是由于运行时交换而不是递归的奇怪函数。 这是编译器第一次为我的代码生成警告,并且我知道它在运行时将是错的。

嗯,这是一个有趣的学术练习。

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
        }
    }
}

谢谢,但我真的需要替换UIScrollView及其子类的setContentOffset。原因是 - 我正在实现自定义下拉刷新控件。 - brigadir
我在UIScrollView接口中看到了public var contentOffset: CGPoint - 所以它是可读写的,而不是只读的。setContentOffset(animated:)是一个独立的方法。当拖动滚动视图时,它的contentOffset是通过setter设置的,而不是通过setContentOffset:animated设置的。看起来你的答案并没有解决我的问题。 - brigadir
你是对的,我误读了Xcode中‘V’的意思,以为它是只读状态。现在已经修改过来了。无论如何,请查看我的新注释。所有的功能似乎都能正常运行。 - BaseZen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接