Event Bubbling
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 Event Bubbling이라는 개념에 대해 학습해보겠습니다 🙋🏻
바로 이전 포스팅인 BorderlessButtonStyle에 대해 학습해보면서 짧게 이벤트 버블링이라는 개념이 적용되어서 그렇다~라고 소개만 하고 지나갔었는데요.
그걸 이번 포스팅에서 조금 더 구체적으로 알아보려합니다!
만약 BorderlessButtonStyle에서 어떻게 흐름상 말이 나왔는지 필요하다면 아래 포스팅을 먼저 참고해주세요 🙏🏻
그럼 한번 바로 알아볼까요?
Event Bubbling
사실 이벤트 버블링에 대한 직접적인 개념 자체는 웹 개발에서 사용되는것 같아요.
그래서 이번에 개념 학습을 위해 찾아봤던 레퍼런스들도 모두 웹 진영에서 설명이 주로 되어 있었습니다.
웹에서는 특정 요소에서 이벤트가 발생하면 그 요소의 부모 요소로 이벤트가 버블링 되어 올라갑니다.
버블링의 원리가 어떻게 될까요?
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고 이어서 부모 요소의 핸들러가 동작해요.
가장 최상단의 요소를 만날때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.
예를들어 아래와 같은 코드를 볼까요?
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
이 코드는 이렇게 표현되죠.
여기서 가장 안쪽의 p를 누르면 어떻게 버블링이 발생할까요?
1️⃣ p에 할당된 onclick 핸들러가 동작
2️⃣ 그 위 바깥인 div에 할당된 핸들러 동작
3️⃣ 또 그 위 바깥인 form에 할당된 핸들러 동작
4️⃣ document 객체를 만날때까지 각 요소에 할당된 onclick 핸들러 동작
이런 흐름이기에 세개의 얼럿창이 순차적으로 뜨게 되는것입니다.
이런 흐름을 이벤트 버블링이라고 칭하고 있어요!
물론 모든 이벤트가 버블링되는것은 아니지만 거의 수많은 이벤트들은 버블링을 일으킵니다.
웹 개발에서는 이런 버블링을 중단시키기 위해서 아래와 같이 버블링 중단을 심어주곤 합니다.
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
stopPropagation 메서드를 통해 버블링을 중단해줍니다.
다만 해당 메서드는 위쪽으로 일어나는 버블링은 중단할 수 있지만 다른 핸들러들이 동작하는건 중단시킬 수 없습니다.
만약 다른 핸들러의 동작까지도 막고 싶다면 stopImmediatePropagation() 메서드를 사용해야 합니다.
그런데 자바스크립트 레퍼런스에서는 이 이벤트 버블링을 막아아 하는 경우는 거의 없다고 말합니다.
꼭 필요한 경우를 제외하고는 버블링을 막지 말라고 권장하고 있습니다!
그 이유는 해당 버블링으로 막아놓은 영역에선 분석 시스템의 코드가 동작하지 않기에 제대로 분석할 수 없으며 stopPropagation을 사용한 영역은 데드 존이라 불리는 죽은 영역이 되어버린다고 말합니다.
그렇기에 이벤트 버블링을 막지말고 만약 막아야 해결되는 문제라면 커스텀 이벤트 등을 이용하여 이를 우회하는걸 권장하고 있어요 😀
갑자기 자바스크립트로 개념을 공부하니까 조금 새로운데요 ㅎㅎ...
이걸 iOS에서 한번 접목시켜보죠!
다시 iOS로 돌아와서 iOS에서는 UIKit이나 SwiftUI를 사용해 UI를 구성하고 이벤트를 처리하죠.
그런데 iOS에서의 이벤트 처리 모델은 웹의 이벤트 버블링과 약간 다릅니다.
UIKit에서는 예를들어 터치 이벤트가 있다면 이런 스텝으로 전달됩니다.
1️⃣ 시스템이 터치 감지하여 해당 터치 정보와 함께 UIApplication에 이벤트를 전송
2️⃣ UIApplication은 터치 이벤트를 key window에 전달
3️⃣ key window는 hit-test 알고리즘을 사용해 어떤 뷰가 실제 터치 이벤트를 받아야 하는지 결정
4️⃣ hit-test 결과로 선택된 뷰는 터치 이벤트에 응답할 수 있음
5️⃣ 만약 hit-test 뷰가 터치 이벤트에 응답하지 않으면 해당 뷰의 부모 뷰가 응답할 기회를 얻음
그럼으로 표현은 조금 다르지만 이 경우도 이벤트 버블링이라고 말할 수 있습니다 🕺🏻
그럼 코드로 한번 간략히 짜보고 과정을 예시로 말해볼까요?
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 200, height: 50))
button.backgroundColor = .blue
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
self.view.addSubview(button)
}
@objc func buttonTapped() {
print("Button was tapped!")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("ViewController's view was touched!")
}
}
해당 코드를 살펴보죠!
버튼을 탭하면 Button was tapped!라는 메시지가 출력되요.
그 후 시스템은 VC의 touchesBegan 메서드 호출을 통해 동일한 터치 이벤트를 VC의 루트 뷰에게 알려줍니다.
마지막으로 그럼 ViewController's view was touched!라는 메시지도 출력될까요?
아닙니다ㅎㅎ
여기서 중요한 포인트는 iOS에선 이벤트 전파 자체가 웹과 다르게 항상 발생되는것이 아닌 특정 조건에서만 발생합니다!
예를들면 버튼이 터치 이벤트를 소비했다면 그 이벤트는 상위 뷰로 전파되지 않죠.
즉 위의 코드를 실행해보면 실제 Button was tapped! 메시지만 노출되고 ViewController's view was touched! 메시지는 노출되지 않아요.
이미 buttonTapped에서 메시지를 출력함으로 해당 이벤트를 소비했다는것입니다.
만약 buttonTapped의 동작을 하지 않도록 print문을 지우고 동일하게 터치를 하면 ViewController's view was touched! 메시지가 출력됩니다!
이것이 바로 웹의 이벤트 전파와 다른점입니다.
마무리
이번엔 거품 그자체인 이벤트 버블링을 알아봤는데요!
학습하다보니 관련되어서 이벤트 캡쳐링도 연관이 있더라구요ㅎㅎ
그래서 다음 주제는? 캡쳐링~
레퍼런스