월욜에 한것
sudo arch -x86_64 gem install ffi
sudo arch -x86_64 gem install cocoapods
cocoapod 설치 m1 버전
arch -x86_64 pod install
pod 설치
SwiftLint 설치함
gitignore에서 Pods를 트래킹안하게 만들어 줘야함!
노드 만들기
클래스로할지 구조체로할지
근데 클래스로 해야한다!
링크드 리스트 만들기
macOS로 패키지 만들고(command line app) 테스트 코드를 추가하는 경우 타겟을 추가해야 한다.
화욜에 리뷰 기다림
수욜에 머지 기다림
목욜에 한거
은행 타입 구현 -> 모델
고객 타입 구현 -> 모델
구조체의 프로퍼티가 Private이면 memberwisw initializer를 사용 못함
https://www.swiftbysundell.com/tips/when-can-memberwise-initializers-be-used/
```swift
mutating func open() -> CFAbsoluteTime {
let customers: Int = createCustomer()
let startTime: CFAbsoluteTime = CFAbsoluteTimeGetCurrent()
for _ in 1...customers {
bank.performTask()
}
let workTime: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() - startTime
return workTime
}
```
위와 같이 타이머를 사용할 수도 있지만, 정확히 업무 처리에 걸린 시간만을 계산하는 것이 과제의 취지라고 생각해서 사용하지 않았습니다.
identifyMenu 기능 분리
bank에서 bankmanager로 처리한 고객 수 전달하는 메서드(또는 프로퍼티)
타이머
에러처리
타입 구현 - 은행, 고객
- 은행에는 n명의 은행원이 근무한다.
- 이게 DispatchQueue가 n개일까
- 사용하는 스레드가 n개일까
- 사용하는 스레드의 개수를 명확하게 해줄 수 있나?? 불가능???
- 은행에는 n명의 고객이 업무처리를 위해 대기한다.
- 고객의 대기열은 Queue타입을 활용한다.
- 고객 타입을 요소로 받는 Queue를 사용해야뎀
- 모든 고객의 업무가 끝나면 은행 업무를 마감한다.
- isEmpty???
- 업무를 마감할 때 "업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 XX명이며, 총 업무시간은 XX초입니다."라고 출력합니다.
- 업무 시간은 뭘로 잴까??
- 타이머?
- 걍 0.7초씩 더해줘야하나??
- **DispatchSourceTimer**
- [https://medium.com/@jungkim/timer에-대한-고찰-b10b07bdccc3](https://medium.com/@jungkim/timer%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-b10b07bdccc3)
- CFAbsoluteTimeGetCurrent
- 은행원은 고객의 업무를 처리한다.
- 이게 스레드냐고 큐냐고
- 각 고객의 업무를 처리하는 데 걸리는 시간은 0.7초이다.
- sleep 써야되나?
- asyncAfter? → 이건 아닌 것 같기도하고
- 대기중인 고객의 업무처리를 시작할 때 아래와 같이 출력합니다.
- 3번 고객 업무 시작
- 고객의 업무를 처리하면 아래와 같이 출력합니다.
- 5번 고객 업무 완료
콘솔앱 구현
- Step-2의 은행에는 한 명의 은행원이 근무함
- 은행원은 한 번에 한명의 고객의 업무를 처리할 수 있음
- 앱을 실행하면 두 개의 메뉴가 출력됨
- 1: 은행 개점
- 2: 종료
- 사용자가 1을 입력하면 은행을 개점하고 10~30명의 고객이 방문함
- 고객의 업무를 처리하면 은행문이 닫히고 다시 메뉴를 출력
- 사용자가 2를 입력하면 프로그램을 종료한다.
뱅크 매니저?
뱅크랑 고객을 모델로만 사용하고
로직은 뱅크 매니저에 때려 박아야되나,,,
### 월요일 (11/7)
Queue에 POP 적용하기
작업 비동기 처리하기
- 3명의 은행원 존재 (2명은 예금 업무, 1명은 대출 업무)
- 큐가 3개인지 스레드가 3개인지 -> GCD를 쓰면 쓰레드는 알아서 함
- 그럼? waitingLine(번호표)큐가 1개 있고, 이거를 비동기적으로 각 은행원 큐(큐가 3개)에 보낸다? (은행원 큐가 비어있으면?)
- 예금 업무를 보내는 큐가있고 대출 업무를 보내는 큐가 이게 3명의 은행원에 맞는지 (큐가 2개)
- 3명의 은행원이 waitingLine에 비동기적으로 접근을 할껀데 이때 race condition을 어떻게 해결할껀가
- 뮤텍스락
- 세마포어
- 시리얼큐
- 배리어
- 뭘 선택할꺼고 왜 선택했는지
BankService enum타입 추가
Customer banking 프로퍼티 추가
Bank class로 변경
https://stackoverflow.com/questions/70649135/escaping-closure-captures-mutating-self-parameter-struct
세마포어 구현
- depositQueue에 접근하는 semaphore 개수 2개로 제한
- finishedCustomerCount에 접근하는 semaphore 개수 1개로 제한
- semaphore = 동시에 수행할 수 있는 작업의 수
시작이 끝나면 asyncAfter를 이용해서 종료가 되도록 하는 코드
단, 이럴 경우 쓰레드를 더 많이 생성하기 때문에 성능상에 단점이 있을 수 있다.
```swift
case .loan:
loanQueue.async(group: dispatchGroup) {
print("here")
self.sema.wait()
DispatchQueue.global().async {
print("시작")
self.sema.signal()
}
self.sema.wait()
DispatchQueue.global().asyncAfter(deadline: .now() + 1.1) {
print("종료")
self.sema.signal()
}
}
```
timePerTask -> 타이머로 변경
### 화요일 할일
POP 적용 ^^
UML 그리기 (처음부터 그려야함 ㅎㅎ)
PR 메시지 작성
---
# 은행 창구 매니저 [STEP 1] 하모, SummerCat
@wonhee009
안녕하세요, 라자냐! 은행 창구 매니저에서 함께하게 된 하모, 썸캣입니다.
STEP-1 구현 완료하여 PR보냅니다!!
## 고민했던 점
- 어떤 의존성 관리도구를 사용해야하는지에 대한 고민
- SwiftLint를 사용하기 위해서 의존성 관리도구를 사용해야 했습니다.
- Swift Package Manager(SPM)는 애플이 만든 First-Party Tool이기 때문에 가능하면 SPM을 사용해 보려고 했습니다.
- 그런데 SwiftLint는 SPM을 제대로 지원하지 않아서 CocoaPods를 사용하게 되었습니다.
- `Undefined symbols:` 오류
- BankManagerConsoleApp`과 같은 Command Line App에서는 `@testable import`로 타겟을 불러올 수 없어 오류가 발생했습니다.
- 테스트 타겟에서 사용하는 파일들의 Target Membership에 테스트 타겟을 추가해주니 해결되었습니다.
- 타입구현 클래스 VS 구조체
- `Node`의 경우 다른 요소들과 연결하기 위해서 참조값을 사용하기 때문에 class로 구현하였고, 내부에서 자기 자신의 타입을 가진 프로퍼티를 멤버로 가지고 있기 때문에 struct는 사용이 불가능합니다.
- `LinkedList`, `Queue`의 경우 참조나 상속이 필요하지 않고, 의도하지 않은 데이터 변경을 방지하기 위해서 struct를 사용하였습니다.
## 조언을 얻고 싶은 부분
- Queue와 LinkedList를 각각 구현해야 하는 이유
- Queue 타입 구현을 위한 LinkedList 타입을 직접 구현하도록 되어 있어 Queue 타입과 LinkedList 타입을 별개로 구현해 주었습니다.
- 하지만 그냥 큐를 LinkedList 구조로 구현해도 되지 않을까? 라는 생각이 들었고, Queue와 LinkedList를 각각 따로 구현했을 때와의 차이점이 무엇일지 고민해 보았는데 따로 구현했을 때의 이점이 크게 생각나지 않았습니다. 이 부분에 대한 라자냐의 의견이 궁금합니다.
---
# 은행 창구 매니저 [STEP 2] 하모, SummerCat
@wonhee009
안녕하세요, 라자냐!
STEP-2 구현 완료하여 PR보냅니다!!
## UML

## 고민했던 점
- Dispatch Queue의 사용
- 요구사항에 은행원 n명이 근무한다는 내용이 있어 Dispatch Queue를 활용해야 하는지 고민하였습니다.
- 하지만 이번 스텝에서는 은행원 1명이 모든 일을 순차적 & 비동기적으로 처리하기 때문에 Dispatch Queue를 사용할 필요가 없다고 판단했습니다.
- Timer의 사용
- 아래와 같은 형태로 타이머를 구현해 모든 업무를 처리하는 데 걸린 시간을 계산할 수도 있지만, 이런 경우 작업을 처리하는데 걸린 시간 외에 print 등의 다른 작업을 수행하는 시간까지 포함된 시간을 구하게 됩니다.
- 정확히 업무 처리에 걸린 시간만을 계산하는 것이 과제의 취지라고 생각해서 사용하지 않았습니다.
```swift
mutating func open() -> CFAbsoluteTime {
let customers: Int = createCustomer()
let startTime: CFAbsoluteTime = CFAbsoluteTimeGetCurrent()
for _ in 1...customers {
bank.performTask()
}
let workTime: CFAbsoluteTime = CFAbsoluteTimeGetCurrent() - startTime
return workTime
}
```
## 조언을 얻고 싶은 부분
- 타입 추상화 및 일반화를 적용할 수 있는 부분
- 현재의 스텝 요구사항에서는 재사용하는 메서드가 매우 적어 저희끼리 논의했을 때에는 POP적으로 타입 추상화/일반화를 적용할 수 있는 부분을 발견하지 못했습니다. 어떤 부분에 적용할 수 있을까요?
---
# 은행 창구 매니저 [STEP 3] 하모, SummerCat
@wonhee009
안녕하세요, 라자냐!
STEP-3 구현 완료하여 PR보냅니다!!
## UML

## 고민했던 점
- Bank 타입 class로 변경
- `Bank` 타입이 `struct`일 때 `DispatchQueue`의 `async` 메서드를 이용하여 해당 구조체를 캡처하면 오류가 발생했습니다.
- 이는 `async`메서드에 전달되는 클로저가 캡처 후 외부에서 해당 구조체를 변경할 수 있기 때문에 발생한다고 생각했습니다.
- 따라서 이를 해결하려면 `capture list`를 통해서 값 타입을 상수로 캡처하고 내부에서 변경이 일어나지 않는다고 표현해주어야 한다고 생각했습니다.
- 이렇게 해결했을 때 캡처해온 값을 수정하지 못하는데 저희 프로젝트에서는 클로저 내부에서 `finishedCustomerCount`를 수정해주어야 하기 때문에 `class`로 변경하였습니다.
- POP를 어디에 적용할 것인가?
- `Bank`와 `Queue`를 확장할 수 있는 가능성이 있다고 판단하여 적용해보았습니다.
- `Bank`의 경우 `Bankable`이라는 프로토콜을 만들어서 공통적인 프로퍼티나 메서드를 추상화하고 `BankManager`는 `Bank`라는 구체적인 타입이 아닌 추상적인 `Bankable`을 의존하여 `DIP`를 준수하도록 하였습니다.
- `Queue`의 경우 `Queueable` 프로토콜로 추상화했을 때 다양한 타입을 요소로 받기 위해 `associatedType`을 사용하여 구현하였는데 프로토콜과 구조체, 클래스에서 해당 프로토콜을 타입으로 사용할 때 다양한 에러가 발생했고 이를 해결해주려면 모든 타입에 제네릭을 붙여주고 프로토콜을 더 구현해주어야 하는 오버엔지니어링이 발생하여 필요없다고 판단하였습니다.
- 예금 업무를 2명의 은행원이 처리하는 상황, 처리한 고객 수 카운트의 Race Condition 해결
- Race Condition을 해소하기 위한 방법으로는 크게 뮤텍스락, 세마포어, 시리얼 큐, 배리어를 활용하는 4가지 방법이 있습니다. 이 중에서 **세마포어를 활용**하는 방법을 선택했습니다.
- 뮤텍스락: `lock()`과 `unlock()`을 일일이 직접 넣어주어야 해서 휴먼 에러가 발생하기 쉽고, **공유 자원에 동시에 접근할 수 있는 프로세스의 수가 1개**이기 때문에 **은행원 2명이 접근해야 하는 상황에서는 적절하지 못하다**고 판단했습니다.
- 시리얼 큐: 시리얼 큐를 이용할 경우 각 은행원마다 시리얼 큐를 생성해 sync로 작업을 처리하게 하는 방식으로 구현이 가능합니다. 하지만 이런 방식으로 구현할 경우, 예금 업무를 처리하는 두 개의 은행원 큐에 예금 고객을 보낼 때 **각 큐에 쌓여있는 작업의 개수를 매번 확인해서 더 적은 쪽으로** 고객을 보내야 합니다.
- 배리어: 배리어는 **특정한 작업이 실행되기 전까지 해당 작업을 실행하지 않는** 개념이라고 생각해서 이 상황에 활용하기에는 적절하지 못하다고 판단했습니다.
- 세마포어: **공유 자원에 동시에 접근할 수 있는 프로세스의 개수를 제한**하는 방법으로, 예금 업무를 2명의 은행원이 처리하는 상황에 가장 적합하다고 생각해 세마포어를 활용했습니다. `depositQueue`의 semaphore를 2로, `finishedCustomerCount`의 semaphore를 1로 제한해 경쟁 상태를 방지했습니다.
- 업무 처리 시간 딜레이를 주는 방법 선택 (Thread.sleep vs asyncAfter)
- `Thread.sleep(forTimeInterval: )`을 이용해서 딜레이를 주는 방법
- STEP 2에서 `usleep()`으로 딜레이를 주었던 이유는, `sleep()`이 매개변수로 `Int` 타입만 받을 수 있기 때문이었습니다.`Thread.sleep(forTimeInterval: )`을 이용하면 `Double`을 매개변수로 받을 수 있어 `Thread.sleep`으로 수정했습니다.
- 하지만 `Thread.sleep()`, `sleep()`을 활용해 의도적으로 딜레이를 발생시킬 경우, 코드가 실행되는 스레드를 지연시키기 때문에 성능을 저하시킬 수 있습니다.
- asyncAfter를 이용해서 딜레이를 주는 방법
- `"시작"`이 끝난 후 1.1초 후에 `"종료"`가 되도록 하는 코드
- `loanQueue`와 `DispatchQueue`로 큐가 2개가 되어 둘 다 작업을 계속 async로 던지기 때문에 `loanQueue` 하나만 사용할 때보다 스레드를 더 많이 생성하게 되어 성능이 저하될 수 있다.
```swift
case .loan:
loanQueue.async(group: dispatchGroup) {
self.sema.wait()
DispatchQueue.global().async {
print("시작")
self.sema.signal()
}
self.sema.wait()
DispatchQueue.global().asyncAfter(deadline: .now() + 1.1) {
print("종료")
self.sema.signal()
}
```
- 위의 두 가지 방법을 비교했을 때, 스레드의 수가 늘어나는 것이 성능 저하에 더 큰 영향을 줄 수 있다고 생각해 최종적으로 `Thread.sleep`을 사용했습니다.
## 조언을 얻고 싶은 부분
## 출처
BankService enum타입 추가
Customer banking 프로퍼티 추가
Bank class로 변경
https://stackoverflow.com/questions/70649135/escaping-closure-captures-mutating-self-parameter-struct
세마포어 구현