Метод swizzling для собственности в swift

Пока можно заменить setMyProperty: метод в obj-c, мне интересно, как это сделать в swift?

Например я хочу заменить UIScrollView::setContentOffset::

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

... но после казни originalSelector содержит setContentOffset:animaed, Итак, как передать метод установки свойства в selector?

2 ответа

Решение

Начиная с Swift 2.3 (XCode 8) можно назначить сеттер и геттер для переменной селектора:

На селекторы Objective C для метода получения или установки свойства теперь можно ссылаться с помощью #selector. Например:

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

Подробнее здесь

[Переписано после дальнейших исследований]

Вот сложный обходной путь, основанный на

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

[ПРЕДУПРЕЖДЕНИЕ от авторов]

В заключение, помните, что работа с Objective-C должна быть в большей степени последним средством, чем местом для начала. Изменение фреймворков, на которых основан ваш код, а также любого стороннего кода, который вы запускаете, - это быстрый способ дестабилизировать весь стек. Ступай мягко!

Так вот, все аксессоры и мутаторы должны быть покрыты, так что это много. Кроме того, поскольку вам нужно вмешиваться в значения, но необходимо повторно использовать исходное хранимое свойство, поскольку вы не можете вводить здесь новое хранилище, у вас есть несколько странных функций, которые кажутся рекурсивными, но не из-за перебоев во время выполнения. Это первый раз, когда компилятор сгенерировал предупреждение для моего кода, которое, как я знаю, будет неверным во время выполнения.

Ну что ж, это интересное академическое упражнение.

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
        }
    }
}
Другие вопросы по тегам