Delegate Pattern 이란?
"객체가 자신의 책임을 다른 객체에게 위임(delegate)하는 디자인 패턴"을 의미한다.
예를 들어, 테이블뷰 또는 컬렉션 뷰를 사용할 때 어떤 액션을 취할지에 대한 책임을
뷰컨트롤러에게 UITableViewDelegate 또는 UICollectionViewDelegate를 사용하여 위임한다.
그럼 왜 굳이 "위임"이라는 것을 사용하는 것일까?
애플이 미리 구현해 놓은 UITableView 또는 UICollectionView 등의
내부 코드는 비공개이기 때문에 개발자가 이를 알 수 없고, 그렇기에 수정할 수도 없다.
위에서 예를 든 테이블의 셀을 탭했을때, 상황에 맞는 리액션을 개발자가 코드로 작성해야 한다.
하지만 개발자는 애플이 구현해 놓은 내부 코드를 수정할 수 없다.
따라서 다른 객체에서 개발자가 구현할 코드를 작성한 뒤에
테이블뷰가 그 객체를 호출하는 방식을 사용한다.
즉, 테이블뷰와 객체를 연결하는 방식이 바로 Delegate Pattern 이다.
Delegate Pattern의 핵심은 두 객체를 연결하는 것이다.
이벤트를 받는 객체 (예: UITableView)가 이벤트를 받아
어떤 리액션을 취할지를 delegate (예: ViewController)에게 위임한다.
정리하면 delegate는 어떤 객체가 이벤트를 만났을 때, 그 객체를 대신하는 것을 말하고,
delegating 객체는 이벤트를 받고 처리하는 responder 객체이다.
실제로 앱에서는 어떤 방식으로 사용되나?
Delegate Pattern을 가장 많이 사용하는 경우는 두 개의 뷰컨트롤러 사이에 데이터를 전달할 때다.
예를 들어 사용자 프로필 수정창에서 사용자 정보를 수정하고 확인 버튼을 누르면
이전 화면으로 돌아가고, 이 때 입력받은 정보들을 보여줘야하는 경우에 사용한다.
그럼 어떻게 사용하나?
ProfileViewController 화면이 밑에 있는 상태에서 그 위에 EditProfileViewController 화면이 올라오고,
EditProfileViewController 에서 수정한 값을 ProfileViewController로 전달하고,
EditProfileViewController 화면은 해제된다.
1. 먼저 프로토콜을 생성한다.
별도의 스위프트 파일을 생성하여 그 파일 내에 프로토콜을 생성한다.
changeNameDelegate 라는 요리법을 가이드라인만 잡는다.
protocol changeNameDelegate {
func changeName(name: String)
}
2. ProfileViewController 클래스 내에 위에 생성한 프로토콜을 채택한다.
ProfileViewController 가 바로 요리사라고 생각해보자
class ProfileViewController: UIViewController, changeNameDelegate {
...
}
3. ProfileViewController 클래스 내에 프로토콜을 채택할 경우,
프로토콜 내의 함수를 구체적으로 구현한다.
changeName은 name이라는 파라미터를 profileView 내의 nameTextField의 값에 대입한다.
ProfileViewController가 요리사 이기 때문에 요리법에 대해 제대로 알 필요가 있다.
class ProfileViewController: UIViewController, changeNameDelegate {
func changeName(name: String) {
profileView.nameTextField.text = name
}
var profileView = ProfileView()
...
}
4. EditProfileViewController 파일 내에 코드를 구현한다.
4.1 delegate 변수를 선언한다.
EditProfileViewController에 delegate라는 주문서를 생성한다.
var delegate: changeNameDelegate?
4.2 didTapButton() 메서드 내에서 delegate를 통해 프로토콜 내의 함수에 접근한다.
changedName이라는 변수에 nameTextField 내의 입력한 값을 할당한다.
그리고 delegate.changeName에 changeName을 전달한다.
dismiss를 통해 EditProfileViewController를 제거한다. (이전 화면으로 돌아간다.)
delegate?.changeName(name: changeName) 이라는 주문서를 전달한다.
@objc private func didTapButton() {
changedName = editView.nameTextField.text ?? "잘못입력"
delegate?.changeName(name: changedName)
dismiss(animated: true)
}
4.3 EditProfileViewController 내의 전체 코드
class EditProfileViewController: UIViewController{
var delegate: changeNameDelegate?
var editView = EditView()
var changedName: String = ""
override func loadView() {
self.view = editView
}
override func viewDidLoad() {
super.viewDidLoad()
editView.editButton.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
}
@objc private func didTapButton() {
changedName = editView.nameTextField.text ?? "잘못입력"
delegate?.changeName(name: changedName)
dismiss(animated: true)
}
}
5. 마지막으로 ProfileViewController 클래스 내에서
didTapButton() 메서드에 아래코드를 구현한다.
여기서 "editVC.delegate = self" 라는 의미는 editVC의 대리자를
ProfileViewController로 선언한다는 것이다.
editVC.delegate = self 라고 주문서를 확인한다.
@objc private func didTapButton() {
let editVC = EditProfileViewController()
editVC.modalPresentationStyle = .fullScreen
editVC.delegate = self
present(editVC, animated: true)
}
Delegate로 데이터 전달을 사용하는 경우
두 개의 화면이 있다. 두 번째 화면에서 데이터를 첫 번째 화면으로 옮길 때,
이미 메모리에 올라와 있는 상태에서 데이터 전달 할 때 사용한다.
즉, 첫 번째 화면 위로 두 번째 화면을 올리고, 두 번째 화면에 첫 번째 화면으로 present를 통해
데이터를 전달할 때 사용한다.
또는 A 라는 뷰 컨트롤러 위에 TableViewCell, CollectionViewCell 를 올릴 때,
이를 갖고 있는 상위 뷰 컨트롤러로 데이터를 전달할 때 사용한다.
⭐️ 주의할 점이 하나 있다. 바로 Strong Reference Cycle 이다.
사실 위에서 delegate 프로퍼티를 정의할 때 weak로 선언해야 한다.
이유는 두 개의 클래스 (ProfileViewController, EditProfileViewController) 사이에서
델리게이트 패턴을 사용할 경우 Strong Reference Cycle이 생길 수 있기 때문이다.
'iOS > Swift' 카테고리의 다른 글
iOS 앱의 생명주기(App Life Cycle)에 대해 설명해주세요. (0) | 2024.08.16 |
---|---|
Swift에서 옵셔널이란 무엇이며, 언제 사용해야 하나요? (0) | 2024.08.16 |
영어 단어 중에 첫 번째 글자를 대문자로 하고 나머지는 소문자로 처리하는 함수 (0) | 2024.07.04 |
ObservableObject와 @Published (0) | 2024.06.03 |
Combine (MVVM 패턴) (0) | 2024.05.30 |