[iOS] Delegation 재정복하기 (feat. 야곰 아카데미)
👨🏻‍💻iOS 공부/iOS & Swift

[iOS] Delegation 재정복하기 (feat. 야곰 아카데미)

728x90
반응형
본 게시글은 야곰 아카데미의 Coda가 제공한 실험실 콘텐츠를 학습하며 얻은 지식과 경험을 정리한 글 입니다.
Thanks for Coda!!🙇🏻‍♂️🙏🏻

오랜만에 블로그에 글을 남겨봅니다~!

(아카데미에서 공부하느라... 블로그가 뒷전이 되었네요... 블로그에만 올리지 않고 github에는 TIL로 매일 정리하고 있습니다!)

 

각설하고 Delegation에 대해서 알아봅시다!

1. Delegation이란? 

 

Delegation이란 무엇일까? 

뭔가 익숙한듯 익숙하지 않은 단어.... 여기저기서 delegation pattern, delegate pattern이라는 말을 들어봤을 것이다. 

 

답은 항상 단어의 뜻에 있다. 

 

단어의 뜻을 알아보자.

delegation의 정의 [from 네이버 백과사전]

 

 

"위임"이라는 뜻이 현재 문맥에서는 적합한 것 같다. 

그렇다면 무엇을 위임하는 것인지, 위임이라는 행동에 연관되어있는 객체는 무엇이 있을지 생각해볼 수 있다. 

 

근본적으로 위임이란, 내가 해야할 일을 다른 누군가에게 대신 시킨다는 의미를 가지고 있다. 이 맥락을 그대로 프로그래밍 세계에 가져와보자. 

 

주로 delegation을 설명할 때 상하관계의 객체들을 예시로 한다. "회장님-비서", "점장님-알바" 등 딱 봐도 수평적인 관계가 아님을 알 수 있다. 이는 모두 이해를 위해 현실세계에서 "시킬 수 있는 누군가", "시킴을 당하는 누군가"를 가져온 것 뿐이지 무조건적으로 상하관계이어야 하는 것은 아니다! 

 

즉, 수평적인 관계여도 서로 어떠한 행위를 위임할 수 있는 것이다. 

친구끼리 부탁을 한다거나, 직장 동료에게 부탁을 한다거나 수평적인 관계에서도 위임은 항상 우리의 주변에서 쉽게 볼 수 있다. 

(물론 일회성이 많지만...)

 

핵심은 수직/수평 관계가 아니라 "A가 시키고자 하는 일을 B가 대신 해준다" 라는 것이다.

 

2. 왜 delegation을 할까?

 

이제 delegation이 어떤 목적을 위해 있는 것인지는 얼추 이해가 되었을 것이다. 그렇다면 왜 delegation을 하는걸까??

생각해보면 여러 이유가 있을 수 있다. 

 

  • 내가 해야 할 일이 너무 많아서, 몇 가지 일은 다른 누군가에게 책임을 넘기고 싶은 경우 
  • 내가 하기 귀찮아서, 다른 누군가 대신 해줬으면 해서

 

등등 여러가지 이유로 현실세계에서, 프로그래밍 세계에서 위임이 일어나고 있을 것 이다. 

 

여기서 핵심이라고 생각되는 부분은 "책임"인 것 같다. 어떠한 일을 위임함으로써 책임을 일부 넘겨준다고 볼 수 있을 것 같다. 책임을 일부 넘겨줌으로써 서로의 역할과 책임이 명확해질 수 있다. 객체 지향에 대해서 조금이나마 공부해봤다면 "역할과 책임"의 중요성은 말하지 않아도 알 것이다. 그래서 개인적인 생각으로는 "객체들의 역할과 책임을 나눔으로써 객체들은 서로 요청/응답만 하는 과정을 거치며, 결국에는 의존성을 낮출 수 있다"는게 delegation의 핵심 역할인 것 같다. 

 

학창시절 다니던 학교를 생각해보자. 수업이 끝나고 보통 교실 청소를 하던 모습을 쉽게 기억해볼 수 있을 것이다. 선생님은 청소 구역을 나눠주고 청소시간을 알려주고, 학생들은 정해진 청소 구역에 따라 청소를 진행하게 된다. 이 또한 위임이라고 볼 수 있을 것 같다. 선생님은 청소 시간만 알려줄 뿐, 실제로 청소는 학생들이 진행하는 것이다. (요새는 어떤지 모르지만...) 우선 예시라고 보고, "선생님""청소"라는 행위를 "학생"에게 위임해주는 것이다. 

 

선생님과 학생간의 위임 관계

3. Delegation 구현 (feat. 상호 의존성이 높은 케이스)

 

그렇다면 이 예시로 한 번 delegation을 구현해보자.

 

먼저 선생님 객체를 만들어보자. 

 

class Teacher {
    // 청소 구역을 나타낸다 (구역명: 현재 청소여부)
    var cleaningArea: [String: Bool]
    // 청소를 시킬 학생을 지정
    var student: Student
    
    // 청소 구역과 학생을 할당한다
    init(cleaningArea: [String: Bool], student: Student) {
        self.cleaningArea = cleaningArea
        self.student = student
        self.student.delegator = self
    }
    
    // 오늘 청소해야하는 구역명을 알려준다
    func notifyTodayCleanArea() -> [String] {
        return cleaningArea.keys.map { String($0) }
    }
}

 

선생님(Teacher)은 청소 구역을 나누고, 정해진 청소구역을 특정 학생에게 알려주는 역할을 한다. 

여기서는 Teacher 인스턴스가 생성되는 시점에 바로 위임자-대리인 관계가 형성되도록 init() 메서드 내부에 위임자를 정해주는 로직이 들어가있다. 

 

그럼 학생(Student)는 무엇을 해야할까? 청소다. 청소 구역을 할당 받으면 청소를 하고, 청소 구역을 완료했다는 의미로 기존에 false이던 값을 true로 바꿔주면 된다. 

 

class Student {
    // 일을 시킬 수 있는 사람은 선생님(Teacher)이다.
    var delegator: Teacher?
    
    // 청소 시작!
    func clean() {
        // 청소 구역 할당 받기
        let todayCleanTargetArea = delegator?.notifyTodayCleanArea()
        
        // 청소를 마치고 청소 구역을 청소했다는 의미로 true로 값을 변경해준다
        todayCleanTargetArea?.forEach { area in
            delegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}

 

위임의 관계에서 봤을 때 학생은 대리인이고, 선생님은 위임자가 되는 것이다. 현재는 학생(대리인)과 선생님(위임자)이 서로를 알도록 구현하였다. 

 

이제 각 객체를 인스턴스화 해보고 청소를 진행해보자. 

 

// 1반 학생
let class1Student = Student()
// 1반 선생님
let class1Teacher = Teacher(cleaningArea: ["복도":false, "교실":false], student: class1Student)

// 1반 학생 청소 시작
class1Student.clean()

// 1반 청소 구역 정보
class1Teacher.cleaningArea // ["복도": true, "교실": true]

 

중요한 부분은 Teacher 타입을 인스턴스화 했을 때, 1반 학생이 위임을 받은 순간부터 1반 선생님이 시키는 일을 할 수 있다는 것이다. 

청소를 마치고, 청소 구역에 대한 정보를 알맞게 변경시킨 것도 볼 수 있다. 

 

이렇게 위임을 해주고, 대리인은 대신 일을 해주게 되는 것이다. 

 

하지만 여기서 더 나아가서 학창시절을 회상해보자... 

청소는 학교에서만 하지 않았다..! 집에서도 청소를 할 수 있는데, 그 때는 청소를 시키는 객체가 선생님이 아닌 부모님이 될 수 있다. 

그렇다면 현재 코드를 재사용 할 수 있을까??

 

그럴 수 없다. 

 

왜냐하면 현재 Teacher라는 클래스 타입이어야 Student 타입에게 청소 구역을 알려줄 수 있기 때문이다. 

반대로 선생님이 학생에게만 청소를 시킬 수 있을까? 너무 꼰대같은 예시이지만 학교 경비 아저씨에게도 청소를 시킬 수 있다고 생각해보자.(예시는 예시일 뿐, 평등한 세상을 추구합니다...🙇🏻‍♂️)

 

이 경우에 현재 Teacher 타입은 Student 타입에게만 청소를 시킬 수 있기에, 다른 타입의 객체에게는 청소를 시킬 수 없게 된다. 

 

결국에 현재 서로가 서로에게 너무 의존하고 있는 것이다. 

 

  • 선생님은 "학생"에게만 청소를 시킬 수 있다
  • 학생은 "선생님"이 시키는 청소만 할 수 있다. 

 

두 문장만 봐도 서로 많이 의존하고 있음을 알 수 있다. 그렇다면 이러한 의존성을 낮춰주기 위해서는 어떻게 해야할까?? 

 

4. Delegation 구현 (feat. 상호 의존성을 낮춘 케이스)

 

많이 들어봤겠지만 의존성을 줄이기 위해서는 서로에게 의존하는 것이 아니라 "추상 개념"에 의존해야한다. 

추상 개념이란 무엇일까? 현재 예시에 대입을 해보고 생각해보자. 

 

지금까지 청소를 시킬 수 있는 선생님과 청소를 할 수 있는 학생이라는 관계에 대해서 이야기해봤다. 

 

위 문장을 봤을 때, 어떤 부분을 추상 개념에 속할 수 있을까? 

추상화란 대표적인 특징을 꼽아내는 과정이라고도 설명해 볼 수 있을 것 같다. 그렇다면 현재 상황에서 추상화를 해보자면 "누구인지"가 중요한게 아니라 "어떤 행동"을 할 수 있는지가 보다 더 중요하다는 것을 알 수 있다. 

 

  • 청소를 할 수 있는
  • 청소를 시킬 수 있는

 

추상화를 해보면 이 두 가지 추상 개념을 얻을 수 있을 것이다. 

 

선생님이 학생에게 청소를 시킨다는 상황을 다른 시선으로 바라보면, 청소를 시킬 수 있는 사람이 청소를 할 수 있는 사람에게 청소를 시킨다고 볼 수 있는 것이다. 

 

자 그러면 이제 이 추상 개념을 코드로 나타내보고 각 객체들을 추상 개념에 의존하도록 수정해주면 기존의 의존성을 낮춰 볼 수 있을 것이다. 

 

Swift에서 추상 개념이란, 거의  protocol이라고 생각해도 좋을 것 같다. 

기능 명세서, 청사진 등의 역할을 하는 protocol은 세부 내용이 구현되어 있지 않고 특정한 틀만을 제공해주기 때문에 추상 개념이라는 것과 개념이 같다고 볼 수 있다. 

 

이제 추상 개념들을 코드로 나타내보자.

 

// 청소를 할 수 있는
protocol Cleanable {
    func clean()
}

// 청소를 시킬 수 있는
protocol CleanOrderable {
    var cleaningArea: [String: Bool] { get set }
    
    func notifyTodayCleanArea() -> [String]
}

이제 부모님 클래스, 선생님 클래스를 만들어도 "CleanOrderable" 프로토콜만 채택해준다면 청소를 시킬 수 있는 역할과 책임을 얻을 수 있다!! 학생, 청소부 등등도 마찬가지로 "Cleanable" 프로토콜을 채택하는 순간 청소를 할 수 있는 역할과 책임을 얻게 되는 것이다. 

 

자 그러면 본인이 선생님 뿐만 아니라 부모님에게도 청소를 요청 받을 수 있도록 코드를 만들어보려면 어떻게 해야할까? 우선 선생님과 부모님 클래스를 각각 만들어보자. 

 

class Teacher: CleanOrderable {
    var cleaningArea: [String : Bool]
    var cleaner: Cleanable?
    var myClass: String
    
    init(area: [String : Bool], myClass: String) {
        self.cleaningArea = area
        self.myClass = myClass
    }
    
    func notifyTodayCleanArea() -> [String] {
        return cleaningArea.keys.map { String($0) }
    }
}

class Parent: CleanOrderable {
    var cleaningArea: [String : Bool]
    var cleaner: Cleanable?
    var dueDate: String
    
    init(area: [String : Bool], dueDate: String) {
        self.cleaningArea = area
        self.dueDate = dueDate
    }
    
    func notifyTodayCleanArea() -> [String] {
        return cleaningArea.keys.map { String($0) }
    }
}

Teacher은 myClass라는 프로퍼티를 추가로 가질 수 있고, Parent는 dueDate라는 프로퍼티를 추가로 가지도록 했다. 

현재는 아무 기능을 하지는 않지만, 실제로 서로 다른 객체에서 시킬 수 있다는 상황을 가정하기 위해서 일부 다른 속성을 넣어줬다!

 

자 이제 청소를 할 수 있는 객체들을 만들어 보자. 

 

청소를 할 수 있는 객체로는 Student와 Brother을 만들어보자. 둘 다 Cleanable 프로토콜을 채택하도록 하고, Brother 객체에는 청소 거부 의사를 나태낼 수 있는 메서드를 하나 추가해봤다. (이 또한 마찬가지로 서로 다른 객체가 특정 위임자가 시키는 일을 할 수 있다는 것을 보여주기 위해 추가한 것이다!)

 

class Student: Cleanable {
    var cleanDelegator: CleanOrderable?
    
    func clean() {
        let todayCleanTargetArea = cleanDelegator?.notifyTodayCleanArea()
        
        todayCleanTargetArea?.forEach { area in
            cleanDelegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}

class Brother: Cleanable {
    var cleanDelegator: CleanOrderable?
    
    func refuseCleaning() {
        print("하기 싫은데...")
    }
    
    func clean() {
        let todayCleanTargetArea = cleanDelegator?.notifyTodayCleanArea()
        
        todayCleanTargetArea?.forEach { area in
            cleanDelegator?.cleaningArea.updateValue(true, forKey: area)
        }
    }
}

 

자 이제 모든 준비가 되었다. 청소를 할 수 있고, 청소를 시킬 수 있는 객체들이 이제 있기에 서로가 서로에게 청소를 시키고, 청소를 할 수 있게 된다. 

 

 

위임자-대리인 관계

이제 더 이상 무서울게 없다!! 

 

CleanOrderable 프로토콜을 채택하기만 하면 일을 시킬 수 있는 존재가 되고, Cleanable 프로토콜을 채택하기만 하면 청소를 할 수 있는 존재가 된다

 

자 그러면 이제 서로에게 위임하고, 위임 받은 일을 하는 과정을 코드로 살펴보자. 

 

let teacher = Teacher(area: ["복도": false, "교실": false], myClass: "1반")
let parent = Parent(area: ["니 방": false], dueDate: "오늘 저녁까지")

let student = Student()
let brother = Brother()

student.cleanDelegator = teacher
student.clean()
teacher.cleaningArea // ["복도": true, "교실": true]

student.cleanDelegator = parent
student.clean()
parent.cleaningArea // ["니 방": true]

// teacher와 parent가 정한 청소 구역이 초기화되었다고 생각해보자.

brother.cleanDelegator = teacher
brother.clean()
teacher.cleaningArea // ["복도": true, "교실": true]

brother.cleanDelegator = parent
brother.clean()
parent.cleaningArea // ["니 방": true]

아까 봤던 상호 의존도가 높은 케이스와의 차이점을 알아 볼 수 있을 것이다. 

 

아까는 무조건 서로의 클래스의 인스턴스이어야 위임자-대리인의 관계가 성립했었으나, 지금은 어떠한 객체에 의존하는게 아니라 추상개념인 protocol에 의존함으로써 상호 의존도를 낮춘 모습을 볼 수 있다! 

 

Delegation의 무궁무진함

 

이렇게 단순하게 객체들 간에 어떤 일을 대신 해줄 수도 있고, 실제 iOS에 접해서 생각해보면 VC 사이에서 대신 어떤 행위를 해준다거나, 데이터를 전달해 줄 수도 있을 것이다. 

 

하지만 결국에는 근본 개념이 "위임"이라는 것이기 때문에, 근본 개념만 이해한다면 다른 delegation 또한 쉽게 이해해보고 응용해 볼 수 있을 것이다!

 

이전에 정리했던 글이긴 한데, TableView와 TableViewCell 사이에서 delegtaion을 통해 대신 원하는 일을 해주는 그런 케이스들도 볼 수 있다. (https://leechamin.tistory.com/500?category=941561)

 

그러니 의존성을 낮추기 위한 시도가 필요한 상황에서 delegation을 떠올려봐도 좋을 것 같다!


이렇게 delegation에 대해 정리를 해봤다!

 

머릿속으로는 다 알고 있다고 생각했는데... 역시 예제를 만들며 설명하려고 하니 부족한 부분이 눈에 들어왔다. 그 덕분에 조금 더 찾아보고 생각을 정리할 수 있었던 것 같다.

 

혹시나 틀린 부분이나 생각이 다른 부분이 있다면 적극적으로 피드백 부탁드립니다! 🙇🏻‍♂️

 

아주 초반에는 이해도 못하고 delegation 패턴을 사용했던 나를 반성하며... 

앞으로는 근본, 원리를 완벽하게 이해하고 설명해보고, 응용해보는 방식으로 학습해야겠다

 

끄읕

728x90
반응형