Introduce Delegate Pattern
UIKit에서는 여러가지 UI 관련 View를 제공합니다. 이 중에선 동작에 필요한 세부 구현은 숨겨진 채로 특정 상황이 발생했을 때만 ViewController에게 데이터를 전달하는 UIView가 있습니다.
예를 들어 UITextField
의 경우, 사용자가 입력창을 눌렀을 때 OS에게 키보드를 열 것을 명령하며 사용자가 입력한 값을 UITextField
에 전달하는 동작이 구현되어 있을 것입니다. 하지만 ViewController에선 이러한 세부 동작은 알 필요 없이 특정 상황이 발생했을 때만 데이터를 전달 받아 이용하면 됩니다.
UIkit에서는 이렇게 세부 구현은 숨긴 채로 특정 상황이 발생했음을 알리거나 동작의 제어를 위임하기 위해 Delegate Pattern을 활용하고 있습니다. UITextField
는 UITextFieldDelegate
프로토콜을 통해 이를 구현하고 있으며, 이를 채택한 ViewController는 다음의 메서드를 구현하여 데이터를 전달받고 동작을 제어할 수 있습니다.
UITextFieldDelegate
를 채택하여 구현할 수 있는 메서드 목록은 다음과 같습니다.
// 편집을 시작할 때 호출합니다. Bool 값을 반환하여 편집을 허용할지 여부를 결정할 수 있습니다.
optional func textFieldShouldBeginEditing(_:) -> Bool
// 편집이 시작된 직후 호출됩니다.
optional func textFieldDidBeginEditing(_:)
// 편집이 종료될 때 호출됩니다. Bool 값을 반환하여 편집을 중지할지 여부를 결정할 수 있습니다.
optional func textFieldShouldEndEditing(_:) -> Bool
// 편집이 종료된 직후 호출됩니다.
optional func textFieldDidEndEditing(_:)
// 텍스트 필드의 문자열이 변경될 때 호출됩니다. Bool 값을 반환하여 변경을 허용할지 여부를 결정할 수 있습니다.
optional func textField(_:shouldChangeCharactersIn:replacementString:) -> Bool
// 텍스트 필드의 선택 영역이 변경될 때 호출됩니다.
optional func textFieldDidChangeSelection(_:)
// 텍스트를 삭제할 때 호출됩니다. Bool 값을 반환하여 삭제를 허용할지 여부를 결정할 수 있습니다.
optional func textFieldShouldClear(_:) -> Bool
// 텍스트 필드의 리턴 키가 눌렸을 때 호출됩니다. Bool 값을 반환하여 리턴 키를 허용할지 여부를 결정할 수 있습니다.
optional func textFieldShouldReturn(_:) -> Bool
optional
키워드로 정의되어 있으니 ViewController는 이 중에서 필요한 메서드만 구현하면 됩니다.
정의한 메서드가 호출되려면 UITextField
인스턴스의 delegate
프로퍼티에 ViewController를 할당해야 합니다.
final class ViewController: UIViewController {
private let textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
// ...
}
func textFieldDidBeginEditing(_ textField: UITextField) {
// ...
}
}
이런 식으로 Delegate Pattern을 통해 동작의 제어권을 위임받을 수 있습니다.
Understand Delegate Pattern
그렇다면 Delegate Pattern은 어떤 식으로 동작하기에 다른 객체에게 동작의 제어권을 위임할 수 있는걸까요? 간단한 Delegate
프로토콜을 구현하여 동작을 확인해보겠습니다.
protocol UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool
func textFieldDidBeginEditing(_ textField: UITextField)
}
class UITextField {
weak var delegate: UITextFieldDelegate?
func userTextFieldTapped() {
guard let delegate else { return }
if delegate.textFieldShouldBeginEditing(self) {
// OS에게 키보드 열기 요청
delegate.textFieldDidBeginEditing(self)
}
}
}
class ViewController {
let textField = UITextField()
func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
print(#function)
return true
}
func textFieldDidBeginEditing(_ textField: UITextField) {
print(#function)
}
}
let controller = ViewController()
controller.viewDidLoad()
controller.textField.userTextFieldTapped()
// output:
// textFieldShouldBeginEditing(_:)
// textFieldDidBeginEditing(_:)
소스 코드가 공개되어 있지 않으므로 정확한 세부 구현은 알 수 없으나 Delegate 패턴을 활용한 내부 모습은 대략 위와 비슷할 것으로 추정합니다.
UITextField
의 경우 외부로부터 주입 받은 delegate
객체를 통해 textFieldShouldBeginEditing
를 호출하여 동작의 제어권을 위임한 모습입니다. textFieldShouldBeginEditing
에서 false
를 반환하면 textFieldDidBeginEditing
는 호출되지 않을 것입니다.
이러한 원리를 응용하여 Custom Delegate를 추가할 수 있습니다. Delegate Pattern 적용하기