# 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() } ```