At the current evening of the launch, the test found that there was no response when a button was clicked. The reappearance path given by the test was as follows: in the Release package, when entering the corresponding interface, the keyboard pop-up button could be clicked normally, while the keyboard retreat button could not be clicked, and the DEBUG package could also be clicked normally. If you are interested in this bug, click here to download the simplest demo.
The core code that can be reproduced is as follows
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 = "Please enter"
textField.frame = CGRect(x: 100, y: 160, width: 60, height: 40)
return textField
}()
override func viewDidLoad(a) {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
view.addSubview(btn)
view.addSubview(textField)
}
@objc func onTap(a) {
print("onTap")}}extension UIWindow {
#if DEBUG
#else
open override var canBecomeFirstResponder: Bool {
return true
}
#endif
}
Copy the code
Before looking at the explanation below, you can take a look at the code above. Should it reproduce the above bug?
The answer, of course, must be that the above question will definitely arise. Of course you might have the following question, right?
- Why is the onTap method called during debug?
- Why is the onTap method called when the keyboard is up?
In the above code, the BTN was intended to be lazy-loaded, but a copy error resulted in one less lazy being copied, causing the BTN to become a non-lazy-loaded stored variable, followed by an immediately executed closure. The ViewController hasn’t been initialized yet when the closure executes, and the self in the closure is really a Function, and since the target of BTN is going to try to convert to NSObject, it’s going to fail, so we’re simply going to say that the target of BTN is equal to nil.
That is, the above code can simply be equivalent to btn.addTarget(nil, action: #selector(onTap), for:.touchupinside).
And when you add an event to a button, if target is nil, the system does something like this
- First get the current APP’s firstResponder, if firstResponder is nil do step 4, otherwise do the next step
- Determine if firstResponder implements the action for this target, and if so, call.
- If not, the nextResponder of the firstResponder is realized, and so on until the nextResponder is nil
- Determine whether the BTN implements the corresponding action, if so, then call.
- Otherwise, it will judge whether the nextResponder of BTN has realized the corresponding action, and so on, until the nextResponder is nil
For more information about the addTarget method of UIButton, see the addTarget method exploration of UIButton
Back to the code above: