# ๐ฆ ์ํ ์ฐฝ๊ตฌ ๋งค๋์ ํ๋ก์ ํธ
- ํ๋ก์ ํธ ๊ธฐ๊ฐ: [2์ฃผ] 2021-12-20(์) ~ 2021-12-31(๊ธ)
- ์บ ํผ: ๋๋ฌด(@jsim27), ์๊ฑฐ(@Jager-yoo)
- ๋ฆฌ๋ทฐ์ด: ์ฐ๋ฃจ๋ฃจ(@jae57)
<br>
# Step4 - UI์ฑ ๊ตฌํ
## gif ์ฝ์
## ๊ณ ๋ฏผํ ์
### ์ฝ๋๋ก ์คํฌ๋กค๋ทฐ constraint๋ฅผ ์ก๋ ๋ฐฉ์
`๊ฐ๋ก ์คํ๋ทฐ` ๋ด๋ถ์ `์คํฌ๋กค๋ทฐ` 2๊ฐ๊ฐ ๋ค์ด๊ฐ๊ณ , ๊ฐ ์คํฌ๋กค๋ทฐ ์์ `์ธ๋ก ์คํ๋ทฐ`๊ฐ ํ๋์ฉ ๋ค์ด๊ฐ๋๋ก ์ค๊ณํ์ต๋๋ค. ๋ด๋ถ `๊ฐ๋ก ์คํ๋ทฐ`์ Label์ด ์ถ๊ฐ ๋ฐ ์ ๊ฑฐ๋๋ฉฐ ๊ทธ์ ๋ฐ๋ผ `์คํฌ๋กค๋ทฐ`์ `ContentLayoutGuide`์ topAnchor, bottomAnchor๊ฐ ๋ณ๊ฒฝ๋๋๋ก ๊ตฌ์ฑํ์ต๋๋ค.
<p align="center"><img src="https://i.imgur.com/csfiClB.png" width="30%"></p>
`์คํฌ๋กค๋ทฐ`์ `Frame Layout Guide`๋ ์์ `๊ฐ๋ก StackView`์ `distribution - fillEqually`๋ฅผ ํตํด ์๋์ผ๋ก ๊ณ ์ ๋ฉ๋๋ค. ๊ทธ๋ ๊ธฐ์ ๋ด๋ถ `์ธ๋ก StackView`์ `width`๋ฅผ ๊ณ ์ ์์ผ์ฃผ๊ณ , ์์ ์๋(`topAnchor`, `bottomAnchor`)๋ ์ ๋์ ์ผ๋ก ๋ณํ๋๋ก ์ค์ ํด ์คฌ์ต๋๋ค.
```swift
NSLayoutConstraint.activate([
waitingStackView.topAnchor.constraint(equalTo: waitingScrollView.topAnchor),
waitingStackView.bottomAnchor.constraint(equalTo: waitingScrollView.bottomAnchor),
waitingStackView.widthAnchor.constraint(equalTo: waitingScrollView.widthAnchor)
])
```
### ์ด๊ธฐํ ๊ธฐ๋ฅ ๊ตฌํ ๋ฐฉ์
- GCD์ ํน์ฑ ์ DispatchQueue์ ์ฌ๋ผ๊ฐ ์์
์ ์ทจ์ํ ์ ์์ต๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์, ์ด๊ธฐํ ์ ์ด์ ์์
์ด ๋ค์ ๋ทฐ์ ํ์๋ ์๋ฐ์ ์์ต๋๋ค. Operation์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ด ๋ถ๋ถ์ ์ง์ ์ ์ผ๋ก ํด๊ฒฐํ๊ธด ํ๋ค ๊ฒ ๊ฐ์๋ฐ์. ์ ํฌ๋ ์ด ๋ถ๋ถ์ `Bank`๊ฐ์ฒด๊ฐ `BankViewController` ์๊ฒ ๋์ด์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ง ๋ชปํ๋๋ก Bank์ delegate์ nil์ ํ ๋นํ์ฌ ๊ด๊ณ๋ฅผ ๋์ด๋ฒ๋ฆฌ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ์ต๋๋ค.
- View๋ ๋ณ๋๋ก ๋ด๋ถ์ Label๋ค์ removeFromSuperViewํ๊ณ , Timer๋ 00:00:000 ๋ก ๋ฆฌ์
ํด์ฃผ๋๋ก ๊ตฌํํ์ต๋๋ค.
## ์๊ฒ ๋ ์
### Timer.ScheduledTimer
Timer.ScheduledTimer๋ timeInterval์์ ์ค์ ํ ์๊ฐ๋ง๋ค ๋ฐ๋ณต๋์ด selector์ ๋ฉ์๋๋ฅผ ์คํํฉ๋๋ค.
ํน์ง์ ์ธ ์ ์, Timer๊ฐ ์ธ์คํด์ค๋ฅผ ์์ฑํ์ ๋ง์ ๋ฐ๋ก ์์๋๋ค๋ ์ ์
๋๋ค.
```swift=
timer = Timer.scheduledTimer(timeInterval: 0.001,
target: self,
selector: #selector(timerShouldUpdate),
userInfo: nil,
repeats: true)
```
Timer๋ฅผ ๋ฉ์ถ๊ธฐ ์ํด์๋ `timer.invalidate()` ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ฉ๋๋ค.
<br>
## ํธ๋ฌ๋ธ ์ํ
### โฑ ์คํฌ๋กค ์ ํ์ด๋จธ๊ฐ ๋ฉ์ถ๋ ๋ฌธ์ - Runloop
๋๊ธฐ์ค์ธ ๊ณ ๊ฐ์ด ํ ํ๋ฉด์ ๋ด๊ธฐ์ง ์์ ๋งํผ ๋ง์์ง๋ฉด, `์คํฌ๋กค ๋ทฐ`๊ฐ ํ์ฑํ๋ฉ๋๋ค.
์ด๋ ์คํฌ๋กค์ ์ํด ์คํฌ๋กค ๋ทฐ ์์ญ์ `ํฐ์น(ํด๋ฆญ)`ํ๋ ์๊ฐ, ์๋ ์ค์ธ ํ์ด๋จธ๊ฐ ์ผ์ ์ ์ง๋๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์ ํฌ๊ฐ ์๊ฐํด๋ณธ `์์ธ`์ ์คํฌ๋กค ๋ทฐ๋ฅผ ์์ง์ผ ๋ ํ๋ฉด์ UI๊ฐ ์๋ก ๊ทธ๋ ค์ง๋ ์ผ๊ณผ, ํ์ด๋จธ๊ฐ 0.001์ด ๊ฐ๊ฒฉ์ผ๋ก ์
๋ฐ์ดํธ ๋๋ ์ผ์ด ๋ชจ๋ `RunLoop`๊ฐ ๋์ํ๊ณ ์๋ `Main Thread`์์ ์ฒ๋ฆฌ๋๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค.
์คํฌ๋กค ๋ทฐ๋ฅผ ์์ง์ด๋ ์ด๋ฒคํธ๋ `RunLoop`๋ฅผ `event tracking mode`๋ก ๋ง๋ค๊ณ ์ด๋ ์คํฌ๋กค ์ ๋๋ฉ์ด์
์ ๋ถ๋๋ฝ๊ฒ ๋ง๋ค์ด์ฃผ๊ธฐ ์ํด, ์์คํ
์ ์ฐ์ ์์๊ฐ ๋ฎ์ UI ์
๋ฐ์ดํธ์ ๋์์ ์ผ์ ์ ์ง์ํค๊ฒ ๋ฉ๋๋ค. [์ฐธ๊ณ ๋ธ๋ก๊ทธ](http://bynomial.com/blog/?p=67)
์ ํฌ๊ฐ ๋ง๋ ํ์ด๋จธ์ `RunLoop.Mode`๊ฐ `default`์ด๊ธฐ ๋๋ฌธ์ ๊ฐ์ฅ ๋ฎ์ ์ฐ์ ์์๋ก ์ทจ๊ธ๋๊ณ ์๋๋ฐ์, ์ด๋ ํ์ด๋จธ์ `RunLoop.Mode`๋ฅผ `common`์ผ๋ก ์ค์ ํด์, ์คํฌ๋กค ๋ทฐ๋ฅผ ์์ง์ผ ๋์๋ ํ์ด๋จธ๊ฐ ์ผ์ ์ ์ง๋์ง ์๋๋ก ๋ง๋ค์ด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
```swift
func activate() {
timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(timerShouldUpdate), userInfo: nil, repeats: true)
RunLoop.current.add(timer ?? Timer(), forMode: .common)
}
```
<br>
# Step3 - ๋ค์ค ์ฒ๋ฆฌ ๊ตฌํ
## ๊ณ ๋ฏผํ ์
### โ๏ธ 1. ๋ค์ค ์ฒ๋ฆฌ ๋ก์ง
์ฌ๋ฌ ์ํ์์ด ๋์์ `client`์ ์
๋ฌด๋ฅผ ์ฒ๋ฆฌํ๋, ํ ๋ฒ์ ์ผํ๋ ์ํ์์ ์๋ฅผ ์ ํํ์ฌ ๋ค์ค ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์ด๋ป๊ฒ ๊ตฌํํ ์ง ๊ณ ๋ฏผํ์์ต๋๋ค.
์ปค์คํ
์๋ฆฌ์ผ ํ๋ฅผ 3๊ฐ ๋ง๋๋ ๋ฐฉ๋ฒ, 3๊ฐ์ `global().async` ๋ธ๋ก ๋ด๋ถ์์ ๊ฐ๊ฐ while ๋ฐ๋ณต๋ฌธ์ผ๋ก `client`๋ฅผ `dequeue`ํ๋ ๋ฐฉ๋ฒ ๋ฑ๋ฑ์ ๊ณ ๋ฏผํด ๋ณด์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, ํ๋์ while๋ฌธ ๋ด๋ถ์์ `dequeue`ํ ํ, `global().async`๋ก ์์
์ ๋ณด๋ด๋, ์์
์ ๋์์ ์ ๊ทผํ ์ ์๋ thread ๊ฐฏ์๋ฅผ `semaphore`๋ก ์ ํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ฌ ๊ตฌํํ์ต๋๋ค.
<br>
### โ๏ธ 2. ์ฌ๋ฌ ๊ฐ์ ์ธ๋งํฌ์ด๋ก ์
๋ฌด๋ณ ๋ด๋น ์ํ์ ์ซ์๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ
๋ค์ค ์ฒ๋ฆฌ๋ฅผ ํ๋, ์
๋ฌด(์๊ธ, ๋์ถ)๋ง๋ค ๋ด๋นํ๋ ์ํ์์ ๊ณ ์ ํ๋ ๋ก์ง์ ๊ณ ๋ฏผํ์ต๋๋ค.
์๊ตฌ์ฌํญ์์๋ ์๊ธ ์
๋ฌด๋ฅผ ๋ด๋นํ๋ ์ํ์ 2๋ช
, ๋์ถ ์
๋ฌด๋ฅผ ๋ด๋นํ๋ ์ํ์ 1๋ช
์ผ๋ก ์ ์๋์ด ์๋๋ฐ์.
while๋ฌธ ๋ด๋ถ์์ `client`๋ฅผ `dequeue`ํ ํ ์กฐ๊ฑด๋ฌธ์ผ๋ก `client.task`๋ฅผ ๊ฒ์ฌํ์ฌ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ธ๋ก์ผ๋ก ๋ถ๊ธฐํ๋, ๊ฐ ์๊ธ ๋ธ๋ก์์๋ value๊ฐ 2์ธ semaphore, ๋์ถ ๋ธ๋ก์์๋ value๊ฐ 1์ธ semaphore๋ก ์ ์ด๋๋๋ก ๊ตฌํํ์ต๋๋ค.
๋ด๋ถ์ ์ผ๋ก๋ thread๊ฐ `client` ์๋งํผ ๋ง์ด ๋ง๋ค์ด์ง๊ฒ ์ง๋ง, ์ค์ ๋ก ๋์์ ์ผํ๋ thread๋ ์๊ธ 2, ๋์ถ 1๋ช
์ผ๋ก ์ด 3๊ฐ๊ฐ ๋์ด, ํ์ค ์ธ๊ณ์์ ์ํ์ 3๋ช
์ด ๋ด๋น์
๋ฌด๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ์ฒ๋ผ ๋์ํฉ๋๋ค.
<br>
### โ๏ธ 3. ์ธ๋งํฌ์ด ์ซ์ ํ๋์ฝ๋ฉํ์ง ์๊ณ main.swift ์์ ์ ์ธํ ๋ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ์ ์๊ฒ ์ค๊ณ
`DispatchSemaphore`์ `value`๋ฅผ ํตํด ์ํ์ ์ซ์๋ฅผ ์ ์ดํ๊ธฐ์, ์
๋ฌด๋ณ ์ํ์ ์๋ฅผ ํ์
์ด๊ธฐํ ์ ์ง์ ํ ์ ์๊ฒ ๋์์ต๋๋ค. ์ด๋ฅผ ํตํด ๋งค์ง๋๋ฒ ํ๋์ฝ๋ฉ์ ํผํ ์๋ ์๊ณ , ์ถํ ๊ธฐ๋ฅ ์์ ์๋ ๋์ํ ์ ์๊ฒ ๋์์ต๋๋ค.
<br>
### โ๏ธ 4. delegate โ ๋ฆฌ๋๋ฏธ์๋ค๊ฐ ์ ์ฉ (์ด์ ๋งํฌ)
๊ธฐ์กด `BankManager`๊ฐ `BankDelegate`๋ฅผ ์ฑํํ์ง๋ง, `struct`๋ก ๊ตฌํ๋์ด ์์์ต๋๋ค.
ํ ์ฝ๋ ์ํ์์๋ `class`์ด๋ `struct`์ด๋ ์๊ด์์ง๋ง, ์ถํ `BankManager`์ ์ ์ฅ ํ๋กํผํฐ๊ฐ ์ถ๊ฐ๋๊ณ , ์ด๋ฅผ ๋ณ๊ฒฝํ๋ `mutating func`๊ฐ ๊ตฌํ๋๋ค๋ฉด ์ธ์คํด์ค๋ฅผ ์ถ์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์๋ํ์ง ์์ ๋์์ด ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด์ ๋๋นํ๊ธฐ ์ํด `BankManager`๋ฅผ `class`๋ก, `BankDelegate`๋ `AnyObject` ์ ์ฝ์ ๊ฑธ์์ต๋๋ค. ๋ํ, `Bank.delegate`๋ ์ํ ์ฐธ์กฐ๋ฅผ ๋ง๊ธฐ ์ํด `weak`๋ก ์ ์ธํ์ต๋๋ค.
์ด์ ๋งํฌ
<br>
### โ๏ธ 5. NumberFormatter ๋ณ๊ฒฝํ ๊ฑฐ (์ด์ ๋งํฌ)
๊ธฐ์กด ์
๋ฌด ์๊ฐ(`CFAbsoluteTime`)์ ํ์ํ๊ธฐ ์ํด `String`์ผ๋ก ๋ณํ ๋ฐ ์์์ 2์๋ฆฌ ๋ฏธ๋ง์ ๋ฒ๋ฆฌ๋ ๋ก์ง์์ ๊ฒฐํจ์ด ๋ฐ๊ฒฌ๋์์ต๋๋ค. ์๋๋ `๋ฒ๋ฆผ`์ด์์ง๋ง, ์ฌ์ฉํ `String(format:"%.2f")`์ `๋ฐ์ฌ๋ฆผ`์ ํ๊ธฐ์, ๋ก์ง์ ์์ ํ์ต๋๋ค.
`String(format:)` ๋์ `NumberFormatter`๋ฅผ ์ฌ์ฉํ๊ณ , `roundingMode`๋ฅผ `.floor`๋ก, `fractionDigit`์ 2๋ก ์ ํํ์ฌ ํด๊ฒฐํ์ต๋๋ค.
์ด์ ๋งํฌ
<br>
## ๐ค ๊ถ๊ธํ ์
### โ ๊ณ ๊ฐ์ ์ ๋งํผ thread ๋ฅผ ๋ง๋ค๊ณ semaphore ์ฌ์ฉํ๋ ๊ตฌ์กฐ๊ฐ ๊ด์ฐฎ์๊น์?
์ํ์์ด ๋ด๋นํ๋ ์
๋ฌด๋ ์๊ธ(deposit)๊ณผ ๋์ถ(loan)์ด ์กด์ฌํฉ๋๋ค.
์๊ตฌ์ฌํญ์ ๋ฐ๋ผ, 2๋ช
์ ์ํ์์ ์๊ธ ์
๋ฌด๋ง, 1๋ช
์ ๋์ถ ์
๋ฌด๋ง ์ฒ๋ฆฌํด์ผ ํ๋๋ฐ์.
๐ก `ํ ๊ฐ์ ํ์ฑํ๋ thread == ํ ๋ช
์ ์ํ์`
์ผ์ ํ ์ ์๋(ํ์ฑํ๋) Thread ๊ฐ์์ ์ํ์์ ์ซ์๋ฅผ ๋๊ธฐํํ๊ธฐ ์ํด, ์ ํฌ๋ `2๊ฐ์ semaphore` ๋ฅผ ์์ฑํ์ต๋๋ค.
์๋ก ๋ค๋ฅธ thread ์ ์
๋ฌด ์ข
๋ฃ ์์ ์ ํธ๋ํนํ๊ธฐ ์ํด `DispatchGroup` ๋ํ ์ฌ์ฉํ์ต๋๋ค.
```swift
// Bank ํด๋์ค
let depositSemaphore = DispatchSemaphore(value: numberOfDepositBankers)
let loanSemaphore = DispatchSemaphore(value: numberOfLoanBankers)
let group = DispatchGroup()
// numberOf~Bankers ๋ Bank ํด๋์ค์ ์ธ์คํด์ค ํ๋กํผํฐ์
๋๋ค.
```
๊ทธ๋ฆฌ๊ณ ์๋ ์ฝ๋์ ๊ฐ์ด, `DispatchQueue.global().async`๋ฅผ ๊ตฌํํ๊ณ ๋ด๋ถ์ `switch๋ฌธ`์ ๋ฃ์ด ์
๋ฌด์ ์ข
๋ฅ์ ๋ฐ๋ผ ๊ฐ๊ธฐ ๋ค๋ฅธ `semaphore`์ ์ ํ์ด ๊ฑธ๋ฆฌ๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์์ต๋๋ค.
```swift
DispatchQueue.global().async(group: group) {
switch client.task {
case .deposit:
depositSemaphore.wait()
self.service(for: client)
depositSemaphore.signal()
case .loan:
loanSemaphore.wait()
self.service(for: client)
loanSemaphore.signal()
}
}
```
์ ํฌ๊ฐ ์ํ์ ๋ง๋ค ํ๋์ thread ๋ฅผ ์ฌ์ฉํ๋ ์ปจ์
์ผ๋ก ์ฝ๋๋ฅผ ๋ง๋ค๊ธด ํ์ผ๋, ์ค์ thread ๊ฐ์๋ while๋ฌธ์ ๋๋ฉด์ `๊ณ ๊ฐ์ ์ ๋งํผ ์์ฑ`๋ฉ๋๋ค.
๊ทธ ์ดํ์ ์ฃผ์ด์ง semaphore ์ ํ์ ๋ฐ์ผ๋ฉด์ ์
๋ฌด๋ฅผ ์ฒ๋ฆฌํ๊ฒ ๋๋๋ฐ์!
๐๐ปโโ๏ธ ์ด๋ ๊ฒ thread ๋ฅผ ๊ณ ๊ฐ์ ์ ๋งํผ ๋ง๋ค์ด๋๊ณ semaphore ๋ก ์ ํ์ ๊ฑฐ๋ ๊ตฌ์กฐ๋ ๋นํจ์จ์ ์ธ ๊ฒ์ผ๊น์?
์ฐจ๋ผ๋ฆฌ Custom Serial Queue ๋ฅผ ์ํ์์ ์ ๋งํผ ๋ง๋ค๊ณ `๋จ์ผ thread`์์๋ง ์
๋ฌด๋ฅผ ํ๊ฒ ๋ง๋๋ ๊ฒ ์ปจ์
๋ ์งํค๊ณ thread ์์ฑ ๋น์ฉ์ ์๋ ์ ์๋ ๋ฐฉ๋ฒ์ผ๊น์? ๐ค
<br>
## ์๊ฒ๋ ์
### ํ์ต ํค์๋
- ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋
์ ์ดํด
- ๋๊ธฐ(Synchronous)์ ๋น๋๊ธฐ(Asynchronous)์ ์ดํด
- ์ค๋ ๋(Thread) ๊ฐ๋
์ ๋ํ ์ดํด
- GCD๋ฅผ ํ์ฉํ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ ๊ตฌํ (DispatchSemaphore, DispatchGroup)
<br>
# Step2 - ์ํ, ๊ณ ๊ฐ ํ์
๊ตฌํ
## ๐ Class Diagram

<br>
## ๐ท ํ์
๊ฐ์
- `Bank` -> ๊ฐ์ฅ ํต์ฌ์ด ๋๋ ํ์
์ผ๋ก, ๊ณ ๊ฐ ๋๊ธฐ์ด `clientQueue`์ ๊ณ ๊ฐ์ด ๊ฐ๊ณ ์๋ `task`๋ฅผ ์ฒ๋ฆฌํ๋ ์ญํ ์ ๋ด๋นํฉ๋๋ค.
- `BankManager` -> Console์ ์ ์ดํ๋ฉฐ(์ฌ์ฉ์ ์
์ถ๋ ฅ), `Bank` ๊ฐ์ฒด์ ์์์ ๊ด๋ฆฌํฉ๋๋ค.
- `BankDelegate` -> `Bank`๊ฐ `BankManager`์๊ฒ ๋ณด๋ผ ๋ฉ์ธ์ง๋ฅผ ์ ์ํฉ๋๋ค.
- `Client` -> `Bank`์ `clientQueue`์ ๋ค์ด๊ฐ ๊ณ ๊ฐ ํ์
์
๋๋ค. ๋๊ธฐ๋ฒํธ(`waitingNumber`), ์
๋ฌด(`task`)๋ฅผ ์ํ๊ฐ์ผ๋ก ๊ฐ์ง๋๋ค.
- `Task` -> ๊ณ ๊ฐ์ ์ํ์
๋ฌด๋ฅผ ์ ์ํ ํ์
์
๋๋ค.
- `Queue` -> `Bank`์ `clientQueue`๋ฅผ ์ํด ๊ตฌํํ ํ ํ์
์
๋๋ค.
- `LinkedList` -> `Queue` ํ์
์ ์ํด ๊ตฌํํ ๋จ์ผ์ฐ๊ฒฐ๋ฆฌ์คํธ ํ์
์
๋๋ค.
- `Node` -> `LinkedList` ํ์
์ ์ํด ๊ตฌํํ Node ํ์
์
๋๋ค.
- `ConsoleMessage` -> ์ฝ์์์ ์ฌ์ฉ์์๊ฒ ์
์ถ๋ ฅ๋ string literal์ ์ ์ํ ํ์
์
๋๋ค.
<br>
## ๊ณ ๋ฏผํ ์
### โ๏ธ 1. Delegation Pattern์ ํ์ฉํ ๊ฐ์ฒด๊ฐ ์ญํ ๋ถ๋ฆฌ
์ถํ ์ด ํ๋ก์ ํธ๋ Console ์ฑ -> UI ์ฑ ์ผ๋ก ์ ํ๋ ์์ ์
๋๋ค.
MVC ํจํด์ ๊ธฐ๋ฐํ์ฌ ๊ฐ์ฒด์ ์ญํ ์ ๋ถ๋ฆฌํด์ผ ํ ๊ฒ์ ์๊ฐํ๋ฉด, ํ์ฌ Console ๊ธฐ๋ฐ ์ฑ์์๋ View์ ๊ด๋ จ๋ ๊ธฐ๋ฅ์ธ `print` ๋ฉ์๋๊ฐ ๊ฐ์ฒด ์ฌ๊ธฐ์ ๊ธฐ์ ์ฐ์ด๋ฉด ์๋ฉ๋๋ค.
View ๊ด๋ จ ๊ธฐ๋ฅ๋ง ๋ด๋นํ ๊ฐ์ฒด(ํ `BankManager`, ์ถํ `ViewController`)์๋ง ํด๋น ์ญํ ์ด ํ ๋น๋์ด์ผ ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
๊ทธ๋์ Console์ ๋ณด์ฌ์ง ๊ฒ์ ๊ด๋ฆฌํ๋ `BankManager`์์ ๋ชจ๋ `print`๋ฅผ ์ํํ๋๋ก ์ค๊ณํ๊ณ ์ถ์์ต๋๋ค.
์ด๋ฅผ ์ํด, `Bank`์ `BankManager` ๊ฐ์ `BankDelegate` ํ๋กํ ์ฝ์ ํตํ์ฌ Delegation Pattern์ ๊ตฌํํด ๋ณด์์ต๋๋ค.
`Bank`์์๋ ์ํ์
๋ฌด์ ์ํ๋ฅผ ์๋ฆด ํ์๊ฐ ์์ ๋ `delegate`๋ฅผ ํตํด `BankManager`์๊ฒ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ , `BankManager`๋ ํ๋กํ ์ฝ ์๊ตฌ์ฌํญ์ผ๋ก ๊ตฌํํ ๋ฉ์๋ ๋ด๋ถ์์ `print`๋ฅผ ์ํํ๋๋ก ํ์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, ์ถํ UI ์ฑ์ผ๋ก ์ ํ ์ `ViewController`์ `BankDelegate`๋ฅผ ์ฑํํ์ฌ ์๊ตฌ ๋ฉ์๋๋ง ๊ตฌํํ๋ฉด ์ฝ๊ฒ ์ ์ง๋ณด์๊ฐ ์ด๋ฃจ์ด์ง ์ ์๊ฒ ๋์๋ค๊ณ ์๊ฐํฉ๋๋ค.
<br>
### โ๏ธ 2. Task ์ด๊ฑฐํ์ ๊ฒฐํฉ๋๋ฅผ ๋ฎ๊ฒ ์ ์ง
์ ํฌ๋ ๊ณ ๊ฐ์ ์
๋ฌด๋ฅผ ์ ์ํ๋ Task ์ด๊ฑฐํ์ ์ผ์ด์ค๊ฐ ๋ค์ STEP์์ ์ถ๊ฐ๋๋ ์ํฉ์ ์์ํ์ต๋๋ค.
์ง๊ธ์ ์
๋ฌด ์ข
๋ฅ๊ฐ `์๊ธ(deposit)` ํ๋๋ฟ์ด์ง๋ง, ์ถํ ์
๋ฌด์ ์ข
๋ฅ(์ผ์ด์ค)๊ฐ ๋์ด๋๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ๋๋ผ๋, ์ด๊ฑฐํ์ ์ฝ๋๋ง ๋ณ๊ฒฝํ๋ฉด ๋๋ ์์ ์ ์ธ ์ฝ๋๋ฅผ ๋ง๋ค๊ณ ์ถ์์ต๋๋ค.
(์์ง๋๋ ๋์ด๊ณ ๊ฒฐํฉ๋๋ ๋ฎ์ถ๋ค.)
๊ทธ๋์ ์ด๊ฑฐํ์ `CaseIterable` ํ๋กํ ์ฝ์ ์ฑํ์ํค๊ณ ๊ณ ๊ฐ์ ์
๋ฌด๋ฅผ ๋๋คํ๊ฒ ์์ฑํด์ฃผ๋ ์ฐ์ฐ ํ๋กํผํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ตฌํํ์ต๋๋ค.
<p align="center"><img src="https://i.imgur.com/k3tkMmD.png" width="30%"></p>
<br>
## ๐ค ๊ถ๊ธํ ์
### โ 1. ์ ํฌ๊ฐ ์ ์ฉํ Delegation Pattern ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉ๋ ๊ฒ์ผ๊น์?
๐๐ปโโ๏ธ ์ ํฌ๊ฐ ๋ง๋ ์ฝ๋๋ฅผ `๊ตฌ์กฐ์ ์ธ ์ธก๋ฉด`์์ ์ด๋ป๊ฒ ์๊ฐํ์๋์ง, ํผ๋๋ฐฑ์ ๋ฐ์๋ณด๊ณ ์ถ์ต๋๋ค! ๐ฅบ
<br>
### โ 2. NumberFormatter ์ธ์คํด์ค ์์ฑ ๋น์ฉ์ด ๊ฝค ํฌ๋ค๊ณ ๋ค์ด์, ๋ค๋ฅธ ๋ฐฉ์์ ์ฌ์ฉํ๋๋ฐ ๊ด์ฐฎ์ ๊ฑธ๊น์?
```swift
let numberFormatter = NumberFormatter()
```
์์ ๊ฐ์ด NumberFormatter ์ธ์คํด์ค ์์ฑํ๋ ๊ฒฝ์ฐ์ ๋น์ฉ์ด ํฌ๋ค๋ ๋ง์ ๋ค์ ์ ์ด ์์ต๋๋ค. :sweat_smile:
๋ค๋ฅธ ๋ฆฌ๋ทฐ์ด ๋ถ๋ค ์ค์์๋ ๊ทธ๋ ๊ฒ ๋ง์ํด์ฃผ์๋ ๋ถ์ด ์์๊ณ ๊ด๋ จ ์คํ์ค๋ฒํ๋ก์ฐ ๋งํฌ๋ ์ฐพ์๋ดค์ต๋๋ค. (์ข ์ค๋๋ ์๋ฃ์ด๊ธด ํฉ๋๋ค.)
- ["Why is allocating or initializing NSDateFormatter considered "expensive"?"](https://stackoverflow.com/questions/8832768/why-is-allocating-or-initializing-nsdateformatter-considered-expensive)
์ด๋ฒ ํ๋ก์ ํธ์์๋ ์ํ ์
๋ฌด์ ์์๋ ์๊ฐ์ `์์์ ์๋ 2๋ฒ์งธ ์๋ฆฌ๊น์ง๋ง` ๋ณด์ฌ์ฃผ๋ฉด ๋๋ ๊ฒ์ด๋ผ์, ์ ํฌ๋ numberFormatter ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ Double ํ์
์ extension ์ผ๋ก ์๋์ ๊ฐ์ ์ฐ์ฐ ํ๋กํผํฐ๋ฅผ ๊ตฌํํ๋๋ฐ์!
```swift
extension Double {
var roundedOff: String {
String(format: "%.2f", self)
}
}
```
๐๐ปโโ๏ธ NumberFormatter ๋ฅผ ๋ฐ๋์ ์ฌ์ฉํด์ผ ํ๋ ๊ฒ ์๋๋ผ๋ฉด, ์ด๋ฐ ์์ผ๋ก ๋์ฒดํ ์ ์๋ ํฌ๋งทํฐ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ๊ด์ฐฎ์ ๊ฑธ๊น์?
<br>
## ์๊ฒ๋ ์
### โ
1. CFAbsoluteTime ํ์
== Double ํ์
์
๋ฌด์ ์์๋ ์๊ฐ์ ๊ณ์ฐํ๊ธฐ ์ํด, ์์คํ
์ ํ์ฌ ์๊ฐ์ ๋ฐํํด์ฃผ๋ `CFAbsoluteTimeGetCurrent()` ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
์ด ๋ฉ์๋๋ `CFAbsoluteTime` ํ์
์ ๋ฐํํ๋๋ฐ์, ์ ํฌ๋ ์์์ ์๋ 2๋ฒ์งธ ์๋ฆฌ๊น์ง๋ง ๋ณด์ฌ์ฃผ๋ ์ฐ์ฐ ํ๋กํผํฐ๋ฅผ `Double` ํ์
์ extension ์ผ๋ก ๊ตฌํํด๋จ์์๋ ์ปดํ์ผ ์๋ฌ๊ฐ ๋์ง ์์์ต๋๋ค.
๊ทธ๋์ `CFAbsoluteTime` ํ์
์ ์ ์๋ฅผ ํ์ธํด๋ณด๋, `typealias` ๊ฐ 2๋ฒ ๊ฑธ๋ ค์, ๊ฒฐ๊ตญ `Double` ํ์
์ด์๋ค๋ ๊ฑธ ์ ์ ์์์ต๋๋ค.
```swift
// CFAbsoluteTime
typealias CFAbsoluteTime = CFTimeInterval
// CFTimeInterval
typealias CFTimeInterval = Double
```
<br>
### โ
2. ํน์ ์์์ ์๋ฆฌ ์ดํ๋ก '๋ฒ๋ฆฌ๋' String ์ด๋์
๋ผ์ด์
Double ํ์
์ ์ซ์ self ์์ ์์์ ์๋ 2์๋ฆฌ ๋ฏธ๋ง์ ๋ฒ๋ฆฌ๊ณ ์ถ์ ๋ `"%.2f"`๋ฅผ ์ ๋ฌ์ธ์๋ก ๋ฃ์ด์ฃผ๋ฉด ๊ฒฐ๊ตญ String ํ์
์ผ๋ก ๋ฐํ์์ผ์ฃผ๋ ์ด๋์
๋ผ์ด์ ๋ฅผ ํ์ฉํ์ต๋๋ค.
```swift
String(format: "%.2f", self)
// ์์
// 2.34567 -> 2.34
```
---
<br>
# STEP 1 - Queue ํ์
๊ตฌํ
## ๊ณ ๋ฏผํ ์
### โ๏ธ LinkedList ๊ตฌํ
1. **๋จ์ผ ์ฐ๊ฒฐ๋ฆฌ์คํธ / ์ด์ค ์ฐ๊ฒฐ๋ฆฌ์คํธ**
์ด์ค ์ฐ๊ฒฐ ๋ฆฌ์คํธ๋ก ๊ตฌํํ๋ฉด `์ค๊ฐ์ ์๋ ์์` ํ์ ์ ์๊ฐ๋ณต์ก๋์ ์ด์ต์ด ์์ต๋๋ค.
ํ์ง๋ง ์๊ตฌ์ฌํญ์ Queue ํ์
์์ ํ์๋ก ํ๋ ๊ธฐ๋ฅ๋ค์ `์ค๊ฐ ์์`๋ฅผ ํ์ํ ํ์๊ฐ ์๊ธฐ์ ๋จ์ผ ์ฐ๊ฒฐ๋ฆฌ์คํธ๋ก ๊ตฌํํ์ต๋๋ค.
2. **subscript ๊ตฌํ ์ฌ๋ถ**
1๋ฒ๊ณผ ๊ฐ์ ์ด์ ๋ก, ์ธ๋ฑ์ค๋ฅผ ํตํด `๊ฐ์ด๋ฐ ์์`์ ์ ๊ทผํ ํ์๊ฐ ์๊ธฐ์ ๊ตฌํํ์ง ์์์ต๋๋ค.
3. **Node ํ์
์ ์**
Node๋ LinkedList ๋ด๋ถ์์๋ง ์ฌ์ฉ๋๋ ํ์
์ด๊ธฐ์ `private Nested Type`์ผ๋ก ๊ตฌํํ์ต๋๋ค.
`next` ํ๋กํผํฐ๋ฅผ ํตํ์ฌ ์ฐธ์กฐ๋์ด์ผ ํ๊ธฐ์ `class`๋ก ๊ตฌํํ์ต๋๋ค.
4. **class / struct**
๋จ์ผ ์ฐ๊ฒฐ ๋ฆฌ์คํธ๋ `head`์ `tail`์ด๋ผ๋ Node๋ฅผ ์ ์ฅ ํ๋กํผํฐ๋ก ๊ฐ์ง๋๋ค.
์ ์ฅ๊ฐ์ด ๋ง์ง ์์ ๊ฐ๋ฒผ์ด ํ์
์ด๊ธฐ์ ๊ฐ ๋ณต์ฌ๊ฐ ์ผ์ด๋๋ ์ฑ๋ฅ์ ๋ฌธ์ ๊ฐ ์๋ค๊ณ ์๊ฐํ์ผ๋ฉฐ, ๊ตณ์ด ์ฐธ์กฐ ํ์
์ผ๋ก ๊ตฌํํ ์ด์ ๊ฐ ์๊ธฐ์ `struct`๋ก ๊ตฌํํ์์ต๋๋ค.
5. **tail**
`tail`์ ๊ตฌํํ๋ฉด ๋ง์ง๋ง์ `append`ํ ์์์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์๋ ์ด์ ์ด ์์ด์ `head` ์ด์ธ์ `tail`๋ ๊ตฌํํ์ต๋๋ค.
๋ํ, `tail`์ ์ฐ์ฐํ๋กํผํฐ๋ก ๊ตฌํํ๋ฉด ์ ๊ทผํ ๋๋ง๋ค O(n)์ ์ฐ์ฐ์ ํด ์ฃผ์ด์ผ ํ๋๋ฐ, ์ ์ฅ ํ๋กํผํฐ๋ก ๊ตฌํํ์ฌ ๋ฆฌ์คํธ์ ์์๊ฐ ์ถ๊ฐ๋ ๋๋ง๋ค `tail`์ ๊ฐฑ์ ํด์ฃผ๋ฉด `tail`์ ์ ๊ทผ ํ ๋ ์ฑ๋ฅ์ ์ด์ ์ด ์๋ค๊ณ ํ๋จํ์ฌ ์ ์ฅ ํ๋กํผํฐ๋ก ๊ตฌํํ์ต๋๋ค.
### โ๏ธ Queue ๊ตฌํ
1. ๊ตฌํํ LinkedList๋ฅผ ํ์ฉํ์ฌ ์๊ตฌ์ฌํญ๋๋ก `enqueue`, `dequeue`, `clear`, `peek`, `isEmpty` ๊ธฐ๋ฅ์ ๊ตฌํํ์ต๋๋ค.
์๊ตฌ์ฌํญ ์ธ์ ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํ์ง ์๋ ๊ฒ์ผ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
### โ๏ธ UnitTest ์์ฑ
1. **์ ๋ ํ
์คํธ์ ๋์**
์ ๋ ํ
์คํธ์ ๋์์ ์ธ๋ถ์์ ์ ๊ทผํ ์ ์๋ ์์์ ๋ํด์๋ง ์คํํ๋ ๊ฒ์ด ์ ์ ํ๋ค๊ณ ์๊ฐํ์ฌ, Node, LinkedList์ ๋ํ ํ
์คํธ๋ ์์ฑํ์ง ์๊ณ , ์ธ๋ถ์์ ์ฌ์ฉํ Queue ํ์
์ ๋ํด์๋ง ํ
์คํธ๋ฅผ ์์ฑ, ์ํํ์ต๋๋ค.
2. **Mock ์ธ์คํด์ค ํ์ฉ**
์ ๊ทผ์ ์ด๋ก ์ธํด Queue์ `list`์ ์ ๊ทผํ์ง ๋ชปํฉ๋๋ค.
๊ทธ๋ ๊ธฐ์ ์ ๊ทผ์ ์ด ์ค์ ์ ํ์ง ์์ MockQueue๋ฅผ ๋ง๋ค์ด ํ
์คํธ์ ํ์ฉํ์ต๋๋ค.
<br>
## ๐ค ๊ถ๊ธํ ์
### โ CFGetRetainCount ๋ฉ์๋๋ฅผ ํตํด ๋๋ฒ๊น
์ reference count๊ฐ ์ถ๊ฐ๋ก ํ๋ ๋ ํ์๋๋ ์ด์ ๊ฐ ๋ญ๊น์?

- list ๋ณ์์ enqueue ๋ฉ์๋๋ก 3๊ฐ์ ์์(1, 2, 3)๋ฅผ append ํด์ค ์ํ์์
- list ๋ฅผ ๋น์ฐ๋ removeAll() ๋ฉ์๋๊ฐ ์ ์๋ํ๋์ง ๊ฒ์ฆํ๊ธฐ ์ํด `RetainCount`๋ฅผ ์ง์ ํ์ธํด๋ณด๊ณ ์ถ์์ต๋๋ค.
- head, tail ๋ณ์์ `nil`์ ํ ๋นํ๊ธฐ ์ ์ breakpoint๋ฅผ ๊ฑธ๊ณ tail ์ `RetainCount` ์ฌ๋ณด๋ฉด `3๊ฐ`๊ฐ ๋์์ต๋๋ค.
- ์ ํฌ๊ฐ ์์ํ ๊ฑด `2๊ฐ`๊ฐ ์๋๋ฐ์, ์๋๋ฉด ์์ `Node().next`๊ฐ ํ๋๋ฅผ ์ฌ๋ ค์ฃผ๊ณ , ๋ณ์ tail ์ด ํ๋๋ฅผ ์ฌ๋ ค์ค๋ค๊ณ ์๊ฐํ์ด์.
- ๊ทธ๋ฌ๋ฉด ์ `3๊ฐ`๊ฐ ๋์ฌ๊น์? ๋๋จธ์ง 1๊ฐ๋ ์ด๋์ ๋์จ ๊ฑด์ง ๊ถ๊ธํ์ต๋๋ค.
๐๐ปโโ๏ธ ์ ํฌ ๊ฐ์ค์ `CFGetRetainCount` ๋ฉ์๋์์ tail์ ์ ๋ฌํ๊ธฐ ๋๋ฌธ์ ํ๋๊ฐ ๋ ๋์ด๋๋ค๋ ๊ฒ์ธ๋ฐ์, ์ ํฌ๊ฐ ์๊ฐํ ์ด์ ๊ฐ ๋ง๋์ง ๊ถ๊ธํฉ๋๋ค!
---
## ์๊ฒ ๋ ๋ด์ฉ
### โ
1. ๋ชจ๋ ํ๋กํผํฐ๊ฐ private ์ ๊ทผ์ ์ด์ธ ๊ตฌ์กฐ์ฒด์ ์ด๊ธฐํ
๊ตฌ์กฐ์ฒด๋ก ๊ตฌํํ `LinkedList` ํ์
์ `private` ์ ๊ทผ์ ์ด์ `์ต์
๋`์ด ๋ถ์ฌ๋ 2๊ฐ์ ์ธ์คํด์ค ํ๋กํผํฐ head, tail ์ ๊ฐ์ง๋๋ค.
```swift
private var head: Node<Element>?
private var tail: Node<Element>?
```
์ด๋, ์ธ๋ถ์์ `LinkedList` ํ์
์ ์ด๊ธฐํ๋ฅผ ์๋ํ๋ ๊ฒฝ์ฐ, ๋ฉค๋ฒ์์ด์ฆ ์ด๋์
๋ผ์ด์ ์์ ํ๋ผ๋ฏธํฐ๊ฐ ๋ณด์ด์ง ์์ต๋๋ค. (`private` ์ ๊ทผ์ ์ด๋ก ์ธํด ์ธ๋ถ์์ ํ๋กํผํฐ ์ ๊ทผ ๋ถ๊ฐ)
```swift
private var list: LinkedList<Element> = LinkedList()
```
๋ฐ๋ผ์ ์์ ๊ฐ์ด `list` ๋ณ์๋ฅผ ์ ์ธํ๋ฉด, head ์ tail ๋ชจ๋ ์๋์ผ๋ก `nil`์ด ํ ๋น๋ ์ฑ๋ก ์ด๊ธฐํ๋ฉ๋๋ค.
<br>
### โ
2. ์ ๋ ํ
์คํธ์์ sut ๋ณ์์ setUp, tearDown ๋ฉ์๋ ํ์ฉ
- `sut` = System Under Test
```swift
var sut: MockQueue<String>!
override func setUpWithError() throws {
try super.setUpWithError()
sut = MockQueue()
}
override func tearDownWithError() throws {
try super.tearDownWithError()
sut = nil
}
```
MockQueue ํ์
์ ๊ฐ๋ `sut` ๋ณ์๋ฅผ `!(์์์ ์ถ์ถ ์ต์
๋)`์ ๋ถ์ฌ ์ ์ธํฉ๋๋ค.
`setUpWithError` ๋ฉ์๋๋ ๊ฐ๊ฐ์ ํ
์คํธ ๋ฉ์๋๊ฐ ์คํ๋๊ธฐ ์ ์ `sut` ๋ณ์์ MockQueue ํ์
์ ์ด๊ธฐํํด์ ํ ๋นํฉ๋๋ค.
`tearDownWithError` ๋ฉ์๋๋ ๊ฐ๊ฐ์ ํ
์คํธ ๋ฉ์๋๊ฐ ์คํ๋ ์ดํ `sut` ๋ณ์์ `nil`์ ํ ๋นํ์ฌ ๋ค์ ํ
์คํธ๋ฅผ ๋์ผํ ์กฐ๊ฑด์์ ์คํํ ์ ์๋๋ก ๋ง๋ญ๋๋ค.
<br>
### โ
3. TestDouble
> ํ
์คํธ ๋๋ธ์ด๋ ํ
์คํธ๋ฅผ ์งํํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ ์ด๋ฅผ ๋์ ํ์ฌ ํ
์คํธ๋ฅผ ์งํํ ์ ์๋๋ก ๋ง๋ค์ด์ฃผ๋ ๊ฐ์ฒด์
๋๋ค. ํ
์คํธ ๋๋ธ์๋ Dummy, Stub, Fake, Spy, Mock ๋ฑ์ด ์์ต๋๋ค. ๊ฐ๊ฐ ์ญํ ์ด ๋ค๋ฅด์ง๋ง, ๋ช
ํํ๊ฒ ๊ตฌ๋ถ๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ๊ทธ๋ ๊ธฐ์ ๋ค๋ฅธ ํ
์คํธ ๋๋ธ์ ์์ธ๋ฌ Mock์ผ๋ก ํต์นญํ๊ธฐ๋ ํฉ๋๋ค.
>
> ์ถ์ฒ: [Test Double - ์ผ๊ณฐ๋ท๋ท, by. ์ค๋๋๋ฌด](https://yagom.net/courses/unit-test-%ec%9e%91%ec%84%b1%ed%95%98%ea%b8%b0/lessons/%ed%85%8c%ec%8a%a4%ed%8a%b8%eb%a5%bc-%ec%9c%84%ed%95%9c-%ea%b0%9d%ec%b2%b4-%eb%a7%8c%eb%93%a4%ea%b8%b0/topic/test-double/)
์ ํฌ `MockQueue`์ ๊ฒฝ์ฐ์๋ ์ฌ์ค์ Stub์ ๊ฐ๊น์ด ์ญํ ์ธ ๊ฒ ๊ฐ์ง๋ง, ์ญ์ ๋ช
ํํ ๋ค์ด๋ง๋์ง๋ ๋ชจ๋ฅด๊ฒ ๊ธฐ์ Mock์ผ๋ก ์ด๋ฆ ์ง์์ต๋๋ค.
<br>
### โ
4. CFGetRetainCount
LLDB์์ `po CFGetRetainCount(๊ฐ์ฒด)` ๋ช
๋ น์ด๋ก ํด๋์ค ์ธ์คํด์ค์ `RetainCount(์ฐธ์กฐ ํ์)`๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
<p align="center"><img src="https://i.imgur.com/CWdXNeO.png)" width="40%"></p>
<br>
### โ
5. breakpoint ์๋ ์์
ํน์ ์ฝ๋ ๋ผ์ธ์ breakpoint ๋ฅผ ๊ฑธ์ด๋จ๋ค๋ฉด, ๊ทธ ๋ผ์ธ์ ์ฝ๋๊ฐ ์๋ํ๊ธฐ `์ ์` breakpoint ๊ฐ ์๋ํฉ๋๋ค.