Delegate pattern이란?

Minyoung Lee·2023년 12월 23일

Delegate 정의

delegate: (자신의 일을)위임하다, 대리자(본인 일을 맡기는 사람) 라는 뜻

  • delegate - 메뉴얼(프로토콜)을 만든 사장님
  • delegator - 메뉴얼 대로 사장님 대신 일 하는 알바생

Delegate pattern 정의

  • 컴포넌트/객체 간의 상호작용을 위해 만들어진 디자인 패턴 중 하나

    • 컴포넌트 간의 통신하는 방법 중 하나!
  • 객체 간의 통신을 위해 하나의 객체가 다른 객체를 대신하여 동작하도록 하는 구조를 제공한다.

  • SwiftUI에선 @State, @Binding, @Published 등의 래퍼에선 상태를 관리하고 뷰 간의 데이터를 전달하는게 동시에 가능하지만,
    UIKit에선 변화 상태를 관리, 뷰 간의 데이터 전달을 delegate, protocol로 시행하는 것이다.

쉬운 버전의 Delegate pattern 설명

  1. 사장님이 알바생에게 일을 시키기 위해서 여러 정보들을 전달해야한다.

  2. 알려줄 여러 정보들(가게 메뉴얼)을 프로토콜에 저장한다.

    • 실제론 textfield 입력 후 엔터를 쳤을때 보낼 함수, 버튼을 눌렀을때 동작할 함수 이름을 프로토콜에 작성한다.
  3. 알바생은 프로토콜을 채택해서 자기 방식대로 함수를 작성하고 수행한다.

    • '자기 방식대로 함수를 작성한다' 표현한 이유는,
      프로토콜에 함수를 채택한 후 안에서는 데이터를 여러 형태로 변형 가능하기 때문에!

여기서도 알 수 있듯 사장님, 알바생은 서로 각자가 한 일에 대해 자세히 보지 못한다.
즉, 사장님이 메뉴얼을 적을때 어떤 과정으로 자세히 적었는지는 알 수 없고, 알바생은 프로토콜에 함수 이름으로만 알 수 있다
알바생도 데이터를 받아 자기 방식대로 함수를 작성하는데, 이것 또한 사장님이 개입하거나 알 수 없다.
=> 이런 부분에서 결합도가 낮다고 표현한다

Delegate pattern 구현 방법

1. protocol 정의(메뉴얼 작성)

델리게이트를 위한 프로토콜을 정의한다.
여기엔 통신할 메서드나 이벤트, 데이터를 전달하기 위한 요구사항 등을 작성한다.

protocol SomeComponentDelegate: AnyObject {
    func didReceiveData(_ data: String)
}

2. 델리게이트 프로퍼티 추가

델리게이트를 가질 프로퍼티를 컴포넌트에 추가한다.

-맡길 일이 있는 컴포넌트(사장님, 데이터 발신자 컴포넌트)에 델리게이트를 추가한다
-지금은 Sendercomponent에 delegate가 있으므로 사장님이 일을 맡기려고 준비하는 것


class SenderComponent {
    weak var delegate: SomeComponentDelegate?
    ...
}

3. 델리게이트 호출

이벤트가 발생했을 때 델리게이트에게 메시지를 보내어 해당 이벤트에 대한 처리를 요청한다.

-주문 이벤트가 발생하면 델리게이트를 호출해서 didReceiveData를 수행하라고 요청하는 것
-아직 delegator(알바생)를 여기선 모르므로 우선 저장

class SenderComponent {
    func sendData() {
        // ...
        delegate?.didReceiveData("Hello from SomeComponent")
    }
}
  • 위 코드의 자세한 설명
delegate?.didReceiveData("Hello from SenderComponent")

: Optional 체이닝과 함께 Delegate 객체에 있는 didReceiveData 메서드를 호출하는 코드이다.

  • 여기서 delegate는 SomeComponentDelegate 프로토콜을 채택한 객체를 참조하는 변수
  • Optional 체이닝(?.)은 Optional 값이 nil이 아닌 경우에만 해당 메서드를 호출하고, 값이 nil인 경우에는 호출을 건너뛰고 그대로 종료된다.

따라서 delegate가 nil이 아니라면, 해당 Delegate 객체의 didReceiveData 메서드를 호출하고, "Hello from SenderComponent"라는 문자열을 전달합니다.

이 코드는 Delegate 패턴에서, SenderComponent가 delegate를 통해 데이터를 수신하는 방식을 구현하는 것으로, Delegate 객체에 의존하지 않으면서 해당 객체에 데이터를 전달합니다. Delegate 객체가 이 메서드를 구현하고 있는 경우, SenderComponent는 해당 이벤트나 데이터를 처리할 수 있습니다.

4. 델리게이트 구현

델리게이터쪽에 델리게이트 프로토콜을 채택하고, 해당 프로토콜의 요구사항을 구현한다.

-알바생이 해당 델리게이트 프로토콜(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)")
    }
}

Delegate pattern의 장단점

장점

델리게이트 패턴은 객체 간의 효율적인 통신을 위해 사용됩니다. 이 패턴을 사용하면 컴포넌트들 간의 결합도를 낮추고, 유연성을 높여주는 장점이 있습니다. 여기에는 몇 가지 이유가 있습니다:

  1. 결합도 감소
    : SenderComponent와 ReceiverComponent 간의 직접적인 의존성이 줄어든다.
    SenderComponent는 어떤 객체로 데이터를 전달하는지 알 필요가 없으며, ReceiverComponent는 데이터를 제공하는 객체에 대한 구체적인 정보 없이도 데이터를 수신할 수 있다. 그러므로 두 객체간의 결합도가 감소한다.
    이는 두 컴포넌트 간의 수정이나 확장을 용이하게 만든다.

  2. 재사용성
    : Delegate pattern을 사용하면 동일한 델리게이트 프로토콜을 채택한 다른 컴포넌트들에서도 동일한 델리게이트를 사용할 수 있다.
    즉, SenderComponent와 ReceiverComponent는 동일한 델리게이트를 공유하여 재사용이 가능하다.

  3. 유연성
    : SenderComponent가 델리게이트 프로토콜을 통해 데이터를 전달하면서, ReceiverComponent가 해당 델리게이트 프로토콜을 구현함으로써, 서로 다른 종류의 객체들 간에도 통신이 가능해진다.

  4. 테스트 용이성: 델리게이트를 사용하면 컴포넌트들을 분리하여 테스트하기 용이하다. Mock 델리게이트를 구현하여 특정 동작이나 데이터를 시뮬레이트하여 각 컴포넌트를 테스트할 수 있다.

단점

  1. 복잡성 및 이해도
    : Delegate pattern은 객체 간의 상호작용을 위해 추가적인 프로토콜 및 메서드를 정의하고 구현해야 한다.
    Delegate 메서드들이 많거나 복잡할 경우, 코드의 이해도가 낮아질 수 있다.

  2. 단방향 통신만 지원
    : 특정 이벤트가 발생했을 때 한 객체가 다른 객체에게 통지하는 단방향 통신 방식을 사용한다.
    때때로 이는 비효율적일 수 있고, 이벤트의 전달이나 처리가 복잡해질 수 있다.

Swift UI 방식 비교

@State, 클로저를 이용하여 데이터를 전달한 방식

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")
        }
    }
}
profile
웩알고👩‍💻

4개의 댓글

comment-user-thumbnail
2023년 12월 23일

지나가다가 봤는데 완전 유익한 글이네요! 잘 보고 갑니다 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 12월 23일

"."

내가 너 점찍었다고

1개의 답글