Skip to content

serena0720/ios-bank-manager

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

💲 은행창구 매니저 for UI-App User💸

📖 목차

  1. 소개
  2. 팀원
  3. 타임라인
  4. UML & 파일트리
  5. 실행 화면
  6. 트러블 슈팅
  7. 팀 회고
  8. 참고 링크

1. 📢 소개

Dear. UI-App User Customer

Bank에 업무를 보러 오셨나요?

저희 은행은 10명대기중에 추가를 해드리고 있습니다!

순서가 되면 BankClerk이 순번에 따라 업무중 라인에서 호출해드립니다.

잠시만 기다려 주세요~

핵심 개념 및 경험

  • MVC
    • Model, View, Controller가 하나의 역할만 할 수 있도록 구현
  • Timer
    • Timer를 이용하여 업무시간을 측정할 수 있는 WorkTimer 구현
  • CustomView
    • CustomView를 만들어 ViewController를 분리하고 코드로만 UI 구현
    • StackView를 활용하여 Label의 추가 삭제 기능을 구현
  • Concurrency UI update
    • 멀티 스레드 환경에서 Operation.main을 이용한 UI 업데이트 작성 수행
  • Swift Package Manager
    • 콘솔앱의 Customer를 로컬 패키지로 만들어 UI앱에 활용

2. 👤 팀원

Serena 🐷 Erick 🦦

3. ⏱️ 타임라인

프로젝트 기간 : 2023.07.10 ~ 2023.07.21

날짜 내용
2023.07.10 ▫️ Linked List 구현
▫️ Linked List를 이용한 Queue 구현
2023.07.11 ▫️ 코드 개선을 위한 리펙토링
2023.07.12 ▫️ 고객의 역할을 하는 Customer
▫️ 은행원의 역할을 하는 BankClerk 구현
▫️ 은행의 업무를 하기 위한 Bank 구현
▫️ Bank를 관리하는 BankManager 구현
2023.07.13 ▫️ 코드 개선을 위한 리펙토링
2023.07.14 ▫️ 업무의 종류를 나누기 위해 WorkType 구현
▫️ 은행원의 인원에 따라 비동기적으로 업무를 처리하는 로직 구현
▫️ 은행원의 WorkType에 따라 업무를 나눠 주는 로직 구현
▫️ README 작성
2023.07.16 ▫️ 코드 개선을 위한 리펙토링
▫️ WorkType에 업무 Name 추가
2023.07.17 ▫️ HTTP 개인 공부
2023.07.18 ▫️ 코드 개선을 위한 리펙토링
2023.07.19 ▫️ Queueprotocol Queueable로 추상화
▫️ 은행창구 매니저 앱의 화면을 구성하는 BankManagerView 구현
▫️ DynamicType 적용
2023.07.20 ▫️ MVC 파일 분리 및 Timer 개인 공부
2023.07.21 ▫️ SPM 활용하여 로컬 라이브러리 추가
▫️Console App에 구현한 BankUI App에 맞게 리펙토링
▫️ 업무시간을 측정하는 WorkTimer 구현
▫️ Controller에서 사용자 입력에 맞게 View를 업데이트하고 Model을 호출하는 로직 구현

4. 📊 UML & 파일트리

UML

파일트리

BankManagerUIApp
├── Application
│   ├── AppDelegate.swift
│   └── SceneDelegate.swift
├── ViewController
│   └── BankManagerViewController.swift
├── View
│   └── BankManagerView.swift
├── Model
│   ├── Bank
│   │   ├── Bank.swift
│   │   └── BankClerk.swift
│   └── WorkTimer.swift
└── Extenstion
    ├── Double+.swift
    └── Notification.Name+.swift

5. 📲 실행 화면

최초 대기인원 추가 시 업무 완료 전 대기인원 추가 시 업무 완료 후 대기인원 추가 시
업무 중 초기화 시 업무 완료 후 초기화 시 초기화 후 대기인원 추가 시

6. 🛎️ 트러블 슈팅

MVC 패턴에 따른 구조 고민

🔥 문제점

  • Model, View, Controller의 역할에 대해 고민했습니다.
    • 또한 객체가 서로의 프로퍼티에 직접 접근하는 것이 아닌 메서드를 통한 의사소통을 해야한다고 생각했습니다.

🧯 해결방법

  • View: ViewView객체를 프로퍼티로 가지고 Constraints 코드와 View객체에 데이터를 세팅할 수 있는 메서드를 가지고 있습니다.
  • Model: 비즈니스 로직을 실행시킬 수 있는 코드와 Controller에서 이를 실행 시킬 수 있는 메서드를 가지고 있습니다.
  • Controller: ViewModel을 객체로 가지고 있고 사용자의 입력이나 특정 이벤트가 발생했을때 뷰를 업데이트 시키거나 Model의 로직을 실행시킵니다.

화면 회전 / scrollview

🔥 문제점

  • 화면 회전을 해도 UI가 기기의 가장자리에 잘리지 않고 출력될 수 있도록 구현하기 위해 고민했습니다.
  • 그리고 화면 스크롤을 대기중/업무중 stackView만 스크롤이 되도록 할지 전체 화면을 모두 스크롤을 할지 고민했습니다.

🧯 해결방법

  • 화면 회전을 해도 UI가 기기의 가장자리에 잘리지 않고 출력될 수 있도록 scrollView를 뷰의 안전구역 내에 위치하게 하였습니다. 이를 위해 view.safeAreaLayoutGuideconstraint를 설정해주었습니다.
    private func setUpScrollViewConstraints() {
        NSLayoutConstraint.activate([
            scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor),
            scrollView.frameLayoutGuide.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor),
            scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor),
            scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
        ])
    }
  • 앱을 이용할 때 화면 내 뷰객체가 스크롤되는 것보단 전체 화면을 스크롤 하는 쪽이 접근성이 더 좋다고 생각하여 전체화면이 스크롤 되도록 설정하였습니다.
    • 특히 야곰 닷넷을 통해 배운 내용 또한 활용하였습니다. 스크롤뷰 설정 시 framemultiply을 1로 맞춘 후 constant 1값을 주면 contentview의 크기가 작아도 드래그 및 스크롤의 효과를 내었습니다.
    let contentViewHeightConstraints = contentView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor, constant: 1)
    contentViewHeightConstraints.priority = .init(1)
    contentViewHeightConstraints.isActive = true

비동기 처리 로직 처리

🔥 문제점

  • 직전 스텝과 달리 UI에 맞추어 프로젝트를 구성하다보니 어떠한 방식으로 비동기를 구현할지 고민하였습니다.
    • 첫번째로 DispatchQueueOperation과 달리 스레드에 작업을 전달 시 변경 및 취소가 되지 않았습니다. 이에 UImain스레드에서만 진행기 때문에 작동 시 충돌의 우려가 있었습니다.
    • 두번째로 콘솔앱과 UI를 구현할 때 뷰컨트롤이라는 가장 큰 차이가 생겼습니다. 이에 구조를 이전과 동일하게 진행하기엔 어색한 부분이 생길 수 있다고 생각했습니다

🧯 해결방법

  • Operation으로 작업을 타입화 시켜줄 수 있고, Queue를 이용한 작업관리가 더 쉬워 코드가 간결하며 가독성이 좋아져 GCD로 구현되어 있던 비동기 로직을 Operation으로 변경하였습니다.
    • BankClerk을 업무별로 나눠 인스턴스를 생성하는 것이 아닌 업무별로 OperationQueue를 나눠 생성하여 스레드 관리를 하였습니다.
    • 그리고 BankClerk.carryOutBankServiceBlockOperation으로 타입화하여 업무별 Queue에 넣어주는 것으로 비동기 처리를 하였습니다.
    • 초기화 버튼을 눌렀을 때 이미 큐에 들어간 작업을 cancelAllOperations를 이용하여 취소하였습니다.
    class Bank {
        // ...
        private let loanOperationQueue: OperationQueue = {
            let operationQueue = OperationQueue()
            operationQueue.maxConcurrentOperationCount = WorkType.loan.numberOfBankClerk
    
            return operationQueue
        }()
    
        private let depositOperationQueue: OperationQueue = {
            let operationQueue = OperationQueue()
            operationQueue.maxConcurrentOperationCount = WorkType.deposit.numberOfBankClerk
    
            return operationQueue
        }()
        
        // ...
        private func startBankService() {
            while !waitingLine.isEmpty {
                guard let currentCustomer = waitingLine.dequeue() else { return }
                let operation = BlockOperation { BankClerk.carryOutBankService(for: currentCustomer) }
    
                switch currentCustomer.workType {
                case .deposit:
                    depositOperationQueue.addOperation(operation)
                case .loan:
                    loanOperationQueue.addOperation(operation)
                default:
                    print("workType이 nil입니다.")
                }
            }
        }
    
        func stopBankService() {
            // ...
            depositOperationQueue.cancelAllOperations()
            loanOperationQueue.cancelAllOperations()
        }
    }

스크롤 시 타이머 멈춤

🔥 문제점

  • 화면을 스크롤 시 타이머가 멈추는 문제가 있었습니다. 이는 사용자가 스크롤을 했을 때 RunLoop가 tracking 모드로 바뀌어 타이머가 실행되지 않는 문제였습니다.

🧯 해결방법

  • 이를 해결하기 위해 현재 런루프에 타이머를 .common 모드로 등록하였습니다.

.common: 하나 이상의 다른 실행 모드를 포함한 모드

RunLoop.current.add(timer, forMode: .common)

Swift Package Manager를 활용하여 콘솔앱과 UI 통합

🔥 문제점

  • 기존에 구현한 콘솔앱 프로젝트를 활용하여 새롭게 UI프로젝트를 진행함에 있어 어떻게 파일을 합칠지 고민을 했습니다. 콘솔앱 프로젝트에 구현한 파일의 경로를 추적하여 파일을 공유하는 방법도 가능하지만 이 방법을 사용할 경우 UI프로젝트를 위해 파일을 변경할 경우 콘솔앱의 프로젝트도 함께 수정된다는 단점이 존재했습니다.

🧯 해결방법

  • 콘솔앱과 UI 프로젝트 모두 Queue, Linked-List, Customer 등 큐 구조와 Customer 내용을 로컬 framework로 만들었습니다. 프레임워크를 활용하는데 CocoaPods, Carthage, SPM 등 여러 방법이 있지만 로컬로 바로 연동이 가능한 SPM을 사용하여 콘솔앱 프로젝트의 공통 내용을 UI 프로젝트에 적용하였습니다.


Public 키워드

🔥 문제점

  • swift packmanager를 사용하여 customer 로컬 패키지를 생성하여 활용하는 과정에서 직접 만든 패지를 활용하다보니 새로운 문제에 직면했습니다. 기본적으로 설정되어있는 internal을 모듈 외부에서도 사용가능하게 수정해야하는 문제였습니다. 모듈 외부에서 사용 가능하게 하는 접근제어자에는 publicopen이 있습니다. 이 둘의 차이점을 고려하여 적용하고자 하였습니다.

🧯 해결방법

  • openpublic은 모두 외부에 개방이 되어 있어 모듈 밖에서 모두 접근이 가능하다는 공통점이 있지만, open의 경우 오버라이드, 서브클래싱이 가능하지만 public의 경우 오버라이드, 서브클래싱이 불가능합니다. 이러한 차이를 고려을했을 때 정형화된 큐와 Customer 객체를 공유하고자 프레임워크를 생성한 저희의 의도에는 public이 더 적절하다 생각하여 public을 각 메소드, 프로퍼티별로 추가하였습니다.
    public struct Customer {
        public let waitingNumber: Int
        public let workType: WorkType? = WorkType.allCases.randomElement()
    
        public init(waitingNumber: Int) {
            self.waitingNumber = waitingNumber
        }
    }

7. 💭 팀 회고

팀 회고

우리팀이 잘한 점😃

  • 코드를 구현함에 있어 서로의 의견을 반영하여 코드를 작성하였습니다.
  • 프로젝트 구현을 함에 있어 한가지 방법이 아닌 여러가지 방법을 고려한 점이 좋았습니다.
  • 코드를 깊이있게 고민하고 공부한 후 프로젝트를 진행하였습니다.
  • 시간 내에 프로젝트를 완성도 있게 마무리하였습니다.

우리팀이 아쉬웠던 점😭

  • 팀원 간 시간이 잘 안 맞아 더 많은 공부를 함께하지 못 했던 것이 아쉬웠습니다.
  • 개인 사정으로 시간 조정 시 어려움이 있었습니다.

8. 🔗 참고 링크

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%