# 은행 창구 매니저🏦 ## 목차 1. [소개](#1-소개) 2. [팀원](#2-팀원) 3. [타임라인](#3-타임라인) 4. [프로젝트 구조](#4-프로젝트-구조) 5. [실행화면(기능 설명)](#5-실행-화면기능-설명) 6. [트러블슈팅](#6-트러블-슈팅) 7. [참고링크](#7-참고-링크) <br/> ## 1. 소개 은행에서 1명의 은행원이 10-30명 사이의 고객의 업무를 1:1로 처리해주는 콘솔앱입니다. 은행원은 두 명의 예금 담당 은행원과 한 명의 대출 담당 은행원이 존재합니다. 고객을 생성할 때 고객이 볼 업무의 종류가 정해지며, 그에 따라서 은행원들이 예금과 대출 업무를 처리하는 방식입니다. <br/> ## 2. 팀원 |⭐️Rhode| ⭐️Sehong | | :--------: | :-------: | |<img height="210px" src="https://i.imgur.com/XyDwGwe.jpg">| <img height="210px" src="https://i.imgur.com/64dvDJl.jpg"> | | Navigator / Driver | Navigator / Driver | </br> ## 3. 타임라인 ### 프로젝트 진행 기간 **23.03.06 (월) ~ 23.03.17 (금)** |날짜|스텝| 타임라인 | | :-------: | :-------: | ------- | |03.06 (월) | STEP1 | BankQueue 구현 및 BankQueueTest 구현 | |03.07 (화) | STEP1 | BankQueue 리팩토링 | |03.08 (수) | STEP2 | Bank, Client, BankManager 구현 | |03.09 (목) | STEP2 | - | |03.10 (금) | STEP2 | Bank, Client, BankManager 리팩토링 | |03.13 (월) | STEP3 | Bank, Client, BankManager 리팩토링 및 clientWaitingLine 클래스 구현 | |03.14 (화) | STEP3 | Bank 리팩토링 | |03.15 (수) | STEP2 | - | |03.16 (목) | STEP2 | - | |03.17 (금) | STEP2 | - | <br/> ## 4. 프로젝트 구조 ### 폴더 구조 ```` BankManagerConsoleApp │   ├── Bank.swift │   ├── Client.swift │   ├── ClientWaitingLine.swift │   ├── Queue │   │   ├── LinkedList.swift │   │   ├── Node.swift │   │   └── Queue.swift │   └── main.swift └── QueueTest └── QueueTest.swift ```` ### 클래스 다이어그램 ![](https://i.imgur.com/UwatlYH.png) <br/> ## 5. 실행 화면(기능 설명) | Invalid Input | Input 1 | Input 2 | | :--------: | :--------: | :--------: | | ![](https://i.imgur.com/3bERhPM.gif) | ![](https://i.imgur.com/gvtOjTE.gif) | ![](https://i.imgur.com/r0D61ll.gif) | | 1, 2 외의 것을 입력하게 되면 '입력이 잘못되었습니다.' 라는 문구와 함께 다시 입력창이 나타납니다. | 1을 입력하면 10부터 30까지 중 랜덤한 숫자의 고객들이 들어오고 비동기적으로 그들의 업무를 처리하게 됩니다. 업무가 마감될 때 총 처리한 고객의 수와 처리에 걸린 시간이 나타납니다. | 2를 입력하면 프로그램이 종료됩니다. | </br> ## 6. 트러블 슈팅 ### 1. DispatchQueue.global().async가 실행되지 않았던 문제 현재의 코드는 다음과 같습니다: ```swift private mutating func distributeClient(bankManagerCount: Int) { var clientList = managingClientQueue() let bankManager = BankManager() let group = DispatchGroup() let semaphore = DispatchSemaphore(value: 1) for _ in Int.zero..<bankManagerCount { DispatchQueue.global().async(group: group) { semaphore.wait() while !clientList.isEmpty { guard let client = clientList.dequeue()?.clientWaitingNumber else { return } bankManager.work(client: client) semaphore.signal() } } } group.wait() } ``` 원래는 코드에 `group.wait()`이 없었습니다. 그래서 `DispatchQueue.global().async(group: group)` 내부 코드가 실행되지 않았습니다. 비동기이기 때문에 업무를 던져주고 결과까지 기다리지 않는다는 것을 간과했었습니다. 그래서 `group.wait()` 코드를 삽입해주었습니다. 그 결과 해당 그룹의 모든 작업이 완료때까지 현재 스레드를 block 시킬 수 있었습니다. `wait()`의 정의는 다음과 같습니다: ![](https://i.imgur.com/4Q4xCqb.png) 이와 비슷한 기능으로는 `notify(queue:)`가 있는 것으로 알고 있습니다. `notify(queue:)`는 그룹으로 묶인 모든 작업이 끝났을 때 실행될 작업을 넘겨줍니다. ### 2. 함수 실행 시간을 계산하는 workTime 메서드 구현 은행원 n명이 단 하나의 업무를 수행하고 완료하는데 걸리는 시간은 0.7초 입니다. 해당 부분을 구현하기 위해서 단순히 작업 하나(손님 1명) 에 0.7을 곱하는 단순 계산을 선택하는 방식과 실제 작업시간을 계산하는 방식 중 어떤것을 사용할지 고민하였습니다. 아래의 workTime 함수는 workTimeFunction이라는 매개변수를 가지는 클로저를 입력 받고, 해당 함수는 어떤 작업을 수행하는 함수를 의미합니다. 함수 내부에서는 먼저 작업 시작시간을 저장하고, 클로저를 호출하여 수행한 뒤 작업이 종료 되는 시간을 저장하고 있습니다. 이후 시작시간과 종료시간의 시간 차를 timeIntervalSince 메서드가 초단위로 계산하여 workTime을 반환해줍니다. ```swift private func workTime(workTimeFunction: () -> Void) -> TimeInterval { let startTime = Date() workTimeFunction() let endTime = Date() let workTime = endTime.timeIntervalSince(startTime) return workTime } ``` ### 3. 3명의 은행원이 동시에 업무를 처리하기 은행에는 3명의 은행원이 근무하고, 2명은 예금업무를 1명은 대출업무를 처리합니다. 저희는 은행원들이 일을 동시에 할 수 있도록 커스텀큐를 생성해주었습니다. 커스텀큐의 기본적인 설정은 Serial이기때문에 대출업무는 attributes를 설정해주지 않았고, 예금업무는 2명이서 동시적으로 업무를 보고 있기때문에 concurrent로 설정하고 semaphore의 value를 2로 설정하여 예금 업무를 담당하는 은행원 2명만이 동시적으로 일을 할 수 있도록 구현하였습니다. ```swift private let depositSemaphore = DispatchSemaphore(value: 2) private let depositQueue = DispatchQueue(label: "loan", attributes: .concurrent) private let loanQueue = DispatchQueue(label: "deposit") ``` ```swift private mutating func distributeClient() { let group = DispatchGroup() var clientQueue = clientWaitingLine.manageClientQueue() let bankManager = BankManager() while let client = clientQueue.dequeue() { switch client.banking { case .deposit: depositQueue.async(group: group) { [self] in depositSemaphore.wait() bankManager.work(client: client) depositSemaphore.signal() } case .loan: loanQueue.async(group: group) { bankManager.work(client: client) } } } group.wait() } ``` ### 4. BankingType으로 ClientQueue생성하기 저희는 처음에 Client를 생성할때 숫자를 랜덤으로 뽑아서 예금고객과 대출고객 번호를 각각 생성해주었습니다. 리뷰어의 조언을 받고 해당 부분은 예금과 대출이라는 banking 타입을 랜덤으로 고객을 생성하는 방식으로 변경해주었습니다. ```swift func manageClientQueue() -> Queue<Client> { var clientQueue = Queue<Client>() clientCount = Int.random(in: 10...30) for i in 1...clientCount { Client.Banking.allCases.randomElement().map { clientQueue.enqueue(Client(clientWaitingNumber: i, banking: $0)) } } return clientQueue } ``` ## 7. 참고 링크 > - [야곰닷넷 - 동시성프로그래밍](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/) > - [WWDC 2015 Protocol - Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2016/720/) # 팀 회고 ### 우리팀이 잘한 점 - 서로 의견을 존중하며 상대방을 배려함 - 다양한 방법을 시도해보고자 함 - 고민이 생겼을 때 둘이서 고민하다가도 답이 나오지 않으면 주변의 도움을 적극적으로 받는 태도를 가졌음 - 적절한 휴식을 취했음 ### 팀원 서로 칭찬하기 Sehong -> Rhode : 하나부터 열까지 천천히 꼼꼼하게 생각한대로 지금까지 배운 부분을 활용하여 구현하려고 하는 모습이 인상깊었습니다. 저는 부족한 부분이 많아서 구현하는데 어려움을 느꼈는데 그럴때마다 로데가 해당 부분에 대한 개념을 설명해주고 도와줘서 이번 프로젝트를 잘 따라올 수 있었습니다. 또한 제가 그냥 대충 이해하고 넘어가려는 부분도 하나하나 꼼꼼하게 짚고 넘어가주셔서 저 또한 꼼꼼하게 한번 더 짚고 넘어갈 수 있었고 이런것들이 쌓여서 이번 프로젝트 기간동안 많이 배울 수 있었습니다. Rhode -> Sehong: sehong은 정말 열심히하는 캠퍼입니다. 준비성이 철저해서 매번 모이기 전에 코드에 대한 준비를 많이 해오십니다. sehong에겐 남을 살피려는 마음이 있고, 모르는 게 있으면 공부하고 넘어가려는 끈기도 있습니다. 그리고 코드를 직관적으로 논리정연하게 짤 수 있는 능력도 있습니다. 한 마디로, sehong은 누구보다 열심히 하는 캠퍼라고 볼 수 있습니다.