안녕하세요 iOS 개발자 루크입니다!
오늘은 delegation 패턴에 대해 배워보려고 합니다
바로 시작할게요!
Delegation 패턴
이 글은 위 링크의 게시글을 참조하여 작성되었습니다.
디자인 패턴이라는 것은 본디 프로그래밍을 하면서 흔히 마주하는 문제들에 대한 명쾌한 해결책이며,
Delegation 또한 흔히 마주하는 문제들에 대한 해결책이다.
Delegation 패턴은 그렇다면 어떤 문제에 대한 해결책일까?
하나의 클래스에 모든 기능을 구겨넣게되면 코드가 길어지고 가독성이 떨어져 유지보수가 힘들어진다.
그렇기 때문에 우리는 자주 많은 기능들을 다른 클래스에게 나눠주어야한다(역할 분담)
이렇게 역할 분담을 하였을 때, 흔히 이러한 역할들 간에는 서로 의존관계가 있을 수 있다.
예를 들어서 내가 쿠키 회사를 운영하고있다.
아래와 같은 함수를 만들려고한다.
1.
쿠키를 만든다.
2.
쿠키를 판다.
3.
쿠키를 배송한다.
아직 회사 규모가 작아서
하나의 클래스(쿠키 베이커리 클래스)내에 위 3가지 함수를 모두 구현한다고 했을 때는 별 문제가 생기지 않는다.
문제는 바로 이러한 함수들을 각각 다른 클래스에게 나누어 주었을 때 생긴다.
1.
쿠키를 만든다. (쿠키 공장클래스)
2.
쿠키를 판다. (쿠키 상점클래스)
3.
쿠키를 배송한다. (쿠키 유통클래스)
1.
"쿠키를 판다" 같은 경우 존재하는 쿠키가 없다면 쿠키를 팔 수 없다.
2.
"쿠키를 배송한다" 같은 경우 쿠키가 팔렸을 때 반드시 실행되어야한다.
클래스간 소통이 이루어지지 않는 다면 굉장히 큰 문제가 발생할 수 있다.
( 쿠키가 없는데도 쿠키를 주문을 받는다니,,,)
따라서 클래스 간의 소통 로직을 반드시 구현해주어야 하는데, 여기 까지는 또 괜찮다.
그냥 소통하고자 하는 클래스의 객체를 다른 클래스에 선언하고, 객체를 통해 다른클래스의 함수를 호출하면 되기 때문이다.
진짜 문제는 지금부터다.
내가 쿠키 공장을 프랜차이즈로 내려고 한다 (장사가 잘돼서)
그래서 쿠키 공장을 여러개 지으려고 하다보니까,,,,
쿠키를 파는 배송 클래스 내부에 이렇게 모두 지어진 쿠키공장의 객체를 일일히 배송 클래스 내부에 선언하려고 하다보니,,,,
쿠키공장을 지을 때마다 배송클래스에 객체를 생성해야한다!!!
이 문제에 대한 해답이 바로 Delegation 이다.
무엇인가?
Delegation is a design pattern that enables a class to hand off (or “delegate”) some of its responsibilities to an instance of another class.
꽤 복잡하다 풀어서 설명하면.
delegatoin 이 세상에 있다고 생각하자.
한 클래스 내부에서의 이벤트를 정의하고 이 이벤트 발생에 따른 로직을 다른 클래스에서 구현하는 것. 클래스를 분할하고 싶은데 이들은 독립적이니까 즉 클래스간의 소통 을 구현해 주겠다!!
그럼 그냥 클래스를 상속(subclassing)받으면 되는거 아냐? A: NO
단순한 기능 하나를 위해서 전체 클래스를 전부 상속받는 것은 너무 코드가 무거워진다.
1.
이벤트 정의
2.
이벤트 신호 전달
3.
이벤트 신호에 대한 처리
a.
데이터 전달
b.
기타 보일러플레이트 코드
왜 사용하는가?
•
Delegatoin 과 Delegation 패턴은 hand-off ( 한 클래스에서 하던 작업을 다른 클래스에서 이어서하는 것) 클래스간의 상호작용에 접근하는 가장 가벼운 방법이다.
•
클래스간의 소통을 위해개발자는 protocol 만 만들면 된다. 클래스들간의 기능의 분할은 유지보수를 쉽게 만들어준다.
•
이러한 상호작용에 반응하는 클래스로부터의 상호작용을 생성하는 클래스의 책임을 분할시켜준다. >> 하나의 클래스에 작성되는 코드의 양을 줄여준다.
delegation 의 대안
•
Create a separate delegate class, a controller, that’s responsible for responding to delegate functions
•
Use Swift’s extensions to separate your code, giving each set of delegate functions its own extension
•
Abstract away multiple delegate functions into one controller (or “manager”) and use closures to respond to the fine-grained data you actually need
delegation 대신에 클로져 사용하면 되는거 아니야??
A: 맞다. closure 가 더 유연하고, 가볍고, decoupled 근데 단 하나의 단점이 있다. 너 무 많은 클로져를 사용한다면 유지보수 및 개발이 어려워진다.
요약
•
delegation 은 subclassing 보다 가볍다.
•
1 대 1 관계에서 유용하다. 1 대 다, 다대다 관계 >> Observer pattern 을 사용하자.
•
delegation 은 유연하다. 프로토콜을 준수하는 것만으로도 delegate 에 관한 모든 것을 알 수 있기 떄문이다.
•
클로져는 좋은 대안이다, 단 base 클래스가 클로져를 지원해야하며, 개발자는 단순한 클로져를 제공해야한다.
•
뷰컨트롤러가 모든것을 delegate 하지 않도록 조심하자.
Delegation 의 가장 쉬운 예시
설명하기에 앞서서 우리는 Cookie 라는 struct 를 정의하겠다.
struct Cookie {
var size:Int = 5
var hasChocolateChips:Bool = false
}
Swift
복사
그리고 우리는 Bakery 라는 클래스를 정의하겠다.
class Bakery
{
func makeCookie() //쿠키를 만드는 함수.
{
var cookie = Cookie()
cookie.size = 6
cookie.hasChocolateChips = true
}
}
Swift
복사
Bakery 클래스는 makeCookie() 함수를 호출해서 쿠키 객체를 생성하고. 이객체의 프로퍼티를 설정한다.
이 지점에서 우리는 쿠키를 3가지 다른 방식으로 팔고자한다.
1.
베이커리의 상점 내부에서
2.
베이커리의 웹사이트에서
3.
쿠키 디스트리뷰터 전체에서
쿠키를 파는 것은 베이커리의 책임이 아니다. 하지만 쿠키를 만든다. 그래서 우리는 만든 쿠키를 배송할 방법이 필요하다 Bakery 클래스에 코딩을 하지않으면서!!!
여기서 delegation 개념이 나온다.
첫번째로 우리는 우리가 손뗴고자 하는 책임을 캡슐화하는 프로토콜을 정의한다.
protocol BakeryDelegate {
func cookieWasBaked(_ cookie: Cookie)
func preferredCookieSize() -> Int
}
Swift
복사
BakeryDelegate 프로토콜은 하나의 함수를 정의한다. 바로 cookieWasBaked(_:) 이 delegate function 은 쿠키가 완성될 때마다 호출 될 것이다.
두번째, 우리는 Delegation (위임) 을 Bakery 클래스에 포함시킬 것이다. 아래처럼
class Bakery
{
var delegate:BakeryDelegate? //1. 🔑 핵심
func makeCookie()
{
var cookie = Cookie()
cookie.size = delegate?.preferredCookieSize() ?? 6
cookie.hasChocolateChips = true
delegate?.cookieWasBaked(cookie)
}
}
Swift
복사
두가지가 변경되었다.
1.
BakeryDelegate? 타입의 delegate 프로퍼티가 추가되었다.
2.
cookieWasBaked 함수가 delegate 프로퍼티 에서 호출되었다. makeCookie()
delegate 프로퍼티 의 타입은 우리가 앞서 정의한 프로토콜이다. delegate 프로퍼티에 어떠한 값이든 할당할 수 있다. BakeryDelegate 프로토콜을 채택하는 값만!!!
delegate 프로퍼티는 optional 이다. cookieWasBaked() 함수가 호출될때 optional chaining 을 사용하였다. 만약 delegate 가 nil 값이면 함수는 호출되지 않는다.
요약하자면, 너는 이제 BakeryDelegate 프로토콜을 정의했고 이는 Bakery 가 위임할 몇몇의 책임들이 정의도어있다. 그리고 너느 이러한 hand-off 를 makeCookie() 함수 안에 구현하였다.
마지막으로 진짜 delegate 클래스를 만들자.
class CookieShop: BakeryDelegate
{
func cookieWasBaked(_ cookie: Cookie)
{
print("Yay! A new cookie was baked, with size \(cookie.size)")
}
func preferredCookieSize() -> Int
{
return 12
}
}
Swift
복사
CookieShop 은 BakeryDelegate 프로토콜을 채택하였고, cookieWasBaked(_:) 함수를 구현함으로써 프로토콜을 준수하였다.
이제 코드들을 함께사용해보자.
let shop = CookieShop() // delegate 프로토콜 준수하는 객체
let bakery = Bakery() //
bakery.delegate = shop // delegate 프로퍼티에 delegate 프로토콜 준수하는 객체 할당
bakery.makeCookie()
//쿠키가 만들어 질 때마다, delegate 프로토콜 준수하는 객체에 있는 함수가 실행됨.
// Output: Yay! A new cookie was baked, with size 6
Swift
복사
아래와 같은 일들이 벌어진다.
1.
먼저 CookieShop() 객체가 shop constant 에 할당된다.
2.
그다음 Bakery 객체가 bakery constant 에 할당된다.
3.
그다음, shop 을 bakery.delegate 에 할당한다.
a.
이말의 뜻은 shop 이 bakery 의 delegate 로 만든다.
4.
베이커리에서 쿠키를 만든다.
어떠한 이벤트가 발생하였을 때 자동으로 이 이벤트들에 반응하는 delegate 로직을 구성할 수 있음에 주목하자. 그리고 이 이벤트에 대한 반응은 중심 클래스에서 어떠한 컨트롤도 갖지 못한다.
iOS 개발에서 사용되는 곳
The CLLocationManager
The UITextView uses the
UITextView 에 대한 예시
아래의 단순한 뷰 컨트롤러를 보자.