🩵 Delegate Pattern의 조건
델리게이트 패턴은 객체간의 상호 작용을 하기 위해서 사용됩니다.
A객체가 B객체에게 이벤트나 데이터, 특정한 동작을 위임하는 등 말이죠.
이때, 프로토콜을 채택하는 객체가 클래스 타입이라면 참조에 의한 전달이 이루어지게 됩니다.
참조 타입은 메모리 상에서 한 객체를 여러 곳에서 공유할 수 있으므로,
여러 객체가 동일한 델리게이트를 참조하고 해당 델리게이트를 통해 통신할 수 있습니다.
반면 값 타입(구조체, 열거형 등)은 복사에 의한 전달이 이루어지기 때문에 여러 객체가 동일한 델리게이트를 참조하는 것이 어렵습니다.
값 타입의 특성 상 복사본이 만들어지기 때문입니다.
따라서 델리게이트로 사용되는 프로토콜을 정의할 때, 해당 프로토콜을 채택하는 객체는
반드시 참조 타입인 클래스여야 합니다.
이것은 델리게이트 역할을 수행하기 위한 필수 조건입니다.
protocol SomeDelegate: AnyObject {
func didReceiveData(data: SomeData)
}
🩵 AnyObject는 무엇일까?
AnyObject를 프로토콜에 사용하면,
해당 프로토콜을 채택하는 객체가 클래스 타입일 경우에만 사용할 수 있게 제약을 둘 수 있습니다.
기본적으로 프로토콜은 구조체, 열거형, 클래스 등 어떤 타입이든 채택이 가능합니다.
하지만 델리게이트 패턴에서는 클래스 간 상호 작용을 위해 프로토콜을 사용하기 때문에 클래스에 제한하고자 AnyObject를 사용합니다.
🩵 강한 참조 순환 방지하기
델리게이트 패턴에서는 대표적으로 다음과 같은 상황 때문에 String Reference Cycle, 강한 참조 순환이 발생할 수 있습니다.
- 델리게이트 객체가 자체를 델리게이트로 가지는 경우 : A 객체가 B를 델리게이트로 가지고 있고, B 객체가 다시 A를 델리게이트로 가지는 경우. 서로가 해제되지 않는 한 메모리에서 해제되지 않음.
이 문제를 해결하기 위해 weak 참조를 사용해야 합니다.
weak 참조는 참조하고 있는 객체가 메모리에서 해제될 때 자동으로 nil로 설정되기 때문에, 순환 참조로 인한 메모리 누수를 방지할 수 있습니다.
델리게이트 패턴에서 weak 참조를 사용하려면 해당 프로토콜을 채택하는 객체가 클래스 타입이어야 하기 때문에 AnyObject를 사용합니다.
✔️ 예시
protocol ManagerDelegate {
func taskCompleted()
}
class Manager {
var delegate: ManagerDelegate?
func performTask() {
print("Manager: Performing task...")
// 작업 완료 후 델리게이트에게 알림
delegate?.taskCompleted()
}
}
class TaskExecutor: ManagerDelegate {
let manager: Manager
init(manager: Manager) {
self.manager = manager
// 자기 자신을 델리게이트로 설정
manager.delegate = self
}
func taskCompleted() {
print("TaskExecutor: Task completed notification received!")
}
}
// 사용 예시
let taskManager = Manager()
let taskExecutor = TaskExecutor(manager: taskManager)
// 작업 수행
taskManager.performTask()
Manager 클래스가 작업 완료 시 알림을 받기 위해 Delegate 프로토콜을 사용하는 상황입니다.
- TaskExecutor 객체가 생성될 때 Manager 객체의 델리게이트로 자기 자신을 설정합니다.
- Manager 객체는 TaskExecutor 객체를 가리키는 델리게이트 속성을 가집니다.
- 이때, 서로가 서로를 강한 참조로 가지고 있게 됩니다.
이 상태에서 두 객체는 서로를 해제할 수 없게 되며 메모리 누수가 발생합니다.
Manager 객체가 해제되더라도 TaskExecutor 객체가 해제되지 않고, 그 반대의 경우도 마찬가지입니다.
따라서, 델리게이트 패턴을 사용할 때는 일반적으로 델리게이트 속성을 weak로 선언하여 순환 참조 문제를 방지합니다.
weak 참조는 참조하고 있는 객체를 강제로 해제하지 않으므로 순환 참조를 방지하면서도 메모리 누수를 방지할 수 있습니다.
weak var delegate: ManagerDelegate?
🩵 Delegate Pattern에서 AnyObject를 사용하는 이유는?
결론적으로, 이렇게 정리할 수 있겠습니다.
anyobject를 사용하면 ARC가 참조 계수를 관리할 수 있음
- 델리게이트 패턴에서는 강한 참조 순환 문제가 발생할 확률이 높아서 메모리 누수가 우려된다
- 따라서 weak 참조를 사용해서 순환 참조와 메모리 누수를 방지해야 한다
- weak 참조는 클래스에서만 사용할 수 있다
- AnyObject를 프로토콜에 사용하면 클래스 타입일 때만 프로토콜을 채택할 수 있게 제약을 걸 수 있다.
- 델리게이트 패턴은 객체 간의 통신을 위한 것이므로 참조 타입이어야 한다.
- 값 타입인 구조체와 열거형은 참조 타입의 특성을 가지지 않아서 (값 복사가 일어나기 때문에) 객체 간 통신에는 적합하지 않다.
- AnyObject로 제약을 둔 프로토콜을 클래스에서만 채택하여, 강한 순환 참조를 방지하기 위해서 델리게이트를 선언할 때 weak 참조로 사용할 수 있게 할 수 있다.
'iOS > Swift' 카테고리의 다른 글
[Swift] 네트워크 통신(2) - URLSession(GET)과 Decodable 프로토콜 (1) | 2024.01.04 |
---|---|
[Swift] 네트워크 통신(1) - URL과 REST API (1) | 2024.01.04 |
[Swift] UIStackView 사용, UILabel.font 속성 연속 사용, @objc 어노테이션 (0) | 2023.12.27 |
[Swift] tableview가 변경되었을 때 처리하는 방법, beginUpdates() endUpdates() (1) | 2023.12.19 |
[Swift] TableViewCell 내부 UIButton Action 처리하기 (tag) (0) | 2023.12.15 |