---
# System prepended metadata

title: 은행창구 매니저

---

# 은행창구 매니저

## 📖 목차
1. [소개](#🌱-소개)
2. [타임라인](#📆-타임라인)
3. [시각화된 프로젝트 구조](#👀-시각화된-프로젝트-구조)
4. [실행 화면](#💻-실행-화면)
5. [트러블 슈팅](#❓-트러블-슈팅)
6. [참고 링크](#🔗-참고-링크)

## 🌱 소개

|Gundy|jpush|
|:-:|:-:|
|<img src="https://i.imgur.com/od5Wj9n.png" width = 300/>|<img src="https://avatars.githubusercontent.com/u/82566116?v=4" width = 300/>|

`Gundy`와 `jpush`의 iOS 은행창구 매니저 프로젝트입니다.

- **핵심적으로 다뤄본 기술**
    - OpenSource
    - POP
    - TDD
    - NameSpace
    - Operation
    - DispatchQueue

## 📆 타임라인

<details>
<summary>STEP 1</summary>

221101 
    
- LinkedList, Node, BankcustomerQueue 생성
- BankcustomerQueue 타입 내부 stub 구현
    - enqueue, dequeue, isEmpty, peek, clear   
- LinkedList 내부 stub 구현
    - head, tail, pushLast, popFirst, clear, peek, isEmpty
- UnitTest 작성
</details>

<details>
<summary>STEP 1 Feedback 반영</summary>

221103
    
- 테스트 케이스 추가
- Queue의 Generic 설정
</details>

<details>
<summary>STEP 2</summary>

221104
    
- STEP 1에서 구현한 BancCustomerQueue를 SPM으로 제작하여 프로젝트에 추가
- BankCustomer 생성
- BankProtocol 생성
- Bank 생성 및 구현
    - BankProtocol 채택
    - 메서드 구현
        - open, floatingMenu, work, close, init
    - 중첩타입으로 Constant 구현
</details>
 
<details>
<summary>STEP 2 Feedback 반영</summary>

221108
    
- 내부 Constant 타입을 enum 에서 struct로 변경 및 CustomerType 추가
- open 메서드의 네이밍 변경 및 기능을 쪼개서 분리
</details>
<details>
    
<summary>STEP 3</summary>

221108
    
- 예금 업무를 처리하는 deposit() 메서드 구현
- 업무 마감 후 초기화 시키는 configure 메서드 구현
- 대출 업무를 처리하는 loan() 메서드 구현
</details> 

## 👀 시각화된 프로젝트 구조

### TREE

```
├── BankManagerConsoleApp
│   ├── Bank
│   │   ├── Bank.swift
│   │   ├── BankCustomer.swift
│   │   ├── CustomerType.swift
│   │   ├── BankProtocol.swift
│   │   └── BankDesk.swift
│   └── main.swift
├── BankManagerConsoleAppTests
│   ├── BankCustomerTests.swift
│   └── BankManagerConsoleAppTests.swift
└── BankCustomerQueueModule
    ├── DataStructure
    │   ├── BankCustomerQueue.swift
    │   ├── LinkedList.swift
    │   └── Node.swift
    └── Protocols
        ├── List.swift
        └── Queue.swift
```

### 실행 로직
![](https://i.imgur.com/TBmcoQz.png)

CustomerQueue에 고객을 담고
각 타입별로 deposit과 loan으로 나누어 준 뒤
타입의 CustomerQueue에서 DispatchQueue.global.async로 Task를 실행해줍니다.

각각의 DispatchQueue는 하나의 그룹에 넣어서 그룹이 다 끝날 때까지 기다린 후 업무 마감 메세지를 출력하도록 구성했습니다.

## 💻 실행 화면


| 동기로 실행되는 화면 | 비동기로 실행되는 화면 |
|:--:|:--:|
|![](https://i.imgur.com/Uv04ICE.gif)|![](https://i.imgur.com/GbOgG0K.gif)|


## ❓ 트러블 슈팅

### Equatable
<details>
<summary>자세히</summary>
Queue에서 꺼낸 Queueable 타입의 값을 Equatable을 채택해주어 비교할 수 있도록 하고 싶었습니다.

![](https://i.imgur.com/VoYAGwz.png)

이부분을 해결하기 위하여 찾아보니 내부 요소인 Queueable 타입에 Equatable인 타입이 들어올 수 있도록 처리를 해 주었으나 잘 해결되지 않았고 직접적으로 Queue의 타입을 지정해주어서 출력되는 값이 String, Int 같은 Equatable 이 채택된 타입의 값을 받을 수 있도록 처리해서 해결해주었습니다.

![](https://i.imgur.com/C1wMhZ5.png)
</details>

### 오픈소스 제작
<details>
<summary>자세히</summary>
STEP 1에서 구현한 제네릭 타입을 지원하는 Queue를 오픈소스로 제작하기 위해 Swift Package Manager를 사용하였습니다. 오픈소스를 제작하고 나서 프로젝트에 적용하고 커밋을 하면 해당 커밋을 pull한 다른 사람들도 패키지가 적용된 상태로 바로 사용할 수 있을 줄 알았습니다. 

하지만 적용한 사람 이외에는 사용할 수도 없었고, 같은 패키지를 추가하여 사용할 수도 없었습니다. 이미 추가되어있는 패키지라서 추가할 수 없다는 멘트였습니다. 이 문제를 해결하기 위해 프로젝트 폴더도 뒤져봤지만 패키지는 프로젝트 폴더에 저장되지 않았고, 더 알아본 결과 Xcode 폴더 내부에서 발견할 수 있었습니다. 해당 파일을 삭제하는 것도 정답은 아니었는데, Xcode 프로젝트 상에서 해당 패키지를 resolve 하는 것으로 해결하였습니다.
</details>

### DispatchGroup
<details>
<summary>자세히</summary>
STEP 3에서 비동기 작업을 구현하면서 DispatchQueue를 활용했습니다. 이를 적용하면서 각각의 예금업무와 대출업무가 모두 마무리 된 이후에 업무 마감 안내문구를 출력해야하기 때문에 예금업무 대기열과 대출업무 대기열을 그룹으로 만들어 `wait()`을 사용하였습니다. 하지만 작업이 끝날 때까지 기다리는 경우도, 기다리지 않는 경우도 임의로 발생해 문제가 되었습니다. 저희가 그룹에 작업을 추가하기 위해 `enter()`와 `leave()` 메서드를 사용한 부분에서 문제가 된 것이라고 생각해, task를 추가할 때 group을 매개변수로 받아서 그룹에 추가하였습니다. 그렇게 해서 너무 빠른 속도로 코드가 진행돼 task가 제대로 수행되기 전에 다음 코드가 실행되는 것을 막았습니다.
</details>
    
### @escaping 메서드의 캡처 리스트
<details>
<summary>자세히</summary>
    
![](https://i.imgur.com/ylgIeiL.png)

비동기로 task를 그냥 보내주는 경우 
`Escaping closure captures mutating 'self' parameter` 의 오류가 발생했습니다.

escaping closure는 mutating 한 parameter를 캡처해야 했습니다.
혹시 함수 바깥에서 실행되다보니 같은 프로퍼티에 접근할 경우 충돌이 발생해서 캡쳐를 해야하는 것일까? 싶었습니다.

![](https://i.imgur.com/mtRCpbD.png)

캡처를 해 주었으나 이번에는 캡처 리스트는 **캡처**되었기 때문에 변할 수 없는 `상수, let`이 되었습니다. 
이것을 해결하기 위해서는 self의 타입이 class 가 되거나 프로퍼티가 외부에서 접근 가능한 타입 프로퍼티가 되어야 했습니다.

class로 변경하지 않고 현 상태인 struct를 유지하며 수정하고 싶었기 때문에 타입프로퍼티로 변경해주었습니다.

![](https://i.imgur.com/Hc4Uorq.png)
</details>
    
## 🔗 참고 링크

- Swift Language Guide
    - [Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html)
    - [Inheritance](https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html)
    - [Generics](https://docs.swift.org/swift-book/LanguageGuide/Generics.html)
    - [Subscripts](https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html)
    - [Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html)
- WWDC
    - [Concurrent Programming With GCD in Swift 3 (WWDC 2016)](https://developer.apple.com/videos/play/wwdc2019/257/)
- Apple Developer Documentation
    - [Concurrency Programming Guide](https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html)
- 야곰닷넷
    - [오토레이아웃 정복하기](https://yagom.net/courses/autolayout/)

---

[🔝 맨 위로 이동하기](#은행창구-매니저)

---

### 그라운드 룰

- Commit convention (Karma Style)

| 타입| 설명 |
|--|--|
|feat	|새로운 기능 추가|
|fix|	버그 수정|
|docs|	문서 수정|
|refactor|	코드 리팩토링|
|style|	코드 포맷팅 (코드 변경이 없는 경우)|
|test	|테스트 코드 작성|
|chore	|소스 코드를 건들지 않는 작업(빌드 업무 수정)|

ex> docs: README.md 수정

- 연락 가능시간
    - 평일: Whenever

- 작업시간
    - 10시 시작, 저녁먹기 전까지
    - 수요일은 휴일(활동학습 준비).
    - 활동학습 공부하다가 모르는 거 상대방에게 서로서로 물어보기
    - 주말은 사생활

- 커밋 단위
    - 알잘딱깔센

- 코드 컨벤션
    - 알잘딱
    - PR 보내기 전 통일성 검토
        - 간혹 private 누락 발견됨

- PR 피드백 코멘트
    - 피드백에 대한 답변은 기본적으로 다음날 아침에 같이 검토하고 같이 대답하기.
    - 리팩토링에 대한 답변은 알잘딱

- PR 일정 목표
    - 되면 맞추고, 더 꼼꼼히 보자.

- 목표
    - 단일 책임 원칙 준수하기
    - 리스코프 치환 원칙 준수하기
        - 상속받은 자녀가 부모님 역할 수행할 수 있어야하는데, 잘못된 override로 원칙 위배될 가능성을 제거하는 하나의 방법이 final 키워드
    - Protocol Oriented Programming
    - TDD
    - 탁월한 이름 짓기

---

은행창구 매니저 [STEP 1] Gundy, jpush

@stevenkim18
안녕하세요 스티븐!
STEP2 구현이 완료되어 PR 보내드립니다.

# 고민했던 점

## 접근제어자

배포될 것이라고 생각하는 타입은 `BankCustomerQueue`이므로 해당 타입 및 연관된 타입들, 메서드를 `public` 접근 수준으로 사용하였습니다.

그 외에 `public`으로 접근하지 못하는 부분은 `internal`과 내부에서도 알 필요가 없다고 생각되는 프로퍼티는 `private` 처리를 해주었습니다.

# 조언이 필요한 점

## 제네릭 타입이 반복적으로 사용될 때

노드에서부터 링크드리스트, 큐에 이르기까지 타입이 특정되지 않습니다. 현재는 노드 및 링크드리스트는 제네릭으로, 큐에서는 해당 타입을 `queueable` 프로토콜로 설정하였습니다. 어느 단계에서 `queueable`과 같은 프로토콜을 준수하는 타입을 제네릭 자리에 넣어주는 것이 적절한지 궁금합니다. 여러 문제를 생각하면 최상위에서 타입을 제한하는 것이 맞는 것 같긴 합니다.

## Node의 요소간 비교를 위해 Equatable 설정

Queue에서 꺼낸 Queueable 타입의 값을 Equatable을 채택해주어  비교할 수 있도록 하고 싶었습니다.

![](https://i.imgur.com/VoYAGwz.png)

이부분을 해결하기 위하여 

![](https://i.imgur.com/Fzfy6JE.png)

찾아보니 내부 요소인 Queueable 타입에 Equatable인 타입이 들어올 수 있도록 처리를 해 주는 방법이 있는 것 같아 사용해보았는데 잘 해결되지 않았습니다

어떤 방법을 시도해보면 해결할 수 있을까요?

---

은행창구 매니저 [STEP 2] Gundy, jpush

@stevenkim18
안녕하세요 스티븐!
STEP2 구현이 완료되어 PR 보내드립니다.

# 고민했던 점

## 오픈소스 제작

이번 프로젝트의 경우 오픈소스를 배우는 커리큘럼이 있기 때문에, 이전 프로젝트에서 이미 구현해본 Queue 자료구조를 오픈소스로 만들어서 채택하는 것이 또 다른 공부가 될 것이라 생각해 시도해 보았습니다.

# 조언이 필요한 점

## 오픈소스 선택

어떤 기준으로 오픈 소스를 선택하면 좋을까요? 오픈소스가 나중에 지원하지 않게끔 서비스 종료할지도 모른다는 생각을 하면, 범용적으로 쓰일 법한 자료구조 등은 모두 라이브러리로 만들어두는 것이 좋을까 하는 생각이 듭니다. 이 부분에 대해서 스티븐의 생각이 궁금합니다!

## POP

이번 프로젝트에 POP를 적용해보고 싶었습니다.
하지만 Bank의 경우 Bank의 역할 프로토콜을 만들어서 제작을 하려다 보니 
은닉화 때문에 대부분의 메서드가 프로토콜에서 빠지게 되었습니다.
그러다보니 뭔가 POP를 잘못쓰고 있다는 생각이 들었는데 이번 프로젝트의 경우 어떤식으로 사용하는 것이 좋았을까요?

---

이 밖에도 다른 조언해주실 사항이 있다면 꼭! 말씀해주세요! 저희가 몰라서 조언을 구하지 못하고 넘어가는 부분이 생기면 항상 아쉽습니다 😂

---



은행창구 매니저 [STEP 3] Gundy, jpush

@stevenkim18
안녕하세요 스티븐!
STEP3도 아주 재밌게 수행한 것 같습니다!
다른 한 쪽의 링크는 리드미에도 적혀있듯이 [Operation으로 구현](https://github.com/jjpush/ios-bank-manager/tree/step3-operation)을 참고해주세요!


# 고민했던 점

## 비동기 프로그래밍 구현 방식 선택

배우는 입장에서 다양한 방법을 체험해보는 것이 좋다고 생각했습니다. 그래서 기본이 되는 GCD의 DispatchQueue 방식부터 더욱 사용성이 좋아진 Operation, Async / Await 방식까지 체험을 해보고 싶었는데, 우선은 DispatchQueue과 Operation 방식으로 먼저 스텝을 수행해보았습니다. 개인적인 느낌으로는 DispatchQueue를 경험하고 Operation을 사용해보아서 그런지는 모르겠으나, Operation의 경우가 더욱 쉽게 사용할 수 있었던 것 같습니다.

## DispatchQueue의 로직 고민

DispatchQueue의 실행 로직을 고민해보았습니다.

![](https://i.imgur.com/lADFX0a.png)

CustomerQueue에 고객을 담고
각 타입별로 deposit과 loan으로 나누어 준 뒤
타입의 CustomerQueue에서 DispatchQueue.global.async로 Task를 실행해줍니다.

각각의 DispatchQueue는 하나의 그룹에 넣어서 그룹이 다 끝날 때까지 기다린 후 업무 마감 메세지를 출력하도록 구성했습니다.

## Operation의 로직 고민

DispatchQueue에서 비동기 프로세스 로직을 이미 경험해보았기 때문에 비슷한 기능을 수행하는 메서드로 대체하는 것을 주안점으로 삼았습니다.

![](https://i.imgur.com/pPUz7Zb.png)

다만 바뀌는 부분이 있다면 Operation 에는 group의 개념이 없는 것 같아서
각 queue를 기다려주는 부분을 연속으로 적어주었습니다.

```swift
deposit() // 예금 실행 메서드
loan() // 대출 실행 메서드

depositOperationQueue.waitUntilAllOperationsAreFinished()
loanOperationQueue.waitUntilAllOperationsAreFinished()

print("출력메세지")
```


## @escaping 메서드의 캡처 리스트

![](https://i.imgur.com/ylgIeiL.png)

비동기로 task를 그냥 보내주는 경우 
`Escaping closure captures mutating 'self' parameter` 의 오류가 발생했습니다.

escaping closure는 mutating 한 parameter를 캡처해야 했습니다.
혹시 함수 바깥에서 실행되다보니 같은 프로퍼티에 접근할 경우 충돌이 발생해서 캡쳐를 해야하는 것일까? 싶었습니다.

![](https://i.imgur.com/mtRCpbD.png)

캡처를 해 주었으나 이번에는 캡처 리스트는 **캡처**되었기 때문에 변할 수 없는 `상수, let`이 되었습니다. 
이것을 해결하기 위해서는 self의 타입이 class 가 되거나 프로퍼티가 외부에서 접근 가능한 타입 프로퍼티가 되어야 했습니다.

class로 변경하지 않고 현 상태인 struct를 유지하며 수정하고 싶었기 때문에 타입프로퍼티로 변경해주었습니다.

![](https://i.imgur.com/Hc4Uorq.png)


# 조언이 필요한 점

## DispatchQueue

### DispatchQueue를 선택하는 기준
사용해보니 Operation이 DispatchQueue보다는 체감상 사용하기 좋았다고 앞서 말씀드렸는데요! 그렇다보니 혹시 DispatchQueue도 분명 선택되는 기준이나 장점이 있을텐데 잘 모르겠습니다. 혹시 스티븐께서는 어떤 기준으로 DispatchQueue를 선택하고, Operation이나 다른 방법을 선택하시는지 알 수 있을까요?

## Operation

### addExecutionBlock(), completionBlock의 코드블록 실행 시점
BlockOperation의 경우 Operation의 동작이 끝나고 난 후에 `addExecutionBlock()`의 코드를 실행하고, Operation 및 관련된 executionBlock들이 모두 실행된 다음에 `completionBlock`의 코드가 실행되는 것으로 공부 했습니다.


![](https://i.imgur.com/zq0xnP8.png)

![](https://i.imgur.com/GewDlaM.png)

헌데 저희가 이를 적용하기 위해 

Operation에는 { 시작문구 출력과 sleep }, 
executionBlock에는 { 완료 문구 출력 }, 
completionBolck에는 { 완료 고객 수 증가 }

라는 코드들을 넣어주었는데, 같은 고객에 대해서 완료문구가 시작문구보다 먼저 출력되거나, 고객수 카운팅이 제대로 되지 않는 등의 문제가 발생하였습니다. 
논리적으로는 모두 순서에 맞게 실행이 되어야할 것 같은데, 그 이유를 모르겠습니다. 현재는 operation과 completionBlock만 사용하는 것으로 변경하여 문제들이 해결되었습니다. 이 코드들의 정확한 실행 시점이 궁금합니다!


### BlockOperation 의 재사용
분명 Operation의 DispatchQueue와는 다른 장점은 task의 객체화를 통한 재사용성에 있는 것으로 알고 있습니다. 하지만 이미 추가한 operation에 대하여 해당 인스턴스를 다시 추가하려고 하니 `operation is already enqueue on a queue`와 같은 에러가 발생합니다. 그래서 재사용을 하려고 했으나 재사용하지 못하고, 매번 새로운 인스턴스를 생성해주는 메서드를 통해 문제를 해결하였습니다. 재사용을 위한 특별한 방법이 필요했던 것일까요?

![](https://i.imgur.com/Z7kdIco.png)

