diff --git a/BankManager.swift b/BankManager.swift deleted file mode 100644 index 90347121..00000000 --- a/BankManager.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// BankManager.swift -// Created by yagom. -// Copyright © yagom academy. All rights reserved. -// - -import Foundation diff --git a/BankManagerConsoleApp/.swiftlint.yml b/BankManagerConsoleApp/.swiftlint.yml index 34a28e8e..c18004e9 100644 --- a/BankManagerConsoleApp/.swiftlint.yml +++ b/BankManagerConsoleApp/.swiftlint.yml @@ -3,6 +3,8 @@ disabled_rules: - trailing_whitespace - trailing_comma - class_delegate_protocol +- sorted_imports +- closure_parameter_position force_unwrapping: severity: error diff --git a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj index 6d9e698e..73c3e8e5 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj +++ b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj @@ -7,15 +7,14 @@ objects = { /* Begin PBXBuildFile section */ - 2733E4FA2B5F9FDB00785607 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2733E4F92B5F9FDB00785607 /* LinkedList.swift */; }; - 2733E4FC2B5FA00300785607 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2733E4FB2B5FA00300785607 /* Queue.swift */; }; - 2733E4FD2B5FA11800785607 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2733E4F92B5F9FDB00785607 /* LinkedList.swift */; }; - 2733E4FE2B5FA11B00785607 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2733E4FB2B5FA00300785607 /* Queue.swift */; }; C7694E7A259C3EC00053667F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E79259C3EC00053667F /* main.swift */; }; - C7D65D1B259C8190005510E0 /* BankManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D65D1A259C8190005510E0 /* BankManager.swift */; }; - F447FC3A2B5E8053002A2692 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = F447FC392B5E8053002A2692 /* Node.swift */; }; + F46FA2A42B63A46A00787C3F /* BankManagerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FA2A32B63A46A00787C3F /* BankManagerApp.swift */; }; + F46FA2A82B66309000787C3F /* ConsoleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FA2A72B66309000787C3F /* ConsoleManager.swift */; }; + F46FA2AA2B6630B200787C3F /* TextInputReadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FA2A92B6630B200787C3F /* TextInputReadable.swift */; }; + F46FA2AC2B6630CF00787C3F /* BankManagerAppMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FA2AB2B6630CF00787C3F /* BankManagerAppMenu.swift */; }; + F46FA2AE2B6630E300787C3F /* IOError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F46FA2AD2B6630E300787C3F /* IOError.swift */; }; + F4EC3C292B6784BB006C9D11 /* TextOutputDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EC3C282B6784BB006C9D11 /* TextOutputDisplayable.swift */; }; F4EDF9F72B5F749400C3E54C /* QueueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EDF9F62B5F749400C3E54C /* QueueTest.swift */; }; - F4EDFA002B5F758400C3E54C /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = F447FC392B5E8053002A2692 /* Node.swift */; }; F4EDFA022B5FADF200C3E54C /* LinkedListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EDFA012B5FADF200C3E54C /* LinkedListTest.swift */; }; /* End PBXBuildFile section */ @@ -32,13 +31,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2733E4F92B5F9FDB00785607 /* LinkedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedList.swift; sourceTree = ""; }; - 2733E4FB2B5FA00300785607 /* Queue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; C7694E76259C3EC00053667F /* BankManagerConsoleApp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BankManagerConsoleApp; sourceTree = BUILT_PRODUCTS_DIR; }; C7694E79259C3EC00053667F /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - C7D65D1A259C8190005510E0 /* BankManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BankManager.swift; path = ../../BankManager.swift; sourceTree = ""; }; F447FC382B5E50C7002A2692 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; - F447FC392B5E8053002A2692 /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; + F46FA2A32B63A46A00787C3F /* BankManagerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankManagerApp.swift; sourceTree = ""; }; + F46FA2A72B66309000787C3F /* ConsoleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleManager.swift; sourceTree = ""; }; + F46FA2A92B6630B200787C3F /* TextInputReadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputReadable.swift; sourceTree = ""; }; + F46FA2AB2B6630CF00787C3F /* BankManagerAppMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankManagerAppMenu.swift; sourceTree = ""; }; + F46FA2AD2B6630E300787C3F /* IOError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOError.swift; sourceTree = ""; }; + F4EC3C282B6784BB006C9D11 /* TextOutputDisplayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextOutputDisplayable.swift; sourceTree = ""; }; F4EDF9F42B5F749400C3E54C /* BankManagerConsoleAppTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BankManagerConsoleAppTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F4EDF9F62B5F749400C3E54C /* QueueTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTest.swift; sourceTree = ""; }; F4EDFA012B5FADF200C3E54C /* LinkedListTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedListTest.swift; sourceTree = ""; }; @@ -62,44 +63,65 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - C7694E6D259C3EC00053667F = { + 2776CBDA2B6B8C1F00EF7155 /* Utils */ = { isa = PBXGroup; children = ( - F447FC382B5E50C7002A2692 /* .swiftlint.yml */, - C7694E78259C3EC00053667F /* BankManagerConsoleApp */, - F4EDF9F52B5F749400C3E54C /* BankManagerConsoleAppTest */, - C7694E77259C3EC00053667F /* Products */, + F4EC3C322B6A5B82006C9D11 /* Queue.swift */, + 2776CBD62B6B8BE900EF7155 /* LinkedList.swift */, + 2776CBD82B6B8C1400EF7155 /* Node.swift */, ); + path = Utils; sourceTree = ""; }; - C7694E77259C3EC00053667F /* Products */ = { + 2776CBDB2B6B8C8C00EF7155 /* App */ = { isa = PBXGroup; children = ( - C7694E76259C3EC00053667F /* BankManagerConsoleApp */, - F4EDF9F42B5F749400C3E54C /* BankManagerConsoleAppTest.xctest */, + F4EC3C4F2B6B6701006C9D11 /* BankManagerApp.swift */, + 2776CBD22B6B852F00EF7155 /* BankManagerAppMenu.swift */, + F475FC6B2B6CC0EF00B0E859 /* BankManagerAppError.swift */, ); - name = Products; + path = App; sourceTree = ""; }; - C7694E78259C3EC00053667F /* BankManagerConsoleApp */ = { + 2776CBDC2B6B8CB400EF7155 /* View */ = { isa = PBXGroup; children = ( - C7D65D1A259C8190005510E0 /* BankManager.swift */, C7694E79259C3EC00053667F /* main.swift */, - F447FC392B5E8053002A2692 /* Node.swift */, - 2733E4F92B5F9FDB00785607 /* LinkedList.swift */, - 2733E4FB2B5FA00300785607 /* Queue.swift */, + F4EC3C2A2B678562006C9D11 /* App */, + F4EC3C272B678463006C9D11 /* View */, + ); + path = View; + sourceTree = ""; + }; + F4EC3C272B678463006C9D11 /* View */ = { + isa = PBXGroup; + children = ( + C7694E78259C3EC00053667F /* BankManagerConsoleApp */, + C7694E77259C3EC00053667F /* Products */, + ); + sourceTree = ""; + }; + C7694E77259C3EC00053667F /* Products */ = { + isa = PBXGroup; + children = ( + C7694E76259C3EC00053667F /* BankManagerConsoleApp */, ); - path = BankManagerConsoleApp; + name = Products; sourceTree = ""; }; F4EDF9F52B5F749400C3E54C /* BankManagerConsoleAppTest */ = { isa = PBXGroup; children = ( - F4EDF9F62B5F749400C3E54C /* QueueTest.swift */, - F4EDFA012B5FADF200C3E54C /* LinkedListTest.swift */, + F4EC3C3A2B6A6C18006C9D11 /* BankerEnqueuable.swift */, + F4EC3C3E2B6A6CC3006C9D11 /* BankRunnable.swift */, + F4EC3C402B6A6CE6006C9D11 /* TaskManagable.swift */, + F4EC3C442B6A6D15006C9D11 /* ClientTaskHandlable.swift */, + F4EC3C482B6A70E7006C9D11 /* ClientEnqueuable.swift */, + 2776CBD02B6B844F00EF7155 /* TextInputReadable.swift */, + F4EC3C4A2B6A71E0006C9D11 /* TextOutputDisplayable.swift */, + F4EC3C532B6B7FAE006C9D11 /* TicketProvidable.swift */, ); - path = BankManagerConsoleAppTest; + path = Protocol; sourceTree = ""; }; /* End PBXGroup section */ @@ -137,7 +159,6 @@ ); name = BankManagerConsoleAppTest; productName = BankManagerConsoleAppTest; - productReference = F4EDF9F42B5F749400C3E54C /* BankManagerConsoleAppTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -214,11 +235,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2733E4FC2B5FA00300785607 /* Queue.swift in Sources */, - F447FC3A2B5E8053002A2692 /* Node.swift in Sources */, + F46FA2AC2B6630CF00787C3F /* BankManagerAppMenu.swift in Sources */, + F46FA2AE2B6630E300787C3F /* IOError.swift in Sources */, + F46FA2AA2B6630B200787C3F /* TextInputReadable.swift in Sources */, + F46FA2A42B63A46A00787C3F /* BankManagerApp.swift in Sources */, C7694E7A259C3EC00053667F /* main.swift in Sources */, - 2733E4FA2B5F9FDB00785607 /* LinkedList.swift in Sources */, - C7D65D1B259C8190005510E0 /* BankManager.swift in Sources */, + F4EC3C292B6784BB006C9D11 /* TextOutputDisplayable.swift in Sources */, + F46FA2A82B66309000787C3F /* ConsoleManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -226,10 +249,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2733E4FD2B5FA11800785607 /* LinkedList.swift in Sources */, F4EDF9F72B5F749400C3E54C /* QueueTest.swift in Sources */, - F4EDFA002B5F758400C3E54C /* Node.swift in Sources */, - 2733E4FE2B5FA11B00785607 /* Queue.swift in Sources */, F4EDFA022B5FADF200C3E54C /* LinkedListTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/xcshareddata/xcschemes/BankManagerConsoleAppTest.xcscheme b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/xcshareddata/xcschemes/BankManagerConsoleAppTest.xcscheme index 4612ab25..cc10f137 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/xcshareddata/xcschemes/BankManagerConsoleAppTest.xcscheme +++ b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/xcshareddata/xcschemes/BankManagerConsoleAppTest.xcscheme @@ -19,7 +19,7 @@ diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerApp.swift b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerApp.swift new file mode 100644 index 00000000..91ba1add --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerApp.swift @@ -0,0 +1,47 @@ +// +// BankManagerApp.swift +// BankManagerConsoleApp +// +// Created by Effie on 2/1/24. +// + +final class BankManagerApp { + func start() { + startBank() + } +} + +private extension BankManagerApp { + func startBank() { + let tasks: [BankTask: ClientQueueManagable] = [ + .loan: ClientManager(), + .deposit: ClientManager(), + ] + + let orders: [BankTask: Int] = [.loan: 2, .deposit: 3] + let bankers = makeBankers( + tasks: tasks, + orders: orders + ) + + BankManager( + bankers: bankers, + clientManager: tasks + ).start() + } + + func makeBankers( + tasks: [BankTask: any ClientQueueManagable], + orders: [BankTask: Int] + ) -> [Banker] { + var result = [Banker]() + for (type, bankerCount) in orders { + (1...bankerCount).forEach { _ in + guard let clientManager = tasks[type] else { return } + let banker = Banker(clientManager: clientManager) + result.append(banker) + } + } + return result + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppError.swift b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppError.swift new file mode 100644 index 00000000..a5a76da5 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppError.swift @@ -0,0 +1,10 @@ +// +// BankManagerAppError.swift +// BankManagerConsoleApp +// +// Created by Effie on 2/2/24. +// + +enum BankManagerAppError: Error { + case outOfIndex +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppMenu.swift b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppMenu.swift new file mode 100644 index 00000000..6bb54d0b --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/App/BankManagerAppMenu.swift @@ -0,0 +1,55 @@ +// +// BankManagerAppMenu.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 2/1/24. +// + +enum BankManagerAppMenu { + case open + case end + + private var menuNumebr: Int { + switch self { + case .open: return 1 + case .end: return 2 + } + } + + private var description: String { + switch self { + case .open: return "은행 개점" + case .end: return "종료" + } + } + + private init?(number: Int) { + switch number { + case Self.open.menuNumebr: self = .open + case Self.end.menuNumebr: self = .end + default: return nil + } + } + + init(input: String) throws { + let trimmedInput = input.trimmingCharacters(in: .whitespacesAndNewlines) + guard + trimmedInput.isEmpty == false, + let number = Int(input), + let menu = Self.init(number: number) + else { + throw IOError.invalidInput + } + self = menu + } +} + +extension BankManagerAppMenu: CaseIterable { + static var allMenusPrompt: String { + return allCases.enumerated().reduce(into: "") { result, indexAndMenu in + let adding = "\(indexAndMenu.element.menuNumebr) : \(indexAndMenu.element.description)" + let terminator = (indexAndMenu.offset + 1 == allCases.count) ? "" : "\n" + result += adding + terminator + } + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Model/BankManager.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Model/BankManager.swift new file mode 100644 index 00000000..079b5411 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Model/BankManager.swift @@ -0,0 +1,69 @@ +// +// BankManager.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import Foundation + +struct BankManager { + private let textOut: TextOutputDisplayable + + private let dispenser: TicketDispenser + + init( + textOut: TextOutputDisplayable, + dispenser: TicketDispenser + ) { + self.textOut = textOut + self.dispenser = dispenser + } +} + +extension BankManager: BankRunnable { + func runBank(with orders: [Order], numberOfClient: Int) { + let group = DispatchGroup() + let totalWorkTime = measure { + for order in orders { + let taskManager = TaskManager() + makeClients(order: order, taskManager: taskManager) + makeBankers(order: order, taskManager: taskManager) + taskManager.startTaskManaging(group: group) + } + group.wait() + } + + summarizeDailyStatistics( + totalWorkTime: totalWorkTime, + numberOfClient: numberOfClient + ) + } +} + +private extension BankManager { + func makeBankers(order: Order, taskManager: TaskManager) { + (1...order.bankerCount).forEach { _ in + let banker = Banker(bankerEnqueuable: taskManager, resultOut: self.textOut) + taskManager.enqueueBanker(banker) + } + } + + func makeClients(order: Order, taskManager: TaskManager) { + while let number = self.dispenser.provideTicket(of: order.taskType) { + let client = Client(number: number, task: order.taskType) + taskManager.enqueueClient(client) + } + } + + func measure(_ progress: () -> Void) -> TimeInterval { + let start = Date() + progress() + return Date().timeIntervalSince(start) + } + + func summarizeDailyStatistics(totalWorkTime: Double, numberOfClient: Int) { + let roundedWorkTimeString = String(format: "%.2f", totalWorkTime) + let output = "업무가 마감되었습니다. 오늘 업무를 처리한 고객은 총 \(numberOfClient)명이며, 총 업무시간은 \(roundedWorkTimeString)초입니다." + self.textOut.display(output: output) + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Model/Banker.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Model/Banker.swift new file mode 100644 index 00000000..eff747c0 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Model/Banker.swift @@ -0,0 +1,37 @@ +// +// Banker.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +import Foundation + +struct Banker { + private let bankerEnqueuable: BankerEnqueuable + + private let resultOut: TextOutputDisplayable + + init( + bankerEnqueuable: BankerEnqueuable, + resultOut: TextOutputDisplayable + ) { + self.bankerEnqueuable = bankerEnqueuable + self.resultOut = resultOut + } +} + +extension Banker: ClientTaskHandlable { + func handle(client: Client, group: DispatchGroup) { + DispatchQueue.global().async(group: group) { + resultOut.display(output: "\(client.number)번 고객 \(client.task.name) 시작") + processTask(for: client.task.duration) + resultOut.display(output: "\(client.number)번 고객 \(client.task.name) 종료") + self.bankerEnqueuable.enqueueBanker(self) + } + } + + private func processTask(for duration: TimeInterval) { + Thread.sleep(forTimeInterval: duration) + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankRunnable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankRunnable.swift new file mode 100644 index 00000000..28d44d6f --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankRunnable.swift @@ -0,0 +1,10 @@ +// +// BankRunnable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +protocol BankRunnable { + func runBank(with orders: [Order], numberOfClient: Int) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankerEnqueuable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankerEnqueuable.swift new file mode 100644 index 00000000..d3d56025 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/BankerEnqueuable.swift @@ -0,0 +1,10 @@ +// +// BankerEnqueuable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +protocol BankerEnqueuable { + func enqueueBanker(_ banker: ClientTaskHandlable) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientEnqueuable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientEnqueuable.swift new file mode 100644 index 00000000..de1f3c62 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientEnqueuable.swift @@ -0,0 +1,10 @@ +// +// ClientEnqueuable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +protocol ClientEnqueuable { + func enqueueClient(_ client: Client) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientTaskHandlable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientTaskHandlable.swift new file mode 100644 index 00000000..3d18c0b8 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/ClientTaskHandlable.swift @@ -0,0 +1,12 @@ +// +// ClientTaskHandlable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +import Foundation + +protocol ClientTaskHandlable { + func handle(client: Client, group: DispatchGroup) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TaskManagable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TaskManagable.swift new file mode 100644 index 00000000..08587b9e --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TaskManagable.swift @@ -0,0 +1,12 @@ +// +// TaskManagable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +import Foundation + +protocol TaskManagable { + func startTaskManaging(group: DispatchGroup) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextInputReadable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextInputReadable.swift new file mode 100644 index 00000000..af458bbc --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextInputReadable.swift @@ -0,0 +1,10 @@ +// +// TextInputReadable.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 2/1/24. +// + +protocol TextInputReadable { + func readInput(prompt: String?) throws -> String +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextOutputDisplayable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextOutputDisplayable.swift new file mode 100644 index 00000000..70932a83 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TextOutputDisplayable.swift @@ -0,0 +1,10 @@ +// +// TextOutputDisplayable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +protocol TextOutputDisplayable { + func display(output: String) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TicketProvidable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TicketProvidable.swift new file mode 100644 index 00000000..2b2a2942 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Protocol/TicketProvidable.swift @@ -0,0 +1,10 @@ +// +// TicketProvidable.swift +// BankManagerConsoleApp +// +// Created by Effie on 2/1/24. +// + +protocol TicketProvidable { + func provideTicket(of taskType: BankTask) -> Int? +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Utils/LinkedList.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/LinkedList.swift new file mode 100644 index 00000000..cd09430d --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/LinkedList.swift @@ -0,0 +1,66 @@ +// +// LinkedList.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 2/1/24. +// + +final class LinkedList { + private(set) var head: Node? + + private(set) var tail: Node? + + private(set) var count: Int + + var first: Value? { + return head?.value + } + + var isEmpty: Bool { + return head == nil + } + + init(head: Node? = nil) { + self.head = head + self.count = self.head == nil ? 0 : 1 + var lastNode = self.head + while let next = lastNode?.next { + lastNode = next + self.count += 1 + } + self.tail = lastNode + } + + func add(value: Value) { + let newNode = Node(value: value) + guard let currentTail = self.tail else { + self.head = newNode + self.tail = self.head + self.count = 1 + return + } + currentTail.next = newNode + self.tail = newNode + self.count += 1 + } + + @discardableResult + func removeFirst() -> Value? { + let result = self.head?.value + guard self.head !== self.tail else { + self.head = nil + self.tail = nil + self.count = 0 + return result + } + self.head = self.head?.next + self.count -= 1 + return result + } + + func clear() { + self.head = nil + self.tail = nil + self.count = 0 + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Node.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Node.swift new file mode 100644 index 00000000..38645a75 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Node.swift @@ -0,0 +1,16 @@ +// +// Node.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 2/1/24. +// + +final class Node { + let value: Value + var next: Node? + + init(value: Value, next: Node? = nil) { + self.value = value + self.next = next + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Queue.swift b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Queue.swift new file mode 100644 index 00000000..34faf3f1 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/Utils/Queue.swift @@ -0,0 +1,47 @@ +// +// Queue.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +final class Queue { + private var linkedList: LinkedList + + var front: Node? { + return self.linkedList.head + } + + var rear: Node? { + return self.linkedList.tail + } + + var isEmpty: Bool { + return self.linkedList.isEmpty + } + + var count: Int { + return self.linkedList.count + } + + init(linkedList: LinkedList = LinkedList()) { + self.linkedList = linkedList + } + + func enqueue(_ value: Value) { + self.linkedList.add(value: value) + } + + @discardableResult + func dequeue() -> Value? { + return self.linkedList.removeFirst() + } + + func clear() { + self.linkedList.clear() + } + + func peek() -> Value? { + return self.linkedList.first + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/View/ConsoleManager.swift b/BankManagerConsoleApp/BankManagerConsoleApp/View/ConsoleManager.swift new file mode 100644 index 00000000..159e2d73 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/View/ConsoleManager.swift @@ -0,0 +1,26 @@ +// +// ConsoleManager.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/31/24. +// + +struct ConsoleManager { } + +extension ConsoleManager: TextOutputDisplayable { + func display(output: String) { + print(output) + } +} + +extension ConsoleManager: TextInputReadable { + func readInput(prompt: String?) throws -> String { + if let prompt { + print(prompt, terminator: " ") + } + guard let input = readLine() else { + throw IOError.unexpectedError + } + return input + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/View/IOError.swift b/BankManagerConsoleApp/BankManagerConsoleApp/View/IOError.swift new file mode 100644 index 00000000..323299ca --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/View/IOError.swift @@ -0,0 +1,22 @@ +// +// IOError.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 2/1/24. +// + +import Foundation + +enum IOError: LocalizedError { + case invalidInput + case unexpectedError + + var errorDescription: String? { + switch self { + case .invalidInput: + return "메뉴 번호를 보고 다시 입력해주세요." + case .unexpectedError: + return "앱을 다시 실행해주세요." + } + } +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/View/TextInputReadable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/View/TextInputReadable.swift new file mode 100644 index 00000000..a85bacfb --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/View/TextInputReadable.swift @@ -0,0 +1,10 @@ +// +// TextInputReadable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/28/24. +// + +protocol TextInputReadable { + func readInput(prompt: String?) throws -> String +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/View/TextOutputDisplayable.swift b/BankManagerConsoleApp/BankManagerConsoleApp/View/TextOutputDisplayable.swift new file mode 100644 index 00000000..37584b39 --- /dev/null +++ b/BankManagerConsoleApp/BankManagerConsoleApp/View/TextOutputDisplayable.swift @@ -0,0 +1,10 @@ +// +// TextOutputDisplayable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/29/24. +// + +protocol TextOutputDisplayable { + func display(output: String) +} diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/main.swift b/BankManagerConsoleApp/BankManagerConsoleApp/main.swift index 1aa9854c..01185fc5 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp/main.swift +++ b/BankManagerConsoleApp/BankManagerConsoleApp/main.swift @@ -1,7 +1,8 @@ // // BankManagerConsoleApp - main.swift -// Created by yagom. +// Created by yagom. // Copyright © yagom academy. All rights reserved. -// +// + -import Foundation +BankManagerApp().start() diff --git a/BankManagerConsoleApp/BankManagerConsoleAppTest/LinkedListTest.swift b/BankManagerConsoleApp/BankManagerConsoleAppTest/LinkedListTest.swift index b610acc6..35c5e9d4 100644 --- a/BankManagerConsoleApp/BankManagerConsoleAppTest/LinkedListTest.swift +++ b/BankManagerConsoleApp/BankManagerConsoleAppTest/LinkedListTest.swift @@ -152,19 +152,79 @@ final class LinkedListTest: XCTestCase { XCTAssertNil(head) XCTAssertNil(tail) } + + // MARK: - Count + + func test_비어있으면_count는_0이다() { + // given + setEmptySUT() + + // when + let result = self.sut.count + + // then + XCTAssertEqual(result, 0) + } + + func test_한개일때_count는_1이다() { + // given + setSUTWithOneElement("1") + + // when + let result = self.sut.count + + // then + XCTAssertEqual(result, 1) + } + + func test_1개일때_하나를_add하면_2개이다() { + // given + setSUTWithOneElement("old") + + // when + self.sut.add(value: "new") + let result = self.sut.count + + // then + XCTAssertEqual(result, 2) + } + + func test_1개일때_remove하면_0개이다() { + // given + setSUTWithOneElement("value") + + // when + self.sut.removeFirst() + let result = self.sut.count + + // then + XCTAssertEqual(result, 0) + } + + func test_0개일때_remove하면_0개이다() { + // given + setEmptySUT() + + // when + self.sut.removeFirst() + let result = self.sut.count + + // then + XCTAssertEqual(result, 0) + } } -extension LinkedListTest { - private func setEmptySUT() { +private extension LinkedListTest { + func setEmptySUT() { self.sut = LinkedList() } - private func setSUTWithOneElement(_ oneValue: String) { + func setSUTWithOneElement(_ oneValue: String) { let node = Node(value: oneValue) self.sut = LinkedList(head: node) } - private func setSUTWithTwoElements(_ firstValue: String, _ secondValue: String) { + func setSUTWithTwoElements(_ firstValue: String, _ secondValue: String) { let secondNode = Node(value: secondValue) let firstNode = Node(value: firstValue, next: secondNode) self.sut = LinkedList(head: firstNode) diff --git a/BankManagerConsoleApp/BankManagerConsoleAppTest/QueueTest.swift b/BankManagerConsoleApp/BankManagerConsoleAppTest/QueueTest.swift index 3c3bccbe..a138981a 100644 --- a/BankManagerConsoleApp/BankManagerConsoleAppTest/QueueTest.swift +++ b/BankManagerConsoleApp/BankManagerConsoleAppTest/QueueTest.swift @@ -134,17 +134,17 @@ final class QueueTest: XCTestCase { } } -extension QueueTest { - private func setEmptySUT() { +private extension QueueTest { + func setEmptySUT() { self.sut = Queue() } - private func setSUTWithOneElement(_ oneValue: String) { + func setSUTWithOneElement(_ oneValue: String) { let first = Node(value: oneValue) self.sut = Queue(linkedList: .init(head: first)) } - private func setSUTWithTwoElements(_ firstValue: String, _ secondValue: String) { + func setSUTWithTwoElements(_ firstValue: String, _ secondValue: String) { let second = Node(value: secondValue) let first = Node(value: firstValue, next: second) self.sut = Queue(linkedList: .init(head: first)) diff --git a/BankManagerModule/Model/BankManager.swift b/BankManagerModule/Model/BankManager.swift new file mode 100644 index 00000000..2a3b9c2b --- /dev/null +++ b/BankManagerModule/Model/BankManager.swift @@ -0,0 +1,162 @@ +// +// BankManager.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import Foundation + +final class BankManager { + weak var delegate: BankManagerDelegate? + + private let bankers: [Banker] + + private let clientManagers: [BankTask: ClientEnqueuable & ClientClearable] + + private var currentClientNumber: Int + + private var group: DispatchGroup? + + private var isWorking: Bool = false + + private lazy var timer = BankTimer(delegate: self) + + init( + bankers: [Banker], + clientManagers: [BankTask: ClientEnqueuable & ClientClearable] + ) { + self.bankers = bankers + self.clientManagers = clientManagers + self.currentClientNumber = 0 + } + + func start(with count: Int) { + addNewClients(count: count) + trigger() + } + + func resetBank() { + guard self.isWorking else { return } + self.timer.reset() + for banker in bankers { + banker.stop() + } + self.isWorking = false + for (_, clientManager) in self.clientManagers { + clientManager.clearClients() + } + resetClientCount() + } + + func addClients(count: Int) { + addNewClients(count: count) + + if self.isWorking { + guard let group else { return } + for banker in bankers { + banker.start(group: group) + } + } else { + trigger() + } + } +} + +private extension BankManager { + func addNewClients(count: Int) { + let types = self.clientManagers.map { $0.key } + for number in (currentClientNumber + 1)...(currentClientNumber + count) { + guard let bankTaskType = types.randomElement() else { return } + let client = Client(number: number, task: bankTaskType) + self.clientManagers[bankTaskType]?.enqueueClient(client: client) + self.currentClientNumber = number + } + } + + func trigger() { + guard self.isWorking == false else { return } + self.isWorking = true + + self.group = DispatchGroup() + guard let group else { return } + + self.timer.start() + DispatchQueue.global().async { + for banker in self.bankers { + banker.start(group: group) + } + group.wait() + self.timer.end() + self.resetClientCount() + self.isWorking = false + } + } +} + +private extension BankManager { + func measure(_ progress: () -> Void) -> TimeInterval { + let start = Date() + progress() + return Date().timeIntervalSince(start) + } + + func resetClientCount() { + self.currentClientNumber = 0 + } +} + +extension BankManager: ClientManagerDelegate { + func handleEnqueueClient(client: Client) { + self.delegate?.handleEnqueueClient(client: client) + } + + func handleDequeueClient(client: Client) { + self.delegate?.handleDequeueClient(client: client) + } + + func handleClearClient() { + self.delegate?.handleClearClient() + } +} + +extension BankManager: BankerDelegate { + func handleStartTask(client: Client) { + self.delegate?.handleStartTask(client: client) + } + + func handleEndTask(client: Client) { + self.delegate?.handleEndTask(client: client) + } +} + +extension BankManager: BankTimerDelegate { + func handleUpdating(timeString: String) { + self.delegate?.handleTimer(timeString: timeString) + } +} + +protocol BankManagerEnqueueClientDelegate: AnyObject { + func handleEnqueueClient(client: Client) +} + +protocol BankManagerDequeueClientDelegate: AnyObject { + func handleDequeueClient(client: Client) +} + +protocol BankManagerClearClientDelegate: AnyObject { + func handleClearClient() +} + +protocol BankManagerStartTaskDelegate: AnyObject { + func handleStartTask(client: Client) +} + +protocol BankManagerEndTaskDelegate: AnyObject { + func handleEndTask(client: Client) +} + +protocol BankManagerTimerDelegate: AnyObject { + func handleTimer(timeString: String) +} + +typealias BankManagerDelegate = BankManagerEnqueueClientDelegate & BankManagerDequeueClientDelegate & BankManagerClearClientDelegate & BankManagerStartTaskDelegate & BankManagerEndTaskDelegate & BankManagerTimerDelegate diff --git a/BankManagerModule/Model/BankTask.swift b/BankManagerModule/Model/BankTask.swift new file mode 100644 index 00000000..25868308 --- /dev/null +++ b/BankManagerModule/Model/BankTask.swift @@ -0,0 +1,31 @@ +// +// BankTask.swift +// BankManagerConsoleApp +// +// Created by 강창현 on 1/30/24. +// + +enum BankTask { + 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 "대출" + } + } +} + +extension BankTask: CaseIterable { + static var random: Self? { + return allCases.randomElement() + } +} diff --git a/BankManagerModule/Model/BankTimer.swift b/BankManagerModule/Model/BankTimer.swift new file mode 100644 index 00000000..ad4c0f40 --- /dev/null +++ b/BankManagerModule/Model/BankTimer.swift @@ -0,0 +1,61 @@ +// +// BankTimer.swift +// BankManagerUIApp +// +// Created by Effie on 2/8/24. +// + +import Foundation + +final class BankTimer { + private static let interval: TimeInterval = 0.001 + + private let delegate: BankTimerDelegate + + private var milliseconds: Int = 0 + + private var timer: Timer? + + init(delegate: BankTimerDelegate) { + self.delegate = delegate + self.milliseconds = 0 + } + + func start() { + self.timer = makeTimer() + self.timer?.fire() + } + + func end() { + self.timer?.invalidate() + } + + func reset() { + end() + self.milliseconds = 0 + let string = format() + self.delegate.handleUpdating(timeString: string) + } + + private func makeTimer() -> Timer { + let timer = Timer.scheduledTimer(withTimeInterval: Self.interval, repeats: true) { timer in + self.milliseconds += 1 + let formattedString = self.format() + self.delegate.handleUpdating(timeString: formattedString) + } + return timer + } + + private func format() -> String { + return String( + format: "%02d:%02d:%03d", + self.milliseconds / 60_000, + (self.milliseconds % 60_000) / 1_000, + self.milliseconds % 1_000 + ) + } +} + +protocol BankTimerDelegate { + func handleUpdating(timeString: String) +} diff --git a/BankManagerModule/Model/Banker.swift b/BankManagerModule/Model/Banker.swift new file mode 100644 index 00000000..5a40d1a8 --- /dev/null +++ b/BankManagerModule/Model/Banker.swift @@ -0,0 +1,63 @@ +// +// Banker.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/26/24. +// + +import Foundation + +final class Banker { + private let clientManager: ClientDequeuable + + weak var delegate: BankerDelegate? + + private var isWorking: Bool = false + + init( + clientManager: ClientDequeuable + ) { + self.clientManager = clientManager + } + + func start(group: DispatchGroup) { + guard self.isWorking == false else { return } + DispatchQueue.global().async(group: group) { + self.isWorking = true + while self.isWorking, let client = self.clientManager.dequeueClient() { + self.work(for: client) + } + self.isWorking = false + } + } + + func stop() { + self.isWorking = false + } +} + +private extension Banker { + func work(for client: Client) { + self.startTask(for: client) + Thread.sleep(forTimeInterval: client.task.duration) + self.endTask(for: client) + } + + func startTask(for client: Client) { + self.delegate?.handleStartTask(client: client) + } + + func endTask(for client: Client) { + self.delegate?.handleEndTask(client: client) + } +} + +protocol BankerStartTaskDelegate: AnyObject { + func handleStartTask(client: Client) +} + +protocol BankerEndTaskDelegate: AnyObject { + func handleEndTask(client: Client) +} + +typealias BankerDelegate = BankerStartTaskDelegate & BankerEndTaskDelegate diff --git a/BankManagerModule/Model/Client.swift b/BankManagerModule/Model/Client.swift new file mode 100644 index 00000000..90c0ef32 --- /dev/null +++ b/BankManagerModule/Model/Client.swift @@ -0,0 +1,17 @@ +// +// Client.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/26/24. +// + +struct Client { + let number: Int + let task: BankTask +} + +extension Client: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(self.number) + } +} diff --git a/BankManagerModule/Model/ClientManager.swift b/BankManagerModule/Model/ClientManager.swift new file mode 100644 index 00000000..021c0c11 --- /dev/null +++ b/BankManagerModule/Model/ClientManager.swift @@ -0,0 +1,65 @@ +// +// ClientManager.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/26/24. +// + +import Foundation + +final class ClientManager { + weak var delegate: (ClientManagerDelegate)? + + private let clientQueue: Queue + + private let semaphore: DispatchSemaphore = .init(value: 1) + + init() { + self.clientQueue = Queue() + } +} + +extension ClientManager: ClientEnqueuable { + func enqueueClient(client: Client) { + self.semaphore.wait() + self.clientQueue.enqueue(client) + self.delegate?.handleEnqueueClient(client: client) + self.semaphore.signal() + } +} + +extension ClientManager: ClientDequeuable { + func dequeueClient() -> Client? { + self.semaphore.wait() + guard let client = self.clientQueue.dequeue() else { + self.semaphore.signal() + return nil + } + self.delegate?.handleDequeueClient(client: client) + self.semaphore.signal() + return client + } +} + +extension ClientManager: ClientClearable { + func clearClients() { + self.semaphore.wait() + self.clientQueue.clear() + self.delegate?.handleClearClient() + self.semaphore.signal() + } +} + +protocol ClientManagerEnqueueClientDelegate: AnyObject { + func handleEnqueueClient(client: Client) +} + +protocol ClientManagerDequeueClientDelegate: AnyObject { + func handleDequeueClient(client: Client) +} + +protocol ClientManagerClearClientDelegate: AnyObject { + func handleClearClient() +} + +typealias ClientManagerDelegate = ClientManagerEnqueueClientDelegate & ClientManagerDequeueClientDelegate & ClientManagerClearClientDelegate diff --git a/BankManagerModule/Model/ClientQueueManagable.swift b/BankManagerModule/Model/ClientQueueManagable.swift new file mode 100644 index 00000000..6f382c78 --- /dev/null +++ b/BankManagerModule/Model/ClientQueueManagable.swift @@ -0,0 +1,20 @@ +// +// ClientQueueManagable.swift +// BankManagerConsoleApp +// +// Created by Effie on 1/26/24. +// + +protocol ClientEnqueuable { + func enqueueClient(client: Client) +} + +protocol ClientDequeuable { + func dequeueClient() -> Client? +} + +protocol ClientClearable { + func clearClients() +} + +typealias ClientQueueManagable = ClientEnqueuable & ClientDequeuable & ClientClearable diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/LinkedList.swift b/BankManagerModule/Util/DataStructure/LinkedList.swift similarity index 83% rename from BankManagerConsoleApp/BankManagerConsoleApp/LinkedList.swift rename to BankManagerModule/Util/DataStructure/LinkedList.swift index e3f22a12..322d0021 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp/LinkedList.swift +++ b/BankManagerModule/Util/DataStructure/LinkedList.swift @@ -10,6 +10,8 @@ final class LinkedList { private(set) var tail: Node? + private(set) var count: Int + var first: Value? { return head?.value } @@ -20,9 +22,11 @@ final class LinkedList { init(head: Node? = nil) { self.head = head + self.count = self.head == nil ? 0 : 1 var lastNode = self.head while let next = lastNode?.next { lastNode = next + self.count += 1 } self.tail = lastNode } @@ -32,10 +36,12 @@ final class LinkedList { guard let currentTail = self.tail else { self.head = newNode self.tail = self.head + self.count = 1 return } currentTail.next = newNode self.tail = newNode + self.count += 1 } @discardableResult @@ -44,14 +50,17 @@ final class LinkedList { guard self.head !== self.tail else { self.head = nil self.tail = nil + self.count = 0 return result } self.head = self.head?.next + self.count -= 1 return result } func clear() { self.head = nil self.tail = nil + self.count = 0 } } diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Node.swift b/BankManagerModule/Util/DataStructure/Node.swift similarity index 100% rename from BankManagerConsoleApp/BankManagerConsoleApp/Node.swift rename to BankManagerModule/Util/DataStructure/Node.swift diff --git a/BankManagerConsoleApp/BankManagerConsoleApp/Queue.swift b/BankManagerModule/Util/DataStructure/Queue.swift similarity index 92% rename from BankManagerConsoleApp/BankManagerConsoleApp/Queue.swift rename to BankManagerModule/Util/DataStructure/Queue.swift index 271f92dc..63cb2259 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp/Queue.swift +++ b/BankManagerModule/Util/DataStructure/Queue.swift @@ -20,6 +20,10 @@ final class Queue { return self.linkedList.isEmpty } + var count: Int { + return self.linkedList.count + } + init(linkedList: LinkedList = LinkedList()) { self.linkedList = linkedList } diff --git a/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj b/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj index 62a20a2d..1640ddbb 100644 --- a/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj +++ b/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj @@ -9,13 +9,33 @@ /* Begin PBXBuildFile section */ C7694E3B259C3E9F0053667F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E3A259C3E9F0053667F /* AppDelegate.swift */; }; C7694E3D259C3E9F0053667F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E3C259C3E9F0053667F /* SceneDelegate.swift */; }; - C7694E3F259C3E9F0053667F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E3E259C3E9F0053667F /* ViewController.swift */; }; - C7694E42259C3E9F0053667F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7694E40259C3E9F0053667F /* Main.storyboard */; }; C7694E44259C3EA20053667F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C7694E43259C3EA20053667F /* Assets.xcassets */; }; C7694E47259C3EA20053667F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C7694E45259C3EA20053667F /* LaunchScreen.storyboard */; }; C7694E52259C3EA20053667F /* BankManagerUIAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E51259C3EA20053667F /* BankManagerUIAppTests.swift */; }; C7694E5D259C3EA20053667F /* BankManagerUIAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E5C259C3EA20053667F /* BankManagerUIAppUITests.swift */; }; - C7D65D1E259C81BD005510E0 /* BankManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D65D1D259C81BD005510E0 /* BankManager.swift */; }; + F435ABA82B7407CB009B67C5 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435AB9B2B7407CB009B67C5 /* Queue.swift */; }; + F435ABA92B7407CB009B67C5 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435AB9C2B7407CB009B67C5 /* Node.swift */; }; + F435ABAA2B7407CB009B67C5 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435AB9D2B7407CB009B67C5 /* LinkedList.swift */; }; + F435ABAB2B7407CB009B67C5 /* BankTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435AB9F2B7407CB009B67C5 /* BankTask.swift */; }; + F435ABAC2B7407CB009B67C5 /* Banker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABA02B7407CB009B67C5 /* Banker.swift */; }; + F435ABAE2B7407CB009B67C5 /* ClientQueueManagable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABA22B7407CB009B67C5 /* ClientQueueManagable.swift */; }; + F435ABB02B7407CB009B67C5 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABA42B7407CB009B67C5 /* Client.swift */; }; + F435ABB12B7407CB009B67C5 /* BankManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABA52B7407CB009B67C5 /* BankManager.swift */; }; + F435ABB22B7407CB009B67C5 /* ClientManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABA62B7407CB009B67C5 /* ClientManager.swift */; }; + F435ABB52B740A46009B67C5 /* BankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABB42B740A46009B67C5 /* BankViewModel.swift */; }; + F435ABC42B740D12009B67C5 /* ClientListTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABB82B740D12009B67C5 /* ClientListTableView.swift */; }; + F435ABC52B740D12009B67C5 /* ClientListDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABB92B740D12009B67C5 /* ClientListDataSource.swift */; }; + F435ABC62B740D12009B67C5 /* ClientListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABBA2B740D12009B67C5 /* ClientListTableViewCell.swift */; }; + F435ABC72B740D12009B67C5 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABBB2B740D12009B67C5 /* ReusableView.swift */; }; + F435ABC82B740D12009B67C5 /* ClientListHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABBC2B740D12009B67C5 /* ClientListHeaderView.swift */; }; + F435ABC92B740D12009B67C5 /* ListLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABBD2B740D12009B67C5 /* ListLabel.swift */; }; + F435ABCB2B740D12009B67C5 /* TimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABBF2B740D12009B67C5 /* TimerView.swift */; }; + F435ABCC2B740D12009B67C5 /* BankList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABC02B740D12009B67C5 /* BankList.swift */; }; + F435ABCE2B740D12009B67C5 /* BankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABC32B740D12009B67C5 /* BankViewController.swift */; }; + F435ABD12B740D41009B67C5 /* BMColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABD02B740D41009B67C5 /* BMColor.swift */; }; + F435ABD32B7443CB009B67C5 /* BankTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F435ABD22B7443CB009B67C5 /* BankTimer.swift */; }; + F45041062B74DCD300DC4DE4 /* BankIntput.swift in Sources */ = {isa = PBXBuildFile; fileRef = F45041052B74DCD300DC4DE4 /* BankIntput.swift */; }; + F45041082B74DCE500DC4DE4 /* BankOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = F45041072B74DCE500DC4DE4 /* BankOutput.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,8 +59,6 @@ C7694E37259C3E9F0053667F /* BankManagerUIApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BankManagerUIApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; C7694E3A259C3E9F0053667F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C7694E3C259C3E9F0053667F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - C7694E3E259C3E9F0053667F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - C7694E41259C3E9F0053667F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C7694E43259C3EA20053667F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C7694E46259C3EA20053667F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C7694E48259C3EA20053667F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -50,7 +68,29 @@ C7694E58259C3EA20053667F /* BankManagerUIAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BankManagerUIAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C7694E5C259C3EA20053667F /* BankManagerUIAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankManagerUIAppUITests.swift; sourceTree = ""; }; C7694E5E259C3EA20053667F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C7D65D1D259C81BD005510E0 /* BankManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BankManager.swift; path = ../../BankManager.swift; sourceTree = ""; }; + F435AB9B2B7407CB009B67C5 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; + F435AB9C2B7407CB009B67C5 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; + F435AB9D2B7407CB009B67C5 /* LinkedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkedList.swift; sourceTree = ""; }; + F435AB9F2B7407CB009B67C5 /* BankTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankTask.swift; sourceTree = ""; }; + F435ABA02B7407CB009B67C5 /* Banker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Banker.swift; sourceTree = ""; }; + F435ABA22B7407CB009B67C5 /* ClientQueueManagable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientQueueManagable.swift; sourceTree = ""; }; + F435ABA42B7407CB009B67C5 /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; + F435ABA52B7407CB009B67C5 /* BankManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankManager.swift; sourceTree = ""; }; + F435ABA62B7407CB009B67C5 /* ClientManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientManager.swift; sourceTree = ""; }; + F435ABB42B740A46009B67C5 /* BankViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankViewModel.swift; sourceTree = ""; }; + F435ABB82B740D12009B67C5 /* ClientListTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientListTableView.swift; sourceTree = ""; }; + F435ABB92B740D12009B67C5 /* ClientListDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientListDataSource.swift; sourceTree = ""; }; + F435ABBA2B740D12009B67C5 /* ClientListTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientListTableViewCell.swift; sourceTree = ""; }; + F435ABBB2B740D12009B67C5 /* ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; + F435ABBC2B740D12009B67C5 /* ClientListHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientListHeaderView.swift; sourceTree = ""; }; + F435ABBD2B740D12009B67C5 /* ListLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLabel.swift; sourceTree = ""; }; + F435ABBF2B740D12009B67C5 /* TimerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimerView.swift; sourceTree = ""; }; + F435ABC02B740D12009B67C5 /* BankList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankList.swift; sourceTree = ""; }; + F435ABC32B740D12009B67C5 /* BankViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankViewController.swift; sourceTree = ""; }; + F435ABD02B740D41009B67C5 /* BMColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMColor.swift; sourceTree = ""; }; + F435ABD22B7443CB009B67C5 /* BankTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankTimer.swift; sourceTree = ""; }; + F45041052B74DCD300DC4DE4 /* BankIntput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankIntput.swift; sourceTree = ""; }; + F45041072B74DCE500DC4DE4 /* BankOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankOutput.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -101,14 +141,13 @@ C7694E39259C3E9F0053667F /* BankManagerUIApp */ = { isa = PBXGroup; children = ( - C7D65D1D259C81BD005510E0 /* BankManager.swift */, - C7694E3A259C3E9F0053667F /* AppDelegate.swift */, - C7694E3C259C3E9F0053667F /* SceneDelegate.swift */, - C7694E3E259C3E9F0053667F /* ViewController.swift */, - C7694E40259C3E9F0053667F /* Main.storyboard */, - C7694E43259C3EA20053667F /* Assets.xcassets */, - C7694E45259C3EA20053667F /* LaunchScreen.storyboard */, - C7694E48259C3EA20053667F /* Info.plist */, + F435ABD42B74DB63009B67C5 /* App */, + F435ABB62B740D12009B67C5 /* View */, + F435ABC22B740D12009B67C5 /* ViewController */, + F435ABD52B74DBEB009B67C5 /* ViewModel */, + F435ABCF2B740D41009B67C5 /* Resource */, + F45041092B74DEB400DC4DE4 /* Supportings */, + F435AB982B7407CB009B67C5 /* BankManagerModule */, ); path = BankManagerUIApp; sourceTree = ""; @@ -131,6 +170,116 @@ path = BankManagerUIAppUITests; sourceTree = ""; }; + F435AB982B7407CB009B67C5 /* BankManagerModule */ = { + isa = PBXGroup; + children = ( + F435AB992B7407CB009B67C5 /* Util */, + F435AB9E2B7407CB009B67C5 /* Model */, + ); + name = BankManagerModule; + path = ../../BankManagerModule; + sourceTree = ""; + }; + F435AB992B7407CB009B67C5 /* Util */ = { + isa = PBXGroup; + children = ( + F435AB9A2B7407CB009B67C5 /* DataStructure */, + ); + path = Util; + sourceTree = ""; + }; + F435AB9A2B7407CB009B67C5 /* DataStructure */ = { + isa = PBXGroup; + children = ( + F435AB9B2B7407CB009B67C5 /* Queue.swift */, + F435AB9C2B7407CB009B67C5 /* Node.swift */, + F435AB9D2B7407CB009B67C5 /* LinkedList.swift */, + ); + path = DataStructure; + sourceTree = ""; + }; + F435AB9E2B7407CB009B67C5 /* Model */ = { + isa = PBXGroup; + children = ( + F435AB9F2B7407CB009B67C5 /* BankTask.swift */, + F435ABA02B7407CB009B67C5 /* Banker.swift */, + F435ABA22B7407CB009B67C5 /* ClientQueueManagable.swift */, + F435ABA42B7407CB009B67C5 /* Client.swift */, + F435ABA52B7407CB009B67C5 /* BankManager.swift */, + F435ABA62B7407CB009B67C5 /* ClientManager.swift */, + F435ABD22B7443CB009B67C5 /* BankTimer.swift */, + ); + path = Model; + sourceTree = ""; + }; + F435ABB62B740D12009B67C5 /* View */ = { + isa = PBXGroup; + children = ( + F435ABB72B740D12009B67C5 /* TableView */, + F435ABBF2B740D12009B67C5 /* TimerView.swift */, + F435ABC02B740D12009B67C5 /* BankList.swift */, + ); + path = View; + sourceTree = ""; + }; + F435ABB72B740D12009B67C5 /* TableView */ = { + isa = PBXGroup; + children = ( + F435ABBD2B740D12009B67C5 /* ListLabel.swift */, + F435ABB82B740D12009B67C5 /* ClientListTableView.swift */, + F435ABB92B740D12009B67C5 /* ClientListDataSource.swift */, + F435ABBA2B740D12009B67C5 /* ClientListTableViewCell.swift */, + F435ABBB2B740D12009B67C5 /* ReusableView.swift */, + F435ABBC2B740D12009B67C5 /* ClientListHeaderView.swift */, + ); + path = TableView; + sourceTree = ""; + }; + F435ABC22B740D12009B67C5 /* ViewController */ = { + isa = PBXGroup; + children = ( + F435ABC32B740D12009B67C5 /* BankViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + F435ABCF2B740D41009B67C5 /* Resource */ = { + isa = PBXGroup; + children = ( + F435ABD02B740D41009B67C5 /* BMColor.swift */, + ); + path = Resource; + sourceTree = ""; + }; + F435ABD42B74DB63009B67C5 /* App */ = { + isa = PBXGroup; + children = ( + C7694E3A259C3E9F0053667F /* AppDelegate.swift */, + C7694E3C259C3E9F0053667F /* SceneDelegate.swift */, + ); + path = App; + sourceTree = ""; + }; + F435ABD52B74DBEB009B67C5 /* ViewModel */ = { + isa = PBXGroup; + children = ( + F435ABB42B740A46009B67C5 /* BankViewModel.swift */, + F45041052B74DCD300DC4DE4 /* BankIntput.swift */, + F45041072B74DCE500DC4DE4 /* BankOutput.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + F45041092B74DEB400DC4DE4 /* Supportings */ = { + isa = PBXGroup; + children = ( + C7694E43259C3EA20053667F /* Assets.xcassets */, + C7694E45259C3EA20053667F /* LaunchScreen.storyboard */, + C7694E48259C3EA20053667F /* Info.plist */, + ); + path = Supportings; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -236,7 +385,6 @@ files = ( C7694E47259C3EA20053667F /* LaunchScreen.storyboard in Resources */, C7694E44259C3EA20053667F /* Assets.xcassets in Resources */, - C7694E42259C3E9F0053667F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -261,10 +409,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C7694E3F259C3E9F0053667F /* ViewController.swift in Sources */, + F435ABB12B7407CB009B67C5 /* BankManager.swift in Sources */, + F435ABC52B740D12009B67C5 /* ClientListDataSource.swift in Sources */, + F435ABAE2B7407CB009B67C5 /* ClientQueueManagable.swift in Sources */, + F435ABC62B740D12009B67C5 /* ClientListTableViewCell.swift in Sources */, + F435ABD12B740D41009B67C5 /* BMColor.swift in Sources */, + F435ABAB2B7407CB009B67C5 /* BankTask.swift in Sources */, + F435ABCE2B740D12009B67C5 /* BankViewController.swift in Sources */, + F435ABA82B7407CB009B67C5 /* Queue.swift in Sources */, + F435ABB52B740A46009B67C5 /* BankViewModel.swift in Sources */, + F435ABB02B7407CB009B67C5 /* Client.swift in Sources */, + F435ABCC2B740D12009B67C5 /* BankList.swift in Sources */, + F45041062B74DCD300DC4DE4 /* BankIntput.swift in Sources */, + F435ABD32B7443CB009B67C5 /* BankTimer.swift in Sources */, + F435ABC42B740D12009B67C5 /* ClientListTableView.swift in Sources */, + F435ABCB2B740D12009B67C5 /* TimerView.swift in Sources */, C7694E3B259C3E9F0053667F /* AppDelegate.swift in Sources */, + F45041082B74DCE500DC4DE4 /* BankOutput.swift in Sources */, + F435ABC92B740D12009B67C5 /* ListLabel.swift in Sources */, + F435ABA92B7407CB009B67C5 /* Node.swift in Sources */, + F435ABC72B740D12009B67C5 /* ReusableView.swift in Sources */, + F435ABAC2B7407CB009B67C5 /* Banker.swift in Sources */, + F435ABC82B740D12009B67C5 /* ClientListHeaderView.swift in Sources */, C7694E3D259C3E9F0053667F /* SceneDelegate.swift in Sources */, - C7D65D1E259C81BD005510E0 /* BankManager.swift in Sources */, + F435ABAA2B7407CB009B67C5 /* LinkedList.swift in Sources */, + F435ABB22B7407CB009B67C5 /* ClientManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -300,14 +469,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - C7694E40259C3E9F0053667F /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C7694E41259C3E9F0053667F /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; C7694E45259C3EA20053667F /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -441,7 +602,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = BankManagerUIApp/Info.plist; + INFOPLIST_FILE = BankManagerUIApp/Supportings/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -460,7 +621,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = BankManagerUIApp/Info.plist; + INFOPLIST_FILE = BankManagerUIApp/Supportings/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/BankManagerUIApp/BankManagerUIApp/App/AppDelegate.swift b/BankManagerUIApp/BankManagerUIApp/App/AppDelegate.swift new file mode 100644 index 00000000..8bbe5b8f --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/App/AppDelegate.swift @@ -0,0 +1,29 @@ +// +// BankManagerUIApp - AppDelegate.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return true + } + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role + ) + } +} + diff --git a/BankManagerUIApp/BankManagerUIApp/App/SceneDelegate.swift b/BankManagerUIApp/BankManagerUIApp/App/SceneDelegate.swift new file mode 100644 index 00000000..aac407b6 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/App/SceneDelegate.swift @@ -0,0 +1,70 @@ +// +// BankManagerUIApp - SceneDelegate.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import UIKit + +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = (scene as? UIWindowScene) else { return } + self.window = UIWindow(windowScene: windowScene) + self.window?.rootViewController = Self.makeViewController() + self.window?.makeKeyAndVisible() + } + + static func makeViewController() -> UIViewController { + let clientManagers: [BankTask: ClientManager] = [ + .loan: ClientManager(), + .deposit: ClientManager(), + ] + + let orders: [BankTask: Int] = [.loan: 1, .deposit: 2] + + let bankers = makeBankers( + tasks: clientManagers, + orders: orders + ) + + let bankManager = BankManager( + bankers: bankers, + clientManagers: clientManagers + ) + + bankers.forEach { banker in banker.delegate = bankManager } + + for (_, clientManager) in clientManagers { + clientManager.delegate = bankManager + } + + let mirror = BankViewModel(bankManager: bankManager) + bankManager.delegate = mirror + + let viewController = BankViewController(bankMirror: mirror) + mirror.delegate = viewController + return viewController + } + + static func makeBankers( + tasks: [BankTask: ClientQueueManagable], + orders: [BankTask: Int] + ) -> [Banker] { + var result = [Banker]() + for (type, bankerCount) in orders { + (1...bankerCount).forEach { _ in + guard let clientManager = tasks[type] else { return } + let banker = Banker(clientManager: clientManager) + result.append(banker) + } + } + return result + } +} + diff --git a/BankManagerUIApp/BankManagerUIApp/AppDelegate.swift b/BankManagerUIApp/BankManagerUIApp/AppDelegate.swift deleted file mode 100644 index 71013f64..00000000 --- a/BankManagerUIApp/BankManagerUIApp/AppDelegate.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// BankManagerUIApp - AppDelegate.swift -// Created by yagom. -// Copyright © yagom academy. All rights reserved. -// - -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - -} - diff --git a/BankManagerUIApp/BankManagerUIApp/Base.lproj/Main.storyboard b/BankManagerUIApp/BankManagerUIApp/Base.lproj/Main.storyboard deleted file mode 100644 index ce80ec92..00000000 --- a/BankManagerUIApp/BankManagerUIApp/Base.lproj/Main.storyboard +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/BankManagerUIApp/BankManagerUIApp/Resource/BMColor.swift b/BankManagerUIApp/BankManagerUIApp/Resource/BMColor.swift new file mode 100644 index 00000000..6cc17cde --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Resource/BMColor.swift @@ -0,0 +1,13 @@ +// +// BMColor.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +enum BMColor { + static let purple = UIColor(named: "BMPurple") + static let green = UIColor(named: "BMGreen") +} diff --git a/BankManagerUIApp/BankManagerUIApp/SceneDelegate.swift b/BankManagerUIApp/BankManagerUIApp/SceneDelegate.swift deleted file mode 100644 index 63d2d2a1..00000000 --- a/BankManagerUIApp/BankManagerUIApp/SceneDelegate.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// BankManagerUIApp - SceneDelegate.swift -// Created by yagom. -// Copyright © yagom academy. All rights reserved. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - -} - diff --git a/BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AccentColor.colorset/Contents.json b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMGreen.colorset/Contents.json b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMGreen.colorset/Contents.json new file mode 100644 index 00000000..ea797610 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x54", + "green" : "0xCF", + "red" : "0x5E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMPurple.colorset/Contents.json b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMPurple.colorset/Contents.json new file mode 100644 index 00000000..58716df3 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/BMPurple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE2", + "green" : "0x5A", + "red" : "0x68" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Assets.xcassets/Contents.json b/BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/Contents.json rename to BankManagerUIApp/BankManagerUIApp/Supportings/Assets.xcassets/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Base.lproj/LaunchScreen.storyboard b/BankManagerUIApp/BankManagerUIApp/Supportings/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Base.lproj/LaunchScreen.storyboard rename to BankManagerUIApp/BankManagerUIApp/Supportings/Base.lproj/LaunchScreen.storyboard diff --git a/BankManagerUIApp/BankManagerUIApp/Info.plist b/BankManagerUIApp/BankManagerUIApp/Supportings/Info.plist similarity index 94% rename from BankManagerUIApp/BankManagerUIApp/Info.plist rename to BankManagerUIApp/BankManagerUIApp/Supportings/Info.plist index 5b531f7b..2688b32b 100644 --- a/BankManagerUIApp/BankManagerUIApp/Info.plist +++ b/BankManagerUIApp/BankManagerUIApp/Supportings/Info.plist @@ -33,8 +33,6 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main @@ -43,8 +41,6 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/BankManagerUIApp/BankManagerUIApp/View/BankList.swift b/BankManagerUIApp/BankManagerUIApp/View/BankList.swift new file mode 100644 index 00000000..367e267b --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/BankList.swift @@ -0,0 +1,27 @@ +// +// BankList.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +enum BankList { + case waiting + case working + + var backgroundColor: UIColor? { + switch self { + case .waiting: return BMColor.green + case .working: return BMColor.purple + } + } + + var name: String { + switch self { + case .waiting: return "대기중" + case .working: return "작업중" + } + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListDataSource.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListDataSource.swift new file mode 100644 index 00000000..49b119a0 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListDataSource.swift @@ -0,0 +1,38 @@ +// +// ClientListDataSource.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +enum ClientListSection { + case client +} + +enum ClientListItem: Hashable { + case client(Client) +} + +typealias ClientListSnapShot = NSDiffableDataSourceSnapshot + +final class ClientListDataSource: UITableViewDiffableDataSource { + typealias TableView = ClientListTableView + typealias ClientCell = ClientListTableViewCell + + static let cellProvider: CellProvider = { tableview, indexPath, itemIdentifier in + switch itemIdentifier { + case .client(let client): + guard let cell = tableview.dequeueReusableCell( + withIdentifier: ClientCell.reuseIdentifier, for: indexPath + ) as? ClientCell else { return ClientCell() } + cell.update(with: client) + return cell + } + } + + convenience init(_ listView: TableView) { + self.init(tableView: listView, cellProvider: Self.cellProvider) + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListHeaderView.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListHeaderView.swift new file mode 100644 index 00000000..a04e530e --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListHeaderView.swift @@ -0,0 +1,47 @@ +// +// ClientListHeaderView.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +final class ClientListHeaderView: UITableViewHeaderFooterView { + private let titleLabel: UILabel = { + let label = UILabel(frame: .zero) + label.font = .preferredFont(forTextStyle: .largeTitle) + label.textAlignment = .center + label.textColor = .white + return label + }() + + init(type: BankList) { + super.init(reuseIdentifier: Self.reuseIdentifier) + setType(type: type) + setLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setType(type: BankList) { + self.titleLabel.text = type.name + self.titleLabel.backgroundColor = type.backgroundColor + } + + private func setLayout() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(titleLabel) + NSLayoutConstraint.activate([ + self.titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor), + self.titleLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor), + self.titleLabel.topAnchor.constraint(equalTo: self.topAnchor), + self.titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + ]) + } +} + +extension ClientListHeaderView: ReusableView { } diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableView.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableView.swift new file mode 100644 index 00000000..d06ee6da --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableView.swift @@ -0,0 +1,36 @@ +// +// ClientListTableView.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +final class ClientListTableView: UITableView { + typealias ClientCell = ClientListTableViewCell + typealias HeaderView = ClientListHeaderView + + let type: BankList + + init(type: BankList) { + self.type = type + super.init(frame: .zero, style: .plain) + self.separatorStyle = .none + setCollection() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func makeHeader() -> HeaderView { + return HeaderView(type: self.type) + } +} + +extension ClientListTableView: ReusableView { + private func setCollection() { + register(ClientCell.self) + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableViewCell.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableViewCell.swift new file mode 100644 index 00000000..9deeeabd --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ClientListTableViewCell.swift @@ -0,0 +1,39 @@ +// +// ClientListTableViewCell.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +final class ClientListTableViewCell: UITableViewCell { + private let label: ListLabel = ListLabel() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(with client: Client) { + self.label.configure(client: client) + } + + private func setLayout() { + self.label.translatesAutoresizingMaskIntoConstraints = false + self.addSubview(label) + NSLayoutConstraint.activate([ + self.label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 4), + self.label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -4), + self.label.topAnchor.constraint(equalTo: self.topAnchor, constant: 4), + self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -4), + ]) + } +} + +extension ClientListTableViewCell: ReusableView { } diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ListLabel.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ListLabel.swift new file mode 100644 index 00000000..a72deaeb --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ListLabel.swift @@ -0,0 +1,38 @@ +// +// ListLabel.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +final class ListLabel: UILabel { + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + init() { + super.init(frame: .zero) + self.textAlignment = .center + self.font = .preferredFont(forTextStyle: .title3) + } + + init(client: Client) { + super.init(frame: .zero) + self.textAlignment = .center + self.font = .preferredFont(forTextStyle: .title3) + configure(client: client) + } + + func configure(client: Client) { + self.text = "\(client.number) - \(client.task.name)" + switch client.task { + case .deposit: + self.textColor = .black + case .loan: + self.textColor = .purple + } + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/View/TableView/ReusableView.swift b/BankManagerUIApp/BankManagerUIApp/View/TableView/ReusableView.swift new file mode 100644 index 00000000..cac9f70a --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TableView/ReusableView.swift @@ -0,0 +1,25 @@ +// +// ReusableView.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +protocol ReusableView { + static var reuseIdentifier: String { get } +} + +extension ReusableView { + static var reuseIdentifier: String { + return String(describing: self) + } +} + +extension UITableView { + func register(_ cellType: Cell.Type) { + register(cellType, forCellReuseIdentifier: cellType.reuseIdentifier) + } +} + diff --git a/BankManagerUIApp/BankManagerUIApp/View/TimerView.swift b/BankManagerUIApp/BankManagerUIApp/View/TimerView.swift new file mode 100644 index 00000000..8b620076 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/TimerView.swift @@ -0,0 +1,43 @@ +// +// TimerView.swift +// BankManagerUIApp +// +// Created by Effie on 2/7/24. +// + +import UIKit + +final class TimerView: UIView { + private let timerLabel: UILabel = { + let label = UILabel(frame: .zero) + label.text = "업무시간 - \("00:00:000")" + label.textAlignment = .center + label.font = .preferredFont(forTextStyle: .title2) + return label + }() + + init() { + super.init(frame: .zero) + setLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setLayout() { + self.addSubview(timerLabel) + self.timerLabel.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + self.timerLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), + self.timerLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10), + self.timerLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 10), + self.timerLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10), + ]) + } + + func configure(with timeString: String) { + self.timerLabel.text = "업무시간 - \(timeString)" + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/ViewController.swift b/BankManagerUIApp/BankManagerUIApp/ViewController.swift deleted file mode 100644 index a3182156..00000000 --- a/BankManagerUIApp/BankManagerUIApp/ViewController.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// BankManagerUIApp - ViewController.swift -// Created by yagom. -// Copyright © yagom academy. All rights reserved. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - -} - diff --git a/BankManagerUIApp/BankManagerUIApp/ViewController/BankViewController.swift b/BankManagerUIApp/BankManagerUIApp/ViewController/BankViewController.swift new file mode 100644 index 00000000..b75dc2ed --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/ViewController/BankViewController.swift @@ -0,0 +1,199 @@ +// +// BankManagerUIApp - BankViewController.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import UIKit + +final class BankViewController: UIViewController { + // MARK: - Properties + + private let viewModel: BankIntput + + // MARK: - UI Elements + private let addClientButton: UIButton = { + let button = UIButton(frame: .zero) + button.setTitle("10명 추가", for: .normal) + button.setTitleColor(.systemBlue, for: .normal) + return button + }() + + private let clearButton: UIButton = { + let button = UIButton(frame: .zero) + button.setTitle("초기화", for: .normal) + button.setTitleColor(.red, for: .normal) + return button + }() + + private lazy var buttonStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 5 + stackView.distribution = .fillEqually + + stackView.addArrangedSubview(self.addClientButton) + stackView.addArrangedSubview(self.clearButton) + return stackView + }() + + private let timerView: TimerView = TimerView() + + private let waitingListTableView = ClientListTableView(type: .waiting) + + private let workingListTableView = ClientListTableView(type: .working) + + private lazy var listStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.spacing = 0 + stackView.distribution = .fillEqually + + stackView.addArrangedSubview(self.waitingListTableView) + stackView.addArrangedSubview(self.workingListTableView) + return stackView + }() + + private lazy var containerView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 0 + + stackView.addArrangedSubview(self.buttonStackView) + stackView.addArrangedSubview(self.timerView) + stackView.addArrangedSubview(self.listStackView) + + return stackView + }() + + private lazy var waitingListDataSource = ClientListDataSource(self.waitingListTableView) + + private lazy var workingListDataSource = ClientListDataSource(self.workingListTableView) + + // MARK: - Initializers + + init(bankMirror: BankIntput) { + self.viewModel = bankMirror + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Lifecycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + setLayout() + setButtonAction() + self.waitingListTableView.delegate = self + self.workingListTableView.delegate = self + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let count = (10...30).randomElement()! + runBank(count: count) + } +} + +// MARK: - UITableViewDelegate +extension BankViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return (tableView as? ClientListTableView)?.makeHeader() + } +} + +// MARK: - BankOutput +extension BankViewController: BankOutput { + func updateWaitingList(with clients: [Client]) { + DispatchQueue.main.async { + self.applyUpdatedWaitingList(with: clients) + } + } + + func updateWorkingList(with clients: [Client]) { + DispatchQueue.main.async { + self.applyUpdatedWorkingList(with: clients) + } + } + + func updateTime(with timeString: String) { + DispatchQueue.main.async { + self.applyUpdatedTime(with: timeString) + } + } +} + +// MARK: - Private Methods +private extension BankViewController { + private func setButtonAction() { + self.addClientButton.addAction( + UIAction { _ in self.addClient(count: 10) }, + for: .touchUpInside + ) + + self.clearButton.addAction( + UIAction { _ in self.resetBank() }, + for: .touchUpInside + ) + } + + func setLayout() { + self.view.backgroundColor = .white + self.view.addSubview(containerView) + self.addClientButton.translatesAutoresizingMaskIntoConstraints = false + self.clearButton.translatesAutoresizingMaskIntoConstraints = false + self.buttonStackView.translatesAutoresizingMaskIntoConstraints = false + self.timerView.translatesAutoresizingMaskIntoConstraints = false + self.waitingListTableView.translatesAutoresizingMaskIntoConstraints = false + self.workingListTableView.translatesAutoresizingMaskIntoConstraints = false + self.listStackView.translatesAutoresizingMaskIntoConstraints = false + self.containerView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + self.containerView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), + self.containerView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor), + self.containerView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), + self.containerView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), + + self.buttonStackView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 0.05), + self.timerView.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor, multiplier: 0.1), + ]) + } + + func addClient(count: Int) { + self.viewModel.addClients(count: count) + } + + func runBank(count: Int) { + self.viewModel.startBank(withCount: count) + } + + func resetBank() { + self.viewModel.resetBank() + } + + func applyUpdatedWaitingList(with list: [Client]) { + var snapshot = ClientListSnapShot() + snapshot.appendSections([.client]) + let items = list.map(ClientListItem.client) + snapshot.appendItems(items, toSection: .client) + self.waitingListDataSource.apply(snapshot) + } + + func applyUpdatedWorkingList(with list: [Client]) { + var snapshot = ClientListSnapShot() + snapshot.appendSections([.client]) + let items = list.map(ClientListItem.client) + snapshot.appendItems(items, toSection: .client) + self.workingListDataSource.apply(snapshot) + } + + func applyUpdatedTime(with timeString: String) { + self.timerView.configure(with: timeString) + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/ViewModel/BankIntput.swift b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankIntput.swift new file mode 100644 index 00000000..0ee639d0 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankIntput.swift @@ -0,0 +1,12 @@ +// +// BankIntput.swift +// BankManagerUIApp +// +// Created by Effie on 2/8/24. +// + +protocol BankIntput: AnyObject { + func startBank(withCount count: Int) + func resetBank() + func addClients(count: Int) +} diff --git a/BankManagerUIApp/BankManagerUIApp/ViewModel/BankOutput.swift b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankOutput.swift new file mode 100644 index 00000000..4394cdd4 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankOutput.swift @@ -0,0 +1,12 @@ +// +// BankOutput.swift +// BankManagerUIApp +// +// Created by Effie on 2/8/24. +// + +protocol BankOutput: AnyObject { + func updateWaitingList(with: [Client]) + func updateWorkingList(with: [Client]) + func updateTime(with timeString: String) +} diff --git a/BankManagerUIApp/BankManagerUIApp/ViewModel/BankViewModel.swift b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankViewModel.swift new file mode 100644 index 00000000..df4843df --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/ViewModel/BankViewModel.swift @@ -0,0 +1,122 @@ +// +// BankViewModel.swift +// BankManagerConsoleApp +// +// Created by Effie on 2/5/24. +// + +import Foundation + +final class BankViewModel { + private let bankManager: BankManager + + weak var delegate: BankOutput? + + private var waitingList: [Client] { + didSet { + self.delegate?.updateWaitingList(with: self.waitingList) + } + } + + private var workingList: [Client] { + didSet { + self.delegate?.updateWorkingList(with: self.workingList) + } + } + + private var timeString: String { + didSet { + self.delegate?.updateTime(with: self.timeString) + } + } + + private var waitingSemaphore = DispatchSemaphore(value: 1) + + private var workingSemaphore = DispatchSemaphore(value: 1) + + init( + bankManager: BankManager + ) { + self.waitingList = [] + self.workingList = [] + self.bankManager = bankManager + self.timeString = "업무시간 - \("00:00:000")" + } +} + +// MARK: - BankIntput +extension BankViewModel: BankIntput { + func startBank(withCount count: Int) { + self.bankManager.start(with: count) + } + + func resetBank() { + self.bankManager.resetBank() + } + + func addClients(count: Int) { + self.bankManager.addClients(count: count) + } +} + +// MARK: - BankManagerDelegate +extension BankViewModel: BankManagerDelegate { + func handleDequeueClient(client: Client) { + DispatchQueue.global().async { + self.waitingSemaphore.wait() + guard + let index = self.waitingList.firstIndex(where: { target in client == target }) + else { + self.waitingSemaphore.signal() + return + } + self.waitingList.remove(at: index) + self.waitingSemaphore.signal() + } + } + + func handleEnqueueClient(client: Client) { + DispatchQueue.global().async { + self.waitingSemaphore.wait() + self.waitingList.append(client) + self.waitingSemaphore.signal() + } + } + + func handleEndTask(client: Client) { + DispatchQueue.global().async { + self.workingSemaphore.wait() + guard + let index = self.workingList.firstIndex(where: { target in client == target }) + else { + self.workingSemaphore.signal() + return + } + self.workingList.remove(at: index) + self.workingSemaphore.signal() + } + } + + func handleStartTask(client: Client) { + DispatchQueue.global().async { + self.workingSemaphore.wait() + self.workingList.append(client) + self.workingSemaphore.signal() + } + } + + func handleClearClient() { + DispatchQueue.global().async { + self.waitingSemaphore.wait() + self.workingSemaphore.wait() + self.waitingList.removeAll() + self.workingList.removeAll() + self.waitingSemaphore.signal() + self.workingSemaphore.signal() + } + } + + func handleTimer(timeString: String) { + self.timeString = timeString + } +}