diff --git a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj index 3b5ae1fe..55adff55 100644 --- a/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj +++ b/BankManagerConsoleApp/BankManagerConsoleApp.xcodeproj/project.pbxproj @@ -11,13 +11,13 @@ 8A6A78F82B5FCE0E0027E5D3 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A78F72B5FCE0E0027E5D3 /* Node.swift */; }; 8A6A78FA2B5FCE7C0027E5D3 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A78F92B5FCE7C0027E5D3 /* Queue.swift */; }; C7694E7A259C3EC00053667F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E79259C3EC00053667F /* main.swift */; }; - C7D65D1B259C8190005510E0 /* BankManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7D65D1A259C8190005510E0 /* BankManager.swift */; }; CE98E59E2B6396E600C36337 /* Banker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE98E59D2B6396E600C36337 /* Banker.swift */; }; CE98E5A02B639AD800C36337 /* Customer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE98E59F2B639AD800C36337 /* Customer.swift */; }; CEB1F55C2B621FD0002F2047 /* BankManagerConsoleAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB1F5562B621F3C002F2047 /* BankManagerConsoleAppTests.swift */; }; CEB1F55D2B621FDA002F2047 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A78F52B5F562A0027E5D3 /* LinkedList.swift */; }; CEB1F55E2B621FDD002F2047 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A78F72B5FCE0E0027E5D3 /* Node.swift */; }; CEB1F55F2B621FE0002F2047 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A6A78F92B5FCE7C0027E5D3 /* Queue.swift */; }; + CEC6B7552B721A7700FC29C5 /* BankManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B7542B721A7700FC29C5 /* BankManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -38,11 +38,11 @@ 8A6A78F92B5FCE7C0027E5D3 /* 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 = ""; }; CE98E59D2B6396E600C36337 /* Banker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Banker.swift; sourceTree = ""; }; CE98E59F2B639AD800C36337 /* Customer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Customer.swift; sourceTree = ""; }; CEB1F5542B621F3C002F2047 /* BankManagerConsoleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BankManagerConsoleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; CEB1F5562B621F3C002F2047 /* BankManagerConsoleAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankManagerConsoleAppTests.swift; sourceTree = ""; }; + CEC6B7542B721A7700FC29C5 /* BankManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -84,7 +84,7 @@ C7694E78259C3EC00053667F /* BankManagerConsoleApp */ = { isa = PBXGroup; children = ( - C7D65D1A259C8190005510E0 /* BankManager.swift */, + CEC6B7542B721A7700FC29C5 /* BankManager.swift */, CE98E59D2B6396E600C36337 /* Banker.swift */, CE98E59F2B639AD800C36337 /* Customer.swift */, C7694E79259C3EC00053667F /* main.swift */, @@ -194,9 +194,9 @@ 8A6A78FA2B5FCE7C0027E5D3 /* Queue.swift in Sources */, 8A6A78F62B5F562A0027E5D3 /* LinkedList.swift in Sources */, CE98E5A02B639AD800C36337 /* Customer.swift in Sources */, + CEC6B7552B721A7700FC29C5 /* BankManager.swift in Sources */, CE98E59E2B6396E600C36337 /* Banker.swift in Sources */, C7694E7A259C3EC00053667F /* main.swift in Sources */, - C7D65D1B259C8190005510E0 /* BankManager.swift in Sources */, 8A6A78F82B5FCE0E0027E5D3 /* Node.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/BankManager.swift b/BankManagerConsoleApp/BankManagerConsoleApp/BankManager.swift similarity index 100% rename from BankManager.swift rename to BankManagerConsoleApp/BankManagerConsoleApp/BankManager.swift diff --git a/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj b/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj index 62a20a2d..7d212cce 100644 --- a/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj +++ b/BankManagerUIApp/BankManagerUIApp.xcodeproj/project.pbxproj @@ -3,19 +3,25 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* 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 */; }; + C7694E3F259C3E9F0053667F /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7694E3E259C3E9F0053667F /* MainViewController.swift */; }; 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 */; }; + CEC6B7412B71FBCC00FC29C5 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B7402B71FBCC00FC29C5 /* MainView.swift */; }; + CEC6B7432B72165600FC29C5 /* CustomerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B7422B72165600FC29C5 /* CustomerView.swift */; }; + CEC6B74B2B7218B800FC29C5 /* Banker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B74A2B7218B800FC29C5 /* Banker.swift */; }; + CEC6B74D2B72191400FC29C5 /* Customer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B74C2B72191400FC29C5 /* Customer.swift */; }; + CEC6B7512B72197400FC29C5 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B74E2B72197400FC29C5 /* Node.swift */; }; + CEC6B7522B72197400FC29C5 /* Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B74F2B72197400FC29C5 /* Queue.swift */; }; + CEC6B7532B72197400FC29C5 /* LinkedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC6B7502B72197400FC29C5 /* LinkedList.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,8 +45,7 @@ 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 = ""; }; + C7694E3E259C3E9F0053667F /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; 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 +55,14 @@ 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 = ""; }; + C7D65D1D259C81BD005510E0 /* BankManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BankManager.swift; sourceTree = ""; }; + CEC6B7402B71FBCC00FC29C5 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MainView.swift; path = BankManagerUIApp/View/MainView.swift; sourceTree = SOURCE_ROOT; }; + CEC6B7422B72165600FC29C5 /* CustomerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomerView.swift; sourceTree = ""; }; + CEC6B74A2B7218B800FC29C5 /* Banker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Banker.swift; sourceTree = ""; }; + CEC6B74C2B72191400FC29C5 /* Customer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Customer.swift; sourceTree = ""; }; + CEC6B74E2B72197400FC29C5 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; + CEC6B74F2B72197400FC29C5 /* Queue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Queue.swift; sourceTree = ""; }; + CEC6B7502B72197400FC29C5 /* LinkedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkedList.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -101,13 +113,9 @@ 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 */, + CEC6B73E2B71F95A00FC29C5 /* Model */, + CEC6B73D2B71F94200FC29C5 /* View */, + CEC6B73F2B71F97B00FC29C5 /* Controller */, C7694E48259C3EA20053667F /* Info.plist */, ); path = BankManagerUIApp; @@ -131,6 +139,40 @@ path = BankManagerUIAppUITests; sourceTree = ""; }; + CEC6B73D2B71F94200FC29C5 /* View */ = { + isa = PBXGroup; + children = ( + CEC6B7402B71FBCC00FC29C5 /* MainView.swift */, + CEC6B7422B72165600FC29C5 /* CustomerView.swift */, + C7694E45259C3EA20053667F /* LaunchScreen.storyboard */, + C7694E43259C3EA20053667F /* Assets.xcassets */, + ); + path = View; + sourceTree = ""; + }; + CEC6B73E2B71F95A00FC29C5 /* Model */ = { + isa = PBXGroup; + children = ( + C7D65D1D259C81BD005510E0 /* BankManager.swift */, + CEC6B74A2B7218B800FC29C5 /* Banker.swift */, + CEC6B74C2B72191400FC29C5 /* Customer.swift */, + CEC6B7502B72197400FC29C5 /* LinkedList.swift */, + CEC6B74E2B72197400FC29C5 /* Node.swift */, + CEC6B74F2B72197400FC29C5 /* Queue.swift */, + ); + path = Model; + sourceTree = ""; + }; + CEC6B73F2B71F97B00FC29C5 /* Controller */ = { + isa = PBXGroup; + children = ( + C7694E3A259C3E9F0053667F /* AppDelegate.swift */, + C7694E3E259C3E9F0053667F /* MainViewController.swift */, + C7694E3C259C3E9F0053667F /* SceneDelegate.swift */, + ); + path = Controller; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -193,8 +235,9 @@ C7694E2F259C3E9F0053667F /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1210; - LastUpgradeCheck = 1210; + LastUpgradeCheck = 1520; TargetAttributes = { C7694E36259C3E9F0053667F = { CreatedOnToolsVersion = 12.1; @@ -236,7 +279,6 @@ files = ( C7694E47259C3EA20053667F /* LaunchScreen.storyboard in Resources */, C7694E44259C3EA20053667F /* Assets.xcassets in Resources */, - C7694E42259C3E9F0053667F /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -261,9 +303,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C7694E3F259C3E9F0053667F /* ViewController.swift in Sources */, + CEC6B74B2B7218B800FC29C5 /* Banker.swift in Sources */, + C7694E3F259C3E9F0053667F /* MainViewController.swift in Sources */, C7694E3B259C3E9F0053667F /* AppDelegate.swift in Sources */, + CEC6B7512B72197400FC29C5 /* Node.swift in Sources */, + CEC6B74D2B72191400FC29C5 /* Customer.swift in Sources */, + CEC6B7522B72197400FC29C5 /* Queue.swift in Sources */, C7694E3D259C3E9F0053667F /* SceneDelegate.swift in Sources */, + CEC6B7432B72165600FC29C5 /* CustomerView.swift in Sources */, + CEC6B7412B71FBCC00FC29C5 /* MainView.swift in Sources */, + CEC6B7532B72197400FC29C5 /* LinkedList.swift in Sources */, C7D65D1E259C81BD005510E0 /* BankManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -300,14 +349,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 = ( @@ -356,6 +397,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -417,6 +459,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 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/AppDelegate.swift b/BankManagerUIApp/BankManagerUIApp/Controller/AppDelegate.swift similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/AppDelegate.swift rename to BankManagerUIApp/BankManagerUIApp/Controller/AppDelegate.swift diff --git a/BankManagerUIApp/BankManagerUIApp/Controller/MainViewController.swift b/BankManagerUIApp/BankManagerUIApp/Controller/MainViewController.swift new file mode 100644 index 00000000..fac68e58 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Controller/MainViewController.swift @@ -0,0 +1,79 @@ +// +// BankManagerUIApp - ViewController.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import UIKit + +final class MainViewController: UIViewController { + private let mainView: MainView + private let manager: BankManager + private var startTime: Date? + private var isTimerRepeats: Bool = false + + init(manager: BankManager) { + self.mainView = MainView() + self.manager = manager + super.init(nibName: nil, bundle: nil) + manager.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view = mainView + view.backgroundColor = .white + } + + @objc func addCustomerButtonTapped() { + countElapsedTime() + manager.addCustomer() + manager.startBankingProcess(completion: stopTimer) + } + + @objc func resetService() { + manager.reset() + stopTimer() + } + + private func stopTimer() { + startTime = nil + isTimerRepeats = false + mainView.setTimer("업무시간 - 00:00:000") + } + + private func countElapsedTime() { + if !isTimerRepeats { + startTime = Date() + isTimerRepeats = true + } + DispatchQueue.global().async { + while self.isTimerRepeats { + Thread.sleep(forTimeInterval: 0.005) + let now = Date() + guard let start = self.startTime else { + return + } + let elapsedTime = now.timeIntervalSince(start) + let minutes = Int(elapsedTime) / 60 + let seconds = Int(elapsedTime) % 60 + let milliseconds = Int(elapsedTime * 1000) % 1000 + + let timeString = String(format: "%02d:%02d:%03d", minutes, seconds, milliseconds) + DispatchQueue.main.async { + self.mainView.setTimer("업무시간 - \(timeString)") + } + } + } + } +} + +extension MainViewController { + func showCustomerView(_ customers: [Customer], isWaiting: Bool) { + mainView.appendCustomerView(customers, isWaiting: isWaiting) + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Controller/SceneDelegate.swift b/BankManagerUIApp/BankManagerUIApp/Controller/SceneDelegate.swift new file mode 100644 index 00000000..ba494555 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Controller/SceneDelegate.swift @@ -0,0 +1,18 @@ +// +// 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) { + guard let windowScene = (scene as? UIWindowScene) else { return } + window = UIWindow(windowScene: windowScene) + window?.rootViewController = MainViewController(manager: BankManager()) + window?.makeKeyAndVisible() + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Info.plist b/BankManagerUIApp/BankManagerUIApp/Info.plist index 5b531f7b..2688b32b 100644 --- a/BankManagerUIApp/BankManagerUIApp/Info.plist +++ b/BankManagerUIApp/BankManagerUIApp/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/Model/BankManager.swift b/BankManagerUIApp/BankManagerUIApp/Model/BankManager.swift new file mode 100644 index 00000000..8aa5a0f7 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Model/BankManager.swift @@ -0,0 +1,165 @@ +// +// BankManager.swift +// Created by yagom. +// Copyright © yagom academy. All rights reserved. +// + +import Foundation + +final class BankManager { + var delegate: MainViewController? + private let banker: Banker = Banker() + private(set) var isDepositQueueRunning: Bool = false + private(set) var isLoanQueueRunning: Bool = false + private var customerCountToStart: Int = 1 + private let semaphore: DispatchSemaphore = DispatchSemaphore(value: 1) + + private(set) var totalWaiting: [Customer] = [] { + didSet { + DispatchQueue.main.async { + self.delegate?.showCustomerView(self.totalWaiting, isWaiting: true) + } + } + } + private(set) var totalProgress: [Customer] = [] { + didSet { + DispatchQueue.main.async { + self.delegate?.showCustomerView(self.totalProgress, isWaiting: false) + } + } + } + + private var depositCustomerQueue: Queue = Queue(linkedList: LinkedList()) + private var loanCustomerQueue: Queue = Queue(linkedList: LinkedList()) + private let depositQueue: DispatchQueue = DispatchQueue(label: "예금업무큐", attributes: .concurrent) + private let loanQueue: DispatchQueue = DispatchQueue(label: "대출업무큐") + + init() { + depositCustomerQueue.delegate = self + loanCustomerQueue.delegate = self + } +} + +extension BankManager { + func startBankingProcess(completion: @escaping () -> Void) { + let group: DispatchGroup = DispatchGroup() + + let depositTask = DispatchWorkItem { [weak self] in + self?.isDepositQueueRunning = true + let depositSemaphore = DispatchSemaphore(value: 2) + guard let isEmpty = self?.depositCustomerQueue.isEmpty() else { + return + } + while !isEmpty { + depositSemaphore.wait() + guard let node = self?.depositCustomerQueue.dequeue() else { + depositSemaphore.signal() + return + } + let customer = node.value + let task = DispatchWorkItem { + self?.appendCustomerToProgress(customer) + self?.banker.provideService(to: customer) + self?.removeCustomerFromProgress(customer) + depositSemaphore.signal() + } + self?.depositQueue.async(execute: task) + } + self?.isDepositQueueRunning = false + } + + let loanTask = DispatchWorkItem { [weak self] in + self?.isLoanQueueRunning = true + let loanSemaphore = DispatchSemaphore(value: 1) + guard let isEmpty = self?.loanCustomerQueue.isEmpty() else { + return + } + while !isEmpty { + loanSemaphore.wait() + guard let node = self?.loanCustomerQueue.dequeue() else { + loanSemaphore.signal() + return + } + let customer = node.value + let task = DispatchWorkItem { + self?.appendCustomerToProgress(customer) + self?.banker.provideService(to: customer) + self?.removeCustomerFromProgress(customer) + loanSemaphore.signal() + } + self?.loanQueue.async(execute: task) + } + self?.isLoanQueueRunning = false + DispatchQueue.main.async { + completion() + } + } + + if !isDepositQueueRunning { + DispatchQueue.global().async(group: group, execute: depositTask) + } + if !isLoanQueueRunning { + DispatchQueue.global().async(group: group, execute: loanTask) + } + } + + func addCustomer(_ count: Int = 10) { + for i in customerCountToStart.. { + private var head: Node? + + var count: Int { + if head == nil { + return 0 + } + + var count = 1 + var lastNode: Node? = head + + while let next = lastNode?.next { + lastNode = next + count += 1 + } + + return count + } + + init(head: Node? = nil) { + self.head = head + } +} + +extension LinkedList { + subscript(index: Int) -> Node? { + if index < 0 || index > count - 1 { + return nil + } + + if index == 0 { + return head + } + + var nextNode: Node? = head + + for _ in 1...index { + guard let next = nextNode?.next else { + return nil + } + nextNode = next + } + return nextNode + } + + func getNode(index: Int) -> Node? { + if index < 0 || index > count { + return nil + } + + if index == 0 { + return head + } + + var nextNode: Node? = head + + for _ in 1...index { + guard let next = nextNode?.next else { + return nil + } + nextNode = next + } + return nextNode + } + + func add(_ newNode: Node) { + guard let lastNode = getLastNode() else { + head = newNode + return + } + + lastNode.refer(to: newNode) + } + + func add(_ newNode: Node, after previousNode: Node) { + if let temporaryNode = previousNode.next { + newNode.refer(to: temporaryNode) + } + + previousNode.refer(to: newNode) + } + + func remove(_ node: Node) -> Node? { + guard let previousNode = findPreviousNode(of: node) else { + let result = head + head = head?.next + result?.refer(to: nil) + return result + } + + guard let nextNode = node.next else { + previousNode.refer(to: nil) + return node + } + + previousNode.refer(to: nextNode) + return node + } + + func removeAll() { + head = nil + } +} + +extension LinkedList { + private func findPreviousNode(of node: Node) -> Node? { + if let reference = head?.next, reference == node { + return head + } + + var nextNode = head?.next + + while nextNode?.next != nil { + if let reference = nextNode?.next, reference == node { + return nextNode + } + nextNode = nextNode?.next + } + + return nil + } + + private func getLastNode() -> Node? { + if head == nil { + return nil + } + + var lastNode = head + while let next = lastNode?.next { + lastNode = next + } + + return lastNode + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Model/Node.swift b/BankManagerUIApp/BankManagerUIApp/Model/Node.swift new file mode 100644 index 00000000..e9d19bfb --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Model/Node.swift @@ -0,0 +1,24 @@ + +import Foundation + +final class Node { + private let _value: T + var value: T { + return _value + } + + private(set) var next: Node? + + init(value: T, next: Node? = nil) { + self._value = value + self.next = next + } + + static func == (lhs: Node, rhs: Node) -> Bool { + return lhs.value == rhs.value + } + + func refer(to node: Node?) { + next = node + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/Model/Queue.swift b/BankManagerUIApp/BankManagerUIApp/Model/Queue.swift new file mode 100644 index 00000000..d465bf04 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/Model/Queue.swift @@ -0,0 +1,49 @@ + +import Foundation + +class Queue { + var delegate: BankManager? + private let linkedList: LinkedList + + init(linkedList: LinkedList) { + self.linkedList = linkedList + } + + func isEmpty() -> Bool { + return linkedList.count == 0 + } + + func enqueue(node: Node) { + DispatchQueue.main.async { + self.delegate?.setWaitingCustomer() + } + linkedList.add(node) + } + + @discardableResult + func dequeue() -> Node? { + DispatchQueue.main.async { + self.delegate?.setWaitingCustomer() + } + guard let firstNode = linkedList[0] else { + return nil + } + return linkedList.remove(firstNode) + } + + func peek() -> Node? { + return linkedList[0] + } + + func clear() { + linkedList.removeAll() + } + + func count() -> Int { + return linkedList.count + } + + func getNodeValue(at index: Int) -> T? { + return linkedList[index]?.value + } +} 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/View/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AccentColor.colorset/Contents.json rename to BankManagerUIApp/BankManagerUIApp/View/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/BankManagerUIApp/BankManagerUIApp/View/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to BankManagerUIApp/BankManagerUIApp/View/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Assets.xcassets/Contents.json b/BankManagerUIApp/BankManagerUIApp/View/Assets.xcassets/Contents.json similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Assets.xcassets/Contents.json rename to BankManagerUIApp/BankManagerUIApp/View/Assets.xcassets/Contents.json diff --git a/BankManagerUIApp/BankManagerUIApp/Base.lproj/LaunchScreen.storyboard b/BankManagerUIApp/BankManagerUIApp/View/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from BankManagerUIApp/BankManagerUIApp/Base.lproj/LaunchScreen.storyboard rename to BankManagerUIApp/BankManagerUIApp/View/Base.lproj/LaunchScreen.storyboard diff --git a/BankManagerUIApp/BankManagerUIApp/View/CustomerView.swift b/BankManagerUIApp/BankManagerUIApp/View/CustomerView.swift new file mode 100644 index 00000000..0d182491 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/CustomerView.swift @@ -0,0 +1,41 @@ + +import UIKit + +class CustomerView: UIView { + private var label: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .title3) + label.textAlignment = .center + label.textColor = .black + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setLabelText(customerNumber: Int, serviceType: String) { + self.label.text = "\(customerNumber) - \(serviceType)" + } + + func setLabelColor(_ color: UIColor) { + self.label.textColor = color + } + + private func setupConstraints() { + self.addSubview(label) + self.heightAnchor.constraint(equalToConstant: 25).isActive = true + self.setContentHuggingPriority(.defaultLow, for: .horizontal) + + label.translatesAutoresizingMaskIntoConstraints = false + label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true + label.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true + label.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.9).isActive = true + } +} diff --git a/BankManagerUIApp/BankManagerUIApp/View/MainView.swift b/BankManagerUIApp/BankManagerUIApp/View/MainView.swift new file mode 100644 index 00000000..64b47265 --- /dev/null +++ b/BankManagerUIApp/BankManagerUIApp/View/MainView.swift @@ -0,0 +1,194 @@ + +import UIKit + +class MainView: UIView { + private let buttonStackView: UIStackView = { + let buttonStackView = UIStackView() + buttonStackView.axis = .horizontal + buttonStackView.distribution = .fillEqually + return buttonStackView + }() + private let addCustomerButton: UIButton = { + let addCustomerButton = UIButton() + addCustomerButton.setTitle("고객 10명 추가", for: .normal) + addCustomerButton.setTitleColor(.systemBlue, for: .normal) + addCustomerButton.titleLabel?.font = .preferredFont(forTextStyle: .body) + addCustomerButton.addTarget(nil, action: #selector(MainViewController.addCustomerButtonTapped), for: .touchUpInside) + return addCustomerButton + }() + private let resetButton: UIButton = { + let resetButton = UIButton() + resetButton.setTitle("초기화", for: .normal) + resetButton.setTitleColor(.systemRed, for: .normal) + resetButton.titleLabel?.font = .preferredFont(forTextStyle: .body) + resetButton.addTarget(nil, action: #selector(MainViewController.resetService), for: .touchUpInside) + return resetButton + }() + + private let timerLabel: UILabel = { + let timerLabel = UILabel() + timerLabel.font = .preferredFont(forTextStyle: .title1) + timerLabel.text = "업무시간 - 00:00:000" + timerLabel.textAlignment = .center + return timerLabel + }() + + private let taskLabelStackView: UIStackView = { + let taskLabelStackView = UIStackView() + taskLabelStackView.axis = .horizontal + taskLabelStackView.distribution = .fillEqually + return taskLabelStackView + }() + private let waitingLabel: UILabel = { + let waitingLabel = UILabel() + waitingLabel.backgroundColor = .systemGreen + waitingLabel.textColor = .white + waitingLabel.text = "대기중" + waitingLabel.textAlignment = .center + waitingLabel.font = .preferredFont(forTextStyle: .largeTitle) + return waitingLabel + }() + private let progressLabel: UILabel = { + let progressLabel = UILabel() + progressLabel.backgroundColor = .purple + progressLabel.textColor = .white + progressLabel.text = "업무중" + progressLabel.textAlignment = .center + progressLabel.font = .preferredFont(forTextStyle: .largeTitle) + return progressLabel + }() + + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .horizontal + stackView.distribution = .fillEqually + return stackView + }() + private let scrollView: UIScrollView = UIScrollView() + private let waitingStackView: UIStackView = { + let waitingStackView = UIStackView() + waitingStackView.axis = .vertical + waitingStackView.distribution = .fill + waitingStackView.alignment = .center + return waitingStackView + }() + + private let progressStackView: UIStackView = { + let progressStackView = UIStackView() + progressStackView.axis = .vertical + progressStackView.distribution = .fill + return progressStackView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(buttonStackView) + self.addSubview(timerLabel) + self.addSubview(taskLabelStackView) + self.addSubview(stackView) + + setupButtonStackViewConstraints() + setupTimerLabelConstraints() + setupTaskLabelStackViewConstraints() + setupStackViewConstraints() + setupScrollViewConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension MainView { + func setTimer(_ time: String) { + timerLabel.text = time + } +} + +protocol MainViewDelegate { + func appendCustomerView(_ customers: [Customer], isWaiting: Bool) +} + +extension MainView: MainViewDelegate { + func appendCustomerView(_ customers: [Customer], isWaiting: Bool) { + let stackView = isWaiting ? waitingStackView : progressStackView + + stackView.arrangedSubviews.forEach { view in + stackView.removeArrangedSubview(view) + view.removeFromSuperview() + } + + customers.forEach { customer in + let customerView: CustomerView = CustomerView() + customerView.setLabelText(customerNumber: customer.waitingNumber, serviceType: customer.requiredService.value) + if customer.requiredService == .loan { + customerView.setLabelColor(.purple) + } + stackView.addArrangedSubview(customerView) + } + + let spacerView = UIView() + spacerView.setContentHuggingPriority(.defaultLow, for: .vertical) + spacerView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) + stackView.addArrangedSubview(spacerView) + } +} + +extension MainView { + private func setupButtonStackViewConstraints() { + buttonStackView.addArrangedSubview(addCustomerButton) + buttonStackView.addArrangedSubview(resetButton) + + buttonStackView.translatesAutoresizingMaskIntoConstraints = false + buttonStackView.setContentHuggingPriority(.defaultHigh, for: .vertical) + buttonStackView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true + buttonStackView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true + buttonStackView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true + } + + private func setupTimerLabelConstraints() { + timerLabel.translatesAutoresizingMaskIntoConstraints = false + timerLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + timerLabel.topAnchor.constraint(equalTo: buttonStackView.bottomAnchor).isActive = true + timerLabel.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true + timerLabel.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true + } + + private func setupTaskLabelStackViewConstraints() { + taskLabelStackView.addArrangedSubview(waitingLabel) + taskLabelStackView.addArrangedSubview(progressLabel) + + taskLabelStackView.translatesAutoresizingMaskIntoConstraints = false + taskLabelStackView.setContentHuggingPriority(.defaultHigh, for: .vertical) + taskLabelStackView.topAnchor.constraint(equalTo: timerLabel.bottomAnchor, constant: 10).isActive = true + taskLabelStackView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true + taskLabelStackView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true + } + + private func setupStackViewConstraints() { + stackView.addArrangedSubview(scrollView) + stackView.addArrangedSubview(progressStackView) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.setContentHuggingPriority(.defaultLow, for: .vertical) + stackView.topAnchor.constraint(equalTo: taskLabelStackView.bottomAnchor, constant: 10).isActive = true + stackView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true + stackView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true + } + + private func setupScrollViewConstraints() { + scrollView.addSubview(waitingStackView) + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true + scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true + scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor).isActive = true + + waitingStackView.translatesAutoresizingMaskIntoConstraints = false + waitingStackView.setContentHuggingPriority(.defaultLow, for: .horizontal) + waitingStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor).isActive = true + waitingStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor).isActive = true + waitingStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true + waitingStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor).isActive = true + waitingStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true + } +} 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. - } - - -} -