# 🏦은행창구 매니저 - Jusbug, MINT ![](https://hackmd.io/_uploads/r1JEzJFt2.png) ## Ground Rules ### 규칙 - TIL, 일일 회고 작성 시간(매일 23시부터 1시간 작성 진행) - 월, 목 10 - 2시는 활동학습 예습 시간 ### 스크럼 - 오전 10시 디스코드에서 진행 - 금일 진행 사항 공유하기(오늘의 할일) ### 프로젝트 규칙 - 네이밍 준수하기(가이드 라인) - 커밋 메시지 규칙 진행 - 코드에 대한 기록 그때 그때 하기 - 중간중간 리드미 작성 ### 팀 규칙 - 컨디션이 좋지 않을 때는 꼭 말해주기!! ## 일일 스크럼 ### 🙌 07/10 월 - 오늘의 컨디션 - MINT😈: 아주 나빴어요.. - Jusbug🕷️: 아주 좋았어요👍 - 특이사항 - MINT😈: 병원 다녀왔어요ㅜ - Jusbug🕷️: 없습니다. (실토하시죠?) - 오늘 할 일 - [x] Linked List Queue 구현 - [x] Unit Tests 메서드 구현 - [x] STEP 1 PR 작성 ### 🙌 07/11 화 - 오늘의 컨디션 - MINT😈: 많이 자서 좋아요~ - Jusbug🕷️: 병원 다녀왔어요🏥 - 특이사항 - MINT😈: 낮잠 민트 - Jusbug🕷️: 점심 때 병원 - 오늘 할 일 - [x] 코멘트 달리면 수정 - [x] 동시성 공부 ### 🙌 07/12 수 - 오늘의 컨디션 - MINT😈: 쏘쏘 - Jusbug🕷️: 꿀꿀 - 특이사항 - MINT😈: 😕 - Jusbug🕷️: 민트가 마크다운 꿀팁 알려줌 - 오늘 할 일 - [x] STEP 1 수정 - [x] 동시성 공부 ### 🙌 07/13 목 - 오늘의 컨디션 - MINT😈: 아주 좋아요~! - Jusbug🕷️: 낫벧 - 특이사항 - MINT😈: 짝꿍이 지각쟁이😝 - Jusbug🕷️: 컨디션이 안좋아유 - 오늘 할 일 - [x] STEP 2 - [x] STEP 2 PR - [x] 활동학습 예습 ### 🙌 07/14 금 - 오늘의 컨디션 - MINT😈: 졸려요오 - Jusbug🕷️: 안좋아유 - 특이사항 - MINT😈: 점심 약속 - Jusbug🕷️: 나도 소고기 - 오늘 할 일 - [x] README 작성 - [x] 동시성 공부 ### 🙌 07/17 월 - 오늘의 컨디션 - MINT😈: 해피~ - Jusbug🕷️: - 특이사항 - MINT😈: 찜질방에서 계란 먹고, 매실 먹고, 핫도그 먹고, 만두 먹고, 식혜 먹고~! 해피~ - Jusbug🕷️: 새벽까지 디코 - 오늘 할 일 - [x] step 3 구현 - [x] step 3 PR ### 🙌 07/18 화 : KWDC 오프라인 모각코 - 오늘의 컨디션 - MINT😈: 여독 - Jusbug🕷️: 더워 죽는줄 - 특이사항 - MINT😈: 옴뇸뇸 점심 최악 - Jusbug🕷️: 야곰 캠퍼들과의 조우 및 사진촬영(feat.대왕구슬아이스크림) - 오늘 할 일 - [ ] ### 🙌 07/19 수 - 오늘의 컨디션 - MINT😈: 여독 - Jusbug🕷️: 낫밷 - 특이사항 - MINT😈: 기절 민트 - Jusbug🕷️: 생일 전날 - 오늘 할 일 - [x] step 3 코멘트 따라 수정 ### 🙌 07/20 목 - 오늘의 컨디션 - MINT😈: 헤롱헤롱 - Jusbug🕷️: 행복행복 - 특이사항 - MINT😈: 생일 축하합니다~! - Jusbug🕷️: 생일 당일 - 오늘 할 일 - [x] 저스버그 생일 축하 - [ ] step 4 ui 구현 ### 🙌 07/21 금 - 오늘의 컨디션 - MINT😈: 조금 졸린듯? - Jusbug🕷️: 아주 좋습니다 - 특이사항 - MINT😈: 아이셔 츄 맛있어요 - Jusbug🕷️: 새우깡 맛있어요 - 오늘 할 일 - [x] README 작성 # 🏦은행창구 매니저 - Jusbug🕷️, MINT😈 > 프로젝트 기간 23/07/10 ~ 23/07/21 ## 📖 목차 🍀 1. [소개](#-소개) <br> 👨‍💻 2. [팀원](#-팀원) <br> 🕰️ 3. [타임라인](#-타임라인) <br> 👀 4. [시각화된 프로젝트 구조](#-시각화된-프로젝트-구조) <br> 💻 5. [실행 화면](#-실행-화면) <br> 🧨 6. [트러블 슈팅](#-트러블-슈팅) <br> 📚 7. [참고 링크](#-참고-링크) <br> 👥 8. [팀 회고](#-팀-회고) <br> </br> ## 🍀 소개 민트와 저스버그가 만든 은행창구 매니저 앱.🏦 10명에서 30명 사이의 랜덤한 고객들이 예금과 대출 업무를 처리받을 수 있습니다. </br> ## 👨‍💻 팀원 | 🕷️Jusbug🕷️ | 😈MINT😈 | | :--------: | :--------: | | <Img src = "https://hackmd.io/_uploads/BJjF9oD53.jpg" width="200" height="200"> | <Img src = "https://hackmd.io/_uploads/Hy_v5sD53.jpg" width="200" height="200"> | |[Github Profile](https://github.com/JusBug) |[Github Profile](https://github.com/mint3382) | </br> ## ⏱️ 타임라인 |날짜|내용| |:--:|--| |2023.07.10.| - Linked List Queue 구현 <br> - Unit Test 구현 | |2023.07.11.| - concurrency 공부 | |2023.07.12.| - Linked List와 Queue 분리 | |2023.07.13.| - Bank, ProgramManager, Customer, Teller 구현 | |2023.07.14.| - concurrency 공부 | |2023.07.17.| - mutating, dispatchQueue 공부 <br> - Bank에 DispatchQueue 사용 | |2023.07.18.| - KWDC 참여 | |2023.07.19.| - DispatchGroup 공부, 활용 <br> - 네이밍 및 컨벤션 수정| |2023.07.20.| - ui 구현 공부 | |2023.07.21.| - ui 구현 <br> - autolayout 공부 | </br> ## 👀 시각화된 프로젝트 구조 ### ℹ️ File Tree ```` [BankManager] ├── BankManagerConsoleApp │ ├── main │ ├── Bank │ │ ├── BankManager │ │ ├── Manageable │ │ ├── ProgramManager │ │ ├── Bank │ │ ├── Customer │ │ └── BankTask │ └── Queue │ ├── Queue │ ├── LinkedList │ └── Node │ | └── BankManagerUIApp ├── Resource │ ├── Info │ └── Assets ├── Model │ ├── BankManager │ ├── Node │ ├── LinkedList │ ├── Queue │ └── Queueable ├── View │ ├── Main │ └── LaunchSreen └── Controller ├── AppDelegate ├── SceneDelegate └── ViewController ```` ### 📐 Diagram <p align="center"> ![](https://hackmd.io/_uploads/B1jwRswc2.png) </br> ## 💻 실행 화면 | 1. 개점 | 2. 종료 | |:--------:|:--------:| |<img src="https://hackmd.io/_uploads/BJxV_sPqh.gif" width="300">|<img src="https://hackmd.io/_uploads/H18EOjvc2.gif" width="300">|<img src="" width="250">| | 화면 | 화면 | 화면 | |:--------:|:--------:|:--------:| |<img src="" width="250">|<img src="" width="250">|<img src="" width="250">| </br> ## 🧨 트러블 슈팅 ###### 핵심 트러블 슈팅위주로 작성하였습니다. 1️⃣ **부동소수점 오류, 소수점 표현** <br> - 🔒 **문제점🧐** <br> totalTime을 Double로 주고 더하다 보니 부동소수점 오류로 인해 0.7에 30번 더했을 때 20.9999999가 출력되는 문제가 있었습니다. 또한 예제를 보니 7.00과 같이 소수점 두번째 자리까지 표현되어 있었습니다. 🔑 **해결방법😀** <br> String Format을 사용하여 해결했습니다. NumberFormatter와 String Format 둘 중 고민하였는데 출력하는 부분에서만 사용하기에 String Format을 사용하였습니다. ```swift private func closeBank() { print("업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 \(customerNumber)명이며, 총 업무시간은 \(String(format: "%.2f", totalTime))초입니다.") } ``` <br> 2️⃣ **Thread.sleep(forTimeInterval: )** <br> - 🔒 **문제점🧐** <br> 직원이 고객 1명의 업무를 처리하는데 소요되는 시간은 0.7초인데 실제로 콘솔로 실행했을 때에도 해당 시간만큼 시간이 소요되며 진행 결과를 출력해야 하는지 아니면 업무가 마감되었을 때에만 총 업무시간을 확인하면 되는지 고민하게 되었습니다. 🔑 **해결방법😀** <br> 실제로도 시간이 소요되는 것처럼 보여져야 된다고 생각하여 Thread.sleep를 이용하여 지정된 시간만큼 스레드 동작에 제한을 두어서 해결하였습니다. ```Swift func processCustomer(_ customer: Customer) { print("\(customer.numberTicket)번 고객 업무 시작") Thread.sleep(forTimeInterval: 0.7) print("\(customer.numberTicket)번 고객 업무 완료") } ``` <br> 3️⃣ **DispatchQueue, teller** <br> - 🔒 **문제점** <br> Teller 타입에서 해당 Teller가 처리하는 BankTask에 따라 동시에 업무를 진행하는 숫자가 달라집니다. 즉 각 Task 창구 당 Teller의 수가 작업을 처리하는 쓰레드의 수로 이해했습니다. 때문에 Teller 타입에서 어떻게 하나씩의 쓰레드를 가지고 있게 할 수 있을지 고민하였습니다. 🔑 **해결방법** <br> Teller 타입을 삭제하고 딕셔너리를 통해 각 BankTask key 값에 따라 value 값으로 각 task 창구 당 일하는 Teller의 숫자를 입력받아 for 문을 통해 직원의 수 만큼 쓰레드를 생성하도록 하였습니다. ```swift private func operateWindow(task: BankTask) { guard let tellerCount = tellers[task], let line = line[task] else { return } for _ in 1...tellerCount { DispatchQueue.global().async(group: group) { self.assignCustomerTask(line: line) } } } ``` <br> 4️⃣ **DispatchSemaphore** <br> - 🔒 **문제점** <br> DispatchQueue를 통해 쓰레드를 만들어 Teller의 수만큼 각 line의 손님에게 접근할 수 있습니다. 이때 동시다발적으로 같은 line에 접근할 수 있으므로 같은 손님을 가져다가 처리하는 race condition이 일어날 수 있는, Thread Unsafe한 상태가 만들어져 있었습니다. 🔑 **해결방법** <br> DispatchSemaphore를 활용하여 해결하였습니다. 동시에 접근할 수 있는 쓰레드의 개수를 1개로 지정하여 동시에 두개가 해당 line에 접근할 수 없게 보호해 주었습니다. ```swift counter.wait() guard let customer = line.dequeue() else { counter.signal() return } counter.signal() ``` 5️⃣ **DispatchGroup.wait() 대신 notify()** <br> - 🔒 **문제점** <br> 은행창구 매니저에서 업무를 처리한 후 마지막에 은행 마감 선언을 하는 부분에서 문제가 생겼습니다. 모든 업무가 다 처리된 후에 마감 처리가 들어가야 하는데 비동기로 하다 보니 마감이 중간에 들어가게 된 것으로 이를 해결하기 위해 DispatchQueue.Group을 사용하여 업무 처리하는 비동기들을 전부 넣어 주었습니다. dispatch Group은 여러 스레드로 분배된 작업들이 끝나는 시점을 각각 파악하는 것이 아니라, 하나로 그룹지어서 한번에 파악하게 하는 것입니다. 그리고 group.wait()을 사용하면 비동기 작업들이 전부 끝나는 시점까지 현재 스레드를 멈춰서 기다리게 할 수 있습니다. 이를 사용하여 멈추고, 그 후에 close()가 불릴 수 있게 하였습니다. 그러나 group.wait()의 wait() 키워드는 상당히 위험한 키워드라는 피드백을 받았습니다. 무작정 스레드를 기다리게 하기 때문에 멈춰있는 시간동안 앱이 멈추게 되기에 main에서는 사용하면 안됩니다. 또한 그룹 내의 작업이 wait을 통해서 멈춰놓는 스레드에서 일어나는 작업이라면 Deadlock에 빠지게 됩니다. 때문에 notify()를 사용하였는데 Bank 내부에서는 순서가 제대로 지켜져서 업무가 다 끝난 후 close() 내용이 호출되지만 외부인 ProgramManager에서는 순서가 지켜지지 않았습니다.즉 programManager에서 open()에 있는 작업들이 전부 다른 스레드로 넘어가 작업되니 그 전에 미리 다시 while문이 돌아 displayMenu()가 제일 먼저 출력되어버린 것입니다. 🔑 **해결방법** <br> Serial Queue를 이용하는 방법과 group을 밖으로 빼는 방법이 있었습니다. - 🔥Serial Queue custom serial queue를 하나 만들어서 그 안에서 처리가 이뤄지도록 했습니다. 다만 이 경우 처리가 조금 복잡해졌는데 Program Manager가 Bank 타입이 아닌 Manageable protocol을 알고 있기 때문에 protocol에도 serial queue와 dispatchGroup이 선언되어 있어야 했습니다. ```swift protocol Manageable { var name: String { get } var group: DispatchGroup { get } var orderQueue: DispatchQueue { get } func open() } func open() { giveTicketNumber(numbers: customerNumber) operateWindow(task: .deposit) operateWindow(task: .loan) group.notify(queue: orderQueue) { self.close() } } func selectMenu() { displayMenu() while isWorking { guard let input = readLine() else { return } switch input { case Menu.startProgram.number: program.open() program.group.notify(queue: program.orderQueue) { self.displayMenu() } case Menu.finishProgram.number: isWorking = false default: print("잘못된 입력입니다. 다시 입력해주세요.") } } } ``` 그러나 이 방법의 경우 program의 프로퍼티로 들어가서 그 프로퍼티에서 메서드를 사용하는, 객체 지향 패러다임에서는 지양하는 모양의 구조로 쓰이게 됩니다. 또한 불필요했던 프로퍼티 (queue)가 하나 더 만들어졌고 기존에 private 였던 프로퍼티(group)도 열어야 했습니다. - 🔥.global()과 extension protocol func 때문에 최대한 프로퍼티를 타고 들어가지 않기 위해 protocol manageable에서 extension으로 메서드를 구현해주었습니다. ```swift extension Manageable { func sortNext(to: @escaping () -> Void) { group.notify(queue: .global()) { to() } } } func selectMenu() { displayMenu() while isWorking { guard let input = readLine() else { return } switch input { case Menu.startProgram.number: program.open() program.sortNext { self.displayMenu() } case Menu.finishProgram.number: isWorking = false default: print("잘못된 입력입니다. 다시 입력해주세요.") } } } ``` <br> ## 📚 참고 링크 - [🍎Apple Docs: setTarget(queue:)](https://developer.apple.com/documentation/dispatch/dispatchobject/1452989-settarget) - [🍎Apple Docs: DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) - [🍎Apple Docs: Thread](https://developer.apple.com/documentation/foundation/thread) - [🍎Apple Docs: String format](https://developer.apple.com/documentation/swift/string/init(format:_:)) - [🍎Apple Docs: escaping closure](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Escaping-Closures) - [🍎Apple Docs: Capturing Values](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Capturing-Values) - [🍎Apple Docs: ]() - [🍏Apple Archaive: ]() - [🍏Apple Archaive: ]() - <Img src = "https://hackmd.io/_uploads/SkQWz1wd2.png" width="20"/> [네이버 부스트코스: ]() - <Img src = "https://github.com/mint3382/ios-calculator-app/assets/124643545/56986ab4-dc23-4e29-bdda-f00ec1db809b" width="20"/> [야곰닷넷: 동시성 프로그래밍](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) - <Img src = "https://github.com/mint3382/ios-calculator-app/assets/124643545/56986ab4-dc23-4e29-bdda-f00ec1db809b" width="20"/> [야곰닷넷: ]() - <Img src = "https://hackmd.io/_uploads/ByTEsGUv3.png" width="20"/> [blog: ]() - <Img src = "https://hackmd.io/_uploads/ByTEsGUv3.png" width="20"/> [blog: ]() - <Img src = "https://hackmd.io/_uploads/ByTEsGUv3.png" width="20"/> [blog: ]() - <Img src = "https://hackmd.io/_uploads/ByTEsGUv3.png" width="20"/> [blog: ]() </br> ## 👥 팀 회고 - [팀 회고 링크](https://github.com/iOS-Yetti/ios-exposition-universelle/wiki) ---PR--- # PR -- # STEP 1 ## 고민했던 점 ### Unit Test Naming - 문제점🧐: Unit Test에서 메서드를 네이밍 할 때 "test_" 다음으로 바로 테스트 타깃 메서드의 이름을 넣는 것이 좋은지 아닌지 고민했습니다. - 해결방법😀: "test_" 앞에 바로 테스트 타깃 메서드 이름을 넣어 가독성을 높였습니다. ### Linked List의 특징과 Queue의 특징 - 문제점🧐: Linked List는 중간에 특정 데이터를 삽입(insert)할 수 있다는 특징이 있습니다. 때문에 이 기능을 구현해야하는 것에 대한 고민이 있었습니다. - 해결방법😀: 그러나 이는 선입선출(FIFO)이라는 Queue의 특징과는 알맞지 않았고 현재 저희는 Queue를 구현하고 싶은 것이기에 Linked List 만의 특징인 삽입 기능은 구현하지 않았습니다. ## 조언을 얻고 싶은 점 ### Unit Test 작성 - Unit Test의 이름을 지을 때와 test 메서드의 작성 기준에 대한 조언을 얻고 싶습니다. 저희는 각 메서드 당 하나의 test 메서드를 작성하였고 어떤 테스트를 하는지 바로 알아볼 수 있게 테스트 하고 싶은 메서드를 테스트 이름의 바로 앞에 붙이고 그 뒤부터 문장을 시작하였습니다. 이에 대한 지성만의 작성 기준이 따로 있으실까요? --- # STEP 2 ## 고민했던 점 ### 부동소수점 오류, 소수점 표현 - 문제점🧐: totalTime을 Double로 주고 더하다 보니 부동소수점 오류로 인해 0.7에 30번 더했을 때 20.9999999가 출력되는 문제가 있었습니다. 또한 예제를 보니 7.00과 같이 소수점 두번째 자리까지 표현되어 있었습니다. - 해결방법😀: String Format을 사용하여 해결했습니다. NumberFormatter와 String Format 둘 중 고민하였는데 출력하는 부분에서만 사용하기에 String Format을 사용하였습니다. ```swift private func closeBank() { print("업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 \(customerNumber)명이며, 총 업무시간은 \(String(format: "%.2f", totalTime))초입니다.") } ``` ### 하드코딩, enum 사용 - 문제점🧐: 콘솔창에서 입력 받은 값에 따라 switch 문의 값이 바뀌도록 해주는 데 그 값을 구분 없이 바로 바뀌도록 하드코딩으로 선언하였습니다. ```swift switch input { case "1": program.start() case "2": isWorking = false default: print("잘못된 입력입니다. 다시 입력해주세요.") } ``` - 해결방법😀: 조금 더 알아보기도 쉽고, 구분하기도 쉽도록 enum을 사용하였습니다. rawValue값을 사용하려다가 switch 문에서 알아보기 쉽도록 프로퍼티로 구분하게 하였습니다. ```swift switch input { case Menu.startProgram.number: program.start() case Menu.finishProgram.number: isWorking = false default: print("잘못된 입력입니다. 다시 입력해주세요.") } extension ProgramManager { enum Menu { case startProgram case finishProgram var number: String { switch self { case .startProgram: return "1" case .finishProgram: return "2" } } } } ``` ### Thread.sleep(forTimeInterval: ) - 문제점🧐:직원이 고객 1명의 업무를 처리하는데 소요되는 시간은 0.7초인데 실제로 콘솔로 실행했을 때에도 해당 시간만큼 시간이 소요되며 진행 결과를 출력해야 하는지 아니면 업무가 마감되었을 때에만 총 업무시간을 확인하면 되는지 고민하게 되었습니다. - 해결방법😀: 실제로도 시간이 소요되는 것처럼 보여져야 된다고 생각하여 Thread.sleep를 이용하여 지정된 시간만큼 스레드 동작에 제한을 두어서 해결하였습니다. ```Swift func processCustomer(_ customer: Customer) { print("\(customer.numberTicket)번 고객 업무 시작") Thread.sleep(forTimeInterval: 0.7) print("\(customer.numberTicket)번 고객 업무 완료") } ``` ### Manageable 사용 - 문제점🧐: programManager에서 program의 type이 bank인 것을 보고 은행이 아닌 다른 프로그램의 경우에도 이 매니저를 사용할 수 있게 하고 싶다는 생각이 들었습니다. - 해결방법😀: 이번 활동학습으로 나왔던 POP가 생각나 protocol Manageable을 선언하여 이를 program의 type으로 넣어주었습니다. ```swift protocol Manageable { var name: String { get } mutating func start() } ``` ## 조언을 얻고 싶은 점 ### Queueable 채택 방법 - 문제점🧐: 위의 Managable과 같이 Queue를 사용해야하는 곳에서 바로 선언하지 않고 Queueable을 채택하도록 하고 싶었습니다. 그러나 Queueable은 generic type 때문에 associatedtype을 사용하고 있는데 이로 인해 Queueable을 타입으로 채택할 때 any 키워드를 붙이라는 오류가 발생하였습니다. 또한 any를 붙이면 후에 queue를 사용할 때 다운 캐스팅을 해주어야 합니다. 단순히 인스턴스를 생성하는 걸로 처리하였는데, 타입을 붙일 때 어떤 방식이 좋은지 조언 부탁드립니다. 🥲 ### 콘솔앱 파일 정리 - 문제점🧐: UIApp에서는 MVC 패턴으로 파일을 정리했지만 ConsoleApp에서는 파일들을 어떤 패턴으로 정리해야할지 고민하게 되었습니다. 우선은 Queue만 따로 그룹화하긴 했는데 나머지 파일들은 어떻게 나누는 게 좋을지 조언 부탁드립니다. --- # STEP 3 ## 고민했던 점 ### 1. 구조체 비동기 처리 메서드 - 문제점🧐: 기존에 구조체에서 `DispatchQueue.global().async`로 은행 직원 수만큼 새로운 메서드를 생성해서 큐에 있는 고객들의 번호와 업무를 가져오는 `assignCustomerTask()`를 비동기로 처리할 수 있게 구현을 하려고 헀으나 `Escaping closure captures mutating 'self' parameter`라는 빌드 에러가 발생했습니다. - 이 문제의 원인은 구조체는 값 타입으로서 메서드 내에서 값을 변경하려면 `mutating` 키워드를 명시해야 하는데 여기서 DispatchQueue.global().async로 assignCustomerTask()를 전달하여 self를 캡처하게 되면 escaping closure와 같이 쓰이기 됩니다. 이렇게 함께 사용하게 될 경우, 값 타입이 복사되어 전달될 때 Escaping closure 안에서 mutating 메서드를 사용하면 예상치 못하는 에러가 발생할 우려가 있습니다. 또한 공식문서에서도 Escaping Closure는 self가 구조체나 열거형의 인스턴스일 때 self에 대한 변경 가능한 참조를 캡처할 수 없고 구조와 열거형은 공유 가변성을 허용하지 않는다고 나와 있었습니다. ```Swift mutating private func operateWindow(tellerCount: Int, line: Queue<Customer>, group: DispatchGroup) { for _ in 1...tellerCount { DispatchQueue.global().async(group: group) { [weak self] in self?.assignCustomerTask(line: line) } } } // "Escaping closure captures mutating 'self' parameter" 에러 발생 ``` - 해결방법😀: 이를 해결하기 위해서는 구조체를 클래스로 변경하여 참조타입으로 변경하거나 비동기로 수행하는 메서드 `assignCustormerTask()`에서 값을 변경하지 않도록 수정해야 하는데, 저희는 구조체에서 클래스 수정하는 방법으로 문제를 해결하였습니다. ### 2. DispatchQueue - 문제점🧐: Teller 타입에서 해당 Teller가 처리하는 BankTask에 따라 동시에 업무를 진행하는 숫자가 달라집니다. 즉 각 Task 창구 당 Teller의 수가 작업을 처리하는 쓰레드의 수로 이해했습니다. 때문에 Teller 타입에서 어떻게 하나씩의 쓰레드를 가지고 있게 할 수 있을지 고민하였습니다. - 해결방법😀: Teller 타입을 삭제하고 딕셔너리를 통해 각 BankTask key 값에 따라 value 값으로 각 task 창구 당 일하는 Teller의 숫자를 입력받아 for 문을 통해 직원의 수 만큼 쓰레드를 생성하도록 하였습니다. ```swift private func operateWindow(task: BankTask) { guard let tellerCount = tellers[task], let line = line[task] else { return } for _ in 1...tellerCount { DispatchQueue.global().async(group: group) { self.assignCustomerTask(line: line) } } } ``` ### 3. DispatchSemaphore - 문제점🧐: DispatchQueue를 통해 쓰레드를 만들어 Teller의 수만큼 각 line의 손님에게 접근할 수 있습니다. 이때 동시다발적으로 같은 line에 접근할 수 있으므로 같은 손님을 가져다가 처리하는 race condition이 일어날 수 있는, Thread Unsafe한 상태가 만들어져 있었습니다. - 해결방법😀: DispatchSemaphore를 활용하여 해결하였습니다. 동시에 접근할 수 있는 쓰레드의 개수를 1개로 지정하여 동시에 두개가 해당 line에 접근할 수 없게 보호해 주었습니다. ```swift counter.wait() guard let customer = line.dequeue() else { counter.signal() return } counter.signal() ``` --- ## 조언을 얻고 싶은 점 ### 1. 바인딩 처리 위치 - 고객의 업무를 랜덤으로 할당 받는 BankTask 타입의 파라미터를 바인딩 처리를 해줄 때 해당 변수가 쓰이는 메서드 내에서 처리를 할지 아니면 Customer 구조체 안에서 선언과 동시에 함께 처리를 해줄지 고민이 되었습니다. 어디서 바인딩을 해주는 것이 가장 이상적일까요? ```Swift struct Customer { let numberTicket: Int var bankTask: BankTask { guard let bankTask = BankTask.allCases.randomElement() else { return .deposit } return bankTask } } ``` ### 2. 큐의 분기처리 방식 - 처음에는 `giveTicketNumber()` 메서드 안에서 고객을 대기열로 넣는 enqueue를 수행할 때, 고객의 업무의 따라서 `depositLine`과 `loanLine` 각각 2개의 큐를 생성하여 `Switch case` 문으로 분기처리하여 고객을 관리하였는데, 인스턴스를 중복으로 생성하는 것이 바람직하지 않은 것 같아서 line이라는 배열 안에 2개의 큐를 넣어서 업무타입으로 접근하여 분기처리할 수 있도록 수정하였습니다. 전자와 후자 중 어떤 방식으로 고객의 정보에 접근하면 더 좋을지 궁금합니다. < Array > ```Swift private var line: [BankTask: Queue<Customer>] = [.deposit: Queue<Customer>(), .loan: Queue<Customer>()] ``` < Switch case > ```Swift! switch customer.bankTask { case .deposit: depositLine.enqueue(customer) case .loan: loanLine.enqueue(customer) } ``` ## 팀 회고 ### 우리팀이 잘한점👍 - 프로젝트 진행에 앞서 충분한 의견 제시와 소통으로 같은 팀으로서 가까워질 수 있었고 그만큼 프로젝트에 있어서 문제없이 큰 시너지를 발휘할 수 있었습니다. - 페어프로그래밍을 할 때에도 각자의 구현 단위를 구체적으로 나누어서 커밋을 진행하였고 그 결과 각 기능과 뷰를 세세하게 분석할 수 있게 되었습니다. ### 서로에게 피드백😃 - <To. 민트😈> 우선 프로젝트 팀원을 떠나서 친화력이 좋은 팀원으로써 자연스럽게 팀워크가 향상이 될 수 있었습니다. 또한 트러블 이슈에 대한 통찰력이 뛰어나서 빠르게 문제들을 해결할 수 있었습니다. - <To. 저스버그🕷️> 저스버그! 민트에yo!😈 2주간 저랑 같이 즐겁게 팀 프로젝트 해줘서 너무 고마오요! 덕분에 중간중간 수다도 떨고 하면서 재미있게 프로젝트를 할 수 있었던 것 같아요. 저스버그만의 유연함과 설득력이 제게 큰 도움이 되었습니다ㅏ! 또 코드에 대해서도 제가 부족한 부분에 대한 개념을 다시 한 번 말해주고, 물어봐주고, 정리해주셔서 좋았어요! 앞으로의 남은 3개월도 파이팅입니다~☺️