Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
UIKit/UIKit

Delegate Pattern에 대해 알아보자

by Toughie 2023. 2. 10.

* Delegate Pattern에 대해 간단하게 이해하기 위한 포스팅

 

delegate는 위임하다, 맡기다의 의미이다. 

누군가에게 일을 대신 시킨다. 즉 대리자가 있다는 뜻!

 

앱을 만들다 보면 텍스트필드를 자주 활용하게 되고, 뷰컨트롤러에서 관련 코드를 작성한 경험이 있을 것이다.

그런데 텍스트필드도 고유의 객체이고 뷰컨트롤러도 고유의 객체이다.

 

객체 간 의사소통, 연결을 위해 필요한 것이 바로 Delegate Pattern이라고 이해하면 쉽다.

(* 프로토콜을 사용해서 델리게이트 패턴을 구현한다.

프로토콜은 자격증으로 이해하면 좋다. 

프로토콜을 채택하면 (즉 자격증을 취득하면) 관련 능력(속성, 메서드)이 생긴다. )

(* 스위프트는 프로토콜 지향 프로그래밍 언어로도 볼 수 있는데, 프로토콜을 활용하면 확장성이 매우 좋아지는 장점이 있다.
프로토콜도 타입이기 때문!)

 

일상생활에서 예를 들어 보자면..

퇴근하고 쇼파에 누워 넷플릭스를 보기 위해 리모컨을 집어 들었다.

전원 버튼을 누르면 티비가 켜질 것이다. -> 이것도 델리게이트 패턴으로 이해할 수 있다. 

왜? 어떻게?

 

리모콘의 전원 버튼을 누른다 -> 티비의 전원이 켜진다.

이것을 한단계 더 뜯어서 보면

리모콘의 전원 버튼을 누른다 == 티비에게 전원을 키라고 말한다 -> 티비가 전원을 킨다.

즉 리모콘의 전원 버튼을 누르는 것은 티비가 전원을 키도록 시키는 것이다.

 

무슨 말인지 복잡하니 코드로 다시 한 번 살펴보자.

import UIKit

//원격 통신을 위한 프로토콜을 만든다.
//보통 ~Delegate로 네이밍을 한다.
//프로토콜 안에는 최소한의 요구사항(프로퍼티,메서드)만 작성하고 상세 구현은 해당 프로토콜을 채택한 객체에서 한다

protocol RemoteControlDelegate {
	func turnOn()
    func turnOff()
}

자 이렇게 원격 통신을 위한 RemoteControlDelegate 프로토콜을 만들었으니 리모컨을 만들어 보자

class RemoteControl {
	
    // 리모컨 객체의 대리자를 설정하기 위한 변수를 만들어 준다.
    // 대리자가 없는 경우를 고려하여 옵셔널로 선언한다.
    
    var delegate: RemoteControlDelegate?
    
    // 자 아래 코드가 중요하다!
    // 리모컨에서 turnOn이라는 메서드르 실행하면
    // 우선 delegate, 즉 대리자가 있는지 확인하고 (옵셔널로 선언되어 있으니)
    // 대리자가 turnOn 메서드를 실행하도록 한다.
    // delegate(대리자)는 위에 코드와 같이 RemoteControlDelegate 프로토콜을 채택해야하기에
    // 당연히 필수 요구사항인 turnOn과 turnOff함수가 구현되어 있을 것이다.
    
    func turnOn() {
    	delegate?.turnOn()
    }
    
    func turnOff() {
    	delegate?/turnOff()
    }
}

자 이제 리모컨이 있고 해당 리모컨을 어떤 기기(객체)와 연결(객체간 의사소통) 시켜야 한다. (이 녀석이 대리자가 될 것이다.)

TV를 만들어 보자.  그런데 리모컨의 대리자가 되려면 조건이 있었다. 바로 RemoteControlDelegate 프로토콜을 채택해야 한다는 것!

이를 유의하고 코드를 작성해보자.

class TV: RemoteControlDelegate {

//우선 프로토콜의 최소요구 사항만 충족하고
//세부 구현은 간단하게 프린트문으로 해보자.
//이제 TV 인스턴스는 리모컨 인스턴스의 delegate에 할당 될 수 있을 것이다.
    func turnOn() {
    	print("티비의 전원이 켜진다.")
    }
    
    func turnOff() {
    	print("티비의 전원이 꺼진다.")
}

이제 리모컨과 티비를 만들고 전원을 키고 꺼보자~

let remoteController = RemoteControl()
let appleTV = TV()

//리모컨의 대리자로(RemoteControl 클래스 인스턴스의 delegate 속성)
//애플티비(TV 클래스 인스턴스)를 할당해줬다.
remoteController.delegate = appleTV


//그러면 이제! 아래와 같은것이 가능하다.

remoteController.turnOn()
remoteController.turnOff()

//즉 리모컨의 전원버튼을 켜고 끄는 것인데, 이러면 티비의 전원이 켜지고 꺼지는 것이다.
//리모컨이 "티비야 전원 켜(turnOn 메서드 실행해), 티비야 전원 꺼(turnOff 메서드 실행해)
//라고 대신 일을 시키는 것이다 :)

위 코드에서 대리자를 따로 할당해주는 코드를 보였는데, 처음부터 대리자를 지정하고 객체를 찍어내는 방식도 있다.

에어컨을 만들며 알아보자

class AirConditioner: RemoteControlDelegate {

	//생성자에서 remoteController 프로토콜을 채택한 객체를 remote에 할당하고
    //remote의 delegate 속성에 self, 즉 AirCoditioner의 인스턴스 (airconditioner등)를 할당.
    
    
	init(remote: remoteControl) {
    	remote.delegate = self
    
    func turnOn() {
    	print("에어컨의 전원이 켜진다.")
    }
    
    func turnOff() {
    	print("에어컨의 전원이 꺼진다.")
    }
}

//에어컨을 만들고 켜고 꺼보자.

let airconditioner = Airconditioner(remote: remoteController)

remote.turnOn() //에어컨의 전원이 켜진다.

remote.turnOff() //에어컨의 전원이 꺼진다.

위와 같이 작동하는 것을 알 수 있다.

 

여기서는 리모컨, 티비, 에어컨을 통해 예시를 들었지만 

보통은 앱을 만들면서

 

텍스트필드와 같은 여러 객체들과

뷰컨트롤러 사이에서 델리게이트 패턴을 자주 이용한다.

 

텍스트필드가 리모컨이라면

뷰컨트롤러가 티비이고 에어컨인 것이다.

 

즉 텍스트필드에서 어떤 입력이나 행위를 뷰컨트롤러에 전달하고(시키고)

뷰컨트롤러가 해당 일(함수 실행 등)을 하는 형태인 것이다.

-뷰컨트롤러가 텍스트필드에서 발생한 상황에 대한 판단(Bool) 혹은 동작(메서드 실행)을 담당하는 것이다.

 

그래서 아래와 같은 형태를 자주 발견하게 될 것이다.

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textField.delegate = self
    }
    
    //위에서 알 수 있듯이 뷰컨트롤러 관련 프로토콜을 채택하고
    // delegate 속성에 self, 즉 뷰컨트롤러의 인스턴스를 할당하는 것이다.
    
    //다시 쉽게 말하자면 텍스트필드에서 어떤 일이 일어나서
    //뷰컨트롤러에 알려주면 뷰컨트롤러가 그 일들을 처리한다는 의미다 :)

 

사실 델리게이트 패턴은 많이 사용되고 기본적인 개념이지만, 처음에는 생소하고 조금 헷갈리기도 했다.

점차 많이 써보면서 나아지겠지만, 처음 개념을 스스로 복습해보기 위해서 위와 같이 간단한 코드들과 함께 정리해 보았다. 🙋🏻‍♂️