https://explorer89.tistory.com/226
전체 흐름 요약
- ProfileViewController에서 UserFeedViewController로 이동
pushViewController로 화면 전환. - UserFeedViewController에서 ProfileFeedEditViewController 띄우기
present로 모달 팝업 표시. - ProfileFeedEditViewController에서 삭제 버튼을 눌렀을 때
Delegate를 통해 UserFeedViewController에 삭제 완료를 알림. - UserFeedViewController에서 RootViewController로 이동
popToRootViewController 또는 popToViewController를 호출하여 ProfileViewController로 돌아감.
1. delegate 프로토콜 정의
// 삭제 작업을 완료한 후 ProfileViewController로 돌아가도록 알리는 Delegate를 정의
protocol ProfileFeedEditDelegate: AnyObject {
func didDeleteFeed()
}
2. ProfileFeedEditViewController에서 Delegate 호출 Delegate를 사용하여 삭제 작업을 알리고, 두 화면을 모두 닫아 ProfileViewController로 돌아갑니다.
class ProfileFeedEditViewController: UIViewController {
// MARK: - Variables
weak var delegate: ProfileFeedEditDelegate?
// MARK: - UI Components
...
// MARK: - Initializations
override func viewDidLoad() {
...
didTappedButtons()
}
...
// MARK: - Functions
func didTappedButtons() {
closeButton.addTarget(self, action: #selector(didCalledCloseButton), for: .touchUpInside)
deletButton.addTarget(self, action: #selector(didCalledDeleteButton), for: .touchUpInside)
}
// MARK: - Actions
@objc func didCalledDeleteButton() {
print("데이터 삭제 완료")
feedDataManager.deleteFeedItem(feedID: userFeed!.feedID)
delegate?.didDeleteFeed()
dismiss(animated: true)
}
}
3. UserFeedViewController에서 Delegate 설정 ProfileFeedEditViewController가 닫힐 때 UserFeedViewController가 Delegate를 통해 상위 네비게이션 스택을 관리하도록 설정합니다.
extension UserFeedViewController: ProfileFeedEditDelegate {
func didDeleteFeed() {
// ProfileViewController로 돌아가기
navigationController?.popViewController(animated: true)
}
}
@objc func didTappedSetupButton() {
print("didTappedSetupButton() - called")
let profileFeedEditVC = ProfileFeedEditViewController()
profileFeedEditVC.userFeed = userFeed
if let sheet = profileFeedEditVC.sheetPresentationController {
// sheet.detents = [.medium()]
// sheet 올라오는 높이 조절
sheet.detents = [
.custom { context in
return 200
}
]
sheet.prefersGrabberVisible = true
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
}
profileFeedEditVC.modalPresentationStyle = .pageSheet
// 대리자 설정
profileFeedEditVC.delegate = self
present(profileFeedEditVC, animated: true)
}
1. 주요 역할 비유
- ProfileFeedEditViewController = 손님
- 손님은 "요리사"에게 음식을 요청하는 역할입니다.
- 즉, delegate.didDeleteFeed()를 호출하여 요청을 보냅니다.
- UserFeedViewController = 요리사
- 요리사는 손님의 요청(delegate.didDeleteFeed())을 받고, 손님이 원하는 대로 음식을 준비합니다.
- 요청에 따라 요리사는 navigationController.popViewController를 호출하여 화면을 ProfileViewController로 이동하게 합니다.
- delegate = 주문서
- 손님(ProfileFeedEditViewController)이 요리사에게 요청을 전달하는 통신 수단입니다.
- weak var delegate: ProfileFeedEditDelegate?는 손님이 요리사를 식별하기 위해 사용하는 "주문서"라고 볼 수 있습니다.
2. 과정 비유
이 관계를 요리사와 손님의 상호작용으로 풀어보면 다음과 같습니다:
손님이 레스토랑에 들어감:
UserFeedViewController가 ProfileFeedEditViewController를 present로 띄워 줍니다.
let profileFeedEditVC = ProfileFeedEditViewController()
profileFeedEditVC.delegate = self // 요리사(UserFeedViewController)가 주문서를 준비
present(profileFeedEditVC, animated: true)
손님이 음식을 주문:
ProfileFeedEditViewController에서 deleteAction 버튼을 눌러 delegate.didDeleteFeed()를 호출합니다.
@objc func deleteAction() {
delegate?.didDeleteFeed() // 주문서를 통해 요청을 보냄
dismiss(animated: true) // 손님은 자리를 떠남
}
요리사가 주문서를 확인하고 요리를 준비:
UserFeedViewController는 ProfileFeedEditDelegate를 구현한 덕분에 요청(didDeleteFeed())을 받습니다. 요리사가 요청받은 대로 요리를 준비합니다.
extension UserFeedViewController: ProfileFeedEditDelegate {
func didDeleteFeed() {
navigationController?.popToRootViewController(animated: true) // 요리를 완성하고 ProfileViewController로 이동
}
}
요리사가 음식을 준비하고 서빙:
요리사가 요청을 처리한 결과로 화면이 ProfileViewController로 전환됩니다.
비유에서 얻을 수 있는 델리게이트 패턴의 장점
- 유연성:
요리사가 바뀌어도(즉, delegate의 구현 객체가 달라져도) 손님은 동일한 방식으로 요청을 전달할 수 있습니다. - 재사용성:
ProfileFeedEditViewController는 UserFeedViewController와 강하게 연결되지 않으므로, 다른 화면에서도 동일한 방식으로 동작할 수 있습니다. - 명확한 역할 분리:
손님(ProfileFeedEditViewController)은 요청만 하고, 요청에 대한 처리(요리)는 요리사(UserFeedViewController)가 담당합니다.
왜 주문서(Delegate)가 필요한가?
손님이 직접 요리사에게 요청하지 않고, 왜 "주문서"를 사용하는 걸까요?
- 손님이 직접 요리사와 통신하면, 손님이 요리사에 강하게 의존하게 됩니다.
- 예를 들어, ProfileFeedEditViewController가 UserFeedViewController를 직접 호출하면, 나중에 요리사가 바뀌면 손님의 코드도 수정해야 합니다.
- 하지만 "주문서"(delegate)를 사용하면, 요리사가 바뀌더라도 손님의 코드는 변하지 않습니다.
- 델리게이트 프로토콜을 구현하기만 하면, 새로운 요리사도 손님의 요청을 처리할 수 있습니다.
🔥 만약에 UserFeedViewController와 같이 직접적인 참조 관계가 없는 경우에는 위와 같은 기능을 델리게이트 패턴만으로는 안된다.
왜 델리게이트 패턴만으로는 안 될까?
델리게이트 패턴은 1:1 관계에서 작동하며, 요청자(손님)와 응답자(요리사)가 서로 연결되어 있어야 합니다.
- ProfileFeedEditViewController는 delegate를 통해 **현재 띄운 주체(UserFeedViewController)**에만 요청을 전달할 수 있습니다.
- UserFeedViewController의 네비게이션 스택 위에 있지 않고, 독립적으로 존재하는 HomeViewController에는 직접 연결되지 않으므로 요청이 전달되지 않습니다.
이런 상황에서는 어떤 패턴을 써야 할까?
HomeViewController처럼 네비게이션 스택 상 위에 없거나 다른 뷰 컨트롤러에서도 요청을 받아야 한다면, 다음 중 하나를 사용해야 합니다:
- NotificationCenter
- Shared Data (싱글톤)
- Custom Closure (Callback)
🔥 NotificationCenter를 이용하여 화면 이동 (델리게아트 패턴 대체)
ProfileViewController에서 Notification 등록 먼저 ProfileViewController에서 특정 알림을 등록합니다. 이 알림을 받으면 현재 화면을 pop하여 이전 화면으로 돌아갈 수 있습니다.
import UIKit
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 삭제 완료 알림 등록
NotificationCenter.default.addObserver(self, selector: #selector(handleDeleteNotification), name: NSNotification.Name("DeleteItemNotification"), object: nil)
}
@objc private func handleDeleteNotification() {
// 삭제된 후 pop 동작 수행
self.navigationController?.popViewController(animated: true)
}
deinit {
// 메모리 누수를 방지하기 위해 Notification 해제
NotificationCenter.default.removeObserver(self, name: NSNotification.Name("DeleteItemNotification"), object: nil)
}
}
ProfileFeedEditViewController에서 Notification 전송 ProfileFeedEditViewController에서 삭제 작업이 완료된 후 NotificationCenter를 통해 "DeleteItemNotification" 알림을 전송합니다. 이 알림을 ProfileViewController에서 수신하여 pop 동작을 수행하게 됩니다.
@objc private func deleteAction() {
// 삭제 작업 수행 (예: 데이터 삭제)
print("삭제 작업 실행")
// 첫 번째 dismiss: 팝업을 닫음
dismiss(animated: true) {
// NotificationCenter를 통해 삭제 알림 전송
NotificationCenter.default.post(name: NSNotification.Name("DeleteItemNotification"), object: nil)
}
}
- Notification 등록: ProfileViewController에서 "DeleteItemNotification"이라는 이름의 알림을 등록합니다. 이 알림을 받으면 handleDeleteNotification 메서드가 호출되어 ProfileFeedEditViewController로 이전 화면으로 돌아가게 됩니다.
- Notification 전송: ProfileFeedEditViewController의 deleteAction 메서드에서 알림을 전송하여 ProfileViewController가 이 알림을 받고 화면을 pop하도록 합니다.
델리게이트 패턴과 노티피케이션의 주요 차이점
델리게이트 패턴 | NotificationCenter |
1:1 관계에서 사용됩니다. | 1:다 관계에서 사용됩니다. |
특정 객체(delegate)를 명시적으로 설정해야 작동합니다. | 메시지를 전역적으로 전달하므로 설정 없이 동작합니다. |
강한 객체 참조를 방지하려면 weak로 설정해야 합니다. | 특정 객체 참조 없이 메시지가 전달됩니다. |
의존 관계가 명확합니다. | 의존 관계가 느슨합니다. |
NotificationCenter와 Delegate의 사용 결정
NotificationCenter를 사용해야 할 때
- 다수의 구독자가 메시지를 받아야 하는 경우.
- ViewController의 강한 참조 또는 상태 관리가 복잡한 경우.
Delegate 패턴을 사용해야 할 때
- 명확한 1:1 의존 관계가 필요한 경우.
- 특정 ViewController 간의 상호작용이 필요한 경우 (의존 관계를 명확히 관리 가능).