SeungJae Lee
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    ## 프로젝트 7 - 은행 창구 매니저 # 개요 1. 프로젝트 기간: '21.12.20 ~ '21.12.31 2. 캠퍼 : Lily, 숲재 3. 리뷰어 : 그린 # 학습 키워드 1. Step1 - `LinkedList` - `Queue` - `UnitTest` 2. Step2 - `Concurrency Programming` - `Dispatch Group` - `Dispatch Semaphore` - `Class versus Struct` - `Closure` - `CFAbsoluteTimeGetCurrent` 3. Step3 - GCD - # STEP1 ## 고민한 점 **1. Queue의 Clear메서드 구현** ```swift mutating func removeAll() { head = nil tail = nil } ``` Queue의 `Clear`메서드를 구현하기 위해 LinkedList에 구현 된 `RemoveAll`메서드를 사용하였습니다. 이 과정에서 LinkedList의 head에 nil을 할당하면 tail을 제외한 모든 Node가 deinit이 되는것을 확인할 수 있었습니다. 따라서 head와 tail에 nil을 할당하는 것으로 연결리스트가 가지고 있는 모든 Node를 삭제하는 함수를 구현했습니다. tail만 남아있게 되는 이유는 `LinkedList` 타입에서 참조를 계속 해주고 있기 때문이라고 생각이 되는데, tail변수를 굳이 약한 참조할 필요는 없는 것 같아 그대로 두었습니다. <br> **2. Struct와 Class중 어느것을 선택할지** `Node`,`LinkedList`와 `Queue`를 구조체와 클래스 중 어떤 타입으로 구현할지 고민했습니다. `Node`는 `LinkedList`의 `next`프로퍼티로 전달될 때 참조로 전달되는 것이 효율적이라 판단하여 클래스를 선택했습니다. 그리고 `LinkedList`와 `Queue`는 class를 사용해야할 이유가 없는 것 같아서 디폴트로 struct를 사용하라는 애플의 권고에 따라서 struct로 구현했습니다. <br> ## 궁금한점 **1. Unit Test 네이밍** Unit Test의 테스트 네이밍을 어떤 컨벤션을 사용할지 고민해보았습니다. Should, When을 명시적으로 기재해서 테스트의 조건과 기대 결과를 구분하여 표현했습니다. When에 뒤따르는 내용은 테스트할 메서드에 대한 내용만 기재해주었습니다. 테스트를 위해 전제되어야하는 선행 코드에 대한 내용은 생략해주었습니다.(아래 코드에서는`queue.enqueue(1)`) ```swift func test_Should_returnValueIs1_When_dequeue() { queue.enqueue(1) let returnValue = queue.dequeue() XCTAssertEqual(returnValue, 1) } ``` 그린이 보시기에 저희가 생각한 네이밍이 적절하다고 생각하시나요? 조언 부탁드립니다. # STEP2 ## UML ![](https://i.imgur.com/H7dABci.jpg) ## 고민한 점 **1. BankManager의 역할** 이번 프로젝트는 같은 코드를 공유하는 Console App과 UI App 2개를 만들기 때문에, BankManager는 양쪽에서 모두 쓰일 수 있게 공통적인 기능만 담고 있어야 된다고 생각했습니다. 처음에는 `BankManager`가 main에서 직접 사용되면서 Console App의 실행을 담당 했는데요, `ConsoleManager`라는 별도의 타입을 만들어 Console App의 실행은 전적으로 해당 타입에서 담당하는 것으로 리팩토링 하였습니다. 또한, `ConsoleBundle` name space에서 Console App에서 쓰이는 String을 관리하도록 하여 공통으로 쓰이는 코드에서는 해당 String들을 직접적으로 알지 못하게 하려 했습니다. 다만, 이번 스텝에서는 `Bank`타입과 `BankClerk` 타입에서 `ConsoleBundle`를 직접 사용하는것을 수정하지는 못했습니다. <br> **2. Bank인스턴스 초기화 시점에 대한 고민** `ConsoleManager`의 `run()` 안에서 - 1) case가 은행개점일 때 마다 Bank인스턴스를 생성해줄 지 - 2) while구문 전에 Bank인스턴스를 초기화 한 후, 일처리를 한 뒤 인스턴스의 프로퍼티를 초기화해준 후 인스턴스를 재 사용할 지 고민했습니다. ```swift //1) case가 은행개점일 때 마다 Bank인스턴스를 생성 struct ConsoleManager { static func run() { while true { printMenu() switch receiveInput() { case ConsoleBundle.Menu.open: BankManager.createBank() BankManager.openBank() case ConsoleBundle.Menu.exit: return default: continue } } } ``` ```swift // 2) 하나의 Bank인스턴스 재사용 struct ConsoleManager { static func run(numberOfBankClerk: Int = 1) { BankManager.createBank(bank: Bank(numberOfBankClerk: numberOfBankClerk)) while true { printMenu() switch receiveInput() { case ConsoleBundle.Menu.open: BankManager.openBank() BankManager.closeBank() // Bank인스턴스의 프로퍼티를 초기화하는 작업 수행 case ConsoleBundle.Menu.exit: return default: continue } } } ``` 1번 방법으로 구현하면 은행을 개점할 때마다 인스턴스를 초기화하는 비용이 발생하고, 2번 방법으로 구현하면 인스턴스의 몇몇 프로퍼티를 초기화해야하는 작업(위 코드에서는`BankManager.cloaseBank()`이 필요하다고 생각했습니다. 인스턴스를 초기화하는 것보다 `BankManager.cloaseBank()`를 실행하는 것이 좀 더 비용이 들 것이라고 추측해서 저희는 1번 방법으로 구현했습니다. 프로그램의 주축이 되는 인스턴스를 초기화하는 작업을 보통 어떻게 구현하시는지 궁금합니다! <br> ## 궁금한점 **1.Bank의 타입에 대한 고민** `Queue`와 `LinkedList`를 구조체로 구현하였고, `Bank`타입도 처음에는 구조체로 구현하였습니다. `Bank`타입의 `Queue`타입의 프로퍼티를 소유하고 있는데, 그 프로퍼티를 변경하는 `mutating func distributeClient`라는 메서드가 문제가 되었습니다. Escaping closure관련 컴파일 오류가 발생하여 결국 Bank의 타입을 class로 변경하게 되었습니다.(좋은 해결책이 아니라고 생각하여 스텝3에서는 다른 해결방식을 찾아보려 합니다.) 그린이 직접 설명해 주시기로는 identity가 보장이 되지 않아 발생하는 문제라고 들었는데, 정확히 왜 이런 문제가 발생하는지 이해가 잘 되지 않습니다.😭 조언 부탁드립니다! ```swift struct Bank { ... private func makeBankClerksWork() { let group = DispatchGroup() for bankClerk in bankClerks { DispatchQueue.global().async(group: group) { self.distributeClient(to: bankClerk) } } group.wait() } ... //Error: Escaping closure captures mutating `self` parameter mutating private func distributeClient(to bankClerk: BankClerk) { while !self.clientQueue.isEmpty { semaphore.wait() if let client = self.clientQueue.dequeue() { semaphore.signal() bankClerk.work(for: client) completedClientCount += 1 } } } ... } ``` # STEP3 ## 고민한 점 **1. BankClerk 타입의 사용** - 시도했던 방법 `distributeClient`메서드에서는 BankClerk객체가 클라이언트 Queue를 순회하며 BankClerk이 가지고 있는 `workType`과 Client의 `workType`이 동일할때만 `remove`메서드를 사용하여 꺼내옵니다. BankClerk 객체를 순회하며 서로 다른 쓰레드에서 비동기적으로 `distributeClient`메서드를 수행하도록 시키는 방법으로 구현했습니다. "예금-예금-예금-대출" 이런 순서로 고객이 존재할때 대출 담당 은행원이 놀고있는 시간을 방지하기 위해서 이렇게 구현하였는데, 고객의 수가 많아지는 경우 탐색 시간이 길어져 불완전한 설계라고 판단하였습니다.(고객이 5만명 정도를 넘어가면 유의미하게 실행시간이 길어지는 것을 확인하였습니다.) ```swift= private func makeBankClerksWork() { let group = DispatchGroup() for bankClerk in bankClerks { DispatchQueue.global().async(group: group) { self.distributeClient(to: bankClerk) } } group.wait() } private func distributeClient(to bankClerk: BankClerk) { while !self.clientQueue.isEmpty { semaphore.wait() var i = 0 while let client = self.clientQueue.peek(i) { if client.workType == bankClerk.workType { break } i += 1 } if let client = self.clientQueue.remove(at: i) { semaphore.signal() bankClerk.work(for: client) semaphore.wait() completedClientCount += 1 semaphore.signal() } else { semaphore.signal() } } } ``` - 결정한 방법 DispatchSemaphore의 value를 해당 업무를 할 수 있는 은행원의 수라고 정의하고 아래와 같이 구현했습니다. 각각의 업무마다 DispatchSemaphore를 생성하여 배정된 은행원의 수 만큼 value를 부여했습니다. ```swift let depositSemaphore = DispatchSemaphore(value: inChargeOfDeposits) let loanSemaphore = DispatchSemaphore(value: inChargeOfLoan) let group = DispatchGroup() while let client = self.clientQueue.dequeue() { DispatchQueue.global().async(group: group) { switch client.bankTask { case .deposit: depositSemaphore.wait() self.makeBankClerkWork(for: client) depositSemaphore.signal() case .loan: loanSemaphore.wait() self.makeBankClerkWork(for: client) loanSemaphore.signal() } } } group.wait() } ``` 위와 같은 구현을 통해 기존 방법에서 겪었던 문제는 해결이 되는데, BankClerk타입을 살리면서 위와 같은 구현을 유지하는 방법을 찾는데 실패했습니다. <br> ## 궁금한 점 **1. `init()`에서 묵시적 해제 옵셔널로 초기화하는 것** `BankTask.allCases.randomElement()`는 리턴값이 항상 보장되는 상황이기때문에, 리턴값을 저장하는 변수를 묵시적 옵셔널 타입(Implicitly Unwrapped Optional)으로 정의해준 다음 `bankTask`프로퍼티에 할당해주었습니다. ```swift struct Client { let waitingNumber: Int let bankTask: BankTask init(waitingNumber: Int) { self.waitingNumber = waitingNumber let task: BankTask! = BankTask.allCases.randomElement() self.bankTask = task } } ``` 항상 값이 보장되어있는 값을 받는 변수 타입에 대해서는 Implicitly Unwrapped Optional타입을 사용해도 문제가 없을까요? 항상 @IBOutlet 프로퍼티 선언에서만 사용을 해서 이렇게 이니셜라이져에서 사용해주려하니 괜찮은 방법인가 하는 의구심이 들었습니다 (솔직히 말하면 조금 쫄렸습니다..😰) # STEP4 ## 구현 **Console App과 UIApp의 open해주는 메서드를 각각 만들어주었습니다.** - 기존 `open()`메서드의 구현내용은 UI에서 필요없는 로직이라 `openForUI()`를 만들어 ViewController의 버튼 selecotr메서드와 연결시켜주었습니다. - 고객을 추가하는 로직 또한 각 앱이 달라 `receiveClient()` 와 `receiveClient(of number: Int)`를 각각 구현해주었습니다. <br> ## 고민한 점 **1. `global().async`의 실행 블록내 에서 `global().async`를 할 때 client 순서대로 실행되는 이유** ```swift= func openForUI() { receiveClient(of: 10) DispatchQueue.global().async { self.allocateClientToBankClerk(inChargeOfDeposits: 2, inChargeOfLoans: 1) if self.totalNumberOfClient == self.completedClientCount { self.delegate?.closeBusiness(by: self.completedClientCount, workHours: "1") } } } private func allocateClientToBankClerk(inChargeOfDeposits: Int, inChargeOfLoans: Int) { let group = DispatchGroup() while let client = self.clientQueue.dequeue() { DispatchQueue.global().async(group: group) { switch client.bankTask { case .deposit: self.depositSemaphore.wait() self.makeBankClerkWork(for: client) self.depositSemaphore.signal() case .loan: self.loanSemaphore.wait() self.makeBankClerkWork(for: client) self.loanSemaphore.signal() } } } group.wait() } ``` `고객10명 추가`버튼에 연결될 `openForUI()`에서 `global().async`로 `allocateClientToBank`를 호출해주었습니다. 이 경우 `고객 10명 추가`를 연달아 누를 경우, `allocateClientToBank`가 비동기적으로 다중스레드에서 실행될 것이기 때문에 client를 dequeue해주는 동작이나 while문 안의 global().async도 다중 스레드에서 동시적으로 실행되어 결론적으로는 client 순서대로 처리되지 않을 것이라 예상했습니다. 즉, 새치기가 발생하는 것이죠... 시간관계상 이를 해결할 다른 방법으로 구현은 못 했습니다.😭 그런데 예상과 달리 순서대로 client를 처리해주어서 의문점이 들었습니다. 혹시 저희가 동작을 잘못 이해하고 문제점을 잘못 생각한 걸 까요?? 이 코드는 문제가 없는걸까요?? 이 문제에 대한 그린의 의견이 궁금합니다! **2. 같은 파일을 서로다른 프로젝트에서 공유하는 방법** 콘솔 앱과 UI앱에서 같은 파일을 공유하기 위해서 최상단 디렉토리에 BankManagerShared라는 폴더를 만들고 그 안에 공유하고자 하는 파일을 모두 넣었습니다. 그리고 해당 폴더를 각 프로젝트에 Group 가상 폴더(not reference) 방식으로 추가 해 주었습니다. <br> ## 궁금한 점 **1.코드로 뷰를 만들어 줄때 VC가 비대화 되는 점** ViewController에서 대부분의 뷰를 생성해주고 및 초기설정까지 해주기 때문에 VC가 매우 비대화 되었습니다. 이러한 상황을 피하기 위해, 각 View를 모두 커스텀으로 만든 후에 해당 커스텀 클래스에서 미리 설정까지 진행해서 넘어오는 방법을 생각해봤는데.. 시간 관계상 해보진 못했습니다. 그린의 경우에는 코드로 뷰를 짤때 어떤 방식으로 구현하시는지 궁금하네요! ## 해결하지 못한 점 **1. 초기화 버튼의 동작** 작업이 진행중일 때도 초기화 버튼을 누르면 진행하고 있는 모든 작업이 중단되도록 구현을 해야 했지만 현재 저희 코드는 나머지 작업이 계속 진행이 됩니다. 작업을 시작하고 순식간에 queue에 있는 모든 고객이 dequeue되고 작업 분배까지 끝나기 때문에 계속 작업을 진행한다고 생각이 됩니다. operation Queue를 사용해서 이 문제를 해결할 수 있다고 알게 되었는데, 공부하고 적용 해 볼 시간이 없어서 미완성으로 제출 드립니다. 꼼수로 Bank객체를 아예 초기화 해보려고도 했는데.. 모든 작업이 끝나고 deinit이 이루어져서 이 방법도 실패했습니다.😭 **2. 각 앱에서 사용하는 메서드가 다른 점** 위에서 말씀드렸듯이, 각 앱에서 공유하는 파일 내에서도 앱에 따라 사용하는 메서드가 서로 다르게 되었습니다. 기능 분리를 좀 더 잘 하면 같은 메서드를 사용할 수 있을 것 같은데, 이 점이 아쉽습니다.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully