2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

[Swift-TIL] Delegate랑 친해져 보기

2024. 12. 24. 00:59ㆍIOS/Swift-TIL
SMALL

이번에는 Delegate를 배운것을

정리해보려고 한다.

 

UIKit에서는 거의 필수적으로 알아야 할

구문이지 않을까 한다.

 

검색을 해보면 가장많이 나오는 단어가

"위임자" 라는 단어다.

그리고

"대리자" 라는 단어 이렇게 두개를 넣고

가장 많이 설명을 한다.

 

그런데, 이걸 설명으로 들어보면

굉장한 괴리감을 느낄 수 있다.

 

위임자 = 위임을 하는사람을 뜻한다.

대리자 = 위임을 받아서 일을 하는 사람을 뜻한다.

그래서 위임자가 위임을 하고

그것을 받아서 대리자가 할 것이 존재한다.

 

대리자가 할 것은 프로토콜이다.

만약에 대리자가 대리를 받았다면,

이건 반드시 해야지 라는 느낌이다.

 

이러한 약속이 프로토콜과 비슷하다.

 

좀 더 깊이있게 들어가보면,

Delegate는 위임(Delegation)이라는

개념에 기반한 디자인 패턴이고,

이 패턴은 한 객체가 자신이 해야 할

일부 동작(책임)을 다른 객체에

위임함으로써 구현된다.

 

위임자(Delegator)

자신의 동작 일부를 다른 객체에게

위임하는 역할을 하는 객체

 

대리자(Delegate)

위임자가 위임한 작업을

대신 처리하는 객체

 

Delegate의 핵심 구성 요소

Protocol (프로토콜)

대리자가 반드시 구현해야 하는

메서드나 속성을 정의

 

이 프로토콜은 “이런 기능을 구현해야 한다”는

약속(계약) 역할을 한다는 것이다.

protocol ExampleDelegate: AnyObject {
    func didCompleteTask()
}

 

위임자 (Delegator)

Delegate 프로퍼티를 통해

대리자와 연결한다.

특정 작업이 완료되면 대리자에게 알린다.

class TaskManager {
    weak var delegate: ExampleDelegate? // Delegate 프로퍼티
    
    func completeTask() {
        print("Task completed!")
        delegate?.didCompleteTask() // 대리자에게 알림
    }
}

 

대리자 (Delegate)

위임자의 Delegate 프로퍼티를 채택하고,

프로토콜을 준수하여 구현한다.

class ViewController: UIViewController, ExampleDelegate {
    let taskManager = TaskManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        taskManager.delegate = self // 대리자로 설정
        taskManager.completeTask()
    }
    
    func didCompleteTask() {
        print("ViewController received task completion!")
    }
}

 

Delegate의 동작 과정

위임자가 특정 작업을 완료하거나

이벤트가 발생했을 때,

자신이 직접 처리하지 않고

대리자에게 메시지를 보낸다.

 

대리자는 메시지를 수신하여

필요한 작업을 수행한다.

 

그렇다면,

왜 Delegate를 사용할까?

유연한 설계

객체 간의 결합도를 낮추고,

재사용 가능한 코드 작성을 돕는다.

 

위임자는 대리자가

어떤 객체인지 알 필요가 없으며,

프로토콜에만 의존한다.

 

역할 분리

각 객체가 자신의 책임에만

집중할 수 있다.

 

위임자는 이벤트 발생에만 집중하고,

대리자는 이벤트 처리를 담당한다.

 

UIKit에서 Delegate의 활용 예

UITableView

UITableViewDelegate,

UITableViewDataSource

TableView가 어떻게 동작할지를

대리자에게 위임한다.

tableView.delegate = self
tableView.dataSource = self

 

UITextField

UITextFieldDelegate를 사용해

텍스트 필드의 입력 이벤트를 처리한다.

textField.delegate = self

Delegate를 이해하는 키포인트

위임

위임자가 대리자에게

특정 동작을 위임한다.

 

프로토콜

대리자가 따라야 할 규칙(약속)이다.

 

대리자

위임받은 일을 실제로 처리한다.

 

Delegate는 복잡한 객체 간의 상호작용을

깔끔하게 처리할 수 있도록 도와주는 강력한 도구이다.

 

“위임자와 대리자의 역할”을 중심으로 접근하면,

좀 더 쉽게 이 영역을 이해할 수 있다.


Delegate 실습과정

필자는 이전에 만들어둔 테이블 뷰 파일로 실습을 진행했다.

어떻게 만들었는지는 https://stayjun.tistory.com/265 해당 링크를 참고하면 된다.

간단하게 프로토콜을 생성해보겠다.

 

AdminDelegate 이라는 프로토콜을 만들어준건

우리가 약속을 하겠다는 의미다.

 

근데 어떤 약속을 하는것인가?

위에서 이야기 했듯이 대리자와 위임자를 만들기 위한

하나의 약속을 정의하기 위함이다.

 

그리고

func doThis()는 "AdminDelegate"를 따르려면 

이 기능은 반드시 구현해야 한다“는

구체적인 약속의 항목을 의미한다.

 

더 쉽게 말해서 

"AdminDelegate"는 어떠한 대리인을 의미하고

그 대리인은 반드시 doThis()라는 일을 

수행할 줄 알아야 한다.

 

라고 정의한것이다.

 

조금 이해가 되었으리라 생각한다.

 

프로토콜은 이전에 작성했던 글들에서 알 수 있듯이

지켜지지 않는다면 컴파일 에러가 날 수 있다.

 

가장 하단단에 나는 여기 파일에 extension으로 

새로운 로직을 구현하기 위해 구분 지었고,

위임을 받을 위임자는

"MyViewController"로 설정할 것이다.

이렇게 "AdminDelegate"(대리인)에게

위임을 받았으니 일을 해야겠지요?

 

이런식으로 일을 주는것이다.

 

뭐 또 다르게 해보자면

struct로 데이터를 담는 타입으로

Admin을 선언해주고

위에서 보면 Admin이 Delegate를 즉 대리인을 하는데,

이런걸 말한다.

 

이 코드를 잠시 설명해보자면

관리자"Admin"를 나는 위임"Delegate"할 준비가 되어있다.

라는 의미가 되는 것이다.

그리고 "AdminDelegate" 위임을 받으면

해야할 일을 나타내는 것이다.

 

그러면 이걸 우리가 써봐야겠으니

이와 같이 작성해줬다.

 

왜 이와 같이 작성해 주었는가? 는

간단하게 설명하자면... 델리게이트로부터 어떤 일을
위임받기 위한 통로를 만들기위해
var admin: Admin? 을 선언한것인데

이 뷰컨트롤러(MyViewController)가 어떤 “관리자(Admin)“에게 

일을 맡길 수도 있고, 반대로 그 관리자에게서 “야 너 이거 해봐” 하고 

일을 넘겨받을 수도 있는 구조를 만들기 위한 준비인 것이다.

 

즉, 

admin은 나중에 어떤 일을 요청할 수 있는 

중간 관리자 역할을 하는 객체가 되는 것이다.

 

"admin?.delegate = self" 는

Admin 객체에게 알려주는 주는 것이다.

내가(MyViewController)가 delegate 역할을 해주겠다.

 

즉, 

MyViewController가 AdminDelegate 프로토콜을 따르고 있으니,

 Admin이 "뭔가 실행해야 할 일이 있으면 나한테 시켜줘" 라고

연결해주는 것이다.

 

그러면 여기서(MyViewController)

 어떠한 버튼이 눌렸을 때 

이게 실행이 될것이다.

 

지금 과정에서 하나가 빠진게 있는데,

중요한 Admin 자체를 만들지 않았기 때문에

Admin을 만들어 주어야 한다.

이렇게 말이다.

그러면, 실행하면 어떻게 되는가?

이와 같이 실행이 되는것을 볼 수 있다.

 

전체 코드를 분석해보자면,

import UIKit

protocol AdminDelegate {
    func doThis()
}



class MyViewController: UIViewController {

    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var hiLabel: UILabel!
    var admin: Admin?
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        admin = Admin(delegate: self)
    }
    
    @IBAction func didTabbutton(_ sender: Any) {
        if let name = nameTextField.text {
            hiLabel.text = "Hi! \(name)"
        }
        admin?.delegate.doThis()
    }
}

extension MyViewController: AdminDelegate {
    func doThis() {
        print("나 지금 일하는중!")
    }
}

struct Admin {
    var delegate: AdminDelegate
}

 

천천히 설명해 본다면

위임을 하는 자 (Admin) 선언

delegate 위임을 받는다면

나는 AdminDelegate를 해야 하는데,

위임이 된다면 

이런걸 해야해

관리자를 하나 만들어서

위임을 자기자신 즉, MyViewController한테 하였고,

그래서

아, 내가 위임을 받으면 이렇게 할것이다.

라고 정의가 된것이다.

 

Admin에게 self로 위임 받고

doThis를 한것이다.

 

실습은 여기까지 해보았는데

그러면 Delegate에 대한 이해가 되었는가?

 

조금 더 이해를 하기위해서 테이블뷰로 잠시 가보겠다.

스위프트를 해본 사람이라면 코드 위에서

커맨드를 누르고 있으면 마우스가 호버 되면서

연결된 곳으로 진입하게 된다.

 

여기 테이블 뷰에서도 Delegate가 존재하는데,

위임을 받게 되면 여기에 해야할 일들이

잔뜩 작성되어있다.

 

물론 UITableViewDataSource 에도

잔뜩 정의 되어 있고

그런 UITableView를 채택을 한것이다.

필자는 이렇게 

UITableViewDelegate, UITableViewDataSource

둘이 같이 채택을 하였지만,

구분을 하기 위해서 따로따로 구현하여 선언하는게

좀더 깔끔한 편이긴하다.

이런식으로 따로 뺌으로써 구분하기 쉽게

나중에 내가 이 코드를 봤을때 아~ 그랬었지 정도로

이해할 수 있게 해두면 좋다.

 

중요한건 

Delegate나 DataSource에

무엇이 있는건가가 아니라

이런식으로

Delegate에 self 넣고

dataSource에다가 self 넣고

dataSource 같은경우는 사실 이름이

dataSource지 얘도 Delegate 패턴이다.

 

그러면 앞서 실습했던 것들을 보면

이것과 그리고 Delegate

무엇할지에 대한 정의

그리고 이것을 받는 코드인

이것과 그리고 이것을 받았을때

무슨일을 할지에대한 정의

 

테이블 뷰도 마찬가지다.

여기 테이블뷰에서도 Delegate가 있었고

Delegate에 위임을 받았다.

 

그리고 이 위임을 받은 녀석에게

어떤일을 할지 작성해주면 되는 것이다.

 

간단하게 하나 만들어보면

이렇게 하나 만들어주고,

스토리보드에서 화살표 옮겨주었다.

인스펙터도 살펴보면

"Is Initial View Controller"가

체크가 되어있는지 확인하면 된다.

 

실행을 해보면..

뻥튀기 된것을 알 수 있다.

 

여기서 재밌는건

이렇게 함수를 정의를 하는데

이런 식으로 함수를 정의해 주고

불러오는 방식을 많이 채택을 하는데

그런데 여기서는 정의한것 만으로도
동작을 하는것이다.

 

호출을 하지도 않았는데 동작하는것이

굉장히 의문이 들고 어려울 수 있다.

 

언제 호출이 되는가?

 

자 그러면.. 이전 작업물로 돌아가 봤을 때 

만약에 위임을 받았을 때에는 

"나 지금 일하는중!" 이 프린트되어야 한다고

정의가 되어 있을 뿐이지 이 코드가

언제 어떻게 호출되는지는

admin 안에 들어가 있는것이다.

 

마찬가지로 테이블뷰 코드를 보면

import UIKit

class MyTableViewController: UIViewController {

    @IBOutlet weak var myTableView: UITableView!
    let cellData = ["Hello Noel", "두 번째 셀이다.", "세 번째 셀이다."]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .brown
        configureTableView()
    }
    
    func configureTableView() {
        myTableView.backgroundColor = .green
        myTableView.delegate = self
        myTableView.dataSource = self
    }
}

extension MyTableViewController: UITableViewDelegate {
    
}

extension MyTableViewController: UITableViewDataSource {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cellData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
        cell.textLabel?.text = cellData[indexPath.row]
        return cell
    }
}

테이블뷰 안에 

numberOfSections 이라는 함수가 실행이 되는데,

이건 이미 UITableViewDelegate 또는 프로토콜로

채택이 되어 있는 것이기 때문에

numberOfSections은 처음에 TableView를 그릴 때

호출이 되는 함수일 것이고

언제 호출이 되는가는 보이지 않는다.

그냥 이름만 알려주는 것이고

이 코드와 같이 tableView는 함수 안에 있는 코드를 가지고

그린다고 약속(프로토콜 or Delegate)이 되어 있는것이고,

즉,

둘다 어떠한 약속이 되어 있고

그 약속에 따라서 어떠한일을 해달라고

구현을 하는 것일 뿐이다.

 

델리게이트 패턴이 상당히 어색할 수 있으나

UIkit에 핵심 패턴중에 하나이고

이것대로 많이 구현되어 있기 때문에

익숙해지기를 힘써야한다.

 

 

 

 

 

728x90