-
UIKit에서 delegate 패턴이 어떻게 활용될까?Apple🍎/UIKit 2024. 9. 18. 22:39
Delegate 패턴이란
객체가 어떤(What)일들을 하는지를 정의해두고 해당 객체를 사용할 때
앞서 정의해놓은 사항들을 바탕으로 그 일들을 어떻게(How) 할지를 구현하여 실제 객체가 동작하는 방식을 결정합니다.Protocol 이란?
프로토콜이란 특정 작업이나 기능을 수행하기 위해 어떠한 것들이 필요한지를 사전에 정의해놓은 것을 의미합니다.
Drivable
한 객체가 되기 위해서는start()
와stop()
을 할 수 있어야합니다.
이와 같이 객체가 어떠한 기능을 수행하기 위해서 할 수 있어야하는 목록들을 사전에 정해놓은 것을 프로토콜이라고 합니다.protocol Drivable { func start() func stop() } struct Car: Drivable { func start() { print("차가 출발합니다.") } func stop() { print("차가 멈춥니다.") } } struct Airplane: Drivable { func start(){ print("비행기가 출발합니다") } func stop(){ print("비행기가 멈춥니다") } }
Delegate 패턴으로 해당 컴포넌트가 어떤 역할을 하는지 정해놓는다.
- 프로토콜을 통해
MessageView
가 어떤 일을 하는지를 명시합니다. - 다른말로
MessageView
가 제대로 작동하기 위해서는 프로토콜에 나와 있는 일들을 할 수 있어야한다는 말입니다. - 즉
MessageView
가 어떤일들을 하는지는 알려줄 테니까 이MessageView
를 사용하기 위해서는 이 어떤일을 어떻게 해야할지를 알려줘야한다를 의미합니다. - 여기서는
MessageView
라는 뷰를 사용하기 위해서는messageView
라는 동작을 “어떻게” 수행할지를 알려주어야한다고 명시하고 있습니다.
1 . 프로토콜 정의
protocol MessageViewDelegate: AnyObject { func messageView(_ messageView: MessageView, didEnterMessage message: String) }
MessageView
내부에delegate
프로퍼티는 앞서 프로토콜을 통해 정의한 타입입니다.delegate
프로퍼티를 통해 언제 , 프로콜에서 정의한 동작을 수행할지를 명시합니다. 여기서는sendButtonTapped
메서드 내부에서 해당 버튼이 눌렸을 때messageView
라는 메서드를 실행합니다.- 실행되는
messageView
메서드의 구체적인 동작을MesssageView
를 사용하는 곳에서 delegate 메서드를 구현함으로써 알려줍니다.
2. 컴포넌트 정의
class MessageView: UIView { private let textField: UITextField private let sendButton: UIButton private let displayLabel: UILabel weak var delegate: MessageViewDelegate? ... ... ... @objc private func sendButtonTapped() { guard let message = textField.text, !message.isEmpty else { return } delegate?.messageView(self, didEnterMessage: message) textField.text = "" } func displayMessage(_ message: String) { displayLabel.text = message } }
MessageViewController
는MessageView
를 사용하고 있습니다.- 앞서
MessageView
는MessageViewDelegate
로 자신을 이용하기 위해 필요한 사항들을 프로토콜을 통해 설명하고 있었습니다. - 즉
MessageViewController
는MessageView
를 사용하기 위해서MessageViewDelegate
를 채택한다음에MessageViewDelegate
에서 명시하고 있는messageView
메서드를 구현할 책임이 있습니다. MessageViewController
는 extension을 통해MessageViewDelegate
를 채택하고 있고messageView
가 실제 어떠한 기능을 할지를 구현해줍니다.- 아래에서는
message
라는 파라미터로 넘어온String
값을 이용해Message
라는 인스턴스를 만든 휘에MessageView
가 내부적으로 가지고 있는 메서드를 호출하여 라벨에 입력받은String
을 보여줍니다.
3. 컴포넌트 사용
class MessageViewController: UIViewController { private let messageView = MessageView() private var currentMessage: Message? override func loadView() { view = messageView } override func viewDidLoad() { super.viewDidLoad() messageView.delegate = self } } extension MessageViewController: MessageViewDelegate { func messageView(_ messageView: MessageView, didEnterMessage message: String) { currentMessage = Message(content: message) messageView.displayMessage("You entered: \(message)") } }
전체 흐름을 정리해보면
첫번째로MessageView
가 하나의 컴포넌트로써 역할을 하기 위해 수행할 수 있어야하는 기능목록을MessageViewDelegate
라는 프로토콜을 통해 명시해둡니다.
두번째로MessageView
에서delegate
프로퍼티를 통해 어떤 시점에(예를 들어 뷰가 처음 떴을때, 버튼이 눌렸을때 등) 앞에서 프로토콜을 통해 명시해 놓은 기능이 작동하는 시점을 표시해둡니다.
세번째로MessageViewController
에서MessageView
를 사용하기 위해MessageViewDelegate
를 채택해서 실제 어떠한 동작을 수행할지를 구현하고messageView.delegate = self
를 통해MessageView
의delegate
에다가 자신히 구현한 실제 동작을 넘겨 줍니다. 이렇게 함으로써MessageView
의delegate
가 동작하는 시점에MessageViewController
에서 구현한 내용을 실행할 수 있습니다.UIKit에서의 Delegate 패턴
애플이 UIKit 프레임워크를 만들때도 위와 같은 Delegate 패턴을 따릅니다.
UITableView
를 사용하다고 가정해봅시다.애플에서는
UITableView
가 하나의 컴포넌트로써 기능하기 위해 갖추어야하는 사항들 또는 해당 컴포넌트가 할 수 있는 사항들을 프로토콜을 통해 정의해놓았습니다.1 . 프로토콜 정의
UITableViewDataSource
:UITableView
에 어떠한 데이터들을 보여줄지 결정하는 방법을 명시해둔 프로토콜// MARK: - UITableViewDataSource protocol UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { } }
UITableViewDelegate
:UITableView
와 사용자가 상호작용할때 어떻게 동작할지를 명시해둔 프로토콜
// MARK: - UITableViewDelegate
protocol UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
}
그러면 앞서 봤던 `MessageView`에서 `MessageViewDelegate`를 통해 정의한 동작이 언제 실행될지를 `delegate` 프로퍼티를 이용해 정해놓았던 것처럼 `UITableView` 는 내부적으로 어떤시점에 해당 프로토콜에서 명시한 동작이 실행될지를 정해 놓았을 겁니다. ### 2. 컴포넌트 정의 ```swift class UITableView { ... weak var source: UITableViewDataSource weak var delegate: UITableViewDelegate? ... ... ... }
MemoListView
에서UITableView
를 컴포넌트를 사용하고 있고,MemoViewController
에서MemoListView
를 사용하고 있습니다. 최종적으로UITableView
를 사용하기 위해UITableView
가 기능하기 위해 실제 동작을 구현해야하는 책임은MemoViewController
에게 있습니다. ( 사용하는 구조에 따라 책임의 위치는 달라질수도 있습니다.) 따라서 현재MemoViewController
에서는 extension을 통해UITableViewDataSource
와UITableViewDelegate
를 구현하며 실제 동작을 구현하고 있습니다.3. 컴포넌트 사용
import UIKit class MemoListView: UIView { ... let tableView: UITableView = { let table = UITableView() table.register(MemoTableViewCell.self, forCellReuseIdentifier: MemoTableViewCell.identifier) table.translatesAutoresizingMaskIntoConstraints = false return table }() ... }
import UIKit class MemoViewController: UIViewController { private let memoListView = MemoListView() override func viewDidLoad() { ... memoListView.tableView.delegate = self memoListView.tableView.dataSource = self } } // MARK: - UITableViewDataSource extension MemoViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return memos.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: MemoTableViewCell.identifier, for: indexPath) as? MemoTableViewCell else { fatalError("Failed to dequeue MemoTableViewCell") } let memo = memos[indexPath.row] cell.configure(with: memo) return cell } } // MARK: - UITableViewDelegate extension MemoViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print("선택된 메모: \(memos[indexPath.row].title)") tableView.deselectRow(at: indexPath, animated: true) } }
애플에서는 위와 같이 Delegate 패턴을 이용해
컴포넌트가 할 수 있는 기능과 해당 기능이 언제 실행되는지를 미리 정의해 두는 대신 각 기능이 실제로 어떻게 동작을 하는지는 정해두지 않음으로써
개발자가 애플이 먼저 만들어 놓은 컴포넌트를 이용하여 쉽게 개발을 할 수 있는 동시에 실제 기능에 대한 동작은 개발자가 직접 구현하게 함으로써 자유롭게 자신들의 앱에 맞는 기능을 넣을 수 있도록 하였습니다.정리하면 UIKit에서는 Delegate 패턴을 통해서 애플이 만들어 놓은 컴포넌트를 이용하면서 버튼이나 리스트와 같이 일반적으로 많이 사용되는 것들을 일일이 만들지 않아도 되는 동시에 애플이 해당 컴포넌트에 정의해놓은 기능 중에 필요한 것들만 delegate를 채택해서 자유롭게 원하는 기능을 구현할 수 있습니다.
- 프로토콜을 통해