Delegate는 한 객체가 사전에 정의된 메서드가 있는 프로토콜을 통해 다른 객체에 작업 실행을 위임하는 디자인 패턴입니다. iOS 개발에서 Delegate는 Cocoa Touch의 기본 패턴 중 하나로, 발신자와 수신자 간의 직접적인 결합 없이 비동기 알림에 사용됩니다. Apple Documentation(2025)에 따르면, Foundation과 UIKit에서 테이블 이벤트, 네트워크 요청 및 위치 관리 처리를 위해 위임이 사용됩니다. 이 패턴은 구성 요소의 느슨한 결합과 코드 재사용을 보장합니다.
주요 사항
Delegate(델리게이트)는 특정 프로토콜을 구현하고 다른 객체의 이벤트에 대한 알림을 받는 객체입니다. 위임 패턴은 상속의 대안입니다: 메서드를 재정의하기 위해 하위 클래스를 만드는 대신, 객체가 이벤트 처리를 외부 객체에 위임합니다. iOS에서 위임은 필수 및 선택적 메서드가 있는 Swift 프로토콜을 통해 구현됩니다. delegate 속성은 항상 weak var로 선언되어 객체 간의 순환 참조를 방지합니다.
델리게이트 프로토콜은 객체 간의 상호 작용 계약을 정의합니다. 필수 메서드는 델리게이트에 의해 구현되어야 하며, 그렇지 않으면 코드가 컴파일되지 않습니다. 선택적 메서드는 @objc optional 속성으로 표시되며 델리게이트가 관련 이벤트에만 응답할 수 있도록 합니다. 메서드 이름은 규칙을 따릅니다: 첫 번째 매개변수는 발신자 객체, 두 번째는 이벤트 데이터입니다. 예를 들어, tableView(_:didSelectRowAt:)은 발신자가 UITableView이고 데이터가 선택된 행의 인덱스임을 나타냅니다.
// Delegate 프로토콜
protocol DownloadManagerDelegate: AnyObject {
func downloadManager(_ manager: DownloadManager,
didFinishWith data: Data)
func downloadManager(_ manager: DownloadManager,
didFailWith error: Error)
@objc optional func downloadManager(_ manager: DownloadManager,
didUpdateProgress progress: Float)
}
// Delegate를 사용하는 클래스
class DownloadManager {
weak var delegate: DownloadManagerDelegate?
func startDownload(from url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let self else { return }
if let error = error {
self.delegate?.downloadManager(self, didFailWith: error)
} else if let data = data {
self.delegate?.downloadManager(self, didFinishWith: data)
}
}.resume()
}
}
delegate 속성은 retain cycle을 방지하기 위해 weak var로 선언되어야 합니다. 참조가 강한 경우, 델리게이트와 위임 객체가 서로를 붙잡게 되고 ARC가 메모리를 해제할 수 없습니다. 델리게이트 프로토콜은 AnyObject(클래스만)를 상속하여 weak 사용을 허용합니다. 구조체와 열거형은 값 의미 체계로 인해 델리게이트가 될 수 없습니다. 값 유형에 대한 대안은 콜백 클로저입니다.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — retain cycle 없음
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Delegate 패턴은 일대일로 작동합니다: 하나의 발신자 객체는 특정 시점에 하나의 델리게이트만 가질 수 있습니다. 이벤트가 발생하면 발신자는 델리게이트가 설정되어 있는지 확인하고 해당 프로토콜 메서드를 호출합니다. 직접 호출에 비해 장점은 발신자가 델리게이트의 유형을 알지 못하고 프로토콜을 준수한다는 것만 안다는 점입니다. 이는 SOLID의 의존성 역전 원칙(DIP)을 준수합니다.
델리게이트는 할당을 통해 지정됩니다: someObject.delegate = self. 델리게이트가 할당 해제되면 weak 의미 체계로 인해 속성이 자동으로 nil이 됩니다. 델리게이트 메서드를 호출하기 전에 선택적 체이닝을 통해 델리게이트가 확인됩니다: delegate?.method(). delegate가 nil이면 호출이 충돌 없이 무시됩니다. 선택적 프로토콜 메서드의 경우 추가 확인이 사용됩니다: delegate?.responds(to: #selector(...)). Swift에서는 이 확인이 일반적으로 선택적 메서드 선언을 통해 암시적으로 수행됩니다.
멀티스레드 환경에서 델리게이트는 비동기 결과 반환에 사용됩니다. URLSession은 데이터 수신, 시간 초과 또는 인증 오류 시 호출되는 메서드가 있는 URLSessionDelegate를 제공합니다. 델리게이트 메서드는 URLSession의 백그라운드 큐에서 실행되므로 UI 업데이트를 위해 메인 큐로 디스패치해야 합니다. 비동기 델리게이트는 호출 스레드를 차단하지 않아 다른 작업을 계속할 수 있습니다.
class NetworkService: NSObject, URLSessionDataDelegate {
private lazy var session = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
private var receivedData = Data()
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
receivedData.append(data)
let progress = Float(receivedData.count) / Float(expectedSize)
DispatchQueue.main.async {
self.progressHandler?(progress)
}
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
if let error = error {
delegate?.networkService(self, didFailWith: error)
} else {
delegate?.networkService(self, didReceive: receivedData)
}
}
}
Delegate와 Callback은 동일한 문제(비동기 알림)를 해결하지만 방식이 다릅니다. Delegate는 명명된 메서드가 있는 프로토콜을 사용하고, Callback은 컨텍스트 캡처가 있는 클로저를 사용합니다. 선택은 이벤트 수, 서명 복잡성 및 아키텍처 선호도에 따라 달라집니다. Apple은 여러 이벤트가 있는 API에는 Delegate(UITableView — 20개 이상의 메서드)를, 일회성 완료에는 Callback을 권장합니다.
단일 소스에서 여러 다른 이벤트를 처리할 때 Delegate가 선호됩니다. 예를 들어, CLLocationManager는 위치 변경, 권한 오류, 지오펜스 진입/이탈 및 서비스 상태 변경에 대해 델리게이트에 알립니다. 각 이벤트는 명확한 이름과 유형화된 매개변수가 있는 별도의 프로토콜 메서드입니다. Delegate는 동작 구성(should, will, did 메서드)에도 편리합니다.
Callback은 단일 결과가 있는 일회성 요청에 더 간단합니다. URLSession.dataTask의 Completion handler는 호출 지점에서 최소 세 개의 프로토콜 메서드에 비해 한 줄을 차지합니다. Callback은 함수형 체인(map, flatMap, async/await)에도 더 자연스럽습니다. 그러나 2-3 수준 이상의 중첩에서는 Callback이 Callback Hell이 되는 반면, Delegate는 항상 평평하게 유지됩니다.
iOS SDK에는 다양한 하위 시스템을 위한 수십 개의 내장 델리게이트 프로토콜이 포함되어 있습니다. 각각은 특정 상호 작용 시나리오를 위해 설계되었습니다. Apple Documentation(2025)에 따르면 가장 많이 사용되는 델리게이트는 UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate 및 UNUserNotificationCenterDelegate입니다. 이러한 프로토콜에는 다양한 필수 수준의 3~30개 메서드가 포함되어 있습니다.
UITableViewDelegate는 테이블 셀의 모양과 동작을 관리합니다. 행 선택 처리, 셀 높이 구성, 사용자 지정 header/footer 보기 및 스와이프 동작을 위한 메서드가 포함되어 있습니다. 모든 프로토콜 메서드는 선택 사항이므로 필요한 기능만 구현할 수 있습니다. 델리게이트가 없으면 테이블은 기본 설정으로 작동합니다. 역사적으로 델리게이트는 UITableViewDataSource와 결합되었습니다.
URLSessionDelegate는 HTTP 요청에 대한 세부적인 제어를 제공합니다. 델리게이트 메서드는 서버 응답 수신, 데이터 도착 또는 다운로드 완료 시 호출됩니다. 특수화된 하위 프로토콜 URLSessionTaskDelegate 및 URLSessionDataDelegate는 특정 작업 유형에 대한 기본 기능을 확장합니다. 델리게이트는 백그라운드 다운로드, SSL 인증서 및 사용자 지정 리디렉션 처리를 지원하는 데 필요합니다.
| Delegate | 메서드 수 | 목적 |
|---|---|---|
| UITableViewDelegate | 25 | 테이블 모양 및 상호 작용 |
| UITextFieldDelegate | 8 | 텍스트 입력 및 키보드 처리 |
| CLLocationManagerDelegate | 12 | 위치 업데이트 및 지오펜스 |
| URLSessionDelegate | 6 | HTTP 세션 및 인증서 관리 |
| UNUserNotificationCenterDelegate | 4 | 포그라운드 푸시 알림 처리 |
메모리 관리는 iOS에서 델리게이트 작업의 중요한 측면입니다. ARC(자동 참조 카운팅)는 메모리를 자동으로 관리하지만 weak/unowned 참조의 올바른 사용이 필요합니다. 규칙을 위반하면 메모리 누수 또는 조기 할당 해제가 발생합니다. strong으로 선언된 델리게이트는 델리게이트 소유자가 위임 객체에 대한 참조도 보유하는 경우 retain cycle을 만듭니다.
Retain cycle은 객체 A(소유자)가 자신을 객체 B의 델리게이트로 설정하고 B가 델리게이트에 대한 강한 참조를 보유할 때 발생합니다. 예: ViewController가 URLSession을 만들고 자신을 세션의 델리게이트로 설정하지만 delegateQueue가 지정되지 않은 경우 URLSession은 기본적으로 델리게이트에 대한 강한 참조를 보유합니다. 해결책은 델리게이트 참조 유형(weak 또는 strong)에 대해 API 문서를 항상 확인하고 deinit에서 델리게이트를 명시적으로 nil로 설정하는 것입니다.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// deinit에서 delegate를 nil로 설정 — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// NSObject를 통한 weak delegate가 있는 URLSession
class WeakDelegateSession: NSObject {
private weak var delegate: URLSessionDelegate?
func createSession() -> URLSession {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return URLSession(
configuration: .default,
delegate: self,
delegateQueue: queue
)
}
}
델리게이트 메서드를 호출하기 전에 델리게이트가 존재하고(nil이 아님) 호출된 메서드를 구현하는지 확인해야 합니다. 필수 프로토콜 메서드의 경우 확인이 필요하지 않습니다. 컴파일러가 구현을 보장합니다. 선택적 메서드의 경우 respond(to:) 또는 선택적 체이닝을 사용합니다. 델리게이트가 할당 해제되면 weak 참조가 자동으로 nil이 되고 델리게이트 호출이 무시됩니다. 이는 추가 처리가 필요 없는 안전한 동작입니다.
개발자는 특히 iOS 학습 초기 단계에서 Delegate 패턴으로 작업할 때 실수를 자주 합니다. 가장 일반적인 것은 strong 델리게이트로 인한 retain cycle, delegate?.method() 호출 누락, 잘못된 프로토콜 메서드 서명, 작업 시작 후 델리게이트 설정 및 멀티스레딩 충돌입니다. 각 실수와 방지 방법을 살펴보겠습니다.
가장 중요한 실수는 delegate 속성을 weak var 대신 strong var로 선언하는 것입니다. 이는 델리게이트와 위임 객체 모두 해제할 수 없는 retain cycle을 만듭니다. 결과: 메모리 누수, 앱 속도 저하 및 숨은 버그. 해결책: 델리게이트에는 항상 weak var를 사용하고 프로토콜이 AnyObject를 상속하게 하여 값 유형이 델리게이트로 사용되는 것을 방지하세요.
비동기 메서드를 호출한 후 델리게이트를 설정하면 첫 번째 이벤트가 손실될 수 있습니다. 예: manager.delegate = self를 할당하기 전에 startDownload()를 호출하면 다운로드가 동기적으로 또는 매우 빠르게 실행되는 경우 완료 콜백이 누락됩니다. 해결책: 비동기 메서드를 호출하기 전에 델리게이트를 설정하고 프로토콜 주석에 초기화 순서를 문서화하세요.
자주 묻는 질문
Weak은 델리게이트와 위임 객체 간의 retain cycle을 방지합니다. 참조가 강한 경우 객체가 서로를 붙잡게 되고 ARC가 해제할 수 없습니다. weak 참조는 델리게이트가 할당 해제되면 자동으로 nil이 됩니다. 이는 Objective-C 등장 이후 표준 Cocoa Touch 관행이며 이전 버전과의 호환성을 위해 Swift에서 유지됩니다.
Delegate는 이벤트를 처리하고 동작을 관리합니다(셀 높이, 탭에 대한 응답). DataSource는 표시할 데이터를 제공합니다(행 수, 셀). 델리게이트는 “어떻게?”라는 질문에 답하고, dataSource는 “무엇을?”이라는 질문에 답합니다. iOS에서는 둘 다 프로토콜을 통해 구현되며, 종종 동일한 컨트롤러에 있지만 개념적으로는 분리되어 있습니다.
아니요, 프로토콜이 AnyObject(클래스 프로토콜)를 상속하는 경우에는 불가능합니다. weak 참조는 참조 유형(클래스)에만 사용할 수 있습니다. 값 유형(struct, enum)의 경우 콜백 클로저 또는 별도의 래퍼 클래스를 사용하세요. 프로토콜을 제어할 수 있다면 AnyObject 상속을 피할 수 있지만, 그 경우 weak이 금지됩니다. weak 델리게이트와 struct 델리게이트 사이에서 의식적으로 선택하세요.
responds(to:)는 객체가 지정된 선택자를 구현하는지 확인하는 NSObjectProtocol 메서드입니다. 호출 전에 선택적 @objc 프로토콜 메서드를 확인하는 데 사용됩니다. 이 확인 없이 구현되지 않은 선택적 메서드를 호출하면 NSInvalidArgumentException이 발생합니다. Swift에서는 @objc optional이 있는 프로토콜의 경우 선택적 바인딩을 통해 확인이 암시적으로 이루어질 수 있습니다.
아니요, delegate는 위임 패턴이지 싱글톤이 아닙니다. 싱글톤과 달리 델리게이트는 런타임에 교체할 수 있으며 각 위임 객체에 대해 단일 인스턴스로 존재합니다. 하나의 객체가 여러 발신자의 델리게이트가 될 수 있습니다. 싱글톤은 단일 클래스 인스턴스를 보장하는 생성 패턴으로, 위임과 관련이 없습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.