델리게이트 패턴은 한 객체가 특정 작업을 다른 객체에 위임하는 디자인 패턴이다. 이는 코드의 유연성과 재사용성을 증가시키고, 의존성 역전의 원칙을 잘 따르게 해준다. 과적으로 객체 간의 강한 결합성을 피할 수 있게 된다.
iOS에서는 델리게이트 패턴이 널리 사용된다. 'UITableView'와 'UICollectionView'는 사용자의 상호 작용에 대응하기 위해 델리게이트를 활용한다. 이 외에도 많은 UI 컴포넌트들도 델리게이트 패턴을 활용한다.
Swift에서 델리게이트 패턴을 사용하려면 다음과 같은 단계를 거친다.
protocol DoorDelegate: AnyObject {
func doorDidOpen(_ door: Door)
func doorDidClose(_ door: Door)
}
class Door {
weak var delegate: DoorDelegate?
func open() {
// Some code to open the door...
delegate?.doorDidOpen(self)
}
func close() {
// Some code to close the door...
delegate?.doorDidClose(self)
}
}
}
class DoorObserver: DoorDelegate {
func doorDidOpen(_ door: Door) {
print("The door did open.")
}
func doorDidClose(_ door: Door) {
print("The door did close.")
}
}
let door = Door()
let observer = DoorObserver()
door.delegate = observer
텍스트 필드의 델리게이트 패턴 사용 사례를 살펴보자
UITextFieldDelegate 프로토콜은 텍스트 필드의 작업을 다른 객체에게 위힘할 수 있도록 해준다.
protocol UITextFieldDelegate : NSObjectProtocol { ... }
이 프로토콜을 채택한 객체는 텍스트 필드의 작업을 위임 받을 수 있다.
class ViewController: UIViewController, UITextFieldDelegate { ... }
해당 객체의 delegate 속성에 자신을 할당하여 작업을 위임 받는다.
myTextField?.delegate = self
이렇게 하면, 위임 받은 객체에서 텍스트 필드의 상태 변화에 따라 해당 프로토콜에 정의된 메서드들을 구현하고 해당 메서드들은 각 시점에 호출된다. 이렇게 하면 위임 받은 객체가 원하는 동작을 수행할 수 있게 된다.
재사용성: 동일한 작업을 다른 컨텍스트에서도 쉽게 재사용할 수 있다.
유연성: 런타임 시간에 객체의 동작을 변경할 수 있다. 이는 클래스간 강한 결합을 방지하고 객체의 동작을 유연하게 변경할 수 있게 해준다.
의존성 역전: 객체가 직접 작업을 수행하는 대신 델리게이트에게 그 작업을 위임하기 때문에 의존성 역전 원칙을 따른다. 객체는 ㅅㅇ위 수준의 정책을 정의하고, 델리게이트는 그 정책을 실행하는 데 필요한 세부 군현을 제공한다.
상위 수준의 정책(high-level policy)이란게 뭘까?
"상위 수준의 정책"과 "하위 수준의 세부사항"이라는 용어는 소프트어 아키텍처와 디자인 패턴에서 사용된다.
상위 수준의 정책: 프로그램의 전반적인 동작 원칙을 말한다.
하쉬 수준의 세부사항: 전체적인 원칙을 구현하기 위한 구체적인 세부 사항을 말한다.
복잡성: 코드의 복잡성이 증가할 수 있다. 이로 인해 디버깅이 어려워지고 어떤 델리게이트가 호출되었는지 파악하기 어려울 수 있다.
오버헤드: 추가적인 클래스나 인터페이스를 정의해야할 수도 있다. 이는 코드 베이스의 크기를 증가시키고 메모리와 시간을 소비하는 결과를 가져올 수 있다.
이러한 장단점을 감안하여 델리게이트 패턴을 적절한 순간에 잘 사용한다면 앱의 복잡성을 관리하고 코드 재사용성을 향상시키는 유용한 도구가 될 수 있다.