使用runtime防止Button暴力点击

按钮防止暴力点击是个很平常的需求,测试时候产品经理、测试妹子经常会看哪个按钮不爽,然后使劲戳它,心疼按钮2秒钟。。。

一言不合上代码

首先在按钮的extension里面加入一个结构体和两个计算型属性,结构体记录string常量
1
2
3
4
private struct AssociatedKeys {
static var acceptEventInterval = "acceptEventInterval"
static var waitingTime = "waitingTime"
}

acceptEventInterval用来记录点击间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// 按钮点击间隔时间
public var acceptEventInterval: TimeInterval {
get {
if let acceptEventInterval = objc_getAssociatedObject(self, &AssociatedKeys.acceptEventInterval) as? TimeInterval {
return acceptEventInterval
}
return 0.0
}
set {
assert(newValue > 0, "acceptEventInterval should be valid")
methodSwizzling()
objc_setAssociatedObject(self,
&AssociatedKeys.acceptEventInterval,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

waitingTime用来记录上次有效点击的时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
    
/// 当前等待时间
private var waitingTime: TimeInterval {
get {
if let waitingTime = objc_getAssociatedObject(self, &AssociatedKeys.waitingTime) as? TimeInterval {
return waitingTime
}
return 0.0
}
set {
objc_setAssociatedObject(self,&AssociatedKeys.waitingTime,newValue,.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

交换点击方法

1
2
3
4
5
6
/// 交换方法
private func methodSwizzling() {
let before: Method = class_getInstanceMethod(self.classForCoder, #selector(self.sendAction(_:to:for:)))
let after: Method = class_getInstanceMethod(self.classForCoder, #selector(self._sendAction(_:to:for:)))
method_exchangeImplementations(before, after)
}

点击方法的替换方法,判断当前时间戳减去上次有效点击的时间戳的间隔时间是否小于设置的间隔时长,小于则直接返回,如果大于等于则判定为有效点击,记录时间戳并执行点击方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 点击判断方法
///
/// - Parameters:
/// - action: action description
/// - target: target description
/// - event: event description
@objc private func _sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
if Date().timeIntervalSince1970 - waitingTime < acceptEventInterval {
return
}
waitingTime = Date().timeIntervalSince1970
sendAction(action, to: target, for: event)
}

我们可以看到acceptEventInterval属性的set方法第一行有一个断言,当设置为小于或等于0时候就会触发断言(间隔时长无法设置为0或更小),当newValue通过断言,交换响应方法,objc_getAssociatedObject使用给定的结构体的acceptEventInterval键动态增加关联值,get方法就是取出动态增加的关联值

代码比较简单,这里就不上demo了,希望能帮助到大家