在上线的当时晚上,测验发现有一个按钮点击无呼应,测验给出的复现途径是:在release包里,进入到对应界面,键盘弹出按钮可正常点击,键盘收起按钮无法点击,debug包也能够正常点击。假如你对这个bug感兴趣,点击下载最简化的demo.
其间可复现的核心代码如下
class ViewController: UIViewController {
var btn: UIButton = {
let btn = UIButton(type: .system)
btn.frame = CGRect(x: 100, y: 100, width: 60, height: 40)
btn.setTitle("点我", for: .normal)
btn.addTarget(self, action: #selector(onTap), for: .touchUpInside)
return btn
}()
var textField: UITextField = {
let textField = UITextField()
textField.placeholder = "请输入"
textField.frame = CGRect(x: 100, y: 160, width: 60, height: 40)
return textField
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
view.addSubview(btn)
view.addSubview(textField)
}
@objc func onTap() {
print("onTap")
}
}
extension UIWindow {
#if DEBUG
#else
open override var canBecomeFirstResponder: Bool {
return true
}
#endif
}
在看下面的解释之前,你能够先看看上面的代码,是否应该能复现出上述的bug?
当然,答案一定是肯定会出现上面的问题。当然你可能会有以下的疑问?
- 为什么debug的时分onTap办法会被调用?
- 为什么键盘弹起的时分onTap办法会被调用?
在上面的代码中,btn原意是要写成懒加载的,成果由于仿制失误,导致仿制的时分,少仿制了一个lazy,导致btn变成了非懒加载的存储变量,后边变成了一个当即履行的闭包。在闭包履行的时分ViewController还未进行初始化完结,闭包中的self其实是一个Function, 由于btn的target会测验转为NSObject,这儿一定转化不成功,咱们这儿简单认定为btn的target等于nil。
也就是说上面的代码能够简单的等同于btn.addTarget(nil, action: #selector(onTap), for: .touchUpInside)
.
而当对一个按钮增加事情的时分,假如target为nil,系统会进行一下的操作
- 首先获取APP当时的firstResponder,假如firstResponder为nil,则进行第4步,不然进行下一步
- 判别firstResponder是否完成此target对应的action,假如完成了,则进行调用。
- 不然则判别firstResponder的nextResponder是否完成,如此循环判别,一直到nextResponder为nil
- 判别btn是否完成了对应的action,假如完成,则进行调用。
- 不然则判别btn的nextResponder是否完成了对应的action,如此循环判别,一直到nextResponder为nil
关于UIButton的addTarget,更多信息可检查对UIButton的addTarget办法探求
回到了上面的代码: