---
# System prepended metadata

title: 코드수정중

---

# Model

## Bank
```swift
//
//  Bank.swift
//  BankManagerConsoleApp
//
//  Created by kyungmin, Max on 2023/07/12.
//

import Foundation

class Bank {
    private var depositBankerQueue = OperationQueue()
    private var loanBankerQueue = OperationQueue()
    private var dailyCustomerQueue = CustomerQueue<Customer>()
    private var dailyTotalCustomer: Int = .zero
    
    var delegate: BankManagerViewController?
    
    init() {
        depositBankerQueue.maxConcurrentOperationCount = Configuration.numberOfDepositBanker
        loanBankerQueue.maxConcurrentOperationCount = Configuration.numberOfLoanBanker
    }
    
    func work() {
        let totalCustomer = setUpDailyCustomerNumber()
        
        setDailyCustomerQueue(totalCustomer)
        
        while let customer = dailyCustomerQueue.dequeue() {
            delegate?.addWaitingQueue(customer)
            addCustomerTask(customer)
        }
    }
    
    func work(_ customerNumber: Int)  {
        setDailyCustomerQueue(customerNumber)

        while let customer = dailyCustomerQueue.dequeue() {
            addCustomerTask(customer)
            delegate?.addWaitingQueue(customer)
        }
    }
    
    private func setUpDailyCustomerNumber() -> Int {
        let totalCustomer = Int.random(
            in: Configuration.minimumCustomer...Configuration.maximumCustomer
        )
        
        return totalCustomer
    }
    
    func setDailyCustomerQueue(_ totalCustomer: Int) {
        for _ in 1...totalCustomer {
            guard let work = Bank.Work.allCases.randomElement() else {
                continue
            }
            
            dailyTotalCustomer += 1
            
            let customer = Customer(purpose: work.name, duration: work.duration, waitingNumber: dailyTotalCustomer)
            
            dailyCustomerQueue.enqueue(customer)
        }
    }
    
    private func addCustomerTask(_ customer: Customer) {
        let task = BlockOperation {
            self.delegate?.moveCustomerToProcessQueue(customer)
            Thread.sleep(forTimeInterval: customer.duration)
            self.delegate?.popProcessingQueue(customer)
        }
        
        if customer.purpose == Work.deposit.name {
            depositBankerQueue.addOperation(task)
        } else {
            loanBankerQueue.addOperation(task)
        }
    }

    private func close(_ dailyBusinessHour: CFAbsoluteTime) {
        print(String(format: Namespace.closingMessage, dailyTotalCustomer, dailyBusinessHour))
        reset()
    }
    
    func reset()  {
        depositBankerQueue.cancelAllOperations()
        loanBankerQueue.cancelAllOperations()
        dailyCustomerQueue.clear()
        dailyTotalCustomer = .zero
        
        self.delegate?.resetUI()
    }
}

extension Bank {
    enum Configuration {
        static let numberOfDepositBanker = 2
        static let numberOfLoanBanker = 1
        static let minimumCustomer = 10
        static let maximumCustomer = 30
    }
}

extension Bank {
    enum Namespace {
        static let startTask = "%d번 고객 %@업무 시작"
        static let endTask = "%d번 고객 %@업무 완료"
        static let closingMessage = "업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 %d명이며, 총 업무시간은 %.2f초입니다."
    }
}

extension Bank {
    enum Work: CaseIterable {
        case deposit
        case loan
        
        var duration: Double {
            switch self {
            case .deposit:
                return 0.7
            case .loan:
                return 1.1
            }
        }
        
        var name: String {
            switch self {
            case .deposit:
                return "예금"
            case .loan:
                return "대출"
            }
        }
    }
}

```



## Customer
```swift
//
//  Customer.swift
//  BankManagerConsoleApp
//
//  Created by kyungmin, Max on 2023/07/12.
//

import Foundation

struct Customer: Equatable, Hashable {
    let purpose: String
    let duration: Double
    let waitingNumber: Int
}

```



## BankManager
```swift
//
//  BankManager.swift
//  BankManagerConsoleApp
//
//  Created by kyungmin, Max on 2023/07/10.
//

struct BankManager {
    var bank: Bank
    
    mutating func work() async {
        while selectMenu() == .open {
            await bank.work()
        }
    }
    
    func selectMenu() -> Menu {
        Menu.displayMenu()
        print(Namespace.inputLabel, terminator: Namespace.inputTerminater)
        
        guard let inputMenu = readLine(),
              inputMenu == Menu.open.menuNumber else {
            return Menu.finish
        }
        
        return Menu.open
    }
}

extension BankManager {
    enum Namespace {
        static let inputLabel = "입력 : "
        static let inputTerminater = ""
    }
}

```


# 화면


## BankManagerViewController
```swift
//
//  BankManagerUIApp - ViewController.swift
//  Created by yagom. 
//  Copyright © yagom academy. All rights reserved.
// 

import UIKit


protocol BankManagerViewControllerDelegate {
    func addWaitingQueue(_ customer: Customer)
    func moveCustomerToProcessQueue(_ customer: Customer)
    func popProcessingQueue(_ customer: Customer)
    func resetUI()
}


class BankManagerViewController: UIViewController {
    private var bank = Bank()
    private var waitingDictionary: Dictionary<Customer, CustomerView> = [:]
    private var processingDictionary: Dictionary<Customer, CustomerView> = [:]

    var startWorkTime: Date?
    var timer: Timer?
    var timeInterval: Double = .zero
    
    private let outerStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.spacing = SpacingSetting.outStatckView
        
        return stackView
    }()
    
    private let emptyWaitingView = CustomerView()
    private let emptyProcessingView = CustomerView()
    
    private let queueNameStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        
        let waitingLabel = UILabel()
        waitingLabel.text = Namespace.waiting
        waitingLabel.backgroundColor = .systemGreen
        waitingLabel.textColor = .white
        waitingLabel.textAlignment = .center
        waitingLabel.font = UIFont.preferredFont(forTextStyle: .title1)
        
        let processingLabel = UILabel()
        processingLabel.text = Namespace.processing
        processingLabel.backgroundColor = .systemBlue
        processingLabel.textColor = .white
        processingLabel.textAlignment = .center
        processingLabel.font = UIFont.preferredFont(forTextStyle: .title1)
        
        stackView.addArrangedSubview(waitingLabel)
        stackView.addArrangedSubview(processingLabel)
        
        processingLabel.widthAnchor.constraint(equalTo: waitingLabel.widthAnchor).isActive = true
        
        return stackView
    }()
    
    private let queueStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        
        return stackView
    }()
    
    private let menuStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        
        return stackView
    }()
    
    private let waitingStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = SpacingSetting.waitingStackView
        
        return stackView
    }()
    
    private let processingStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = SpacingSetting.waitingStackView
        
        return stackView
    }()
    
    private let addCustomerButton: UIButton = {
        let button = UIButton()
        button.setTitle(Namespace.addCustomer, for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        
        return button
    }()
    
    private let resetButton: UIButton = {
        let button = UIButton()
        button.setTitle(Namespace.reset, for: .normal)
        button.setTitleColor(.systemRed, for: .normal)
        
        return button
    }()
    
    private let timerLabel: UILabel = {
        let label = UILabel()

        label.textAlignment = .center
        
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground

        configureUI()
        bank.delegate = self
        resetUI()
    }
    
    @objc private func didTapAddCustomer() {
        guard let isTimerOn = timer?.isValid else {
            return
        }
        if !isTimerOn {
            startWorkTime = Date() - timeInterval
            timer = Timer.scheduledTimer(timeInterval: Configuration.timeIntervalRepeat, target: self, selector: #selector(updateTimerLabel), userInfo: nil, repeats: true)
        }
        
        bank.work(Configuration.addCustomerAmount)
    }
    
    @objc private func didTapReset() {
        bank.reset()
    }
}

extension BankManagerViewController {
    private func configureUI() {
        let safeArea = view.safeAreaLayoutGuide
        
        view.addSubview(outerStackView)
        
        menuStackView.addArrangedSubview(addCustomerButton)
        menuStackView.addArrangedSubview(resetButton)
        
        queueStackView.addArrangedSubview(waitingStackView)
        queueStackView.addArrangedSubview(processingStackView)
        
        waitingStackView.addArrangedSubview(emptyWaitingView)
        processingStackView.addArrangedSubview(emptyProcessingView)
        
        outerStackView.addArrangedSubview(menuStackView)
        outerStackView.addArrangedSubview(timerLabel)
        outerStackView.addArrangedSubview(queueNameStackView)
        outerStackView.addArrangedSubview(queueStackView)

        addCustomerButton.addTarget(self, action: #selector(didTapAddCustomer), for: .touchUpInside)
        
        resetButton.addTarget(self, action: #selector(didTapReset), for: .touchUpInside)
        
        NSLayoutConstraint.activate([
            outerStackView.topAnchor.constraint(equalTo: safeArea.topAnchor),
            outerStackView.leftAnchor.constraint(equalTo: view.leftAnchor),
            outerStackView.heightAnchor.constraint(equalTo: safeArea.heightAnchor),
            outerStackView.widthAnchor.constraint(equalTo: view.widthAnchor),
            
            resetButton.widthAnchor.constraint(equalTo: addCustomerButton.widthAnchor),
            waitingStackView.widthAnchor.constraint(equalTo: processingStackView.widthAnchor)
        ])
        
        menuStackView.setContentHuggingPriority(.init(rawValue: HuggingSetting.menuStackView), for: .vertical)
        timerLabel.setContentHuggingPriority(.init(rawValue: HuggingSetting.timerLabel), for: .vertical)
        queueNameStackView.setContentHuggingPriority(.init(rawValue: HuggingSetting.queueNameStackView), for: .vertical)
        queueStackView.setContentHuggingPriority(.init(rawValue: HuggingSetting.queueStackView), for: .vertical)
        emptyWaitingView.setContentHuggingPriority(.init(rawValue: HuggingSetting.emptyWaitingView), for: .vertical)
        emptyProcessingView.setContentHuggingPriority(.init(rawValue: HuggingSetting.emptyWaitingView), for: .vertical)
        
        timerLabel.text = String(format: Namespace.timer, timeInterval.formatTimeIntervalToString())
    }
}


extension BankManagerViewController {
    @objc private func updateTimerLabel() {
        guard let startTime = startWorkTime else {
            return
        }
        timeInterval = Date().timeIntervalSince(startTime)
        timerLabel.text = String(format: Namespace.timer, timeInterval.formatTimeIntervalToString())
    }
    
    private func pauseTimer() {
        timer?.invalidate()
    }
}


extension BankManagerViewController: BankManagerViewControllerDelegate {
    func addWaitingQueue(_ customer: Customer) {
        let task = BlockOperation {
            let customerView = CustomerView()
            customerView.configureUI(waitingNumber: customer.waitingNumber, purpose: customer.purpose)
            
            self.emptyWaitingView.removeFromSuperview()
            self.waitingStackView.addArrangedSubview(customerView)
            self.waitingDictionary[customer] = customerView
            self.waitingStackView.addArrangedSubview(self.emptyWaitingView)
        }

        OperationQueue.main.addOperation(task)
    }
    
    func moveCustomerToProcessQueue(_ customer: Customer) {
        if waitingDictionary[customer] == nil {
            OperationQueue.main.waitUntilAllOperationsAreFinished()
        }
        
        guard let customerView = waitingDictionary[customer] else {
            return
        }

        OperationQueue.main.addOperation {
            customerView.removeFromSuperview()
            self.waitingDictionary[customer] = nil
            self.emptyProcessingView.removeFromSuperview()
            self.processingStackView.addArrangedSubview(customerView)
            self.processingDictionary[customer] = customerView
            self.processingStackView.addArrangedSubview(self.emptyProcessingView)
        }
    }
    
    func popProcessingQueue(_ customer: Customer) {
        guard let customerView = processingDictionary[customer] else {
            return
        }
        
        OperationQueue.main.addOperation {
            customerView.removeFromSuperview()
            self.processingDictionary[customer] = nil

            if self.processingDictionary.isEmpty && self.waitingDictionary.isEmpty {
                self.pauseTimer()
            }
        }
    }
    
    func resetUI() {
        for subView in processingStackView.arrangedSubviews {
            subView.removeFromSuperview()
        }
        
        for subView in waitingStackView.arrangedSubviews {
            subView.removeFromSuperview()
        }
        
        processingDictionary = [:]
        waitingDictionary = [:]
        
        timeInterval = .zero
        timerLabel.text = String(format: Namespace.timer, timeInterval.formatTimeIntervalToString())
        
        startWorkTime = Date()
        
        guard let isTimerOn = timer?.isValid, isTimerOn else {
            timer = Timer.scheduledTimer(timeInterval: Configuration.timeIntervalRepeat, target: self, selector: #selector(updateTimerLabel), userInfo: nil, repeats: true)
            
            bank.work()
            return
        }
        
        bank.work()
    }
}

extension BankManagerViewController {
    enum Configuration {
        static let addCustomerAmount = 10
        static let timeIntervalRepeat = 0.001
    }
    
    enum Namespace {
        static let waiting = "대기중"
        static let processing = "업무중"
        static let addCustomer = "고객 \(Configuration.addCustomerAmount)명 추가"
        static let reset = "초기화"
        static let timer = "업무시간 - %@"
    }
    
    enum SpacingSetting {
        static let outStatckView: CGFloat = 10
        static let waitingStackView: CGFloat = 5
    }
    
    enum HuggingSetting {
        static let menuStackView: Float = 999
        static let timerLabel: Float = 800
        static let queueNameStackView: Float = 700
        static let queueStackView: Float = 500
        static let emptyWaitingView: Float = 1
    }
}



extension Double {
    func formatTimeIntervalToString() -> String {
        let minutes = String(format: "%02d", Int((self/60).truncatingRemainder(dividingBy: 60)))
        let seconds = String(format: "%02d", Int(self.truncatingRemainder(dividingBy: 60)))
        let milliseconds = String(format: "%003d", Int(self.truncatingRemainder(dividingBy: 1) * 1000))

        return "\(minutes):\(seconds):\(milliseconds)"
    }
}

```


## CustomerView

```swift
//
//  CustomerView.swift
//  BankManagerUIApp
//
//  Created by Min Hyun on 2023/07/19.
//

import UIKit

class CustomerView: UILabel {
    func configureUI(waitingNumber: Int, purpose: String) {
        self.text = "\(waitingNumber) - \(purpose)"
        self.textColor = .black
        self.textAlignment = .center
        
        if purpose == Bank.Work.loan.name {
            self.textColor = .systemPurple
        }
    }
}

```


## SceneDelegate
```swift
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: windowScene)
        let mainViewController = BankManagerViewController()
        window?.rootViewController = mainViewController
        window?.makeKeyAndVisible()
    }
```
