안녕하세요!
오늘은 Delegate 패턴에 대해 알아보려고 합니다.
delegate를 처음 접했을때는... '이게 무슨 소리지...?' 라는 생각뿐이었습니다...
delegate 패턴은 사용할 일이 많았지만, 그때는 사실 완전히 이해도 잘 못했고왜 써야되는지도 몰랐는데요..
이제는 터득하게되어 당당하게 포스팅 해보겠습니다!
delegate는 대리자, 위임하다라는 뜻을 가지고 있습니다.
delegate 패턴은 객체 간의 통신 방법 중 하나로,
A라는 특정 객체가 해야 할 일을 B라는 다른 객체에 위임하여 처리 하도록 하는 패턴입니다.
그럼 이때는 A의 delegate(대리자)는 B가 되겠죠?
텍스트필드, 테이블뷰, 컬렉션뷰 등에서 델리게이트 패턴을 활용하여
이벤트를 핸들링하는 코드를 많이 작성하는걸 보셨을거에요
예를 들어 뷰컨트롤러 내부에 텍스트필드 객체를 생성을 하면
텍스트필드가 직접 이벤트를 처리하는 것이 아니죠?
텍스트필드의 대리자를 self(뷰컨트롤러 자기자신)으로 설정한 후
뷰컨트롤러에 텍스트필드에 발생한 이벤트를 처리하는 메서드들을 작성하는데요.
이것이 delegate 패턴의 활용입니다!
처음 보시는 분들은 아마 이해가 안되실건데...
아래 예시를 보시면 조금 이해가 되실겁니다..!!!
설명을 위해서 억지예시를.. 좀 들겠습니다..ㅎㅎ
자신이 카페와 식당 두 가게를 운영하고 있는 사장님이라고 생각을 해봅시다.
카페알바와 식당알바를 뽑아야돼서 알바 구인사이트에 자격요건을 첨부하여 하나의 게시물을 작성했습니다.
사장님이 생각하는 자격요건은 아래 두가지인데요
- 주문 버튼을 눌렀을 때 주문을 받을 수 있어야하고
- 주방에서 음식이 완성되어 끝남 버튼을 때
1) 식당이면 서빙
2) 카페면 손님 호출을 할 수 있어야 함.
그런데 게으른 사장님이 2번 자격요견을 작성하기 귀찮아서 알바 구인 사이트에 다음과 같이 자격요건을 첨부했습니다ㅠㅠ
- callButton이 눌러졌을 때 손님에게 주문을 받을 수 있는 사람
- finishButton이 눌러졌을 때 적절한 대처를 할 수 있는 사람.
두 가게 모두 CallButton이 있습니다.
CallButton에는 또 callButton과 finishButton 두 버튼이 있고
식당과 카페에서 finishButton이 눌러졌을때 해야할 일은 다릅니다.
카페는 서빙을 하면 안되고 식당은 손님 호출만 하면 안되겠죠?
자 이런 상황에서 CallButtonsView와 아래처럼 CallButton이 눌러졌을 때 적절한 행동을 하는
ViewController 타입의 두 알바생을 같이 구현해봅시다!

// 파일명: callButtonsView.swift
protocol PartTime {
func didTapOrderButton()
func didTapFinishButton()
}
알바 자격요건에
callButton이 눌러졌을 때 손님에게 주문을 받을 수 있는 사람finishButton이 눌러졌을 때 적절한 대처를 할 수 있는 사람
이라고 되어있었죠?
따라서 PartTime 프로토콜에
didTapOrderButton()과 didTapFinishButton() 함수를 정의했습니다.
나중에 알바생을 구현할 때는 자격요건을 맞춰야 되니
PartTime 프로토콜을 채택하겠습니다!
그럼 이어서 코드를 작성해보도록 하죠
// 파일명: callButtonsView.swift
import UIKit
protocol PartTime {
func didTapOrderButton()
func didTapFinishButton()
}
final class CallButtonsView: UIView {
var delegate: PartTime?
// 주문 버튼
private let orderButton: UIButton = {
let button = UIButton()
button.setTitle("(손님용 버튼)주문할 때 눌러주세요", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 20)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemCyan
return button
}()
// 음식이 완료됨을 알바생에게 알리는 버튼
private let finishButton: UIButton = {
let button = UIButton()
button.setTitle("(직원용 버튼)음식이 완성되면 눌러주세요", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 20)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemCyan
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureSubviews()
configureLayout()
configureAddTarget()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Configure Action
extension CallButtonsView {
private func configureAddTarget() {
orderButton.addTarget(self, action: #selector(didTapOrderButton), for: .touchUpInside)
finishButton.addTarget(self, action: #selector(didTapFinishButton), for: .touchUpInside)
}
@objc private func didTapOrderButton() {
delegate?.didTapOrderButton() // 대리자(알바생)에게 일을 시키는 것!
}
@objc private func didTapFinishButton() {
delegate?.didTapFinishButton() // 대리자(알바생)에게 일을 시키는 것!
}
}
// MARK: - Configure UI
extension CallButtonsView {
private func configureSubviews() {
[orderButton, finishButton].forEach {
addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
private func configureLayout() {
NSLayoutConstraint.activate([
orderButton.centerXAnchor.constraint(equalTo: centerXAnchor),
orderButton.centerYAnchor.constraint(equalTo: centerYAnchor),
orderButton.leadingAnchor.constraint(equalTo: leadingAnchor),
finishButton.centerXAnchor.constraint(equalTo: centerXAnchor),
finishButton.topAnchor.constraint(equalTo: orderButton.bottomAnchor, constant: 10),
finishButton.leadingAnchor.constraint(equalTo: leadingAnchor)
])
}
}
CallButtonsView는 재사용을 위해 구현한 클래스입니다!
CallButtonsView에는 위에서 말한대로 두가지 버튼이 구현되어 있고,
각 버튼이 탭되었을 때 어떤 행동을 하는지 구현되어져있습니다.
이때 didTapOrderButton(), didTapFinishButton() 함수의 내용은 좀 이따 설명하겠습니다!
이 클래스에는 delegate라는 프로퍼티가 선언 되어있습니다.
delegate 변수는 옵셔널 PartTime 프로토콜 타입으로 정의되어 있는데요.
바로 이 변수를 통해 추후에 대리자를 설정할 수 있습니다!!
무슨 소리인지 모르겠다구요? 미리 말씀드리면
지금은 delegate라는 변수를 정의만 했고, 할당은 하지 않은 상태죠?
(나중에) 다른 뷰컨에서
CalButtonsView의 인스턴스를 생성
->CallButtonsView의 인스턴스의delegate프로퍼티에self(뷰컨트롤러 자신)을 넣기
-> 대리자를 자신으로 설정
이제 식당 알바생을 구현해볼까요?
CallButtonsView 인스턴스 생성, PartTime 프로토콜 채택import UIKit
final class RestaurantPartTimeViewController: UIViewController {
let restaurantCallButtonsView = CallButtonsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureUI()
// restaurantCallButtonsView.delegate = self
}
}
extension RestaurantPartTimeViewController: PartTime {
}
// MARK: - Configiure UI
extension RestaurantPartTimeViewController {
// UI 관련 코드 생략
}
이 뷰 컨트롤러를 보면
CallButtonsView()라는 클래스의 인스턴스로 생성된 restaurantCallButtonsView가 있습니다.
알바생한테 버튼이 이식된건 아니고.. 그냥 넘어가주세요...ㅎㅎ
그리고 PartTime 프로토콜을 채택했는데요
프로토콜을 채택했으니 위에서 정의했던
didTapOrderButton(), didTapFinishButton() 메서드를 구현해야겠죠?
import UIKit
final class RestaurantPartTimeViewController: UIViewController {
let restaurantCallButtonsView = CallButtonsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureUI()
// restaurantCallButtonsView.delegate = self
}
}
extension RestaurantPartTimeViewController: PartTime {
// 프로토콜 필수 메서드 구현
func didTapOrderButton() {
print("짬뽕 두개 주문받았습니다.")
}
func didTapFinishButton() {
print("주문하신 짬뽕 나왔습니다. 맛있게 드세요.")
}
}
// MARK: - Configiure UI
extension RestaurantPartTimeViewController {
// UI 관련 코드 생략
}
이 식당은 중식집인가 보네요
orderButton이 눌러졌을 때는 "짬뽕 두개 주문받았습니다."라고 출력을하고
finishButton이 눌러졌을때는 "주문하신 짬뽕 나왔습니다. 맛있게 드세요."라고 출력을 합니다.
import UIKit
final class RestaurantPartTimeViewController: UIViewController {
let restaurantCallButtonsView = CallButtonsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureUI()
// ✨중요! 이 알바생이 대리자임을 지정해줘야 함✨
restaurantCallButtonsView.delegate = self
}
}
// PartTime 프로토콜 채택했으니 그에 맞는 일을 할 줄 알아야 됨!
extension RestaurantPartTimeViewController: PartTime {
// 프로토콜 필수 메서드 구현
func didTapOrderButton() {
print("짬뽕 두개 주문받았습니다.")
}
func didTapFinishButton() {
print("주문하신 짬뽕 나왔습니다. 맛있게 드세요.")
}
}
// MARK: - Configiure UI
extension RestaurantPartTimeViewController {
// UI 관련 코드 생략
}
마지막으로
restaurantCallButtonsView 인스턴스의 프로퍼티인 delegate에
self(RestaurantPartTimeViewController) 할당해주어 대리자 지정까지 마쳐줍니다.
근로계약서 작성이라고 비유하면 적절할까요..?
이제 우리 가게의 직원이다! 라고 선언을 하는 것과 똑같은 의미입니다!!
분명 CallButtonsView를 구현할 때 delegate 변수는 옵셔널 PartTime 타입이었는데...
UIViewController 타입인 self(RestaurantPartTimeViewController)를 어떻게 넣냐구요?
그 이유는 바로
3. RestaurantPartTimeViewController 구현 단계에서
PartTime 프로토콜을 채택했으니 delegate 변수에 할당을 할 수 있게 되는 것 입니다!
카페 알바생도 똑같이 만들어주면
//
// CafePartTimeViewController.swift
// delegatePattern
//
// Created by -- on 3/23/24.
//
import UIKit
final class CafePartTimeViewController: UIViewController {
let cafeCallButtonsView = CallButtonsView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBrown
configureUI()
// ✨중요! 이 알바생이 대리자임을 지정해줘야 함✨
cafeCallButtonsView.delegate = self
}
}
// PartTime 프로토콜 채택했으니 그에 맞는 일을 할 줄 알아야 됨!
extension CafePartTimeViewController: PartTime {
// 프로토콜 필수 메서드 구현
func didTapOrderButton() {
print("아이스아메리카노 두개 주문받았습니다.")
}
func didTapFinishButton() {
print("주문하신 음료 나왔습니다. 카운터에서 가져가세요")
}
}
// MARK: - Configiure UI
extension CafePartTimeViewController {
private func configureUI() {
// UI 관련 코드 생략
}
}
orderButton이 눌러졌을 때는 "아이스아메리카노 두개 주문받았습니다."라고 출력을 하고
finishButton이 눌러졌을때는 "주문하신 음료 나왔습니다. 카운터에서 가져가세요"라고 출력을 합니다.
다들 잘 따라오셨나요?
이제 이 코드들이 어떻게 동작하는지 정리해보겠습니다!
카페라 가정했을 때
0. cafeCallButtonsView.delegate = self
: CafePartTimeViewController에서 델리게이트 지정을 했죠?
위에서 설명했듯이 이는
CallButtonsView 클래스의 인스턴스인 cafeCallButtonsView의 delegate 프로퍼티에
self(cafePartTimeViewController)를 할당한 것입니다.
1. 사용자가 주문하기 버튼 탭(띵동)
: CafePartTimeViewController의 cafeCallButtonsView = CallButtonsView()의 orderButton이 눌러짐.
2. cafeCallButtonsView 인스턴스는 이벤트 핸들링 수행
: didTapOrderButton() 호출
3. 대리자에게 일 시키기
: didTapOrderButton()이 호출 되었으니 메서드 내부의
delegate?.didTapOrderButton() 실행
-> 0번 단계에서 delegate는 CafePartTimeViewController로 설정이 되었죠?
따라서 CafePartTimeViewController의 didTapOrderButton()를 호출하게 됩니다.
4. 대리자는 구현된 일 실행
대리자인 CafeTimePartTimeViewController()는
자신에게 구현된 didTapOrderButton()을 출력해
"아이스아메리카노 두개 주문받았습니다."를 출력하게 됩니다.
보통은 두가지 경우에서 많이 사용하는데요
이벤트 처리입니다.
A라는 특정 객체가 있는데, 어떤 이유에 의해 A에서는 이벤트를 처리하는 코드를 작성할 수 없다고 가정해보겠습니다!
그때 그 이벤트를 대신 처리해줄 객체가 필요합니다.
따라서 B에게 A의 대리자 역할을 하도록 설정하여 A에게 발생한 이벤트를 B가 처리할 수 있습니다.
위의 예시가 delegate를 활용한 이벤트 처리입니다!
데이터 전달입니다.
이벤트를 전달하는 것 처럼 데이터도 전달이 가능합니다!
그럼 delegate 패턴을 굳이 사용하는 이유가 뭘까요?
delegate 패턴을 이용하면 객체 간 결합도도 낮추고, 재사용성과 유연성을 높일 수 있습니다!
위에서 보신것처럼 CafePartTimeVC, RestaurantPartTimeVC에서 CallButtonViews의 동작이 달랐는데요.
델리게이트 패턴과 프로토콜을 이용하여 CallButtonViews 하나를 재사용할 수 있었습니다!
또한 유지보수에서의 장점이 있는데요
객체는 델리게이트 프로토콜을 통해서만 서로 소통하기 때문에
세부적인 구현을 몰라도 상호작용이 가능합니다!
어떤 일을 할지만 정하고, 상황에 맞는 코드만 작성하면 되는 것이죠!
자세하게 설명하려다보니 너무 글이 길어진 것 같애서 간단하게 정리해보겠습니다!
델리게이트 패턴의 초기 설정
1. 프로토콜 선언
2. 버튼 클래스에 델리게이트 변수 선언(이 변수의 타입은 정의한 프로토콜의 옵셔널 타입임.)
3. 알바 클래스는 버튼 클래스의 인스턴스를 생성
4. 알바 클래스는 프로토콜 채택
5. 알바 클래스는 프로토콜을 채택했으므로 필수 메서드 구현
6. 알바 클래스 내부에서 버튼 클래스 인스턴스의 델리게이트 프로퍼티에 자기자신 넣음
델리게이트 패턴 동작 구조
1. 사용자가 버튼 탭
2. 버튼 클래스는 띵동 버튼이 들어오면 알바를 시키는 메서드가 있음.
이 메서드는 초기 설정의 6번에서 자신에게 저장된 대리자에게 그 일을 시키는 메서드임.
3. 그럼 초기 설정 5번에서 구현된 메서드가 호출됨. -> 원하는 동작 수행.
델리게이트 패턴이 나올 때 마다 식은땀을 흘렸는데..
이젠 반갑게 맞이 할 수 있게 되었어요ㅠㅠ
설명하는 방식으로 포스팅을 하니 이해도 깊이 되고 헷갈리는 부분들을 확실히 잡을 수 있지만 시간이 너무 드네요..
그래도 코드까지 작성하며 공부해서 몸소 깨달아 뿌듯합니다!
예정된 포스팅은
1) 동시성 프로그래밍
2) 뷰, 뷰컨, 앱의 생명주기
이 둘인데요.. 전에 뷰의 생명주기 포스팅하다 너무 어려워서 결국 세상으로 나오지 못했지만.. 다시 한 번 도전해보겠습니다!!
https://developer.apple.com/documentation/swift/using-delegates-to-customize-object-behavior
Delegate 패턴을 카페와 알바로 두고 비유하니까 이해가 쉽게 되네요!!
따로 카페와 알바하는 생각나는 부분이 객체지향에서 쓰는 부분이였는데
객체 지향 사실과 오해 책 초반에 나오는 부분이 생각나서 더 재밌게 봤던 것 같아요!!
혹시 이 책은 보셨을까요??