delegate: (자신의 일을)위임하다, 대리자(본인 일을 맡기는 사람) 라는 뜻
- delegate - 메뉴얼(프로토콜)을 만든 사장님
- delegator - 메뉴얼 대로 사장님 대신 일 하는 알바생
컴포넌트/객체 간의 상호작용을 위해 만들어진 디자인 패턴 중 하나
객체 간의 통신을 위해 하나의 객체가 다른 객체를 대신하여 동작하도록 하는 구조를 제공한다.
SwiftUI에선 @State, @Binding, @Published 등의 래퍼에선 상태를 관리하고 뷰 간의 데이터를 전달하는게 동시에 가능하지만,
UIKit에선 변화 상태를 관리, 뷰 간의 데이터 전달을 delegate, protocol로 시행하는 것이다.
사장님이 알바생에게 일을 시키기 위해서 여러 정보들을 전달해야한다.
알려줄 여러 정보들(가게 메뉴얼)을 프로토콜에 저장한다.
알바생은 프로토콜을 채택해서 자기 방식대로 함수를 작성하고 수행한다.
여기서도 알 수 있듯 사장님, 알바생은 서로 각자가 한 일에 대해 자세히 보지 못한다.
즉, 사장님이 메뉴얼을 적을때 어떤 과정으로 자세히 적었는지는 알 수 없고, 알바생은 프로토콜에 함수 이름으로만 알 수 있다
알바생도 데이터를 받아 자기 방식대로 함수를 작성하는데, 이것 또한 사장님이 개입하거나 알 수 없다.
=> 이런 부분에서 결합도가 낮다고 표현한다
델리게이트를 위한 프로토콜을 정의한다.
여기엔 통신할 메서드나 이벤트, 데이터를 전달하기 위한 요구사항 등을 작성한다.
protocol SomeComponentDelegate: AnyObject {
func didReceiveData(_ data: String)
}
델리게이트를 가질 프로퍼티를 컴포넌트에 추가한다.
-맡길 일이 있는 컴포넌트(사장님, 데이터 발신자 컴포넌트)에 델리게이트를 추가한다
-지금은 Sendercomponent에 delegate가 있으므로 사장님이 일을 맡기려고 준비하는 것
class SenderComponent {
weak var delegate: SomeComponentDelegate?
...
}
이벤트가 발생했을 때 델리게이트에게 메시지를 보내어 해당 이벤트에 대한 처리를 요청한다.
-주문 이벤트가 발생하면 델리게이트를 호출해서 didReceiveData를 수행하라고 요청하는 것
-아직 delegator(알바생)를 여기선 모르므로 우선 저장
class SenderComponent {
func sendData() {
// ...
delegate?.didReceiveData("Hello from SomeComponent")
}
}
delegate?.didReceiveData("Hello from SenderComponent")
: Optional 체이닝과 함께 Delegate 객체에 있는 didReceiveData 메서드를 호출하는 코드이다.
따라서 delegate가 nil이 아니라면, 해당 Delegate 객체의 didReceiveData 메서드를 호출하고, "Hello from SenderComponent"라는 문자열을 전달합니다.
이 코드는 Delegate 패턴에서, SenderComponent가 delegate를 통해 데이터를 수신하는 방식을 구현하는 것으로, Delegate 객체에 의존하지 않으면서 해당 객체에 데이터를 전달합니다. Delegate 객체가 이 메서드를 구현하고 있는 경우, SenderComponent는 해당 이벤트나 데이터를 처리할 수 있습니다.
델리게이터쪽에 델리게이트 프로토콜을 채택하고, 해당 프로토콜의 요구사항을 구현한다.
-알바생이 해당 델리게이트 프로토콜(SomeComponentDelegate)을 채택했으므로 사장님 대신 일하겠다고 선언한 것
-알바생 => delegator
-해당 프로토콜의 요구사항을 구현한다 -> 사장님 메뉴얼인 프로토콜대로 receiveData 함수 구현
class ReceiverComponent: SomeComponentDelegate {
let someComponent = SomeComponent()
init() {
someComponent.delegate = self
someComponent.sendData()
}
func didReceiveData(_ data: String) {
print("Received data: \(data)")
}
}
import SwiftUI
import Combine
protocol SomeComponentDelegate: AnyObject {
func didReceiveData(_ data: String)
}
class SenderComponent: ObservableObject {
weak var delegate: SomeComponentDelegate?
func sendData() {
// 이벤트 발생 시 델리게이트에게 데이터 전달
delegate?.didReceiveData("Hello from SenderComponent")
}
}
struct ContentView: View {
@StateObject var senderComponent = SenderComponent()
var body: some View {
Text("Hello, World!")
.onAppear {
senderComponent.delegate = self
senderComponent.sendData()
}
}
}
extension ContentView: SomeComponentDelegate {
func didReceiveData(_ data: String) {
print("Received data: \(data)")
}
}
델리게이트 패턴은 객체 간의 효율적인 통신을 위해 사용됩니다. 이 패턴을 사용하면 컴포넌트들 간의 결합도를 낮추고, 유연성을 높여주는 장점이 있습니다. 여기에는 몇 가지 이유가 있습니다:
결합도 감소
: SenderComponent와 ReceiverComponent 간의 직접적인 의존성이 줄어든다.
SenderComponent는 어떤 객체로 데이터를 전달하는지 알 필요가 없으며, ReceiverComponent는 데이터를 제공하는 객체에 대한 구체적인 정보 없이도 데이터를 수신할 수 있다. 그러므로 두 객체간의 결합도가 감소한다.
이는 두 컴포넌트 간의 수정이나 확장을 용이하게 만든다.
재사용성
: Delegate pattern을 사용하면 동일한 델리게이트 프로토콜을 채택한 다른 컴포넌트들에서도 동일한 델리게이트를 사용할 수 있다.
즉, SenderComponent와 ReceiverComponent는 동일한 델리게이트를 공유하여 재사용이 가능하다.
유연성
: SenderComponent가 델리게이트 프로토콜을 통해 데이터를 전달하면서, ReceiverComponent가 해당 델리게이트 프로토콜을 구현함으로써, 서로 다른 종류의 객체들 간에도 통신이 가능해진다.
테스트 용이성: 델리게이트를 사용하면 컴포넌트들을 분리하여 테스트하기 용이하다. Mock 델리게이트를 구현하여 특정 동작이나 데이터를 시뮬레이트하여 각 컴포넌트를 테스트할 수 있다.
복잡성 및 이해도
: Delegate pattern은 객체 간의 상호작용을 위해 추가적인 프로토콜 및 메서드를 정의하고 구현해야 한다.
Delegate 메서드들이 많거나 복잡할 경우, 코드의 이해도가 낮아질 수 있다.
단방향 통신만 지원
: 특정 이벤트가 발생했을 때 한 객체가 다른 객체에게 통지하는 단방향 통신 방식을 사용한다.
때때로 이는 비효율적일 수 있고, 이벤트의 전달이나 처리가 복잡해질 수 있다.
import SwiftUI
struct ContentView: View {
@State private var receivedData: String = ""
var body: some View {
VStack {
Text("Received data: \(receivedData)")
SenderView(sendData: { data in
// 클로저를 통해 데이터 수신
receivedData = data
})
}
}
}
struct SenderView: View {
let sendData: (String) -> Void // 클로저 정의
var body: some View {
Button("Send Data") {
// 클로저를 사용하여 데이터 전달
sendData("Hello from SenderView")
}
}
}
지나가다가 봤는데 완전 유익한 글이네요! 잘 보고 갑니다 ㅎㅎ