Delegate: 대리자. 위임자. 위임하다
Delegate패턴은 위임자를 갖고있는 객체가 다른 객체에게 자신의 일을 위임하는 형태의 디자인 패턴이다.
Delegate Pattern
델리게이트 패턴은 보통 "객체가 자신의 책임을 다른 객체에게 위임(delegate)하는 디자인 패턴"이라고 설명된다.
예를 들어 테이블뷰는 셀을 탭했을 때 어떤 행동을 할지에 대한 책임을 뷰컨트롤러에게 UITableViewDelegate를 사용해 위임한다.
테이블뷰 이외에도 콜력션뷰, 텍스트필드 등 많은 UI요소들이 델리게이트패턴을 사용해 다른 객체에게 책임을 위임한다.
예제 1. 델리게이트란?
struct Cookie{
var size: Int = 5
var hasChocolateChips: Bool = false
}
class Bakery{
func makeCookie(){
var cookie = Cookie()
cookie.size = 5
cookie.hasChocolateChips = true
}
}
Bakery라는 클래스는 내부에서 Cookie구조체를 사용하여 cookie를 생성한다.
우리는 세 가지 방색으로 만들어낸 쿠키를 판매한다고 한다.
- 오프라인 베이커리 샵에서 판매
- 온라인 베이커리 웹사이트에서 판메
- 유통업체 도매를 통한 판매
쿠키를 판매하는 것은 우리의 책임이 아니다.
하지만 쿠키를 만들어 전달하는 것 까지는 우리의 책임이다.
따라서 우리는 쿠키를 만들고 나서 이를 전달할 수 있는 방법이 필요하다
여기서 델리게이트(Delegate)라는 개념이 이용된다.
1. 첫 번째로 우리는 프로토콜을 정의한다.
protocol BakeryDelegate{
func cookieWasBaked(_ cookie: Cookie)
}
Bakery가 다른 클래스에 위임할 작업들을 정의한 프로토콜을 생성한다. (델리게이션에서 프로토콜은 해야 할 작업의 목록들을 정의한다)
이 델리게이트 함수(cookieWasBaked(_:))는 쿠기가 구워질 때 마다 호출된다.
2. 두 번째, 이 delegate프로퍼티를 Bakery클래스에 선언한다.
class Bakery{
var delegate: BakeryDelegate?
func makeCookie(){
var cookie = Cookie()
cookie.size = 5
cookie.hasChocolateChips = true
delegate?.cookieWasBaked(cookie)
}
}
Bakery클래스에 두가지가 추가되었다.
BakeryDelegate타입의 delegate가 생성되었다.
cookieWasBaked(_:)함수는 쿠키가 만들어 질 때 마다 호출된다.
3. 위임할 작업들을 정의했으니, 이제는 위임 받을 클래스를 만들어본다
class Onlineshop: BakeryDelegate{
func cookieWasBaked(_ cookie: Cookie) {
print("Yay! A new cookie was baked, with size \(cookie.size)")
print("Onlineshop")
}
}
Onlineshop클래스는 BakeryDelegate프로토콜을 채택하고. cookieWasBaked(_:) 함수를 정의했다.
이 전까지 쿠키가 구워졌을때 무엇을 할지는 정의하지 않았다. Bakery는 쿠키를 만들기만 할 뿐 쿠키가 만들어 진 뒤 어떻게 팔지는 Onlineshop 정한것이다.
4. 모든것을 활용해보자.
let bakery = Bakery()
let onlineShop = Onlineshop()
bakery.delegate = onlineShop
bakery.makeCookie()
//Yay! A new cookie was baked, with size 5
//Onlineshop
Bakery는 쿠키가 어디로 갔는지 알 필요가 없다. Bakery는 BakeryDelegate 프로토콜을 채택하는 모든 클래스에 쿠키를 제공할 수 있다.
bakery는 프로토콜의 이행에 대해 알 필요 없이 필요할 때마다 cookieWasBaked(_:) 만 호출하면 된다.
예제 2. UI요소에서의 DelegatePattern - 위임을 하는 이유?
위임을 하는 이유는 UIComponent 내부의 코드를 수정할 수 없기 때문이다.
ViewController에서 TableView를 사용하려면 아래와 같은 델리게이트, 데이터소스 메서드를 필수적으로 구현해야한다.
tableView(_:numberOfRowsInSection:)
//TableView의 각 섹션(section)에 대한 row 수를 반환합니다.
tableView(_:cellForRowAt:)
//TableView에서 각 셀(cell)을 구성하기 위한 UITableViewCell 객체를 반환합니다.
//추가
tableView(_:didSelectRowAt:)
//TableView에서 특정 셀이 선택되었을 때 호출되는 메서드입니다. 이 메서드에서는 선택된 셀에 대한 작업을 수행할 수 있습니다.
셀을 탭하면 테이블뷰는 탭 이벤트를 받는다. 테이블 뷰가 탭 이벤트를 받으면 delegate에 didSelectRowAt: 메서드를 실행시킨다.
이벤트를 받았을 때 어떤 행동을 할 것인지를 delegate에 위임한것이다
테이블뷰의 각 셀을 구성하는 방법 또한 정의를 하고, 테이블 뷰의 row수를 정의해야한다. 이를 우리는 보통 ViewController에 작성하며
IBOutlet으로 선언한 tableView의 tableView.delegate = self와 같은 코드를 작성한다.
tableView내부의 소스코드를 수정할 수 없으니 ViewController에 tableView가 동작할 방식을 구현하고 이를 tableView에 전달한 것이다.
tableView는 이벤트에 따라 어떤 행동을 할지 ViewController에 위임한것이다.
tableView.dataSource = self( tableView의 dataSource관련 일은 내가 ?? (뷰컨트롤러가) 대신할게)
tableView는 해야할 일에 대한 부분을 객체로 만들어주고 그걸 뷰컨트롤러에 넘기는(위임: Delegate) 디자인 패턴.
예제 3. 델리게이트 패턴을 통한 데이터 전달.
첫 번째 뷰컨 - button, 데이터를 전달받아 표시해줄 label
두 번째 뷰컨 - button, 데이터를 입력할 textField
두 번째 뷰컨이 dismiss될 때 첫 번째 뷰컨에 데이터를 전달하고, 첫 번째 뷰컨은 데이터를 받아 라벨에 표시하는 방식.
1. 프로토콜 생성
첫 번째 뷰컨에서 데이터를 받아 데이터를 라벨에 표시해줄거기 때문에 String을 받는다.
함수 원형만 작성하고 구현부는 작성하지 않는다.
string을 받아서 처리하는 부분은 첫 번째 뷰컨에서 구현한다.
2. 두 번째 뷰컨에 delegate를 선언한다.
3. delegate 프로퍼티을 사용한다.
[ delegate으로 데이터 전달 ] 버튼을 누르면 dataTextField.text를 if let을 통해 nil이 아님을 확인하고 text에 값을 저장한다.
delegate프로퍼티의 dataSend 함수를 불러와 data파라미터에 text를 전달만!한다.
첫 번째 뷰컨
4. SampleProtocol을 채택한다.
5. SampleProtocol이 요구한 dataSend함수를 구현한다.
앞서, 3. 에서 delegate의 dataSend로 text를 전달했다. 이를 받아서 처리하는 부분을 실제로 구현하는 곳은 데이터를 전달받아 사용할 첫 번째 뷰컨이다.
6. delegate위임 nextVC.delegate = self
[ nexrVC.delegate = self ]는 두 번째 뷰컨에서 선언한 delegate프로퍼티(SampleProtocol타입)를 대신 처리하는게 첫 번쨰 뷰턴이라는 뜻이다.