---
# System prepended metadata

title: README(6) 은행 창구 매니저

---

# README(6) 은행 창구 매니저

# 🏦 은행 창구 매니저

**은행 창구 시뮬레이션입니다.**
- 💡 **앱 소개**
    - 앱 시작시 은행이 개점되고 10명~30명의 손님이 방문합니다.
        - 손님의 업무는 예금업무 혹은 대출업무로 랜덤하게 지정됩니다.
        - 손님들은 대기중 화면에 차례로 보여집니다.
    - 예금업무를 처리하는 은행원은 2명, 대출업무를 처리하는 은행원은 1명입니다.
        - 예금업무를 처리하는 은행원은 업무처리에 0.7초를 소비합니다
        - 대출업무를 처리하는 은행원은 업무처리에 1.1초를 소비합니다.
    - 세 명의 은행원은 동시에 업무를 시작합니다.
        - 업무가 시작되면 진행시간을 스톱워치로 실시간 보여줍니다.
        - 손님의 업무처리를 시작하면, 그 손님은 화면의 대기줄 줄에서 진행중 줄로 옮겨집니다.
        - 손님의 업무처리가 끝나면 진행중 줄에서 사라집니다. 
    - 모든 고객의 업무가 끝나면 업무마감이 되고 총 걸린 스톱워치도 작동을 멈춥니다.</br></br>
- **💡 화면은 UIKit을 이용해 코드로만 구성했습니다**</br></br>
- **💡 동시성 적용은 GCD를 적용 했습니다**

## 📖 목차
1. [팀 소개](#-팀-소개)
2. [기능 소개](#-기능-소개)
3. [Diagram](#-diagram)
4. [폴더 구조](#-폴더-구조)
5. [타임라인](#-타임라인)
6. [프로젝트에서 경험하고 배운 것](#-프로젝트에서-경험하고-배운-것)
7. [트러블 슈팅](#-트러블-슈팅)
8. [참고 링크](#-참고-링크)

## 🌱 팀 소개
 |[inho](https://github.com/inho-98)|[써니쿠키](https://github.com/sunny-maeng)|
 |:---:|:---:|
| <img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://user-images.githubusercontent.com/71054048/188081997-a9ac5789-ddd6-4682-abb1-90d2722cf998.jpg">| <img width="180px" img style="border: 2px solid lightgray; border-radius: 90px;-moz-border-radius: 90px;-khtml-border-radius: 90px;-webkit-border-radius: 90px;" src="https://avatars.githubusercontent.com/u/107384230?v=4">|

## 🛠 기능 소개
|**기본 실행화면**|**스크롤 중 실행화면**|
|:-:|:-:|
|<img width=400, src="https://i.imgur.com/lfjYCGx.gif" >|<img width=400, src="https://i.imgur.com/gfkJp88.gif">|
|**`손님 10명 추가 버튼` 실행화면**|**`Reset 버튼` 실행화면**|
|<img width=400, src="https://i.imgur.com/N97c9x9.gif">|<img width=400, src="https://i.imgur.com/15tKdmx.gif">|

## 👀 Diagram
|<img width=900, src="https://i.imgur.com/401PAu7.png)">|
|---|

## 🗂 폴더 구조
```
BankManagerUIApp
├── View
│   └── view
├── ViewController
│   └── ViewController
└── Model
    ├── BankManager
    ├── Bank
    ├── BankClerk
    ├── Customer
    ├── BankingType
    ├── Queue
    │   ├── LinkedList
    │   ├── Node
    │   └── Queue
    └── Protocol
        └── Servable
```

## ⏰ 타임라인

### 👟 Step 1
|날짜|구현 내용|
|--|--|
|22.10.31|`Node`&`LinkedList`&`Queue`타입 구현, 유닛 테스트 작성, `Cocoapods`를 이용한 `SwiftLint`적용|
|22.11.2|`gitignore`파일 추가, 소스코드가 담긴 `Pods`파일 삭제|
<details>
<summary>Details - 구현 내용과 기능 설명 </summary>

#### 1️⃣ Node 
- `Generics` 타입의 데이터를 담을 `data`프로퍼티와 다음 노드 정보를 나타내는 `next`프로퍼티를 생성하였습니다.
#### 2️⃣ LinkedList
- 큐 타입 구현을 위한 `LinkedList`타입을 구현했습니다.
- 첫번째와 마지막 노드를 담는 `head`, `last`프로퍼티를 생성하였습니다.
- 큐에 필요한 기능을 구현하기 위한 메서드들을 가지고 있습니다.
    - `append(datda:)`
    - `removeFirst()`
    - `removeAll()`
#### 3️⃣ Queue
- 다양한 타입의 데이터를 담을 수 있도록 Generics를 이용하였습니다.
- 구현해야할 필수 기능들을 구현했습니다.
    - `isEmpty` 
    - `claer()`
    - `enqueue()`
    - `peek()` : 삭제없이 첫번째 노드의 데이터를 확인합니다.
    - `dequeue()`: 첫번째 노드를 삭제하고, 해당 데이터를 리턴합니다.
</details>

    
### 👟 Step 2
|날짜|구현 내용|
|--|--|
|22.11.2|`Customer`&`Bank` 타입 구현 및 내부 로직 수정|
|22.11.3|`BankManager`타입 구현 및 `Bank`의 기능 옮기기, 각 타입의 네임스페이스 구현 및 접근 제어 수정|
|22.11.4|`BankManager`에서 `CustomerQueue`대신 `Customer`만 전달하도록 수정,`String)(format:)`이용하여 네임스페이스 구현방법 수정|
<details>
<summary>Details - 구현 내용과 기능 설명</summary>
    
### 1️⃣ `BankManager`
- `Bank`와 `Customer`를 생성하고, 프로그램 실행 및 사용자 입력을 받는 메서드를 가지고 있는 타입으로 전체 실행을 담당합니다.
    - `startManagement()`: 메뉴를보여주고 사용자 입력에 따라 (1)은행문을열거나 (2)종료합니다.
    - `setupBank()`: 은행 객체를 만들고, 은행업무를 진행하기위한 준비 후 은행문을 엽니다.
    - `addCustomer(to bank: Bank)`: 10~30명 사이 고객을 은행에 전달합니다.
    - [enum] `Constant` : BankManager의 네임스페이스입니다.
### 2️⃣ `Bank`
- 고객 큐와 은행 업무처리에 관련된 메서드들을 가지고 있는 타입입니다. 
    - `openBank()`: 은행의 문을 엽니다
    - `receive(customer: Customer)`: 손님을 받아 손님 큐에 추가합니다.
    - `startBanking()`: 은행의 업무를 시작합니다.
    - `serveCustomer(number: Int)`: 손님을 한명씩 응대합니다.
    - `closeBanking()`: 은행 업무의 마감을 알립니다.
    - [enum] `Constant` : Bank의 네임스페이스입니다.
### 3️⃣ `Customer`
- 고객을 나타내는 타입으로 프로그램에서 필요한 대기번호를 프로퍼티로 가지고있습니다.
</details>

### 👟 Step 3
|날짜|구현 내용|
|--|--|
|22.11.7|`BankingType`&`DepositBankClerk`&`LoanBankClerk`타입 구현 및 `BankClerk`프로토콜 구현|
|22.11.8|`Bank`와 `BankManager`로직 변경 및 `main`실행부 구현, </br> `BankClerk`->`Servable`프로토콜로 변경 `DispatchGroup` 적용, `Date()`를 이용한 업무 시간 출력 구현|
|22.11.9|`BankingType`을 랜덤하게 생성하는 로직 변경, `Bank`의 함수 네이밍수정|

<details>
<summary>Details - 구현 내용과 기능 설명</summary>
    
### 1️⃣ `Servable`
- 은행에서 손님을 응대할 때 필요한 특징과 기능을 모아둔 프로토콜 입니다. 일처리하는 시간을 나타내는 `Double`타입과 은행 창구를 나타내는 시리얼큐인 `DispatchQueue`타입의 프로퍼티를 가지고 있습니다.
    - `serve(customer: Customer, group: DispatchGroup)`: 전달받은 손님에 대해서 비동기로 업무를 처리합니다.
    
### 2️⃣ `BankClerk`
- `Servable`프로토콜을 채택하고 있어, 프로토콜이 제공하는 기본 구현들을 사용할 수 있습니다.`BankingType`을 프로퍼티로 가지고, 자신의 은행업무 타입과 일치하는 손님을 상대하는 역할을 합니다.
### 3️⃣ `BankingType`
- 은행업무의 종류를 나타내기 위한 열거형입니다.
</details>

## ✅ 프로젝트에서 경험하고 배운 것
- 단방향 `Linked-list` 자료구조의 이해 및 구현 </br>
    ☑️ 단방향 연결리스트를 코드로 구현해보기 </br>
    ☑️ `Generic Type` 이해와 적용하기 </br></br>
- `Queue`의 자료구조의 이해 및 구현 </br>
    ☑️ `Linked List`를 이용한 `Queue` 타입 코드로 구현해보기 </br>
    ☑️ `Generic Type` 개념이해와 적용하기 </br>
    ☑️ 은행 고객(Customer)업무 처리를 Queue에 담아 차례대로 처리하기 </br></br>
- Enum을 이용한 namespace </br>
    ☑️ enum의 인스턴스생성이 불가능한 점을 이용해 `static let`으로 네임스페이스구현하기 </br>
    ☑️ String(format:)을 이용해 변수를 포함한 네임스페이스 구현하기 </br></br>
- 동시성 프로그래밍 이해 및 구현 (Concurrency Programming)</br>
    ☑️ GCD(Grand Central Dispatch)활용해 멀티 스레드 환경 구현하기</br>
    ☑️ 동기(Synchronous)와 비동기(Asynchronous) 동작의 이해와 적용</br>
    ☑️ Race Condition(경쟁 상황) 해결하기 - `SerialQueue` 활용 / `DispatchSemaphore`활용</br></br>
- POP (Protocol Object Programming)</br>
    ☑️ Protocol의 Extension에 기본구현(Defalult Implementation)하기</br>
    ☑️ Protocol 채택으로 특정 타입에 기능 부여하기</br> 

## 🚀 트러블 슈팅
### 1️⃣ `Bank`의 역할분리에 따른 `BankManager`타입 구현
- 기존에는 메뉴를 보여주고, 사용자 입력을 받는 기능과 손님을 만드는 기능을 `Bank`타입에서 수행하였습니다.
- 이 기능들은 `Bank`의 역할이 아니라고 생각되어 이를 관리하는 `BankManager`타입을 구현하였습니다.
- `BankManager`는 메뉴를 보여주고, 사용자입력에 따라서 `Bank`와 `Customer큐`를 생성하고 코드진행을 결정합니다.
    ```swift
    struct BankManager {
        mutating func startManagement() {
            //실행 매뉴얼 출력
            print(Constant.options, terminator: Constant.empty)
            //사용자 입력        
            guard let input = readLine() else { return }
            ...
        }
    }
    ```
### 2️⃣ `Bank`와 `Customer Queue`의 분리에 따른 각 인스턴스 생성 방법
- 기존에는 `Bank`타입 내에서 `Queue<Customer>`를 생성하여 자신의 프로퍼티에 전달하는 방식을 이용했습니다.
- 위와 같이 구현하면 `Customer`타입은 `Bank`인스턴스가 있을때만 존재한다는 문제가 있었습니다.
- 따라서 `BankManager`에 10~30명으로 이루어진 손님의 인스턴스를 생성하고, `bank`인스턴스에 전달하는 `addCustomer`메서드를 구현했습니다.
- 손님을 전달받은 `bank`는 자신이 가지고 있는 큐에 손님을 추가합니다.
    ```swift
    struct BankManager {
        private mutating func setupBank() {
            var bank = Bank() //bank인스턴스 생성
            
            addCustomer(to: &bank) //bank에 customer를 전달
            bank.openBank()
        }

        private mutating func addCustomer(to bank: inout Bank) {
        let customerCount = Int.random(in: Constant.customerRange)
        
        for count in 1...customerCount {
            let customer = Customer.init(waitingNumber: count)
            bank.receive(customer: customer)
            }
        }
    }
    ```
### 3️⃣ `bank`내부 메서드 접근제어 설정에 따른 내부 로직 변경
- 기존에는 은행의 작업을 시작하고 끝내는 메서드인 `startBanking`과 `closeBanking`를 타입 외부인 `BankManager`에서 `bank`를 생성한뒤 각각 호출했습니다.
- 두 메서드를 타입 외부에서 호출할 수 있는게 좋지 않은 방향이라고 생각했습니다. 게다가 작업이 끝났음을 알려주는 `closeBanking`메서드를 직접 호출하면 코드를 누락하는 휴먼이슈도 있다고 생각했습니다. 은행의 작업이 끝나면 자동으로 메서드가 호출될수 있는 방법을 고민했습니다.
- 위 두가지 고민을 해결하기 위해 `Bank`타입에 `isOpen`프로퍼티와 `openBank`메서드를 생성하고, `isOpen`프로퍼티에 프로퍼티 옵저버를 적용해 `didSet`값에 따라 은행이 문을 열면 `startBanking`, 마감하면 `closeBanking`이 호출되도록 수정하였습니다. 타입 외부에서는 `openBank`메서드만 호출합니다. </br></br>
- **코드 변경 내용** </br>
    ☑️ 수정 전 
    - 타입 외부에서 메서드 접근이 가능했습니다.
    ```swift
    struct BankManager {
        ...
        private mutating func setupBank() {
            var bank = Bank() 
            
            addCustomer(to: &bank) 
            bank.startBanking()
            bank.closeBanking()
        }
    }
    ```
    ✅ 수정 후
    - `isOpen`프로퍼티를 이용해 외부에서는 `OpenBank` 메서드로만 은행에 접근해도 은행의 업무는 자동으로 시작되고 마감합니다.
    ```swift
    struct BankManager {
        ...
        private mutating func setupBank() {
            var bank = Bank()

            addCustomer(to: &bank)
            bank.openBank()
        }
    }

    struct Bank {
        ...
        private var isOpen: Bool {
            didSet {
                isOpen ? startBanking() : closeBanking()
            }
        }

        private mutating func serveCustomer(number: Int) { ... }
        private func closeBanking() { ... }
    }
    ```
    
### 4️⃣ 네임스페이스에 변수를 포함하기 위한 `String(format:)`
- 네임스페이스를 생성하여 문자열을 묶어서 관리하려 할때, 특정 구문은 변수를 포함하고 있었습니다.
- 기존에는 클로저를 이용하여 원하는 변수를 전달했습니다.
- 리뷰어님의 조언으로 `String(format:, _:)`를 알게되어 적용했습니다.</br></br>
- **코드 변경 내용** </br>
    ☑️ 수정 전 
    - 클로저를 이용해 변수를 포함하는 네임스페이스입니다.
    ```swift
    enum Constant {
        static let startMessage = { ( number: Int ) -> String in
            return "\(number)번 고객 업무 시작"
        }
    }
 
    // 사용
    func serveCustomer(number: Int) {
        print(Constant.startMessage(number))
    } 
    ```
    ✅ 수정 후
    - `String(format:, _:)`을 이용해 변수를 포함하는 네임스페이스입니다.
    ```swift
    enum Constant {
        static let startMessage = "%d번 고객 업무 시작"
    }

    //사용
    private mutating func serveCustomer(number: Int) {
        print(String(format: Constant.startMessage, number))
    }
    ```
    
### 5️⃣ `RaceCondition`해결방법 -`DispatchSemaphore` / `serialQueue`
- `BankClerk` 3명이 `Queue`에 담긴 손님(업무)을 동시에 처리하게 하기위해서 `GCD`를 이용해 멀티쓰레드환경을 구현해주었습니다. 
- 이 과정 중에 하나의 데이터(`CustomersQueue`)에 동시에 접근하여 생기는 `RaceCondition`을 해결하기 위해서 여러가지 시도를 해보았습니다.</br></br>
- ✅ 👉 **`BankClerk`프로퍼티에 `serialQueue`사용**
    - RaceCondition을 해결하는 방법 중 하나인 `Async`상황에서 `SerialQueue`를 이용해 해결하는 방법을 적용했습니다.
    - 3명의 은행원들이 각자의 은행업무에 맞는 손님을 줄세워 차례대로 응대하기 위해 은행원의 프로퍼티로 본인의 작업대를 의미하는 `counter`를 추가하여 `DispatchQueue`로 만들었습니다. 
    - 이 때, 은행원은 한번에 한명의 손님을 처리할 수 있도록 `serial`큐로 초기화합니다.
    - Bank는 은행원들에게 `Async`하게 손님들을 배분 해 여러명의 은행원들이 동시에 고객을 처리하도록 합니다.
    - 은행원들은 본인의 `counter`에 줄서있는 직원을 한명씩 처리하는데 이 작업을 세명이 동시에 진행합니다.
        ```swift
         while !customers.isEmpty {
            bankClerks.forEach { bankClerk in
                guard let customer = customers.dequeue() else { return }

                bankClerk.serve(customer: customer, group: group)
                ...
            }
        }

        func serve(customer: Customer, group: DispatchGroup) {
            counter.async(group: group) { //counter는 자신의 시리얼 큐입니다.
                //업무 시작 및 종료 출력
            }
        }
        ```
- **☑️ 👉 `DispatchSemaphore`사용**
    <details>
        <summary>details</summary>

    - 동시에 처리할 수 있는 손님이 은행원 인원과 같기 때문에 `쓰레드 수 = 은행원 인원` 이라고 생각했습니다. 
    - `DispatchSemaphore`의 `value = 은행원 인원` 으로 지정하여 은행원 수만큼만 동시진행 될 수 있도록 구현했었습니다.
        ```swift
        struct Bank {
            let cumstomers: Queue<Customer> = Queue()
            let depositBankeClerks: [DopositBankClerk]// 2명
            let loanBankClerks: [LoanBankClerk]  // 1명 

            private mutating func startBanking() {
                let depositSemaphore = DispatchSemaphore(value: depositBankeClerks.count) // 2
                let loanSemaphore = DispatchSemaphore(value: loanBankClerks.count) // 1
                
                while !customers.isEmpty {
                    guard let customer = customers.dequeue() else {
                        return
                    }

                    DispatchQueue.global().async { [self] in
                        switch customer.bankingType {
                        case .deposit :
                            depositSemaphore.wait()
                         👉 DepositBankClerk.serveCustomer(number: customer.waitingNumber)  //손님 업무 시작과 끝프린트 출력하는 함수
                            depositSemaphore.signal()
                        case .loan:
                            loanSemaphore.wait()
                         👉 LoanBankClerk.serveCustomer(number: customer.waitingNumber) //손님 업무 시작과 끝프린트 출력하는 함수
                            loanSemaphore.signal()
                        }
                    }
                }
            }
        }

        struct DepositBankClerk {
            static func serveCustomer(number: Int) {
                // 손님 업무 시작과 끝을 Print하는 코드
            }
        }
        ```
    - 😵채택하지 않은 이유😵
        - 1) BankClerk의 인스턴스를 활용하지 않기 때문이었습니다.
            - BankClerk타입을 구현해놓고, `serveCustomer(number:)`로 손님을 처리하는 함수를 사용할 때, 은행원 각자의 인스턴스 메서드를 사용하지 못해서 의도한 방향과 다르다고 생각했습니다.
            - BankClerk의 인스턴스 메서드 대신 타입메서드를 사용하거나, 처리속도가 같기 때문에 BankClerk의 한명의 인스턴스 메서드를 이용해 구현 할 수 있지만, 추후에 예를들어, 같은 예금처리를 하는 BankClerk두 명의 처리속도가 서로 달라진다고 할 때 적용이 어려워지기 때문에 BankClerk의 각자의 인스턴스가 처리하는게 맞다고 생각했습니다.
        - 2) 1)의 문제로 애초에 BankClerk 타입 구현을 하지않고 (인스턴스를 생성할 일이 없게 되고), Bank 내부에서 각 은행원의 인원수를 Int로 갖는 프로퍼티를 구현하는 방법도 생각했었습니다. 하지만 객체지향 프로그래밍과 맞지 않다고 생각했습니다. </br></br>  
</details>

- **☑️ 👉 `serialQueue`사용 & `customers Queue` 분류없이 직접 처리하기**
    <details>
        <summary>details</summary>
    
    - 채택한 코드에서는 손님이 담겨있는 `Queue`를 은행업무 종류에 따라 `depositQueue`와 `loanQueue`로 다시 분류해서 `Queue`에 담아준 후 타입이 같은 은행원이 손님처리를 하는 로직인데 손님분류 없이 손님의 은행업무타입이 섞여있는 원래의 `Queue`에서 바로 손님처리를 할 수 있는 코드를 구현하고 싶었습니다. 
    - 처음엔 `bankClerk` 이 담긴 `Array`에 `forEach`를 사용해 업무타입을 `Switch`문으로 구분해서 손님을 처리하도록 구현해주었었습니다. 예금은행원 두명이 공평하게 반씩 차례로 손님을 처리하지 않고 중간에 대출손님이 껴있으면 순서가 꼬이는 문제가 있었습니다. 그래서 순서에맞게 손님을 처리하기 위해 `bankClerk Array`에서 한 은행원씩 차례로 불러줘야했습니다. 0부터 +1씩 올라가는 변수에 은행직원 인원보다 커지면 다시 0으로 돌아가도록 설정한 변수를 선언하고 `Array index`로 사용해 은행원이 번갈아가며 손님을 처리할 수 있도록 구현했었습니다.
        ```swift
        func serveCustomer() {
            let depositMaximun = depositBankClerk.count - 1
            let loanMaximum = loanBankClerk.count - 1
            var depositPossible: Int = 0 {
                didSet(oldValue) {
                    depositPossible = oldValue > depositMaximun ? 0 : oldValue
                }
            }
            var loanPossible: Int = 0 {
                didSet(oldValue) {
                    loanPossible = oldValue > loanMaximum ? 0 : oldValue
                }
            }
            
            while !customers.isEmpty {
                guard let customer = customers.dequeue() else {
                    return
                }

                switch customer.bakingType {
                case .deposit:
                    depositBankClerk[depositPossible].call(customer: customer)
                    depositPossible += 1
                case .loan:
                    loanBankClerk[loanPossible].call(customer: customer)
                    loanPossible += 1
                }
            }
        }

        struct banker {
            func call(customer: Customer) {
                counter.async(group: group) {
                    // 손님 업무 시작과 끝을 Print하는 코드
                }
            }
        }
        ```
    - 😵채택하지 않은 이유😵
        - 이 시도는 `customers Queue`를 `BankingType`별로 분류해 다시 두개의 `Queue`(예금별, 대출별)를 생성하는 작업을 하지 않기 위해 시도해본 코드였는데, 작성 후에 비교해보니 `Queue`를 분류해서 처리하 코드가 더 깔끔하고 보기 좋은 것 같다고 판단해서 채택하지 않았습니다.  
    </details>


### 6️⃣ `Servable`프로토콜과 익스텐션의 활용(POP)
- 은행원의 업무를 `손님을 응대할 수 있는`기능이라 판단했고, 이 기능이 코드를 작성하는 중간에 재사용되기도 하여 프로토콜로 분리하였고, `extension`을 통해 기본 구현을 추가했습니다. 이 프로토콜 내에는 은행원이 손님을 응대하는 과정에 필요하다고 생각한 요소들을 포함하였습니다.
- `counter: DispatchQueue`: 은행 창구를 나타내면서, 코드 실행중에는 비동기로 작업을 처리하기 위한 요소입니다. 은행원은 한번에 한명의 손님을 처리해야 하고, 순서가 중요하기 때문에 해당 큐를 `serial`로 초기화 하였습니다.
- `serve(customer:, group:)`: 매개변수로 받은 손님을 자신의 시리얼 큐로 비동기 처리한 후, 고객과 업무종류를 출력하고, 처리하는 기능을 합니다.
    ```swift
    protocol Servable {
        var processingTime: Double { get }
        var counter: DispatchQueue { get }
        func serve(customer: Customer, group: DispatchGroup)
    }

    extension Servable {
        func serve(customer: Customer, group: DispatchGroup) {
            counter.async(group: group) {
                print("\(customer.waitingNumber)번 고객 \(customer.bankingType.name)업무 시작")
                ...
            }
        }
    }
    ```

### 7️⃣ `Command Line Tool`에서 `main`스레드의 런루프가 종료되는 문제
- 프로젝트를 실행할때, `DispatchQueue.main.async`가 작동되지 않고, 다른 스레드의 비동기 작업도 끝까지 기다리지 않고 프로그램이 종료되는 문제가 있었습니다. 찾아보니 `iOS`에서는 `main run loop`를 자동으로 설정해 주지만, `macOS`의 `CLT`환경에서는 그렇지 않아서 코드가 끝나기 전에 종료되었습니다.
- 이를 해결하기 위해 처음에는 `Runloop.main.run(until:)`메서드를 활용했지만, 매개변수에 필요한 시간값이 미리 계산할 수 없는 문제가 있었습니다.
- 최종적으로 사용한 코드는 기다려야 하는 작업들을 `DispatchGroup`으로 묶어서 해당 그룹을 기다리는 `group.wait()`메서드를 이용해서 의도하는 결과를 출력할 수 있었습니다.
    ```swift
    let group = DispatchGroup()

    matchClerk(to: &customers.deposit, of: .deposit, group: group)
    matchClerk(to: &customers.loan, of: .loan, group: group)
    group.wait()
    ```

### 8️⃣ 은행 업무시간을 계산하기 위한 `Date()`와 `timeIntervalSinceNow`
- 은행 업무에 소요된 시간을 계산해야 했습니다. 고민한 부분은 "업무 시작 시간을 언제로 볼것인가" 였습니다. `Bank`의 인스턴스를 만드는 시점에 초기화할 수도 있지만, 저희가 구현한 기능 중 손님을 외부에서 받는 `receive(customer)`메서드와 손님을 업무종류에 따라 분류하는 `sortCustomer()`메서드 중간이 업무 시작시간으로 적합하다 판단하여 해당 시점을 기준으로 합니다. 
- 총 업무 시간은 `DispatchGroup`으로 지정한 업무들이 종료되는 것을 기다리는 `group.wait()`이후에 `Date`의 `timeIntervalSinceNow`프로퍼티를 이용하여 계산하였습니다.

### 9️⃣ `BankingType`을 랜덤으로 생성하는 방법
- 손님마다 은행업무 종류를 랜덤하게 생성하기 위한 방법으로 처음에는 `rawValue`와 `BankingType(rawValue: Int.random(1...2)`를 이용하여 구현하였습니다.
- `rawValue`를 이용하지 않는 방법을 고민하다가 `CaseIterable`의 `allCases`를 사용했습니다. </br></br>
- **코드 변경 내용** </br>
    ☑️ 수정 전 
    - `rawValue`를 이용한 방법입니다.
    ```swift
    for count in 1...customerCount {
        guard let bankingType = BankingType.init(rawValue: Int.random(in: 1...2)) else { return }

        let customer = Customer.init(waitingNumber: count, bankingType: bankingType)
        bank.receive(customer: customer)
    }
    ```
    ✅ 수정 후
    - `CaseIterable`의 `allCases`와 `randomElement()`를 이용한 방법입니다.
    ```swift
    for count in 1...customerCount {
        guard let bankingType = BankingType.allCases.randomElement() else { return }
            
        let customer = Customer.init(waitingNumber: count, bankingType: bankingType)
        bank.receive(customer: customer)
    }
    ```

## 🔗 참고 링크

[공식문서]
- [Generics](https://docs.swift.org/swift-book/LanguageGuide/Generics.html)
- [Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html)
- [Protocol Oriented Programming](https://developer.apple.com/videos/play/wwdc2015/408/)
- [sleep(forTimeInterval:)](https://developer.apple.com/documentation/foundation/thread/1413673-sleep)
- [Date](https://developer.apple.com/documentation/foundation/date)
- [timeIntervalSinceNow](https://developer.apple.com/documentation/foundation/date/1780473-timeintervalsincenow)
- [Concurrent Programming With GCD in Swift 3](https://developer.apple.com/videos/play/wwdc2016/720/)

[그 외 참고문서]
- [Thread.sleep and DispatchQueue.asyncAfter](https://www.advancedswift.com/delay-function-sleep-thread-swift/)
- [블로그-String(format:)](https://beepeach.tistory.com/53) 
- [블로그(날진)-[iOS] 차근차근 시작하는 GCD — 1~17](https://sujinnaljin.medium.com/ios-%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-gcd-grand-dispatch-queue-1-397db16d0305)
- [야곰닷넷 동시성 프로그래밍](https://yagom.net/courses/%eb%8f%99%ec%8b%9c%ec%84%b1-%ed%94%84%eb%a1%9c%ea%b7%b8%eb%9e%98%eb%b0%8d-concurrency-programming)
- [코드스쿼드-조성규POP 특강](https://youtu.be/9gkzHUsQiUc)

