Метод 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
}
}
}