Swift利用Runtime防止按钮重复点击
2018-09-13 本文已影响25人
seasonZhu
OC中通过Runtime防止按钮简直就是轻而易举的事情,在分类中使用load方法,进行方法交换就行了.
但是这个事放在Swift中就不是那么友好了,特别是在Swift3.0中相继封掉了load方法与initialize方法后.
所幸,还是有大神给出了思路,在AppDelegate didFinishLaunchingWithOptions方法中进行方法交换
如下代码就是利用Runtime防止按钮重复点击.
// MARK: - 按钮的反复点击问题 交换方法
extension UIButton {
/// 对外交换方法的方法 AppDelegate Launch中使用
public static func methodExchange() {
DispatchQueue.once(token: "UIButton") {
let originalSelector = Selector.sysFunc
let swizzledSelector = Selector.myFunc
changeMethod(originalSelector, swizzledSelector, self)
}
}
/// Runtime方法交换
///
/// - Parameters:
/// - original: 原方法
/// - swizzled: 交换方法
/// - object: 对象
private static func changeMethod(_ original: Selector, _ swizzled: Selector, _ object: AnyClass) -> () {
guard let originalMethod = class_getInstanceMethod(object, original),
let swizzledMethod = class_getInstanceMethod(object, swizzled) else {
return
}
let didAddMethod = class_addMethod(object, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(object, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
/// 结构体静态key
private struct UIButtonKey {
static var isEventUnavailableKey = "isEventUnavailableKey"
static var eventIntervalKey = "eventIntervalKey"
}
/// 触发事件的间隔
var eventInterval: TimeInterval {
get {
return (objc_getAssociatedObject(self, &UIButtonKey.eventIntervalKey) as? TimeInterval) ?? 0
}
set {
objc_setAssociatedObject(self, &UIButtonKey.eventIntervalKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// 是否可以触发事件
fileprivate var isEventUnavailable: Bool {
get {
return (objc_getAssociatedObject(self, &UIButtonKey.isEventUnavailableKey) as? Bool) ?? false
}
set {
objc_setAssociatedObject(self, &UIButtonKey.isEventUnavailableKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/// 手写的set方法
///
/// - Parameter isEventUnavailable: 事件是否可用
@objc private func setIsEventUnavailable(_ isEventUnavailable: Bool) {
self.isEventUnavailable = isEventUnavailable
}
/// mySendAction
@objc fileprivate func mySendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
print("交换了按钮事件的方法")
if isEventUnavailable == false {
isEventUnavailable = true
mySendAction(action, to: target, for: event)
perform(#selector(setIsEventUnavailable(_: )), with: false, afterDelay: eventInterval)
}
}
}
fileprivate extension Selector {
static let sysFunc = #selector(UIButton.sendAction(_:to:for:))
static let myFunc = #selector(UIButton.mySendAction(_:to:for:))
}
extension DispatchQueue {
private static var onceTracker = [String]()
open class func once(token: String, block:() -> ()) {
//注意defer作用域,调用顺序——即一个作用域结束,该作用域中的defer语句自下而上调用。
objc_sync_enter(self)
defer {
print("线程锁退出")
objc_sync_exit(self)
}
if onceTracker.contains(token) {
return
}
onceTracker.append(token)
block()
defer {
print("block执行完毕")
}
}
}
方法交换使用如下
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UIButton.methodExchange()
return true
}
在button使用如下
let button = UIButton(frame: CGRect(x: 0, y: 300, width: 100, height: 22))
button.setTitle("按钮", for: .normal)
button.setTitleColor(.black, for: .normal)
button.eventInterval = 2.0 // 按的时间间隔, 设置为0的时候就是可以反复点击
view.addSubview(button)