<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Do.</title>
    <link>https://doing-programming.tistory.com/</link>
    <description>Hey_Hen!
iOS Developer
모듈화에 진심</description>
    <language>ko</language>
    <pubDate>Fri, 22 May 2026 03:36:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hey_Hen</managingEditor>
    <image>
      <title>Do.</title>
      <url>https://tistory1.daumcdn.net/tistory/4786069/attach/ed4739c52c3e4ee7a47cfda23e57e903</url>
      <link>https://doing-programming.tistory.com</link>
    </image>
    <item>
      <title>데코라이크(Decorator Like)로 더 유연한 ViewController 구조 만들기</title>
      <link>https://doing-programming.tistory.com/entry/%EB%8D%B0%EC%BD%94%EB%9D%BC%EC%9D%B4%ED%81%ACDecorator-Like%EB%A1%9C-%EB%8D%94-%EC%9C%A0%EC%97%B0%ED%95%9C-ViewController-%EA%B5%AC%EC%A1%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데코라이크(&lt;/b&gt;Decorator Like)로 더 유연한 ViewController 구조 만들기&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱을 개발하다 보면, 공통된 로직(예: 화면 전환 시 네비게이션 바/탭 바 보이기&amp;middot;숨기기, deinit 시점 로그 남기기 등)을 여러 개의 뷰컨트롤러에 반복해서 적용해야 하는 상황이 생깁니다. 이때 종종 가장 먼저 고려되는 방법이 바로 BaseViewController를 만드는 것이죠. BaseViewController에 공통 코드를 모아두고, 모든 뷰컨트롤러에서 이를 상속받으면 편리해 보이지만, 기능이 많아질수록 점점 덩치가 커지고 유지보수가 어려워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 그러한 문제를 해결하고자 &amp;ldquo;데코라이크&amp;rdquo;라는 구조를 소개하려고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BaseViewController의 덩치가 커진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네비게이션 바, 탭 바 제어 등 라이프 사이클에 따라 다양한 기능이 추가되다 보면 BaseViewController는 점점 비대해집니다.&lt;/li&gt;
&lt;li&gt;작은 수정이 발생해도 해당 클래스를 상속하는 모든 자식 뷰컨트롤러에 영향을 미칠 수 있어, 예기치 못한 사이드 이펙트가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 기능만 필요할 때도 전체를 상속받아야 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어 &amp;ldquo;deinit 시점 로그 찍기&amp;rdquo; 기능만 필요한데도, BaseViewController에 들어있는 다른 로직 전부를 상속받게 됩니다.&lt;/li&gt;
&lt;li&gt;상속 구조가 점점 복잡해지면서, BaseViewController의 실제 동작을 예측하기 어려워 집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기능을 필요에 따라 선택적으로 부여
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하단 탭 바 숨기기, 네비게이션 바 숨기기, deinit 로그 처리 등 기능들을 개별적으로 적용할 수 있게 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ViewController 라이프 사이클 기반의 확장성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;viewDidLoad, viewDidAppear, viewWillDisappear 등 각 라이프 사이클 시점에 맞춰서 동작하는 로직을 쉽게 추가/관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;충분히 가볍고 간단한 구조
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 상속 트리 없이도 각 기능을 조합해서 자유롭게 사용할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DefaultViewController와 VCDecorator 등장&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DefaultViewController&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 DefaultViewController라는 기본 뷰컨트롤러를 한 단계 만들어둡니다.&lt;br /&gt;이 컨트롤러에서는 라이프 사이클마다 호출될 콜백 형태의 핸들러를 미리 정의해둡니다. 예시로는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class DefaultViewController: UIViewController {
    var _viewDidLoad: (() -&amp;gt; Void)?
    var _viewDidAppear: (() -&amp;gt; Void)?
    var _viewWillDisappear: (() -&amp;gt; Void)?
    var _deinitBlock: () -&amp;gt; Void)?
    // ... 등등 필요한 콜백

    override func viewDidLoad() {
        super.viewDidLoad()
        _viewDidLoad?()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        _viewDidAppear?()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        _viewWillDisappear?()
    }

    deinit {
       deinitBlock?()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, DefaultViewController를 사용하는 쪽에서 각 라이프 사이클마다 실행할 로직을 옵셔널 클로저로 주입할 수 있게 됩니다.&lt;br /&gt;하지만 아직까지는 이 뷰컨 하나만으로는 &amp;ldquo;옵션별 기능&amp;rdquo;을 관리하기가 조금 번거롭습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VCDecorator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VCDecorator는 말 그대로 &amp;ldquo;디자인 패턴의 Decorator&amp;rdquo;처럼, 뷰컨트롤러에 필요한 기능을 데코레이션(얹어)하는 역할을 합니다. 사용 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;enum VCDecoratorOption {
    case hideTabBar
    case hideNavigationBar
    case logDeinit
    // 필요한 옵션을 자유롭게 추가
}

class VCDecorator {
    static func build(
        target: DefaultViewController,
        options: VCDecoratorOption...
    ) -&amp;gt; DefaultViewController {

        // 옵션별로 target에 필요한 콜백을 셋업
        for option in options {
            switch option {
            case .hideTabBar:
                // viewDidAppear 시점에 tabBar 숨기기
                let originalHandler = target._viewDidAppear
                target._viewDidAppear = {
                    originalHandler?()
                    target.tabBarController?.tabBar.isHidden = true
                }

            case .hideNavigationBar:
                // viewWillAppear 또는 viewDidAppear 시점에 네비게이션 바 숨기기
                // 혹은 viewWillDisappear 등에서 다시 표시
                // 필요 로직을 여기에서 정의
                // (데모라서 간단히 예시만 적음)
                let originalHandler = target._viewDidAppear
                target._viewDidAppear = {
                    originalHandler?()
                    target.navigationController?.setNavigationBarHidden(true, animated: false)
                }

            case .logDeinit:
                // 뷰컨 deinit 시점 로그 남기기
                // DefaultViewController deinit 안에서 처리할 수도 있지만,
                // 만약 동적으로 켜고 끄고 싶다면 별도의 방법으로 처리
                // 예: Weak 래퍼 + deinit 시점 클로저 등
                // 여기선 간단히 예시만 보여줌
                // target.deinitLogger = { print(&quot;ViewController deinit!&quot;) }
                // 같은 방식으로 구현 가능
                // 필요한 구현부를 만들어두고 연결해주는 식
                break
            }
        }

        return target
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시는 단순화된 형태이며, 실제 구현 시에는 DefaultViewController 내 deinit에서 콜백을 호출하게 하거나, 약한 참조(weak)를 사용해 안전하게 로그를 찍도록 할 수 있습니다. 중요한 점은 어떤 옵션이든 손쉽게 추가할 수 있고, 옵션이 많아져도 각각의 로직이 분리된다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 위 방식에서는 간단하게 열거형을 사용했지만, 열거형 사용시 option을 주입할 때 코드가 길어지므로 데코레이터 가능한 프로토콜을 정의하고 별개의 Dacorator 구조체를 만드는 것도 방법입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 1. 일반적으로 DefaultViewController를 하나 생성한다.
let vc = SomeDefaultViewController()

// 2. 필요한 옵션들을 조합해서 VCDecorator.build(...) 호출
let decoratedVC = VCDecorator.build(
    target: vc,
    options: .hideTabBar, .hideNavigationBar
)

// 3. 이제 decoratedVC를 화면에 표시하거나 push, present 하면
//    탭바와 네비게이션 바가 숨겨진 상태로 표시된다.
navigationController?.pushViewController(decoratedVC, animated: true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 탭바 숨기기/네비바 숨기기 등의 기능을 BaseViewController 하나로 전부 해결하는 게 아니라, 옵션을 통한 선택적 부여로&amp;nbsp; 정리할 수 있게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;선택적 기능 부여: 필요한 기능만 추가로 얹어갈 수 있으므로, BaseViewController가 비대해지지 않습니다.&lt;/li&gt;
&lt;li&gt;확장성: 새로운 공통 기능이 필요할 때, VCDecoratorOption에 case를 추가하고, VCDecorator.build 내부에서 셋업 코드를 구현하기만 하면 됩니다.&lt;/li&gt;
&lt;li&gt;Side Effect 최소화: 전부 상속받는 구조가 아니므로, 특정 옵션을 수정해도 다른 ViewController가 영향을 받지 않습니다(해당 옵션을 사용 중이지 않은 이상).&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;초기 세팅 복잡도: 직접 DefaultViewController를 만들고, 옵션들을 늘려가며 콜백을 주입하는 구조를 잡아야 합니다.&lt;/li&gt;
&lt;li&gt;옵션들 간의 충돌 가능성: 두 옵션이 동일한 라이프 사이클 메서드에 서로 상반된 명령을 내릴 수 있다면, 이를 조정하는 로직이 필요해질 수 있습니다(예: .hideTabBar와 .showTabBar가 같은 시점에 동시 적용되는 경우 등).&lt;/li&gt;
&lt;li&gt;런타임에 한 번 더 감싸는 비용: 데코라이크 구조를 쓰게 되면, 작은 성능 오버헤드가 있을 수 있으나 대개 무시해도 될 수준이긴 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BaseViewController 구조를 무작정 확장하기보다, &amp;ldquo;장식(Decorator)&amp;rdquo; 패턴을 적용해 선택적으로 필요한 기능들을 얹어갈 수 있게 만들면, 복잡도가 낮아지고 유지보수가 편리해집니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하자면, 데코라이크 구조는 ViewController에 필요한 기능을 옵션으로 선언하고, build 과정을 거치며 동적으로 주입함으로써 유연하면서도 읽기 쉬운 구조를 확보할 수 있는 좋은 방법 중 하나라고 생각합니다.&lt;/p&gt;</description>
      <category>iOS</category>
      <category>baseviewcontroller</category>
      <category>Decorator</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/89</guid>
      <comments>https://doing-programming.tistory.com/entry/%EB%8D%B0%EC%BD%94%EB%9D%BC%EC%9D%B4%ED%81%ACDecorator-Like%EB%A1%9C-%EB%8D%94-%EC%9C%A0%EC%97%B0%ED%95%9C-ViewController-%EA%B5%AC%EC%A1%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry89comment</comments>
      <pubDate>Thu, 20 Mar 2025 01:23:58 +0900</pubDate>
    </item>
    <item>
      <title>Swift 단위테스트 자동화 xcresult를 junit.xml 변환하기</title>
      <link>https://doing-programming.tistory.com/entry/Swift-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94-xcresult%EB%A5%BC-junitxml-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0</link>
      <description>&lt;h1&gt;xcresult 파일 생성&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 junit.xml 포멧으로 변환하기 전에 xcresult 파일을 생성할 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 xcresult는 xcode에서 바로 생성되기도 하는데요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHnPBc/btstyukO96w/B74gjiSDEaq0EMGAVBdQa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHnPBc/btstyukO96w/B74gjiSDEaq0EMGAVBdQa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHnPBc/btstyukO96w/B74gjiSDEaq0EMGAVBdQa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHnPBc%2FbtstyukO96w%2FB74gjiSDEaq0EMGAVBdQa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;822&quot; height=&quot;274&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(xcode 14.3 기준) 심플하게 찾는 법은 Report Navigator 테스트 결과에서 Ctrl + click 하면 바로 Show in Finder를 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CLI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 보통은 테스트 자동화는 CI/CD를 통해서 이루어 지기 때문에 CLI를 통해 test를 돌릴 필요가 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;xcodebuild -scheme PleaseAttendancePresentationLayer test -destination &quot;platform=iOS Simulator,name=iPhone 14 Pro,OS=16.4&quot; -resultBundlePath &quot;./PresentationlayerTests.xcresult&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 PresentationLayer 단일 패키지 테스트 결과를 출력하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 테스트 결과가 바로 현재 디렉토리로 저장되도록 resultBundlePath를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 xcresult파일을 직접 열어보면 압축되어있어서 읽을 수는 없는데요,&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;xcrun xcresulttool get --format json --path &amp;lt;TESTFILE&amp;gt;.xcresult
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 xcrun과 xcresulttool(wwdc19에 소개됨)을 이용하면 json 형태로 자세한 내용을 얻을 수는 있지만 junit 형태는 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fastlane의 문서를 보면 xcpretty를 이용해서 junit.xml로 변환할 수 있다고 나오지만 xcpretty는 x86 cpu 기준의 gem 설치를 지원합니다. 빌드 머신의 터미널 설정 괜히 잘못건드리면 피를 볼 수 있으므로 arm architecture를 지원하는 parser 어플을 찾아봤습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/a7ex/xcresultparser/tree/main&quot;&gt;GitHub - a7ex/xcresultparser: Parse the binary xcresult bundle from Xcode builds and testruns&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xcresultparser 라는 프로그램으로 마침 최신 업데이트도 있고, 다양한 포맷팅 결과도 지원하고 있습니다&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;xcresultparser -o junit PrLayerTests.xcresult &amp;gt; ./junit_test.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요렇게 작성해주면&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;testsuites tests=&quot;2&quot; failures=&quot;1&quot; time=&quot;23.645&quot;&amp;gt;
    &amp;lt;testsuite name=&quot;PleaseAttendancePresentationLayerTests.xctest&quot; tests=&quot;2&quot; failures=&quot;1&quot; time=&quot;0.027&quot;&amp;gt;
        &amp;lt;testcase name=&quot;test_리프레시동작시_버스리스트가져오기()&quot; time=&quot;0.0013&quot; classname=&quot;MyTransportationListViewModelTests&quot;/&amp;gt;
        &amp;lt;testcase name=&quot;test_리프레시전에는_버스리스트비어있음()&quot; time=&quot;0.025&quot; classname=&quot;MyTransportationListViewModelTests&quot;&amp;gt;
            &amp;lt;failure message=&quot;short&quot;&amp;gt;XCTAssertEqual failed: (&quot;[PleaseAttendanceDomainLayer.FavoriteBusItem(uniqueID: &quot;E8379C4B-4692-4456-BB00-E085AC384EA9&quot;, id: &quot;1245678&quot;, number: &quot;5623&quot;, startLocation: &quot;구로디지털단지역&quot;, currentLocation: &quot;금천우체국&quot;, currentLocation2: &quot;시흥유통센터&quot;, remainStation: 1, remainStation2: 2, remainTime: &quot;60&quot;, remainTime2: &quot;120&quot;, index: 0)]&quot;) is not equal to (&quot;[]&quot;) (/Users/hoseunglee/Dev/PleaseAttendance/PleaseAttendancePresentationLayer/Tests/PleaseAttendancePresentationLayerTests/MyTransportationListViewModelTests.swift:36)&amp;lt;/failure&amp;gt;
        &amp;lt;/testcase&amp;gt;
    &amp;lt;/testsuite&amp;gt;
&amp;lt;/testsuites&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;junit.xml과 같은 폼으로 출력되게 됩니다.&lt;/p&gt;
&lt;h1&gt;배경&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xctest 를 굳이 junit으로 변환해야 할 일이 없을거라고 생각했지만, xcresultparser를 찾는 동안 생각보다 연관검색어와 비슷한 프로그램이 많다는 것에 놀랬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 jira에서 zephyr 를 자동으로 생성하기 위한 용도였는데 미리 구성되어 있는 스크립트가 안드로이드 팀의 테스트 결과인 junit에 맞추어져 있었기 때문에 파싱 스크립트를 두벌을 만들지 않기 위해서 변환했습니다.&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/88</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-%EB%8B%A8%EC%9C%84%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94-xcresult%EB%A5%BC-junitxml-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0#entry88comment</comments>
      <pubDate>Sun, 10 Sep 2023 17:34:08 +0900</pubDate>
    </item>
    <item>
      <title>Thread - Semaphores vs Mutexes</title>
      <link>https://doing-programming.tistory.com/entry/Thread-Semaphores-vs-Mutexes</link>
      <description>&lt;h1&gt;소개&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념 모두 생호 배제를 목적으로 한다는 것은 동일하나 이전글의 출력문을 봐서 알겠지만 다른 양상을 보입니다. 어떤 차이에 의해서 그러한 현상이 발생할까요? 이번에는 단골 질문 중 하나인 Semaphore와 Mutex의 차이에 대해서 알아보려고 합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Semaphores&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Semaphore는 P(wait)와 V(signal)로 이루어 져 있습니다. wait 연산은 Semaphore의 값을 감소시키고, signal은 값을 증가 시킵니다. Semaphore의 값이 0일 때, wait 연산을 수행하는 모든 프로세스는 다른 프로세스가 signal 연산을 수행할 때까지 차단되게 됩니다. 이전글에서 설명했었죠?&lt;br&gt;즉 Semaphore는 사실 정수와 같습니다. 단순히 숫자를 증가시키냐 감소시키냐 와 같다는 것이죠, 해당 정수를 기반으로 Ciritical Section에서 발생하는 공유 자원에 대한 접근을 관리합니다.&lt;br&gt;Semaphore는 두가지 타입이 있습니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
 &lt;li&gt;Binary Semaphore 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;기능적으로는 Mutex와 동일하다고 볼 수 있습니다. 0과 1만 존재하기 때문이죠&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Counting Semaphore 
  &lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt; 
   &lt;li&gt;접근에 대한 제한하는 수를 설정 가능합니다.&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mutexes&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서는 NSLock, NSRecursiveLock, NSConditionLock이 존재하는데, 이것들이 Mutex와 완전히 동일한가? 에 대하여 “그렇다” 라고 서술되어 있는 공식 문서를 보지 못해서 확실하지는 않지만, Mutex의 개념인 Lock, Unlock 연산을 똑같이 사용한다는 것입니다.&lt;br&gt;Semaphore와는 달리 값을 증가 시키고 감소 시키는 개념이 아니라 잠그고, 푸는 로직이기 때문에 두번 잠궜다 한번 풀고 이런 개념이 존재하지 않습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;차이&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Semaphore와 Mutex의 차이점을 설명할 수 있을 것 같습니다.&lt;br&gt;결정적으로 Mutex는 Lock을 한 주체가 Unlock을 할 수 있다는 부분입니다. 화장실 문잠그고 들어간 사람이 직접 문을 열고 나오는 것과 같죠, 직관적이고 쉽죠? 하지만 잘못 사용하게 되면 크리티컬 섹션에 진입 후 스레드가 쉬는 상태이거나, 다른 우선 순위가 높은 작업이 있는데 앞서 스레드로 인해 섹션이 잠기는 바람에 작업자체가 중단될 수 있고, CPU 주기가 낭비될 수 있습니다.&lt;br&gt;반면에 Semaphore는 여러 스레드가 동시에 크리티컬 섹션에 액세스 할 수 있도록 해줍니다. 이로 인해 훨씬 더 유연하게 Thread Safe 환경을 만들 수 있습니다.&lt;br&gt;하지만 Semaphore는 우선순위 역전이 있습니다. 스케줄링에 의해 언제든지 다른 스레드가 와서 자기 할일 하고 나갈 수 있다는 것이죠, 이 때문에 동시적인 작업 수행이 더 도드라져 보입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;또 Semaphore는 여러 스레드에서 접근이 가능하기 때문에&amp;nbsp;Deadlock이 발생할 수도 있습니다. 주의해서 관리할 필요가 있죠. (사실 별 이상한 방법으로 사용하지 않는이상…)&lt;br&gt;그러면 Binary Semaphore는 Mutex와 완전히 동일합니까? 라고 했을 때는 어떨까요?&lt;br&gt;Binary Semaphore라고 해도 여전히 wait과 signal을 다른 스레드에서 수행할 수 있기 때문에 잘못 사용한다면 Dead Lock, Priority Inversion은 여전히 발생할 수 있습니다.&lt;/p&gt;&lt;h1&gt;결론&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Binary Semaphore보다는 Mutex를 선택하는 것이 더 옳은 선택처럼 보일 수도 있습니다. 그러나 반대로 잘 관리하고 사용만 할 수 있다면 Mutex의 단점을 보완할 수 있는 방법이기 때문에 결론은 적재적소에 잘 사용할 수 있도록 둘간의 차이점을 이해하는것이 중요할 것 같습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;# Reference&lt;br&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Difference between Binary Semaphore and Mutex - GeeksforGeeks&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.&quot; data-og-host=&quot;www.geeksforgeeks.org&quot; data-og-source-url=&quot;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SHhtK/hyTjNfJS3V/B5x9sO55M9FKNkDVJkPAvK/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot; data-og-url=&quot;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://www.geeksforgeeks.org/difference-between-binary-semaphore-and-mutex/&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SHhtK/hyTjNfJS3V/B5x9sO55M9FKNkDVJkPAvK/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Difference between Binary Semaphore and Mutex - GeeksforGeeks&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;www.geeksforgeeks.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://en.wikipedia.org/wiki/Lock_(computer_science)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Lock (computer science) - Wikipedia&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;From Wikipedia, the free encyclopedia Synchronization mechanism for enforcing limits on access to a resource In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive: a mechanism that enforces limits on access to a resour&quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot; target=&quot;_blank&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/Lock_(computer_science)&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Lock (computer science) - Wikipedia&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;From Wikipedia, the free encyclopedia Synchronization mechanism for enforcing limits on access to a resource In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive: a mechanism that enforces limits on access to a resour&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;en.wikipedia.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Semaphore_(programming)&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://en.wikipedia.org/wiki/Semaphore_(programming)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/87</guid>
      <comments>https://doing-programming.tistory.com/entry/Thread-Semaphores-vs-Mutexes#entry87comment</comments>
      <pubDate>Sun, 16 Jul 2023 02:14:35 +0900</pubDate>
    </item>
    <item>
      <title>Race Condition / Thread Safe</title>
      <link>https://doing-programming.tistory.com/entry/Race-Condition-Thread-Safe</link>
      <description>&lt;h1&gt;Race Condition&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;레이스 컨디션이란 동시에 여러 스레드 또는 프로세스에서 공유 자원에 접근하고 수정하는 상황에서 발생하는 문제를 말합니다. 특별히 신경쓰지 않으면 멀티스레드 환경에서 반드시 발생할 수 있는 것이죠.&lt;br&gt;흔히 발생하는 현상으로는 결과값이 예상과는 다르게 나오거나 잘못된 순서로 데이터가 적재됩니다. 메모리를 다루는 경우에는 BAC_ACCESS와 함께 앱 크래시가 발생할 수 있는 위험한 경우입니다!&lt;br&gt;어떤 상황에서 레이스 컨디션이 발생하는지 알아보겠습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Example&lt;/h2&gt;&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;let concurrentQueue = DispatchQueue(label: &quot;com.queue&quot;, attributes: .concurrent)
var sharedResource = 0
  func test_task() throws {
    for i in 1...100 {
      concurrentQueue.async {
        self.append(pass: i)
      }
    }
  }

private func append(pass: Int) {
  sharedResource += pass
  if pass == 100 {
    print(sharedResource)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 설명한 것 처럼 레이스 컨디션은 멀티스레드 환경에서 발생합니다.&lt;br&gt;&lt;code&gt;sharedResource&lt;/code&gt;가 있고 이를 append라는 메서드에서 접근하고 있죠, 전달받은 pass값을 sharedResource에 더하는 로직입니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이미 아시겠지만 만약 1 부터 100까지 모두 더한다면 100번째에는 5050이라는 값이 출력될것입니다! 하지만 test_task 에서는 append 메서드를 비동기로 호출하고 있습니다. 심지어 concurrentQueue이기 때문에 append 메서드는 수많은 스레드에서 실행될 예정입니다.&lt;br&gt;즉 위와같은 코드는 순차적으로 발생하지 않기 때문에 우리가 원하는 결과를 얻을 수 없습니다. 동시다발적으로 스케줄링이 이루어졌고 가장 먼저 작업이 수행되는게 pass 100부터 라서 1만 출력되고 아무것도 안나올 수도 있죠.&lt;br&gt;작업이 동시적으로 발생하기 때문에 이러한 경우는 레이스 컨디션 보다는 동시적 작업의 결과입니다.&lt;br&gt;예제를 조금 바꿔 보겠습니다.&lt;/p&gt;&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;let concurrentQueue = DispatchQueue(label: &quot;com.queue&quot;, attributes: .concurrent)
var sharedResource: [Int] = []
  func test_task() throws {
    for i in 1...100 {
      concurrentQueue.async {
        self.append(pass: i)
      }
    }
  }

private func append(pass: Int) {
  sharedResource.append(pass)
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;전체를 다 보실 필요는 없습니다. sharedResource가 Int 에서 Array로 바뀐 것만 주목하시면 됩니다.&lt;br&gt;이제는 단순히 값을 더하는 것이 아니라 배열에 값을 추가하는 형태가 되었네요! 이 경우 가장 치명적인 케이스가 발생할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9uRdZ/btsljgJEM6R/JhIF9hCmFOazK5L7n23hmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9uRdZ/btsljgJEM6R/JhIF9hCmFOazK5L7n23hmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9uRdZ/btsljgJEM6R/JhIF9hCmFOazK5L7n23hmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9uRdZ%2FbtsljgJEM6R%2FJhIF9hCmFOazK5L7n23hmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;859&quot; height=&quot;92&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BAD_ACCESS가 발생했는데 이외에도 똑같은 코드에서 Out_Of_Range나 SIGABRT가 발생할 수도 있습니다.&lt;br&gt;이러한 문제가 왜 발생할까요? 이는 Swift가 Array의 용량을 확장하는 방법과 연관이 있습니다. 하지만 오늘 주제에서 다룰 내용은 아니죠!&lt;br&gt;중요한 점은 레이스 컨디션으로 인한 문제가 앱 강제종료 라는 크리티컬한 상황으로 이어질 수 있다는 것입니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;예방하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;레이스 컨디션을 예방하는 방법에는 무엇이 있을까요?&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Singleton Class 자제하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;레이스 컨디션이 발생하는 데에는 두가지 필수 조건이 있습니다. 첫번째는 &lt;b&gt;멀티스레드&lt;/b&gt; 환경이어야 하고 두번째는 &lt;b&gt;공유된 자원&lt;/b&gt;에 여러 곳에서 접근하는 환경 이어야 한다는 것입니다. 엄밀히 말하면 여러 스레드에서 하나의 공유된 자원에 접근하는 것이지만 iOS 환경은 기본적으로 멀티스레드 환경이기 여러곳 이라는 것은 여러 스레드와 동일할 수 있죠.&lt;br&gt;이러한 공유된 자원에 접근하는 것중 가장 흔한 케이스가 바로 싱글톤 클래스입니다. 싱글톤 클래스는 App State나 User State등 앱 전반에 걸쳐 상태를 관리하는 데에 아주 유용한 디자인 패턴이지만 레이스 컨디션이 발생하기 쉬운 위치에 노출되어 있습니다. 만약 싱글톤을 써야 한다는 각별히 주의해야 합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Concurreny 자제하기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;레이스 컨디션이 발생하는 대부분의 경우는 Concurrent한 작업 때문입니다. 특별히 작업이 아니라면 오히려 대부분의 작업은 작업 순서가 중요한 경우가 많은데요. 일반적인 경우는 Serial 작업으로 진행하는 것이 좋습니다.&lt;/p&gt;&lt;h1&gt;Thread safe&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;각별한 주의에도 어쟀든 레이스 컨디션이 발생하는 상황을 어찌할 수는 없습니다. Singleton Class을 꼭 써야 할 수도 있고, Concurreny 작업 스케줄링이 필요할 수도 있죠, 이런 경우 레이스 컨디션이 발생하지 않도록 Thread safe 환경을 만들어야 합니다. 애플에서는 Thread safe 환경을 위한 다수의 편리한 API를 제공합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Grand Central Dispatch&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이하 GCD는 queue 기반의 멀티스레드 / 병럴처리 작업 API입니다. 우리가 흔히 사용하는 DispatchQueue가 바로 그것이죠.&lt;br&gt;DispatchQueue를 이용해서 레이스 컨디션을 해결하는 법은 매우 쉽습니다.&lt;/p&gt;&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;let concurrentQueue = DispatchQueue(label: &quot;com.queue&quot;, attributes: .concurrent)
let serialQueue = DispatchQueue(label: &quot;com.queue.serial&quot;)
var sharedResource: [Int] = []
  func test_task() throws {
    for i in 1...100 {
      concurrentQueue.async {
        self.append(pass: i)
      }
    }
  }

private func append(pass: Int) {
  serialQueue.async { [unowned self] in
    sharedResource.append(pass)
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;주목할 부분은 새롭게 추가된 serialQueue입니다. DispatchQueue는 별도의 attributes를 지정하지 않으면 serial이 기본 설정입니다.&lt;br&gt;이 경우 append(pass:) 메서드는 동시적으로 발생하지만 이후 작업은 serialQueue에서 순차적으로 진행되기 때문에 sharedResource에 접근에서 발생하는 레이스 컨디션은 사라집니다. 쉽죠?&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;DispatchSemaphore&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;세마포어도 GCD의 기능 중 일부입니다. DispatchQueue는 동시적으로 공유 자원에 접근하려는 것을 직렬로 순서대로 접근하도록 만들었습니다. 반면 세마포어는 스레드간의 상호작용을 제어하는데요,&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;let semaphore = DispatchSemaphore(value: 1)&lt;/code&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;세마포어를 생성할때 value를 할당하는데 이 값은 semaphore가 통제할 스레드의 수 입니다.&lt;br&gt;세마포어는 wait()와 signal() 한쌍의 메서드로 이루어지는데, wait는 값을 감소시키고 signal은 값을 증가시킵니다.&lt;br&gt;wait로 값이 감소해서 0보다 작아지면 해당 작업 블럭은 대기 상태에 들어갑니다. signal이 나올때 까지요,&lt;br&gt;좀더 자세히 알아보기 위해 출력문을 좀 활용해봤습니다.&lt;/p&gt;&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;private func append(pass: Int) {
  print(&quot;start! \(#function) \(pass)&quot;)
  semaphore.wait()
  print(&quot;stop! \(#function) \(pass)&quot;)
  sharedResource.append(pass)
  print(&quot;task done! \(#function) \(pass)&quot;)
  semaphore.signal()
  print(&quot;let! \(#function) \(pass)&quot;)
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성했을 때 콘솔 로그를 보면 아래와 같습니다.&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;start! append(pass:) 1
start! append(pass:) 2
stop! append(pass:) 1
start! append(pass:) 4
task done! append(pass:) 1
let! append(pass:) 1
stop! append(pass:) 2
task done! append(pass:) 2
start! append(pass:) 6
let! append(pass:) 2
start! append(pass:) 8
start! append(pass:) 5
stop! append(pass:) 4
task done! append(pass:) 4
start! append(pass:) 7&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;pass가 1번이 들어왔을 때 작업이 끝나기도 전에 pass 2가 들어왔군요!&lt;br&gt;하지만 이때 세마포어 먼저 들어온 pass 1번에서 세마포어 wait이 먼저 발동됩니다.&lt;br&gt;그 다음 pass 4도 들어왔네요 하지만 그 각 pass에서 작업 진행사항은 pass 1번 밖에 없네요!&lt;br&gt;pass 1번만 유일하게 작업을 끝낸다음 세마포어 signal을 보냈더니 let 이 출력 됐습니다.&lt;br&gt;그 이후엔 pass 1 다음으로 start한 pass 2에서 wait이 걸렸네요.&lt;br&gt;메서드는 동시에 호출 됐지만 세마포어 signal이 나올 때 까지 다른 작업 블록을 정지 시켜 버립니다. 세마포어는 이러한 방식으로 Thread-safe 하도록 만듭니다!&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;NSRecursiveLock&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;NSRecursiveLock은 Objective-C 에서 지원하던 레거시 방법입니다. 사용법은 세마포어와 다르지 않지만 결과 양상이 조금! 다릅니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;let lock = NSRecursiveLock()&lt;/code&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;과 같이 선언 한 뒤&lt;/p&gt;&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;private func append(pass: Int) {
  print(&quot;start! \(#function) \(pass)&quot;)
  lock.lock()
  print(&quot;stop! \(#function) \(pass)&quot;)
  sharedResource.append(pass)
  print(&quot;task done! \(#function) \(pass)&quot;)
  lock.unlock()
  print(&quot;let! \(#function) \(pass)&quot;)
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;세마포어와 똑같죠? 그럼 출력 결과는 어떨까요?&lt;/p&gt;&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;start! append(pass:) 1
stop! append(pass:) 1
task done! append(pass:) 1
let! append(pass:) 1
start! append(pass:) 4
stop! append(pass:) 4
task done! append(pass:) 4
let! append(pass:) 4
start! append(pass:) 5
stop! append(pass:) 5
task done! append(pass:) 5
let! append(pass:) 5
start! append(pass:) 6
stop! append(pass:) 6
task done! append(pass:) 6
let! append(pass:) 6
start! append(pass:) 7
stop! append(pass:) 7
task done! append(pass:) 7
let! append(pass:) 7
start! append(pass:) 10
stop! append(pass:) 10
task done! append(pass:) 10
let! append(pass:) 10&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;unlock을 할때 까지 작업이 뭔가 되게 순차적이지만, 사실 이건 출력문의 일부분일 뿐입니다.&lt;br&gt;전체를 놓고 보면 세마포어와 다를 바 없지만 내부적으로 조금 다른 로직으로 굴러간다는 것 정도는 알 수 있겠네요&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Advanced&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 일일이 스레드 세이프한 코드를 작성해주기는 너무나 귀찮고 까먹기 쉬운 일입니다. 좀더 편한 방법을 찾아보자면 간단한 방법이 존재하는데요!&lt;br&gt;바로 값 자체를 스레드 세이프 하게 만들어 주는 것, Swift의 위대한 property wrapper가 있습니다.&lt;/p&gt;&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;@propertyWrapper
final class Atomic&amp;lt;T&amp;gt;: NSLock {
  var wrappedValue: T {
    get {
      lock()
      defer { unlock() }
      return value
    }

    _modify {
        lock()
        var tmp: T = value

        defer {
            value = tmp
            unlock()
        }

        yield &amp;amp;tmp
    }
  }
  private var value: T

  init(wrappedValue: T) {
    self.value = wrappedValue
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;쫘란~&lt;/p&gt;&lt;h1&gt;Conclusion&lt;/h1&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 우리에겐 이런것보다 더 강력하고 모던한 swift concurrency와 함께 도입된 actor가 있습니다.&lt;br&gt;swift concurrency 공부하세요!&lt;/p&gt;</description>
      <category>General Dev</category>
      <category>concurrency</category>
      <category>DispatchQueue</category>
      <category>GCD</category>
      <category>RaceCondition</category>
      <category>thread-safe</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/85</guid>
      <comments>https://doing-programming.tistory.com/entry/Race-Condition-Thread-Safe#entry85comment</comments>
      <pubDate>Mon, 26 Jun 2023 23:31:14 +0900</pubDate>
    </item>
    <item>
      <title>WWDC23 - What's new in  swift</title>
      <link>https://doing-programming.tistory.com/entry/WWDC23-whats-new-in-swift</link>
      <description>&lt;h1&gt;What&amp;rsquo;s new in swift&lt;/h1&gt;
&lt;h1&gt;&amp;ldquo;pack&amp;rdquo;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 타입추론을 통해서 이러한 동작이 가능했습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;struct Request&amp;lt;Result&amp;gt; { ... }

struct RequestEvaluator {
    func evaluate&amp;lt;Result&amp;gt;(_ request: Request&amp;lt;Result&amp;gt;) -&amp;gt; Result
}

func evaluate(_ request: Request&amp;lt;Bool&amp;gt;) -&amp;gt; Bool {
    return RequestEvaluator().evaluate(request)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Result가 Boolean 이라서 Boolean으로 값이 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러 Boolean 결과를 얻고 싶다면&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;let value = RequestEvaluator().evaluate(request)

let (x, y) = RequestEvaluator().evaluate(r1, r2)

let (x, y, z) = RequestEvaluator().evaluate(r1, r2, r3)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Method Overloads 패턴으로 아래와 같이 작성해줄 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func evaluate&amp;lt;Result&amp;gt;(_:) -&amp;gt; (Result)

func evaluate&amp;lt;R1, R2&amp;gt;(_:_:) -&amp;gt; (R1, R2)

func evaluate&amp;lt;R1, R2, R3&amp;gt;(_:_:_:) -&amp;gt; (R1, R2, R3)

func evaluate&amp;lt;R1, R2, R3, R4&amp;gt;(_:_:_:_:)-&amp;gt; (R1, R2, R3, R4)

func evaluate&amp;lt;R1, R2, R3, R4, R5&amp;gt;(_:_:_:_:_:) -&amp;gt; (R1, R2, R3, R4, R5)

func evaluate&amp;lt;R1, R2, R3, R4, R5, R6&amp;gt;(_:_:_:_:_:_:) -&amp;gt; (R1, R2, R3, R4, R5, R6)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 반복적으로 overload 하는 방법은 한계가있습니다. 만약 3개까지의 오버로드만 했다면 3개까지의 결과만 얻을 수 있죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift5.9에서 제네릭 시스템은 인수 길이에 대한 추상화를 가능하게 합니다. 여러개의 별개의 파라미터를 함께 &quot;Packed&quot;할 수 있죠, 이 새로운 컨셉을 &quot;Pack&quot;이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성했던 메서드를 아래와 같이 &lt;code&gt;each&lt;/code&gt; 키워드로 쓰게 되면&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func evaluate&amp;lt;each Result&amp;gt;(_: repeat Request&amp;lt;each Result&amp;gt;) -&amp;gt; (repeat each Result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 메서드만으로 오버로딩으로 작성한 모든 코드를 커버할 수 있게됩니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;let results = RequestEvaluator.evaluate(r1, r2, r3)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;swift macros&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Macro는 다른 언어(C,C++)에서 존재하던 그 매크로를 그대로 가져온 것으로 컴파일 타임에 지정된 매크로 코드 블럭으로 풀어서 빌드됩니다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;assert(max(a, b) == c)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같이 기존의 assert 코드는 조건이 실패하면 앱이 중단됩니다. 하지만 구체적으로 무엇이 문제였는지 알 수 없죠. 아래처럼 단지 실패했다고만 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 우리가 테스트에 사용하는 &lt;code&gt;XCAssertEqual&lt;/code&gt;은 좀더 나은 결과를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vPcE8/btskguOlitA/JKED4EtTov1REz17eEz0ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vPcE8/btskguOlitA/JKED4EtTov1REz17eEz0ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vPcE8/btskguOlitA/JKED4EtTov1REz17eEz0ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvPcE8%2FbtskguOlitA%2FJKED4EtTov1REz17eEz0ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2323&quot; height=&quot;735&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여전히 a는 뭐였고 또 b는 무엇인지 그리고 c는 무엇인지 아무것도 알 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요지는 우리가 무언가 실패했을 때 로그에서 더 많은 정보를 얻길 바란다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swift 5.9 이전까지는 특별히 만든 기능 없이는 이 요구사항을 충족할 수 없습니다. 매크로 없이는요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예제에서는 &amp;ldquo;hash-assert&amp;rdquo;라고 불리는 문법을 사용하고 있습니다. 기존 assert문법 앞에 #을 붙이는게 다죠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dillGX/btskeu3fHzd/QUPz2402czFEvkQCOe3J5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dillGX/btskeu3fHzd/QUPz2402czFEvkQCOe3J5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dillGX/btskeu3fHzd/QUPz2402czFEvkQCOe3J5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdillGX%2Fbtskeu3fHzd%2FQUPz2402czFEvkQCOe3J5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;775&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 우리는 아주 세부적인 결과를 얻을 수 있습니다. &amp;ldquo;hash-assert&amp;rdquo;는 PowerAssert 패키지안에 내장되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PowerAssert는 오픈소스로 Github에서 찾을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 코드를 보면 &amp;ldquo;hash assert&amp;rdquo;는 하나의 함수인데요&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public macro assert(_ condition: Bool)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;811&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbERCd/btskhBGm3s1/x88kS7M8mpjO7fPOClfc00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbERCd/btskhBGm3s1/x88kS7M8mpjO7fPOClfc00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbERCd/btskhBGm3s1/x88kS7M8mpjO7fPOClfc00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbERCd%2FbtskhBGm3s1%2Fx88kS7M8mpjO7fPOClfc00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;811&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;811&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진은 대부분의 매크로가 #externalMacro를 사용하고 있고 Swift Complier와 Complier Plugin끼리 어떻게 동작하는지 보여주고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Freestanding macro roles&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asset macro는 freestanding expression 매크로의 일종입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;freestanding 라고 부르는 이유가 &amp;ldquo;hash&amp;rdquo; 문법을 통해 매크로 문법을 사용하기 때문에 그렇다고 합니다!&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;@freestanding(expression)
public macro assert(_ condition: Bool) = #externalMacro(
    module: &amp;ldquo;PowerAssertPlugin&amp;rdquo;,
    type: &amp;ldquo;PowerAssertMacro&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;freestanding의 또다른 좋은 예제는 SwiftUI와 SwiftData에서 사용 가능한&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let pred = #Predicate&amp;lt;Person&amp;gt; {
    $0.favoriteColor == .blue
}

let blueLovers = people.filter(pred)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 freestanding 매크로는 제네릭 인풋으로 결과를 생성하는 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// Predicate expression macro

@freestanding(expression) 
public macro Predicate&amp;lt;each Input&amp;gt;(
    _ body: (repeat each Input) -&amp;gt; Bool
) -&amp;gt; Predicate&amp;lt;repeat each Input&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reduce Boilerplate&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매크로는 또한 수많은 보일러 플레이트를 줄이는데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 아래와 같은 연관값이 있는 열거형의 경우&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum Path {
    case relative(String)
    case absolute(String)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열거형의 케이스 필터링이 필요할 때 아래와 같이 쓰고 싶을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서는 열거형의 computed property를 생성해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;let absPaths = paths.filter { $0.isAbsolute }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension Path {
    var isAbsolute: Bool {
        if case .absolute = self { true }
        else { false }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드지만 케이스가 많아질 수록 보일러 플레이트가 증가하게 되는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift Macro는 아래와 같은 문법으로 보일러 플레이트를 줄여줍니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;@CaseDetection
enum Path {
    case relative(String)
    case absolute(String)
}

let absPaths = paths.filter { $0.isAbsolute }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@CaseDetection propertyWrapper 매크로를 사용하게 되면 깔끔하게 해결되죠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ble5XE/btskfYWU1GS/sKXbxgzlGMLjyaK4MrfyB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ble5XE/btskfYWU1GS/sKXbxgzlGMLjyaK4MrfyB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ble5XE/btskfYWU1GS/sKXbxgzlGMLjyaK4MrfyB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fble5XE%2FbtskfYWU1GS%2FsKXbxgzlGMLjyaK4MrfyB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;996&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CaseDetection의 경우는 member에 해당되는 attached입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Observation in SwiftUI&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;final class Person: ObservableObject {
    @Published var name: String
    @Published var age: Int
    @Published var isFavorite: Bool
}

struct ContentView: View {
    @ObservedObject var person: Person

    var body: some View {
        Text(&quot;Hello, \(person.name)&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 Class타입의 변경을 감지하기 위해 ObservableObject를 채용하고 방출이 필요한 이벤트를 Published Wrapper로 감쌉니다. 하지만 속성이 늘어나면 이 또한 보일러 플레이트이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 또한 Swift5.9 에서 지정된 매크로를 통해 아래와 같이 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;@Observable final class Person {
    var name: String
    var age: Int
    var isFavorite: Bool
}

struct ContentView: View {
    var person: Person

    var body: some View {
        Text(&quot;Hello, \(person.name)&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObservableObject 프로토콜도 쓸 필요 없고 속성마다 Published를 선언할 필요도 없죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable은 아래와 같은 3가지 매크로 기능을 사용한 것입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@attached(member, names: ...)
@attached(memberAttribute)
@attached(conformance)
public macro Observable() = #externalMacro(...).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 매크로를 통해 아래와 같은 코드가 완성됩니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;@Observable final class Person {
    @ObservationTracked var name: String { get { &amp;hellip; } set { &amp;hellip; } }
    @ObservationTracked var age: Int { get { &amp;hellip; } set { &amp;hellip; } }
    @ObservationTracked var isFavorite: Bool { get { &amp;hellip; } set { &amp;hellip; } }

        internal let _$observationRegistrar = ObservationRegistrar&amp;lt;Person&amp;gt;()
    internal func access&amp;lt;Member&amp;gt;(
        keyPath: KeyPath&amp;lt;Person, Member&amp;gt;
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }
    internal func withMutation&amp;lt;Member, T&amp;gt;(
        keyPath: KeyPath&amp;lt;Person, Member&amp;gt;,
        _ mutation: () throws -&amp;gt; T
    ) rethrows -&amp;gt; T {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Foundation performance improve&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;calendar calculations: 20% faster&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;date formatting: 150% faster&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json coding: 200-500% faster&lt;/p&gt;
&lt;h1&gt;swift copyable&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;example 1&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;struct FileDescriptor {
  private var fd: CInt

  init(descriptor: CInt) { self.fd = descriptor }

  func write(buffer: [UInt8]) throws {
    let written = buffer.withUnsafeBufferPointer {
      Darwin.write(fd, $0.baseAddress, $0.count)
    }
    // ...
  }

  func close() {
    Darwin.close(fd)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FileDescriptor는 파일을 열고 쓰는 모델인데 실수의 여지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업이 끝난 이후에 명시적으로 close를 호출해야 하는데 코드를 누락할 확률이 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음과 같이 수정할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;example 2&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class FileDescriptor {
  private var fd: CInt

  init(descriptor: CInt) { self.fd = descriptor }

  func write(buffer: [UInt8]) throws {
    let written = buffer.withUnsafeBufferPointer {
      Darwin.write(fd, $0.baseAddress, $0.count)
    }
    // ...
  }

  func close() {
    Darwin.close(fd)
  }

  deinit {
    close()
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스로 변경 후 메모리에서 해제되는 시점에 close를 호출하는 방법이 있다. 하지만 클래스로 변경되면서 발생하는 여러 오버헤드가 있음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copyable은 구조체에서도 클래스 처럼 deinit 호출이 가능하도록 함&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;struct FileDescriptor: ~Copyable {
  private var fd: CInt

  init(descriptor: CInt) { self.fd = descriptor }

  func write(buffer: [UInt8]) throws {
    let written = buffer.withUnsafeBufferPointer {
      Darwin.write(fd, $0.baseAddress, $0.count)
    }
    // ...
  }

  func close() {
    Darwin.close(fd)
  }

  deinit {
    Darwin.close(fd)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 방법은 호출 시점에 강제하는 것인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드에 consuming이라는 코드를 부착합니다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;  consuming func close() {
    Darwin.close(fd)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 호출할 때 호출 순서를 강제할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;let file = FileDescriptor(fd: descriptor)
file.write(buffer: data)
file.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 정상일 때의 예제이고 아무 문제가 발생하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;let file = FileDescriptor(fd: descriptor)
file.close() // Compiler will indicate where the consuming use is
file.write(buffer: data) // Compiler error: 'file' used after consuming
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write전에 close를 쓰게 되면 컴파일 에러가 발생하게 됩니다.&lt;/p&gt;
&lt;h1&gt;Using C++ from swift / C++ interop&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swift에서 c++를 다이렉트로 쓸 수 있는 방법이 새로 들어왔습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/131po/btskfXDGnsC/jTcaAfTCnggx3T14qjo2K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/131po/btskfXDGnsC/jTcaAfTCnggx3T14qjo2K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/131po/btskfXDGnsC/jTcaAfTCnggx3T14qjo2K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F131po%2FbtskfXDGnsC%2FjTcaAfTCnggx3T14qjo2K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;793&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;inform7&quot;&gt;&lt;code&gt;// Person.h
struct Person {
  Person(const Person &amp;amp;);
  Person(Person &amp;amp;&amp;amp;);
  Person &amp;amp;operator=(const Person &amp;amp;);
  Person &amp;amp;operator=(Person &amp;amp;&amp;amp;);
  ~Person();

  std::string name;
  unsigned getAge() const;
};
std::vector&amp;lt;Person&amp;gt; everyone();

// Client.swift
func greetAdults() {
  for person in everyone().filter { $0.getAge() &amp;gt;= 18 } {
    print(&quot;Hello, \\(person.name)!&quot;)
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swift 컴파일러는 기본적으로 c++ 언어를 대다수 이해합니다. 예를들면 위 코드에서처럼 move constructors, assignment operators, destructor 등을 사용할 수 있고 vector나 map을 사용할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;Using Swift from C++&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 C++ 에서도 Swift코드를 호출할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Geometry.swift
struct LabeledPoint {
  var x = 0.0, y = 0.0
  var label: String = &amp;ldquo;origin&amp;rdquo;
  mutating func moveBy(x deltaX: Double, y deltaY: Double) { &amp;hellip; }
  var magnitude: Double { &amp;hellip; }
}

// C++ client
#include &amp;lt;Geometry-Swift.h&amp;gt;

void test() {
  Point origin = Point()
  Point unit = Point::init(1.0, 1.0, &amp;ldquo;unit&amp;rdquo;)
  unit.moveBy(2, -2)
  std::cout &amp;lt;&amp;lt; unit.label &amp;lt;&amp;lt; &amp;ldquo; moved to &amp;ldquo; &amp;lt;&amp;lt; unit.magnitude() &amp;lt;&amp;lt; std::endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 Objective-C처럼 objc attribute를 추가할 필요도 없으며, 별도의 브리징 오버헤드 없이 Swift 타입의 모든 프로퍼티들, 메서드들, 생성자를 추가할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;FoundationDB&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FoundationDB는 분산 Database로 확장 가능한 거대한 key-value 솔루션입니다. macOS, Linux, Windows 환경을 지원하죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++로 작성된 오픈 소스이며 뭔 내용인지 잘 모르겠으므로 생략합니다. ㅎ&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/84</guid>
      <comments>https://doing-programming.tistory.com/entry/WWDC23-whats-new-in-swift#entry84comment</comments>
      <pubDate>Wed, 7 Jun 2023 16:47:56 +0900</pubDate>
    </item>
    <item>
      <title>RxSwift - MainScheduler.instance vs MainScheduler.asyncInstance</title>
      <link>https://doing-programming.tistory.com/entry/RxSwift-MainSchedulerinstance-vs-MainSchedulerasyncInstance</link>
      <description>&lt;h1&gt;개요&lt;/h1&gt;
&lt;p&gt;RxSwift를 사용하게 되면 View Layer에서 사용하게 될 때는 꼭 메인 스레드로 변경하도록 코드를 지정해줘야 합니다.&lt;/p&gt;
&lt;p&gt;이때 일반적으로 저희가 사용하는 코드는&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MainScheduler.instance&lt;/code&gt; 와 &lt;code&gt;MainScheduler.asyncInstance&lt;/code&gt; 가 있는데, 이 둘은 근본적으로 무슨 차이가 있을까요?&lt;/p&gt;
&lt;p&gt;우선 RxSwift 문서에 의하면 다음과 같이 설명하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TQ1y0/btsil02MmYu/z3Nsmm1qo4kR6CGrYhTOuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TQ1y0/btsil02MmYu/z3Nsmm1qo4kR6CGrYhTOuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TQ1y0/btsil02MmYu/z3Nsmm1qo4kR6CGrYhTOuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTQ1y0%2Fbtsil02MmYu%2Fz3Nsmm1qo4kR6CGrYhTOuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1716&quot; height=&quot;770&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;요약하자면 메인 큐에 작업 스케줄이 이미 된 코드라면 별도의 스케줄링 없이 바로 동작한다고 적혀있습니다.&lt;/p&gt;
&lt;p&gt;무슨 뜻인지 좀더 확실하게 알아볼까요?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;override func scheduleInternal&amp;lt;StateType&amp;gt;(_ state: StateType, action: @escaping (StateType) -&amp;gt; Disposable) -&amp;gt; Disposable {
  let previousNumberEnqueued = increment(self.numberEnqueued)

  if DispatchQueue.isMain &amp;amp;&amp;amp; previousNumberEnqueued == 0 {
    let disposable = action(state)
    decrement(self.numberEnqueued)
    return disposable
  }

  let cancel = SingleAssignmentDisposable()

  self.mainQueue.async {
    if !cancel.isDisposed {
      cancel.setDisposable(action(state))
    }

    decrement(self.numberEnqueued)
  }

  return cancel
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;RxSwift에서 스캐줄링을 하게 되면 위 메서드가 실행되는데요, 조건을 보시면 아시겠지만 만약 이미 Main Queue이거나, Rx에서 자체적으로 관리하는 스케줄 number가 0이라면 별도의 스케줄링 없이 동기 코드로 즉시 실행하게 됩니다.&lt;/p&gt;
&lt;p&gt;반면 Main Queue가 아니거나 Rx의 작업스케줄이 아직 남아있는 상태라면 main queue에 적재해서 비동기로 실행하게 됩니다.&lt;/p&gt;
&lt;p&gt;이걸 Rx에서는 최적화라고 표현하고 있는 것이죠 즉 &lt;code&gt;MainScheduler.instance&lt;/code&gt;는 MainScheduler의 싱글톤 인스턴스이고, Upstream의 스케줄에 따라 비동기 일 수도 있고 아닐 수도 있습니다.&lt;/p&gt;
&lt;h2&gt;MainScheduler.asyncInstance&lt;/h2&gt;
&lt;p&gt;그러면 &lt;code&gt;MainScheduler.asyncInstance&lt;/code&gt;는 무엇일까요? MainScheduler 클래스 안에는 Instance 싱글톤 객체 말고도 asyncInstance도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static let asyncInstance = SerialDispatchQueueScheduler(serialQueue: DispatchQueue.main)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;그리고 asyncInstance는 &lt;code&gt;SerialDisatchQueueScheduler&lt;/code&gt;라고 합니다.&lt;/p&gt;
&lt;p&gt;다시 MainScheduler.Instance 코드를 유심히 보다보면 &lt;code&gt;override func scheduleInternal&lt;/code&gt; 는 이미 override 된 함수라는 것을 알 수 있죠.&lt;/p&gt;
&lt;p&gt;또 사실 MainScheduler라는 것은 SerialDispatcherQueueScheduler의 서브클래스라는 것도 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;그렇다면 SerialDispatchQueueScheduler의 scheduleInternal 본연의 동작은 어떻게 될까요? 타고타고 들어가면 아래 코드가 나옵니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func schedule&amp;lt;StateType&amp;gt;(_ state: StateType, action: @escaping (StateType) -&amp;gt; Disposable) -&amp;gt; Disposable {
  let cancel = SingleAssignmentDisposable()

  self.queue.async {
    if cancel.isDisposed {
      return
    }


    cancel.setDisposable(action(state))
  }

  return cancel
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;무조건 async 로 동작하게 됩니다. 그리고 저 queue는 앞서 asyncInstance를 생성할 때 전달한 DispatchQueue.main이죠, 즉 &lt;code&gt;MainScheduler.asyncInstance&lt;/code&gt;는 무조건 비동기로 동작하게 됩니다.&lt;/p&gt;
&lt;h2&gt;결론은 뭐가 다른데?&lt;/h2&gt;
&lt;p&gt;넵 사실 큰 차이 없습니다. asyncInstance나 Instance나 동일한 Serial Queue이고 작업이 메인 스레드에서 동작할 수 있도록 보장해 줍니다.&lt;/p&gt;
&lt;p&gt;다만 Instance의 경우는 그러할 필요가 없다면 Main Queue로 스케줄링 하지 않고 바로 실행한다는 부분이죠.&lt;/p&gt;
&lt;p&gt;그래서 만약 MainScheduler.Instance가 잘 동작한다면 최적화 되고 좋은 동작 일 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bODjoq/btskeua5o5A/VRYKPGb8Z4Y24m6GReZLi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bODjoq/btskeua5o5A/VRYKPGb8Z4Y24m6GReZLi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bODjoq/btskeua5o5A/VRYKPGb8Z4Y24m6GReZLi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbODjoq%2Fbtskeua5o5A%2FVRYKPGb8Z4Y24m6GReZLi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;137&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 코드는 작업은 스케줄링 했을 때와 그렇지 않을 때 코드가 실행된 속도입니다. async가 훨씬 느리죠? RxSwift는 이 부분을 최적화 했다고 볼 수 있습니다.&lt;/p&gt;
&lt;h3&gt;But?&lt;/h3&gt;
&lt;p&gt;하지만 이는 RxSwift의 자체 스케줄러가 잘 동작했을 때 문제입니다. 만약 MainScheduler.Instance가 잘 동작한다면 MainScheduler.asyncInstance는 필요없을 겁니다. 따로 있는 이유가 무엇일까요?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func register(synchronizationErrorMessage: SynchronizationErrorMessages) {
  self.lock.lock(); defer { self.lock.unlock() }
  let pointer = Unmanaged.passUnretained(Thread.current).toOpaque()
  let count = (self.threads[pointer] ?? 0) + 1

  if count &amp;gt; 1 {
    self.synchronizationError(
      &amp;quot;⚠️ Reentrancy anomaly was detected.\n&amp;quot; +
      &amp;quot;  &amp;gt; Debugging: To debug this issue you can set a breakpoint in \(#file):\(#line) and observe the call stack.\n&amp;quot; +
      &amp;quot;  &amp;gt; Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`\n&amp;quot; +
      &amp;quot;    This behavior breaks the grammar because there is overlapping between sequence events.\n&amp;quot; +
      &amp;quot;    Observable sequence is trying to send an event before sending of previous event has finished.\n&amp;quot; +
      &amp;quot;  &amp;gt; Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,\n&amp;quot; +
      &amp;quot;    or that the system is not behaving in the expected way.\n&amp;quot; +
      &amp;quot;  &amp;gt; Remedy: If this is the expected behavior this message can be suppressed by adding `.observe(on:MainScheduler.asyncInstance)`\n&amp;quot; +
      &amp;quot;    or by enqueuing sequence events in some other way.\n&amp;quot;
    )
  }

  self.threads[pointer] = count

  if self.threads.count &amp;gt; 1 {
    self.synchronizationError(
      &amp;quot;⚠️ Synchronization anomaly was detected.\n&amp;quot; +
      &amp;quot;  &amp;gt; Debugging: To debug this issue you can set a breakpoint in \(#file):\(#line) and observe the call stack.\n&amp;quot; +
      &amp;quot;  &amp;gt; Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`\n&amp;quot; +
      &amp;quot;    This behavior breaks the grammar because there is overlapping between sequence events.\n&amp;quot; +
      &amp;quot;    Observable sequence is trying to send an event before sending of previous event has finished.\n&amp;quot; +
      &amp;quot;  &amp;gt; Interpretation: &amp;quot; + synchronizationErrorMessage.rawValue +
      &amp;quot;  &amp;gt; Remedy: If this is the expected behavior this message can be suppressed by adding `.observe(on:MainScheduler.asyncInstance)`\n&amp;quot; +
      &amp;quot;    or by synchronizing sequence events in some other way.\n&amp;quot;
    )
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;당연히 필요하기 때문이죠? 방출 이벤트가 너무 많아 복잡해지면 Rx의 자체적인 스캐줄링이 깨지게 되는데, 이때 asyncInstance를 사용해 달라고 경고문이 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;이러한 문제는 얘기치 못한 결과를 낼 수 있고 이미 QA에서 테스트 중일때는 감지하기가 어렵습니다.&lt;/p&gt;
&lt;p&gt;즉 개발자가 사전에 이를 알고 Instance를 선택할지 async Instance를 선택할지 판단하기 어렵다는 뜻입니다. 그리고 만약 Upstream의 작업 스케줄링을 잘했다면 대부분은 Main Thread가 아닐 것이기 때문에 Instance 동작을 하더라도 사실 꽤 많은 동작이 DispatchQueue.main.async로 스케줄링 된다는 뜻입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;212&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clBdTH/btskhKpEWgJ/J3cpCbrXe8WOclzztpiB50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clBdTH/btskhKpEWgJ/J3cpCbrXe8WOclzztpiB50/img.png&quot; data-alt=&quot;MainScheduler.Instance를 호출했을 때 async 블럭을 타는지 바로 실행하는지 결과 출력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clBdTH/btskhKpEWgJ/J3cpCbrXe8WOclzztpiB50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclBdTH%2FbtskhKpEWgJ%2FJ3cpCbrXe8WOclzztpiB50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;212&quot; height=&quot;725&quot; data-origin-width=&quot;212&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MainScheduler.Instance를 호출했을 때 async 블럭을 타는지 바로 실행하는지 결과 출력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;다시말해서 간단한 경우는 MainScheduler.Instance를 사용할 수 있지만 복잡한 경우는 asyncInstance를 채용하는 것이 안전한 것이죠.&lt;/p&gt;
&lt;p&gt;결론은&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/budrq8/btskg7ljryW/k3RZDt90CqPCjWFH640ZqK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/budrq8/btskg7ljryW/k3RZDt90CqPCjWFH640ZqK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/budrq8/btskg7ljryW/k3RZDt90CqPCjWFH640ZqK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbudrq8%2Fbtskg7ljryW%2Fk3RZDt90CqPCjWFH640ZqK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;359&quot; height=&quot;359&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Combine 쓰세요(찡긋)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Youngminah&quot;&gt;@mint&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>MainScheduler</category>
      <category>MainScheduler.asyncInstance</category>
      <category>MainScheduler.Instance</category>
      <category>rxswift</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/83</guid>
      <comments>https://doing-programming.tistory.com/entry/RxSwift-MainSchedulerinstance-vs-MainSchedulerasyncInstance#entry83comment</comments>
      <pubDate>Fri, 2 Jun 2023 14:18:46 +0900</pubDate>
    </item>
    <item>
      <title>iOS 개발자 부트캠프 - SeSAC(청년취업사관학교)</title>
      <link>https://doing-programming.tistory.com/entry/iOS-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-SeSAC%EC%B2%AD%EB%85%84%EC%B7%A8%EC%97%85%EC%82%AC%EA%B4%80%ED%95%99%EA%B5%90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로 취업한지 벌써 1년이 훌쩍 지났습니다. 아직까지도 그리고 앞으로도 세상에서 가장 잘한 선택인 것은 개발자로 이직을 했다는 것입니다. 여전히 일하는게 즐겁고 또 내일이 기대되는 나날이네요.&lt;br&gt;&amp;nbsp;&lt;br&gt;요즘은 비전공자들을 교육 시켜서 개발자로 취업시켜주는 좋은 교육과정이 정말 많은데 저는 딱 한군데 후회하지 않을 곳을 소개시켜드리고 싶습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;바로 청년취업사관학교(SeSAC)의 &lt;a href=&quot;https://sesac.seoul.kr/course/active/detail.do?courseActiveSeq=1570&amp;amp;srchCategoryTypeCd=&amp;amp;courseMasterSeq=261&amp;amp;currentMenuId=900002011&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt;&lt;b&gt;(영등포 SW과정) iOS 앱 개발자 데뷔 과정 PLUS&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #212121;&quot;&gt; 입니다!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;다른 좋은 교육과정보다 더 좋은곳이라고 소개하는 데에는 합당한 이유가 있어야 겠죠?&lt;br&gt;&amp;nbsp;&lt;br&gt;물론 제가 해당 교육과정을 통해서 취업을 할 수 있었다는 부분이 가장 크지만 그럴 수 있었던 배경에는 무엇이 있을까 생각해보면&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;iOS 앱 개발자 데뷔 과정 PLUS의 특장점!&lt;/h4&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;진심으로 수강생이 잘 되길 바라는 마음에서 적극 지원해주는 멘토님들&lt;/li&gt;&lt;li&gt;아무 회사나 취업해서 단순 취업률을 늘리는 것이 목표가 아닌 내가 할 수 있는 최고의 선택할 할 수 있도록 실시하는 교육과 지도&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위 두가지가 가장 컸던 것 같습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;6개월 동안 매일 밤새도록 공부하다 보면 힘든 나날이 평생 지속될 것 같고 취업을 못하는 거 아닌 가 라는 불안감이 들 수 있는데 그럴때 마다 할 수 있다고, 충분히 잘 하고 있다고 응원해주시는 멘토님들이 아니었으면 취업하기 어려웠을 것 같다고 생각합니다!&lt;br&gt;&amp;nbsp;&lt;br&gt;이외 커리큘럼이나 해당 교육과정을 통해서 얻을 수 있는 포트폴리오는 여러분이 취업할 때 필요한 수준 그 이상인 것은 당연하다고 생각하고 무엇보다 청년취업사관학교라는 정부사업? 을 통해서 진행되기 때문에 실제로 투자하는 돈은 개발에 필요한 맥북과 수료 후 돌려받는 예치금 20만원이 전부(수료 후 돌려 받을 수 있습니다.)! 한마디로 &lt;b&gt;무료&lt;/b&gt;라는 것이죠!&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;신청 자격&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;신청 자격에 대해서도 궁금하실 것 같은데요! 일부 교육과정의 경우에는 애초에 교육과정을 듣지 않아도 취업할 수 있을 만한 스펙을 모집하고는 합니다. 하지만 청년취업사관학교의 &lt;b&gt;iOS 앱 개발자 데뷔 과정 PLUS&lt;/b&gt; 는 &lt;b&gt;단지 서울 시민&lt;/b&gt;이여야 한다는 것이 유일한 조건! 모집 공고에도 나와있지만 필요한 것은 학습에 대한 열정과 취업하겠다는 의지 중요하게 보니 프로그래밍에 대한 기초지식이 없더라도 충분히 도전해보는것을 추천합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/82</guid>
      <comments>https://doing-programming.tistory.com/entry/iOS-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-SeSAC%EC%B2%AD%EB%85%84%EC%B7%A8%EC%97%85%EC%82%AC%EA%B4%80%ED%95%99%EA%B5%90#entry82comment</comments>
      <pubDate>Wed, 3 May 2023 21:58:00 +0900</pubDate>
    </item>
    <item>
      <title>Swift - RxSwift의 withUnretained를 Combine에서도 쓰기</title>
      <link>https://doing-programming.tistory.com/entry/Swift-RxSwift%EC%9D%98-withUnretained%EB%A5%BC-Combine%EC%97%90%EC%84%9C%EB%8F%84-%EC%93%B0%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RxSwift나 Combine 등에서 subscribe에서 escaping closure를 다룰 시 필연적으로 캡처 리스트를 통해 약한 참조를 해야할 때가 있습니다. 아주 지긋지긋 하죠? Xcode 14.3(swift 5.8) 이후 부터는 weak self를 바인딩 한번 한 이후 부터는 self를 붙일 필요가 없긴 합니다만.. 바인딩 코드를 작성하는 것도 매우 귀찮은 일..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 RxSwift(6.0)에서는 withUnretained라는 설탕 문법이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1683005545006&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;observerble
 .withUnretained(self)
 .subscribe { (owner, output) in
   owner.value = output
 }
 .dispose()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 편리한 코드를 그대로 Combine에서 써볼려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 방식은 각자 다를 것 같은데 원리는 비슷할 것 같아요!&lt;/p&gt;
&lt;pre id=&quot;code_1683006308788&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Publisher {
  func withUnretained&amp;lt;O: AnyObject&amp;gt;(_ owner: O) -&amp;gt; Publishers.CompactMap&amp;lt;Self, (O, Self.Output)&amp;gt; {
    compactMap { [weak owner] output in
      owner == nil ? nil : (owner!, output)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요렇게 Publisher의 Extension으로 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용은 심플하게 compactMap을 래핑한 Transform으로 해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경우는 owner에 문제가 생기면 값 자체가 nil로 들어오는 패턴이고 좀더 응용을 해본다면 Error를 발생시키는 식으로 해볼 수 있을 것 같네요!&lt;/p&gt;
&lt;pre id=&quot;code_1683006446203&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useCase
  .value
  .withUnretained(self)
  .sink { (owner, output) in
    owner.currentValue = output
  }
.store(in: &amp;amp;cancelablles)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 self에 대한 바인딩 없이 subscriber를 자유롭게 사용할 수 있습니다.&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/81</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-RxSwift%EC%9D%98-withUnretained%EB%A5%BC-Combine%EC%97%90%EC%84%9C%EB%8F%84-%EC%93%B0%EA%B8%B0#entry81comment</comments>
      <pubDate>Tue, 2 May 2023 14:48:27 +0900</pubDate>
    </item>
    <item>
      <title>iOS Framework - 네트워크 모듈 테스트 (URLSession, Unit Test)</title>
      <link>https://doing-programming.tistory.com/entry/iOS-Framework-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%93%88-%ED%85%8C%EC%8A%A4%ED%8A%B8-URLSession-Unit-Test</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 개발하면 반드시 네트워크에 대한 요청 및 응답 로직을 작성해야 하는데 개발하는 과정에서 로직이 간단한 경우는 최소 한두번, 복잡한 경우는 수십번은 테스트 과정에 네트워크 동작이 발생할 수 있습니다. 뿐만 아니라 몇몇 상황을 고려할 때 실제 서비스(Dev 서버라 하더라도) 를 호출하는 것은 문제점이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 네트워크 모듈 단위 테스트를 실시할 때 어떤 방법이 있을지 알아보려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 네트워크 요청시 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 네트워크 단위 테스트를 할 때 실제 네트워크를 요청하게 되면 어떤 문제가 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 간단한 서비스의 경우는 실제 네트워크 요청이 일어나더라도 크게 문제 없을 수도 있습니다. 하지만 여러분이 하는 대부분의 서비스는 복잡하고, 무겁고 어려울 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 네트워크 모듈 테스트할 때 실제 네트워크를 요청하면 어떤 문제점이 있을까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 환경&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift에서 사용하는 가장 기본적인 테스트 Framework는 XCTest를 사용합니다. 네트워크는 대부분? 비동기 실행이기 때문에 expectation을 선언하고 wait를 하게 됩니다. 이때 wait에 timeout 을 설정하게 되는데요, 실제 네트워크는 필연적으로 요청 및 응답에 대한 지연시간을 가지기 때문에 실제 예상되는 시간을 작성해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func testExample() throws {
  let exp = expectation(description: &quot;network&quot;)
  let session = URLSession.shared
  session.dataTask(with: URLRequest(url: URL(string: &quot;http://do-programming/index&quot;)!)) { data, response, error in
    exp.fulfill()
    XCTAssert(true)
  }.resume()

  wait(for: [exp], timeout: 0.1)
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 비동기 테스트 코드를 작성해 보았는데요. 위 코드대로라면 0.1초 동안 해당 테스트는 지연됩니다. 그런데 만약 실제 네트워크 요청시 환경 문제로 네트워크 응답에 대한 지연시간이 0.1초 보다 크게 되면 해당 테스트는 실패하게 됩니다. 0.1초 안으로 응답이 들어오지 않았거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 timeout을 실제 네트워크 지연시간 만큼 늘리면 어떻게 될까요?&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func testExample() throws {
  let exp = expectation(description: &quot;network&quot;)
  let session = URLSession.shared
  session.dataTask(with: URLRequest(url: URL(string: &quot;http://do-programming/index&quot;)!)) { data, response, error in
    exp.fulfill()
    XCTAssert(true)
  }.resume()

  wait(for: [exp], timeout: 1) // as-is 0.1 to-be 1.0
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 안정적으로 테스트는 성공하게 되겠지만 이 테스트 케이스 한번 돌리는데 1초라는 시간이 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여러분이 TDD(Test Driven Development)를 하고 있다면 동일한 테스트에 대해 최소 3번의 테스트 실행 시켜야 하는데요, 그러면 3초네요, 만약 만들고자 하는 네트워크 모듈의 테스트 케이스가 총 10개라면 전체 테스트를 한번 돌리는데 무려 10초, 그리고 반복적으로 테스트를 하게 되므로 소모되는 시간이 어마무시 하죠? &lt;b&gt;따라서 네트워크 모듈에 대한 단위 테스트를 작성할 때는 실제 네트워크 서버를 이용하지 않는 것이 좋습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 부하&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 서버에 대한 부담이 커질 수 있습니다. 네트워크 요청이 고작 짧은 json이라면 크게 문제되지 않을 수 있지만 테스트하는 모듈이 이미지나 동영상 또한 어떤 패키지(s3 등)이라면 한번의 요청만 해도 수 메가바이트가 넘어갈 수 있죠, 마찬가지로 테스트 케이스가 많을 수록, 기능이 복잡해질 수록 서버에 대한 부담이 커집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 요인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트가 외부 요인에 대해 의존하는 것 자체가 바람직 하지 못합니다. 예를들어 여러분이 테스트 작업을 하는 시간에 Dev서버가 잠시 셧다운이 될 수도 있고 (☕️) 혹은 문제가 생겨서 장시간 셧다운이 될 수도 있습니다. 이외에도 서버를 이전한다던가 하는 각종 외적인 요소들에 이해 업무에 장애가 생기는 경우가 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;이외에도 네트워크 모듈이라 하면 다양한 성공 케이스가 있을 수 있고, 다양한 실패 케이스가 있을 수 있습니다. 예를들어 HTTP Status Code가 200, 304, 401, 408 등등에 대해 다르게 동작해야 할 수 있는데, 백엔드 엔지니어에게 상황에 맞게 변경해달라고 한다던가, 그들이 테스트 할 수 있는 API를 뚫어 준다던가 하는 의존성이 생깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 상기 이유에 따라 네트워크 모듈 단위 테스트는 모의 객체(Mock Object)를 통해 이루어 져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 한번 익혀두면 사실 그간 있었던 불편한 점을 모두 해결할 수 있으므로 사실 해서 손해볼 것은 없습니다,&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;URLProtocol&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 단위 테스트할 때 의존하는 객체에 대해서 Mock이 필요하면 의존 객체의 인터페이스를 채용하는 Mock 모델을 만들게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol UseCase {
  func fetchList() -&amp;gt; [String]
}

final class MockUseCase: UseCase {
  func fetchList() -&amp;gt; [String] {
    [
      &quot;안녕!&quot;,
      &quot;잘가!&quot;,
    ]
  }
}

final class TestTests: XCTestCase {

  private var sut: ViewModel!

  override func setUpWithError() throws {
    try super.setUpWithError()
    sut = ViewModel(useCase: MockUseCase())
  }

  override func tearDownWithError() throws {
    sut = nil
    try super.tearDownWithError()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UseCase를 의존하는 ViewModel이 있으면 구체적인 UseCase를 동작하기 전에 MockUseCase를 적용하는 흔한 테스트 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 UseCase를 저희가 직접 작성하기 때문에 Mock object를 손쉽게 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만드는 것이 네트워크 모듈이라면 애플 환경에서는 필연적으로 URLSession을 의존하게 되죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Downloader -&amp;gt; URLSession&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 MockURLSession을 만들어야 하는데 어떻게 만드나요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;b&gt;URLProtocol&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlprotocol&quot;&gt;https://developer.apple.com/documentation/foundation/urlprotocol&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[URLProtocol | Apple Developer Documentation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;An abstract class that handles the loading of protocol-specific URL data.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;developer.apple.com](&lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlprotocol&quot;&gt;https://developer.apple.com/documentation/foundation/urlprotocol&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession을 통해 Task를 수행할 때 URLProtocol 서브클래스들 내용을 따를 수 있는데요, 이를 통해서 Mock URLProtocol subcless를 만들어서 네트워크 결과를 수정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1547&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mn9Mc/btr7gj6y9EV/5aLREU1xfKxhMKwG0vkBgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mn9Mc/btr7gj6y9EV/5aLREU1xfKxhMKwG0vkBgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mn9Mc/btr7gj6y9EV/5aLREU1xfKxhMKwG0vkBgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMn9Mc%2Fbtr7gj6y9EV%2F5aLREU1xfKxhMKwG0vkBgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1547&quot; height=&quot;880&quot; data-origin-width=&quot;1547&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MockURLProtocol&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 큰 파일을 보내는 서비스가 있다고 가정 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스는 송신되는 데이터 사이즈를 절약하기 위해 데이터를 압축해서 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서는 이 압축된 데이터를 받아서 압축 해제하는 과정을 거치게 되죠.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eD2Sy9/btr7eWj2Rqf/DQAGA54g1Dde9J5C4k3hh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eD2Sy9/btr7eWj2Rqf/DQAGA54g1Dde9J5C4k3hh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eD2Sy9/btr7eWj2Rqf/DQAGA54g1Dde9J5C4k3hh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeD2Sy9%2Fbtr7eWj2Rqf%2FDQAGA54g1Dde9J5C4k3hh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;473&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 추가로 데이터를 더 절약하기 위해 클라이언트에서도 데이터를 캐싱하기로 했습니다. Etag를 기반으로 캐싱한다고 가정하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Etag가 일치 하는지 안하는지 서버에 요청을 보내고 일치하면 200, 일치하지 않으면 304를 받게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 생각에는 클라이언트에서 압축 해제를 매번 하는 것도 디바이스에 따라 많이 부담이 되는 작업이기 때문에 서버에서 보내준 상태 코드가 200이고 패키지 내용이 바뀌었을 때만 압축 해제를 수행하고 그렇지 않을 경우에는 이미 캐싱하고 있던 압축 해제된 데이터를 사용하고 싶습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 이 역할을 수행하는 모델을 Downloader라고 정의해보죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Downloader는 Response의 StatusCode에 따라 다르게 핸들링 할 수 있도록 결과를 반환하고 싶습니다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;if statusCode == 304 {
  completion(data, .useCache, nil)
} else if statusCode == 200 {
  completion(data, .refreshCache, nil)
} else {
  completion(nil, .failure, nil)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 테스트로 돌아가보죠, 그럼 Downloader가 요청 결과에 따라 로직대로 바르게 반환하는지 테스트해 볼 수 있을 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 분기가 모두 커버리지에 들어갈 수 있도록 테스트 코드를 짜야합니다!&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func test_download_sucess() throws {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트할 성공 케이스의 경우는 첫 번째 요청의 etag와 두 번째 요청의 etag가 다를 때, 200을 내려줘서 캐싱된 로컬 데이터가 아닌 서버 데이터르 사용하도록 해야 합니다. 이제 서버에서 반응해줄 동작을 URLProtocol을 통해 구현해보게습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;final class MockURLProtocol: URLProtocol {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tests Target쪽에 우선 MockURLProtocol 서브클래스를 만들고...&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;override class func canInit(with request: URLRequest) -&amp;gt; Bool {
  return true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 canInit(with:) 메서드를 오버라이드 합니다. 해당 메서드를 true로 반환해야지 MockURLProtocol의 동작을 수행하도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 canonicalRequest(for:) 메서드를 오버라이딩 하는데, 구현 안하면 터지기 때문에 인자의 request를 바로 반환하는 코드로 작성 해 줍니다. canonicalRequest를 어떨 때 쓸 수 있는지는 따로 문서를 읽어보는게 좋을 것 같습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;override class func canonicalRequest(for request: URLRequest) -&amp;gt; URLRequest {
  request
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 startLoading() 메서드를 오버라이딩 합니다. startLoading()은 request 요청에 대해 동작을 정의할 수 있습니다. 즉 테스트 용으로 서버에서 그 응답이 온 것처럼 할 수 있다는 것이죠&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;override func startLoading() {
  let response = HTTPURLResponse(
    url: request.url!,
    statusCode: 200,
    httpVersion: nil,
    headerFields: [:])!

  client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
  client?.urlProtocolDidFinishLoading(self)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어떤 요청에도 Request에 대한 응답을 200으로 내려줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이걸 응용해서 서버에서 Etag를 내려주는 것 처럼 할 수 있죠&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;static var serverEtag: String?

override func startLoading() {
  let response = HTTPURLResponse(
    url: request.url!,
    statusCode: 200,
    httpVersion: nil,
    headerFields: headers())!

  client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
  client?.urlProtocolDidFinishLoading(self)
}

private func headers() -&amp;gt; [String: String] {
  guard let etag = Self.serverEtag else { return [:] }
  return [&quot;Etag&quot;: etag]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;serverEtag가 값이 있으면 서버에서 etag를 내려주는 듯한 테스트를 할 수 있습니다. 클라이언트에서는 URLSession.DataTask의 결과로 etag가 포함된 URLResponse를 받을 수 있게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 statusCode도 다르게 줘보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private func statusCode() -&amp;gt; Int {
  return Self.serverEtag == request.allHTTPHeaderFields?[&quot;Etag&quot;] as? String ? 304 : 200
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLRequest에 etag가 포함되어 있고 만약 지정한 etag랑 같으면 304, 다르면 200으로 statusCode를 내려줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 download의 로직에 response에 있는 etag를 저장하고 다음 request에 요청 헤더에 Etag를 잘 포함시켜서 보낸다면 아래 테스트 코드는 성공할 것입니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func test_download_use_cache() throws {
  let exp = expectation(description: &quot;&quot;)
  // Given
  let etag = UUID().uuidString
  MockURLProtocol.serverEtag = etag
  // When
  sut.download(url) { [unowned self] _, _, _ in
    sut.download(url) { _, result, _ in
      exp.fulfill()
      // Then
      XCTAssertEqual(result, .useCache)
    }
  }
  wait(for: [exp], timeout: 0.1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 첫번째 download 이후 etag를 수정하고 두번 째 download를 실시하면 etag가 달라진 상황도 테스트 할 수 있겠네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;OHHTTPStubs&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 네트워크 테스트를 좀더 편하게 해주는 라이브러리가 바로 OHHTTPStubs입니다. OHHTTPStubs는 테스트 타겟 뿐만이 아니라 본 프로젝트에서도 디버그 용도로 활용하기 수월하므로 만약 네트워크 통신 테스트를 중요하게 생각하신다면 충분히 선택해볼한 라이브러리 인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 클라이언트 개발자 입장에서 백엔드 API는 항상 늦게 나오는데 기능을 좀더 테스트 해보기 위해서 꼭 필요한 것 같습니다. 실제 테스트를 위해 코드에 덕지덕지 디버그용 코드를 써넣거나 백엔드 API에 의존하지 않고 기능을 마무리 지을 수 있으니까요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/80</guid>
      <comments>https://doing-programming.tistory.com/entry/iOS-Framework-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EB%AA%A8%EB%93%88-%ED%85%8C%EC%8A%A4%ED%8A%B8-URLSession-Unit-Test#entry80comment</comments>
      <pubDate>Fri, 31 Mar 2023 02:12:18 +0900</pubDate>
    </item>
    <item>
      <title>Data Structure - Heap (w/Swift)</title>
      <link>https://doing-programming.tistory.com/entry/Data-Structure-Heap-wSwift</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;What is Heap&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙은 완전 이진 트리 기반의 자료 구조입니다. 구현 하기 위해서는 트리가 어떤 형태를 가지고 있는 자료 구조인지 미리 알면 좋을 것 같습니다.&lt;br&gt;힙은 일반적으로 두가지 힙이 있습니다. Max Heap과 Min Heap이 있습니다. 최대 힙은 말 그대로 큰 수가 가장 우선 순위가 높고, 최소 힙은 작은 수가 우선 순위가 높습니다.&lt;br&gt;우선 순위라는 말을 썼는데요!, Heap 자료 구조는 Priority Queue 자료 구조를 만들 때 활용할 수 있습니다!&lt;br&gt; &lt;br&gt;힙 자료구조는 어떻게 만들고, 데이터 삽입, 출력은 어떻게 하는지 알아 보도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Building&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 우선 데이터를 삽입 하고 제거할 때, Tree 형태 자료에 맞게 재배치를 하는 과정이 필요합니다. Max-Heap 기준으로 설명하자면&lt;br&gt;데이터를 삽입하면 자신이 있어야 할 위치까지 재배치 하는 과정이 필요하다는 거죠! 예를 들어 볼게요!&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;remove&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nfMx8/btrSZAaF2pi/1VZgtNHHXTwpsaMPDtKAek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nfMx8/btrSZAaF2pi/1VZgtNHHXTwpsaMPDtKAek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nfMx8/btrSZAaF2pi/1VZgtNHHXTwpsaMPDtKAek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnfMx8%2FbtrSZAaF2pi%2F1VZgtNHHXTwpsaMPDtKAek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;305&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;힙에서 제거가 어떤 식으로 이루어 지는지 먼저 봐보겠습니다. 우선 Max-Heap에서는 가장 큰 수가 먼저 나온다고 말씀드렸죠.&lt;br&gt;그러면 10이 remove 동작을 하게 될 경우 가장 먼저 나와야 합니다.&lt;br&gt; &lt;br&gt;힙은 내부적으로 배열로 구현되는데요! 10을 빼겠다고 removeFirst()를 하게되면 배열을 재배치 하는 과정에서 O(n)이라는 시간이 발생하고 맙니다!&lt;br&gt; &lt;br&gt;따라서 배열의 가장 마지막 요소를 제거하면 가장 빨리 제거할 수 있죠!&lt;br&gt;그래서 가장 첫번 째 요소와 가장 마지막 요소를 우선 Swap 합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEKDNp/btrSZ4CjwPl/DzCFmCCfwDryXKkt07rzuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEKDNp/btrSZ4CjwPl/DzCFmCCfwDryXKkt07rzuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEKDNp/btrSZ4CjwPl/DzCFmCCfwDryXKkt07rzuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEKDNp%2FbtrSZ4CjwPl%2FDzCFmCCfwDryXKkt07rzuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;226&quot; height=&quot;200&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;5와 10을 바꾸었네요!, 이제 10은 가장 마지막에 있으니 꺼내주면 됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbMyIu/btrS093Zis0/PnvD7vo47GEmS6FCEONkv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbMyIu/btrS093Zis0/PnvD7vo47GEmS6FCEONkv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbMyIu/btrS093Zis0/PnvD7vo47GEmS6FCEONkv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMyIu%2FbtrS093Zis0%2FPnvD7vo47GEmS6FCEONkv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;165&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;힙에서 가장 우선 순위가 높은 값(10)을 얻었네요, 그런데 여기서 멈추면 Max-heap 구조가 무너지기 때문에 재배치 하는 과정을 거칩니다.&lt;br&gt;재배치 과정은 어렵지 않습니다. 자식 노드 두개를 비교해서 자신 보다 큰 노드와 위치를 바꿔주면 됩니다. 만약 자신 보다 자식 노드 둘다 크다면 더 큰 노드와 바꿔주면 됩니다.&lt;br&gt; &lt;br&gt;즉 다음 동작은 5를 8과 4와 비교하여 더 큰값과 바꿔주면 됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DtoCT/btrS0dFOACC/KXREk2GicfV2HQHEMdbNpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DtoCT/btrS0dFOACC/KXREk2GicfV2HQHEMdbNpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DtoCT/btrS0dFOACC/KXREk2GicfV2HQHEMdbNpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDtoCT%2FbtrS0dFOACC%2FKXREk2GicfV2HQHEMdbNpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;291&quot; height=&quot;232&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;8과 5를 바꾸었죠? 4는 5보다 작기 때문에 대상이 아닙니다. 이제 이 동작을 5에 대해서 더이상 교체할 노드가 없을 때 까지 반복해 주면 됩니다.&lt;br&gt;그러니까 그 다음은 5를 7과 1을 비교해서 더 큰 값과 바꿔주면 됩니다. 7과 바꿔주면 되겠네요!&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Uhu0E/btrSZ4h1ZGs/IfWx0zwhdbGZAKi57jMQe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Uhu0E/btrSZ4h1ZGs/IfWx0zwhdbGZAKi57jMQe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Uhu0E/btrSZ4h1ZGs/IfWx0zwhdbGZAKi57jMQe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUhu0E%2FbtrSZ4h1ZGs%2FIfWx0zwhdbGZAKi57jMQe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;227&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 더 바꿀 노드가 없으므로, 이렇게 되면 이 Heap은 다시 Max-Heap 구조가 완성 되었습니다. 이 과정을 sift-down이라고 합니다. 여기서 트리 구조만 보고서는 이게 정렬이 된건가 의아해 하실 수도 있는데요, 그 다음 큰 값을 제거 해볼까요?&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx6zmI/btrS0L97YXW/5DAzs9IenKmFt37pr03GFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx6zmI/btrS0L97YXW/5DAzs9IenKmFt37pr03GFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx6zmI/btrS0L97YXW/5DAzs9IenKmFt37pr03GFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx6zmI%2FbtrS0L97YXW%2F5DAzs9IenKmFt37pr03GFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;167&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;8을 빼기 위해서 가장 마지막 노드와 자리를 바꿔준 다음 8을 빼줍니다. 그런 다음 3을 sift-down 해볼게요&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhvFHv/btrSZzCNhKB/N7Nl2X01EjsVVIiwMlt5S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhvFHv/btrSZzCNhKB/N7Nl2X01EjsVVIiwMlt5S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhvFHv/btrSZzCNhKB/N7Nl2X01EjsVVIiwMlt5S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhvFHv%2FbtrSZzCNhKB%2FN7Nl2X01EjsVVIiwMlt5S1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;185&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;3을 7,4와 비교해서 더 큰값인 7과 바꿔 준 다음&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqUy3f/btrS1R23Z71/8L4GmtdoJe33EgS0aFkpY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqUy3f/btrS1R23Z71/8L4GmtdoJe33EgS0aFkpY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqUy3f/btrS1R23Z71/8L4GmtdoJe33EgS0aFkpY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqUy3f%2FbtrS1R23Z71%2F8L4GmtdoJe33EgS0aFkpY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;305&quot; height=&quot;194&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 3의 자식 노드인 5, 1과 비교해서 더 큰 값인 5과 변경 해줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwYvqk/btrSZR4h9Cm/bxLM8ENpDpyO5GGRAuj9j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwYvqk/btrSZR4h9Cm/bxLM8ENpDpyO5GGRAuj9j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwYvqk/btrSZR4h9Cm/bxLM8ENpDpyO5GGRAuj9j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwYvqk%2FbtrSZR4h9Cm%2FbxLM8ENpDpyO5GGRAuj9j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;223&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;3은 더 이상 자식 노드가 없으니 sift-down이 끝났죠?&lt;br&gt;그 다음 큰값을 빼게 되면&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/broSn1/btrS08xfqyS/I6FuGbkKSeknmpoajO44c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/broSn1/btrS08xfqyS/I6FuGbkKSeknmpoajO44c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/broSn1/btrS08xfqyS/I6FuGbkKSeknmpoajO44c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbroSn1%2FbtrS08xfqyS%2FI6FuGbkKSeknmpoajO44c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;207&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;568&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;7과 2를 swap 하고 7을 내보 낸 뒤 2를 sift-down 합니다.&lt;br&gt;2를 5,4와 비교하여 가장 큰 값인 5와 바꿔주고, 그 다음 2의 자식인 3,1과 비교하여 더 큰값인 3과 바꿔 줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVh6U1/btrSZ3pRWTN/dYCkVo038W4sUHMNCcmjt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVh6U1/btrSZ3pRWTN/dYCkVo038W4sUHMNCcmjt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVh6U1/btrSZ3pRWTN/dYCkVo038W4sUHMNCcmjt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVh6U1%2FbtrSZ3pRWTN%2FdYCkVo038W4sUHMNCcmjt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;256&quot; height=&quot;233&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Qwtx/btrS09bQL0H/keyN4AD5dvcDzQl0SuADw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Qwtx/btrS09bQL0H/keyN4AD5dvcDzQl0SuADw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Qwtx/btrS09bQL0H/keyN4AD5dvcDzQl0SuADw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Qwtx%2FbtrS09bQL0H%2FkeyN4AD5dvcDzQl0SuADw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;218&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 큰 값인 5가 빠질려면 1과 swap 하게 되고 1로 sift - down을 합니다. 이 때 1의 자식인 3,4를 비교해서 더 큰 값인 4와 바꾸게 되면 1의 여정은 끝이나죠 &lt;br&gt;이 과정은 노드가 다 없어질 때 까지 반복하다 보면&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byUkUb/btrSZSa5ghU/c1nroz5gdxVfk6kZVT9jKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byUkUb/btrSZSa5ghU/c1nroz5gdxVfk6kZVT9jKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byUkUb/btrSZSa5ghU/c1nroz5gdxVfk6kZVT9jKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyUkUb%2FbtrSZSa5ghU%2Fc1nroz5gdxVfk6kZVT9jKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;66&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 순서대로 값이 빠져나온 것을 볼 수 있습니다!&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;insert&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 삽입 하는 방법은 sift-down 이 아닌 sift-up을 하면 됩니다. 마찬가지로 갑 추가는 트리의 가장 마지막 노드에 추가하고, 이를 sift-up하면서 올라가면 됩니다. sifu-up은 sift-down보다 더 쉬운데요! sift-down은 두 자식 노드 중 더 큰 값과 변경 해주는 반면, sift-up은 부모 노드가 자신 보다 더 작으면 무조건 바꿔주면 됩니다. 비교 할 대상이 하나 뿐인 것이죠 (max-heap 기준)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Implementation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제로 Swift 언어로 구현해봅시다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Heap&amp;lt;T: Equatable&amp;gt; {
&amp;nbsp;&amp;nbsp;private var elements: [T] = []
&amp;nbsp;&amp;nbsp;private let sort: (T, T) -&amp;gt; Bool
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;init(sort: @escaping (T, T) -&amp;gt; Bool) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.sort = sort
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Heap을 배열로 구현할 거라고 말씀 드렸습니다. 따라서 내부 데이터로 배열을 가지고 있을 겁니다. &lt;br&gt;타입은 제네릭 T로 표현할 것이고, sift-up, sift-down 할때 두 값을 비교해서 누가 더 큰지 알아야 했기 때문에, Equatable을 준수하는 타입으로 제약을 걸겠습니다.&lt;br&gt;sort라는 함수 타입을 통해 최대 힙인지, 최소 힙인지 구분할 수 있기 때문에 이를 생성 시점에 받도록 하겠습니다. 무슨 소리냐 하면 &lt;br&gt;Sift-down 할 대 부모 노드와 자식 노드를 비교를 하잖아요? lhs값이 부모고, rhs값이 자식일 때 자식이 부모보다 클 때 즉 lhs &amp;gt; rhs이면 최대 힙 조건에 맞게 구성이 됐었기 때문에, sort라는 함수타입에 &amp;gt;를 전달해 주면 최대 힙이 되는 것이고 &amp;lt;를 전달해 주면 최소 힙이 되는겁니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Child Node Index&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap에서 Sift-down, up 할 때 자식 노드와 부모 노드의 인덱스를 Swap 했잖아요? 즉 자식 노드와 부모노드의 인덱스를 알아낼 방법이 필요합니다.&lt;br&gt;트리는 다음과 같은 형태로 순번을 가지죠?, &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX2ORm/btrS0gvMPoz/zd7jVQPl0MG7HtUBSv7gUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX2ORm/btrS0gvMPoz/zd7jVQPl0MG7HtUBSv7gUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX2ORm/btrS0gvMPoz/zd7jVQPl0MG7HtUBSv7gUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX2ORm%2FbtrS0gvMPoz%2Fzd7jVQPl0MG7HtUBSv7gUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;439&quot; height=&quot;441&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 1번 노드(값 8) 를 부모 노드라고 했을 때 자식 노드의 인덱스는 3, 4입니다.&lt;br&gt;즉 왼쪽 자식 노드는 p * 2 + 1 이고 오른쪽 자식 노드는 왼쪽 자식 노드의 무조건 그 다음 이기 때문에 L + 1과도 같습니다. &lt;br&gt;따라서 다음 두 인스턴스 메서드를 추가합니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;private func leftChildIndex(parent: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;parent * 2 + 1
}
&amp;nbsp;&amp;nbsp;
private func rightChildIndex(parent: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;parent * 2 + 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 가장 높은 우선 순위 값을 빼는 순서를 말씀 드렸죠.&lt;br&gt;0번과 가장 마지막 노드를 swap 한 다음 마지막 값을 빼 내고, sift-down이라는 것을 수행 하면 됩니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;mutating
func remove() -&amp;gt; T? {
&amp;nbsp;&amp;nbsp;guard !isEmpty else { return nil }
&amp;nbsp;&amp;nbsp;elements.swapAt(0, count - 1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;defer {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;siftDown(start: 0)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;return elements.removeLast()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 코드겠죠, swapAp을 하기 위해서는 1이상의 개수가 필요하므로 0개일 때는 조기 종료문을 써줍니다!&lt;br&gt; &lt;br&gt;siftDown 메서드는 아직 작성하지 않았죠?&lt;br&gt;해당 메서드에 포함되어야 할 내용은 크게 어렵지 않습니다.&lt;br&gt;왼쪽 자식 노드와 오른쪽 자식 노드와 비교해서 더 큰값과 위치를 바꿉니다.&lt;br&gt;이 동작을 더이상 바꿀 자식 노드가 없을 때 까지 반복하면 됩니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;mutating
private func siftDown(start: Int) {
&amp;nbsp;&amp;nbsp;var parent = start
&amp;nbsp;&amp;nbsp;var candidate = parent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;while true {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let leftChild = leftChildIndex(parent: parent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let rightChild = rightChildIndex(parent: parent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if leftChild &amp;lt; count, sort(elements[leftChild], elements[candidate]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;candidate = leftChild
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if rightChild &amp;lt; count, sort(elements[rightChild], elements[candidate]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;candidate = rightChild
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if parent == candidate {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elements.swapAt(parent, candidate)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parent = candidate
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 삽입 메서드를 만들어 보겠습니다.&lt;br&gt;삽입은 말씀 드렸던 순서대로, 트리 마지막에 새 값을 추가 한뒤 sift-up이라는 동작을 수행하면 된다고 말씀드렸습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;mutating
func insert(_ element: T) {
&amp;nbsp;&amp;nbsp;elements.append(element)
&amp;nbsp;&amp;nbsp;siftUp(start: count - 1)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;siftUp을 구현하기 전에 부모 노드의 인덱스를 알아낼 방법을 찾아보겠습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VsVk4/btrSZKD9sJo/7FFAh0U5upk98C5p8gtXsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VsVk4/btrSZKD9sJo/7FFAh0U5upk98C5p8gtXsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VsVk4/btrSZKD9sJo/7FFAh0U5upk98C5p8gtXsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVsVk4%2FbtrSZKD9sJo%2F7FFAh0U5upk98C5p8gtXsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;263&quot; height=&quot;243&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 자식 노드를 기준으로 보니까 3번 노드(값 5) 를 볼까요?&lt;br&gt;3번의 부모 노드 인덱스는 1이므로 (3 - 1) / 2 를 하면 1이 나오겠네요! 4번 노드의 입장에서도 같습니다. (4 - 1) / 2을 하면 1이 나오네요!&lt;br&gt;즉 (C - 1) / 2 를 하면 부모 노드의 인덱스를 구할 수 있습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;private func parentIndex(child: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;(child - 1) / 2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요렇게 쓰면 되겠습니다!&lt;br&gt;이제 다시 sift-up을 작성하러 가볼까요!&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;mutating
private func siftUp(start: Int) {
&amp;nbsp;&amp;nbsp;var child = start
&amp;nbsp;&amp;nbsp;var parent = parentIndex(child: child)
&amp;nbsp;&amp;nbsp;while child &amp;gt; 0, sort(elements[child], elements[parent]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elements.swapAt(parent, child)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;child = parent
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parent = parentIndex(child: child)
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;이제 기본 적인 모양새는 갖추었으니 실제로 써볼까요?&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var heap = Heap&amp;lt;Int&amp;gt;(sort: &amp;gt;)

heap.insert(1)
heap.insert(7)
heap.insert(4)
heap.insert(10)
heap.insert(2)

while let value = heap.remove() {
&amp;nbsp;&amp;nbsp;print(value)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작위로 숫자를 넣어주고&lt;br&gt;heap을 반복해서 remove 해주면&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KpzPZ/btrSZKYr27Z/UoeW4iQr6IJt1NQpk5iHO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KpzPZ/btrSZKYr27Z/UoeW4iQr6IJt1NQpk5iHO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KpzPZ/btrSZKYr27Z/UoeW4iQr6IJt1NQpk5iHO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKpzPZ%2FbtrSZKYr27Z%2FUoeW4iQr6IJt1NQpk5iHO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;151&quot; height=&quot;81&quot; data-origin-width=&quot;151&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;촤란~ Max Heap이 완성 되었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Additional&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;있으면 좋은 추가 기능을 작성해볼려고 합니다.&lt;br&gt;임의의 노드를 제거 해볼 수 있을텐데요! 이때는 어떻게 해야 할까요?&lt;br&gt;어렵지 않습니다. 해당 노드의 인덱스를 힙의 마지막 노드의 인덱스와 swap 해 준 뒤 선택한 인덱스 위치로부터 sift-up과 sift-down을 수행해주면 됩니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;mutating
func remove(at index: Int) -&amp;gt; T? {
&amp;nbsp;&amp;nbsp;guard index &amp;lt; count else { return nil }
&amp;nbsp;&amp;nbsp;if index == count - 1 {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return elements.removeLast()
&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elements.swapAt(index, count - 1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;defer {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;siftDown(start: index)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;siftUp(start: index)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return elements.removeLast()
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵지 않습니당. 그런데 제거하려는 노드가 어느 인덱스에 있는지는 어떻게 찾을까요?&lt;br&gt; &lt;br&gt;루트 노드 부터 시작해서 왼쪽, 오른쪽 자식을 모두 뒤지는 수 밖에는 없습니다. 재귀 함수로 구현 해 볼게요&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func index(of element: T) -&amp;gt; Int? {
&amp;nbsp;&amp;nbsp;index(of: element, startingAt: 0)
}
&amp;nbsp;&amp;nbsp;
private func index(of element: T, startingAt i: Int) -&amp;gt; Int? {
&amp;nbsp;&amp;nbsp;if i &amp;gt;= count {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return nil
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if sort(element, elements[i]) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return nil
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if element == elements[i] {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return i
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if let j = index(of: element, startingAt: leftChildIndex(parent: i)) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return j
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if let j = index(of: element, startingAt: rightChildIndex(parent: i)) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return j
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;return nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0번 부터 시작해서 전체를 훑을 예정입니다. 외부에 노출된 메서드에는 시작 인자는 숨겨줄게요.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Array Building&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 까지는 힙을 만들 때 insert값을 계속해서 전달해 주었는데요, 배열로 바로 초기화 할 수는 없을까요? 시도해봅시다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;init(sort: @escaping (T, T) -&amp;gt; Bool, elements: [T] = []) {
&amp;nbsp;&amp;nbsp;self.sort = sort
&amp;nbsp;&amp;nbsp;self.elements = elements
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;if !elements.isEmpty {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for i in stride(from: elements.count / 2 - 1, through: 0, by: -1) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;siftDown(start: i)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elements를 받을 수 있도록 인자를 수정하고, elements를 전달 받은 다음 힙을 조건에 맞게 섞어? 줄게요&lt;br&gt;이 때 모든 값을 sift-down 할 필요는 없습니다. 중간에서 sift-down을 실시하면 됩니다. 비교 대상이 되는 부모 노드는 전체 노드의 절반 만큼만 존재하기 때문이죠!&lt;br&gt; &lt;br&gt;이제는 아래 처럼 Heap을 만들 수 있습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var heap = Heap&amp;lt;Int&amp;gt;(sort: &amp;gt;, elements: [1,7,3,2,6,8])

while let node = heap.remove() {
&amp;nbsp;&amp;nbsp;print(node)
}
//8
//7
//6
//3
//2
//1&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Performence&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;remove: O(log n)&lt;/li&gt;
 &lt;li&gt;insert: O(log n)&lt;/li&gt;
 &lt;li&gt;search: O(n)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Reference.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;&lt;a href=&quot;https://github.com/kodecocodes/swift-algorithm-club/blob/master/Heap/Heap.swift&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;kodeco / swift algorithm club&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;</description>
      <category>Data Structure</category>
      <category>data_structure</category>
      <category>heap</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/78</guid>
      <comments>https://doing-programming.tistory.com/entry/Data-Structure-Heap-wSwift#entry78comment</comments>
      <pubDate>Wed, 7 Dec 2022 00:59:55 +0900</pubDate>
    </item>
    <item>
      <title>Data Structure</title>
      <link>https://doing-programming.tistory.com/entry/Data-Structure</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;What is Data Structure&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 구조는 데이터를 저장하고 조직하는 저장소입니다. 자료 집합을 효율적으로 저장하거나 접근할 수 있도록 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Classification of Data Structure&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ejyc7U/btrSXo2Ry1s/HK3m5CK0mzhMMtvSLYq9e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ejyc7U/btrSXo2Ry1s/HK3m5CK0mzhMMtvSLYq9e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ejyc7U/btrSXo2Ry1s/HK3m5CK0mzhMMtvSLYq9e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fejyc7U%2FbtrSXo2Ry1s%2FHK3m5CK0mzhMMtvSLYq9e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1114&quot; height=&quot;546&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자료구조를 크게 선형 자료 구조인지, 비선형 자료 구조인지 구분할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Linear Data Structure:&amp;nbsp;&lt;/b&gt;데이터 요소가 순차적 또는 선형으로 구성되어 있고 이전 및 다음 인접 요소에 연결되는 데이터 구조를 선형 데이터 구조(Linear Data Structure)라고 합니다. 선형 데이터 구조의 대표적인 예로는 배열, 링크드 리스트, 큐, 스택 등이 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Static, Dynamic Data Structure: &lt;/b&gt;정적 자료 구조 인지 동적 자료 구조인지로도 구분이 가능합니다. 정적 자료구조는 C에서 배열과 같이 고정된 메모리 크기를 가지고 있는 형태인 반면 동적 자료 구조는 런타임 중에 그 크기가 변할 수 있죠. Swift에서는 배열이 정적인 메모리 사이즈를 가지지는 않기 때문에 의미있는 구분은 아닙니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Non-Linear Data Structure:&amp;nbsp;&lt;/b&gt;비선형 자료 구조는 데이터가 순차적으로 배치되지 않은 데이터 집합을 얘기합니다. 일반적으로 트리나 그래프를 얘기합니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Data Structure</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/77</guid>
      <comments>https://doing-programming.tistory.com/entry/Data-Structure#entry77comment</comments>
      <pubDate>Tue, 6 Dec 2022 22:28:59 +0900</pubDate>
    </item>
    <item>
      <title>UML(Unified Modeling Language) - feat. Class Diaphragm</title>
      <link>https://doing-programming.tistory.com/entry/UMLUnified-Modeling-Language-feat-Class-Diaphragm</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Unified_Modeling_Language&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;UML. Unified Modeling Language는 시스템 설계를 시각화 하는 표준 방법을 제공하기 위한 소프트웨어 엔지니어링 분야의 범용 개발 모델링 언어입니다.&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baNQdT/btrRfIhuhOR/wotgn6ZYVoWtvfIgIAMttK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baNQdT/btrRfIhuhOR/wotgn6ZYVoWtvfIgIAMttK/img.png&quot; data-alt=&quot;https://en.wikipedia.org/wiki/Unified_Modeling_Language&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baNQdT/btrRfIhuhOR/wotgn6ZYVoWtvfIgIAMttK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaNQdT%2FbtrRfIhuhOR%2Fwotgn6ZYVoWtvfIgIAMttK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;334&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://en.wikipedia.org/wiki/Unified_Modeling_Language&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 글자로 표현되는 코드를 그림으로 표현하기 위한 방법이죠!&lt;br&gt;UML에 대해서 정확하게 알고 사용한 것은 아니지만 정말 유용한 도구라고 생각을 해서 많이 사용했는데요.&lt;br&gt;객체간의 관계와 구조를 코드를 보지 않고도 그림으로 설명할 수 있기 때문에, UML이 있으면 코드를 일일이 분석하지 않아도 어떤식으로 구성되어 있는지 한눈에 알 수 있습니다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;레거시 코드를 유지 보수 하다 보면 코드와 실제 화면이 잘 매치가 안되기도 하고, 추상화가 많이 되어 있는 경우 코드를 파악하는데 오랜 시간이 걸리기도 합니다. 개인의 역량에 따라 다르겠지만 그럴 때 해당 모듈에 대해서 정리한 다이어그램 이 있으면 참 좋겠다고 생각했었습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;뿐만아니라 PR 할 때 리뷰어가 쉽게 코드 구조를 파악할 수 있게 도움을 줄 수도 있고, 코드를 작성하기 전에 구조에 대한 의논을 하기 위해서도 좋은 도구로 쓰입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;UML 다이어그램의 유형에는 여러가지가 있는데 그 중에서 가장 유용하게 사용했던 클래스 다이어그램에 대해서 자세히 알아보려고 합니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Class Diagram&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 다이어그램은 시스템의 Class, Property, Method, Protocol 등을 표현할 수 있고 객체간의 관계를 나타낼 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Class&lt;/h3&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tLQtR/btrRisLfCQb/bZjp8LK7Vyp3X3koRL4cjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tLQtR/btrRisLfCQb/bZjp8LK7Vyp3X3koRL4cjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tLQtR/btrRisLfCQb/bZjp8LK7Vyp3X3koRL4cjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtLQtR%2FbtrRisLfCQb%2FbZjp8LK7Vyp3X3koRL4cjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;228&quot; height=&quot;132&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class는 사각형 박스로 나타냅니다 이 때 얘기하는 클래스는 개발 언어에서 class, struct를 구분하지 않습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Inheritance&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Inheritance는 상속을 뜻합니다. 상속은 비어있는 화살표로 표현됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;104&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA6zh6/btrRfNiUmrr/SrNqdDkhoz3EpXSDdRpSPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA6zh6/btrRfNiUmrr/SrNqdDkhoz3EpXSDdRpSPk/img.png&quot; data-alt=&quot;Inheritance&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA6zh6/btrRfNiUmrr/SrNqdDkhoz3EpXSDdRpSPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA6zh6%2FbtrRfNiUmrr%2FSrNqdDkhoz3EpXSDdRpSPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;70&quot; height=&quot;207&quot; data-origin-width=&quot;104&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Inheritance&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 화살표 방향은 'A가 B를 상속하고 있다' 로 읽는 기준으로 A가 B를 향하도록 하면 됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NOmXH/btrRisxKwge/Z6UAcGCmTNyRLPNCNEvoe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NOmXH/btrRisxKwge/Z6UAcGCmTNyRLPNCNEvoe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NOmXH/btrRisxKwge/Z6UAcGCmTNyRLPNCNEvoe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNOmXH%2FbtrRisxKwge%2FZ6UAcGCmTNyRLPNCNEvoe0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;113&quot; height=&quot;213&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페르시안 고양이(A)가 고양이(B)를 상속하기 때문에 위 그림처럼 나타낼 수 있습니다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Association&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Association은 다른 말로 표현하면 이해가 쉬운데 우리가 Swift에서 특정 엔티티를 작성할 때 엔티티가 가진 Property를 표현하는 것과 같습니다. 그래서 Association은 Property입니다. 그래서 표현할 때 A가 B를 가지고 있다. 로 표현할 수 있으며 화살표가 A에서 B로 향하도록 하면 됩니다. 화살표는 열린 화살표로 표현됩니다&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XE2K6/btrRjqfbSiE/UBiBrBWO9bext6NMLyY1J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XE2K6/btrRjqfbSiE/UBiBrBWO9bext6NMLyY1J1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XE2K6/btrRjqfbSiE/UBiBrBWO9bext6NMLyY1J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXE2K6%2FbtrRjqfbSiE%2FUBiBrBWO9bext6NMLyY1J1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;177&quot; height=&quot;269&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Cat이 배열 즉 Person 하나가 다수의 Cat를 가지고 있다면&amp;nbsp; 1...*과 같이 표현할 수 있습니다. 코드로 보자면 [Cat]과 같은 타입인 것이죠&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dajwGe/btrRikNiuv2/rt0FTcrY5iPcVr49uxjf9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dajwGe/btrRikNiuv2/rt0FTcrY5iPcVr49uxjf9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dajwGe/btrRikNiuv2/rt0FTcrY5iPcVr49uxjf9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdajwGe%2FbtrRikNiuv2%2Frt0FTcrY5iPcVr49uxjf9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;130&quot; height=&quot;230&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Person이 Cat을 상속한 Persian Cat을 가지고 있다고 한다면 아래 처럼 표현이 가능합니다!&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdJELW/btrRhGp2d1C/jn3VgROI8Zr7rznSGVOlk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdJELW/btrRhGp2d1C/jn3VgROI8Zr7rznSGVOlk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdJELW/btrRhGp2d1C/jn3VgROI8Zr7rznSGVOlk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdJELW%2FbtrRhGp2d1C%2Fjn3VgROI8Zr7rznSGVOlk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;275&quot; height=&quot;200&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Protocol&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;SOLID를 설명하다보면 항상 나오는 얘기가 인터페이스를 의존한다 뭐 이런얘기 많이 하는데, Swift는 그럴때 Protocol을 쓸 수 있습니다.&lt;br&gt;Protocol을 표현하는 방법은 Class와 동일한 사각형으로 표현하며 이때 Class와 구분되어야 하므로 별도로 표시 해 줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJoKjo/btrRh8zzVTc/N0sX3kuQSNIOH6Q5xS2cNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJoKjo/btrRh8zzVTc/N0sX3kuQSNIOH6Q5xS2cNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJoKjo/btrRh8zzVTc/N0sX3kuQSNIOH6Q5xS2cNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJoKjo%2FbtrRh8zzVTc%2FN0sX3kuQSNIOH6Q5xS2cNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;109&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &amp;lt;&amp;lt;protocol&amp;gt;&amp;gt; 과 같이 표현한 것을 UML에서는 stereotypes라고 표현합니다. 만약 클래스 다이어그램을 그릴 때 구조체랑 클래스를 명시적으로 표현하고 싶을 때 &amp;lt;&amp;lt;structure&amp;gt;&amp;gt;, &amp;lt;&amp;lt;class&amp;gt;&amp;gt; 으로 표현할 수 있겠죠! &lt;a href=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=elements-uml-stereotypes&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;[IBM Doc]&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;UML stereotypes&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;In UML models, a stereotype is a model element that identifies the purpose of other model elements. UML 2.1 provides a standard set of stereotypes that you can apply to model elements. You can use a stereotype to refine the meaning of a model element. For &quot; data-og-host=&quot;www.ibm.com&quot; data-og-source-url=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=elements-uml-stereotypes&quot; data-og-url=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=elements-uml-stereotypes&quot;&gt;&lt;a href=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=elements-uml-stereotypes&quot; target=&quot;_blank&quot; data-source-url=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=elements-uml-stereotypes&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;UML stereotypes&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;In UML models, a stereotype is a model element that identifies the purpose of other model elements. UML 2.1 provides a standard set of stereotypes that you can apply to model elements. You can use a stereotype to refine the meaning of a model element. For &lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;www.ibm.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Protocol의 경우 이를 채용하는 실제 구현체가 있을텐데요! Implements Protocol은 비어있는 화살표와 점선으로 표현합니다. Swift에서 A는 B를 채용한다라고 했을 때 A가 B에게 화살표가 향하도록 비어있는 화살표와 점선으로 표현할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KiTwN/btrRjWkDYVL/HXfd1Nmw9aQE1zLy3Tklb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KiTwN/btrRjWkDYVL/HXfd1Nmw9aQE1zLy3Tklb0/img.png&quot; data-alt=&quot;Implements Protocol&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KiTwN/btrRjWkDYVL/HXfd1Nmw9aQE1zLy3Tklb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKiTwN%2FbtrRjWkDYVL%2FHXfd1Nmw9aQE1zLy3Tklb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;83&quot; height=&quot;178&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Implements Protocol&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Person(A) 객체가 고양이를 가질 수 있도록 CatOwning(B)이라는 프로토콜을 채용하고 있다면 아래와 같이 그릴 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAy5ai/btrRjV65Rqa/RaIyQcEhL70FH1E08S1UZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAy5ai/btrRjV65Rqa/RaIyQcEhL70FH1E08S1UZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAy5ai/btrRjV65Rqa/RaIyQcEhL70FH1E08S1UZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAy5ai%2FbtrRjV65Rqa%2FRaIyQcEhL70FH1E08S1UZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;173&quot; height=&quot;217&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person이 CatOwning을 Conforms to라고도 표현하죠!&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dependency&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처를 설명할때 의존관계에 대해서 정말 많이 설명하는데요! A가 B를 사용한다! 또는 A가 B를 의존한다라는 뜻으로 말할 수 있으며 열린 화살표와 점선으로 표시됩니다.&lt;br&gt;만약 A가 B를 사용(의존) 한다면 A에서 B로 화살표가 향하도록 할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;88&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nPnu5/btrRhGDBUgH/kd5dX1JxjhlJ66MsNKG6ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nPnu5/btrRhGDBUgH/kd5dX1JxjhlJ66MsNKG6ak/img.png&quot; data-alt=&quot;Uses&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nPnu5/btrRhGDBUgH/kd5dX1JxjhlJ66MsNKG6ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnPnu5%2FbtrRhGDBUgH%2Fkd5dX1JxjhlJ66MsNKG6ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;40&quot; height=&quot;129&quot; data-origin-width=&quot;88&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Uses&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 Cat의 이름은 PetOwning에 의해서 결정되기 때문에 Cat이 PetOwning을 의존하고 있습니다. 따라서 아래처럼 표현할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3cTsO/btrReF6rjBK/0zX9nSuFqtS2hBIavkFU0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3cTsO/btrReF6rjBK/0zX9nSuFqtS2hBIavkFU0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3cTsO/btrReF6rjBK/0zX9nSuFqtS2hBIavkFU0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3cTsO%2FbtrReF6rjBK%2F0zX9nSuFqtS2hBIavkFU0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;87&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 상호관계를 나타내는 Aggregation과 존재하는데 반드시 필요한 관계가 있는 구성 관계 Composition가 있습니다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tool&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 코드에 대해서 손수 UML을 그릴 수도 있지만, 이를 좀더 편하게 그릴 수 있는 도구들이 존재합니다. 다양한 도구가 있는데 저는 Plant UML을 이용했습니다. 오픈소스이고, 작성이 간편해서 사용했던 것 같네요!&lt;br&gt;&lt;a href=&quot;https://plantuml.com/ko/class-diagram&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://plantuml.com/ko/class-diagram&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;클래스 다이어그램 구문 및 기능&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;PlantUML 클래스 다이어그램 구문 : 당신은 정의 할 수 있습니다 인터페이스, 회원, 관계, 패키지, 제네릭, 음 ... 변경 글꼴과 색상도 가능하다.&quot; data-og-host=&quot;plantuml.com&quot; data-og-source-url=&quot;https://plantuml.com/ko/class-diagram&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/logBf/hyQBgXQzmr/i1Ssz7iUkMlsI8EYWHAnZK/img.png?width=613&amp;amp;height=358&amp;amp;face=0_0_613_358&quot; data-og-url=&quot;https://plantuml.com/ko/class-diagram&quot;&gt;&lt;a href=&quot;https://plantuml.com/ko/class-diagram&quot; target=&quot;_blank&quot; data-source-url=&quot;https://plantuml.com/ko/class-diagram&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/logBf/hyQBgXQzmr/i1Ssz7iUkMlsI8EYWHAnZK/img.png?width=613&amp;amp;height=358&amp;amp;face=0_0_613_358')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;클래스 다이어그램 구문 및 기능&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;PlantUML 클래스 다이어그램 구문 : 당신은 정의 할 수 있습니다 인터페이스, 회원, 관계, 패키지, 제네릭, 음 ... 변경 글꼴과 색상도 가능하다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;plantuml.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Conclusion&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그간 정확하기 않은 클래스 다이어그램을 좀 그렸던거 같은데, 공통 언어이다 보니 바르게 작성해서 생각한 바를 잘 전달할 수 있도록 원칙을 지켜서 작성해야 할 것 같습니다.  &lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference.&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Class_diagram&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://en.wikipedia.org/wiki/Class_diagram&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.ibm.com/docs/en/rsm/7.5.0?topic=models-uml-diagrams&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.ibm.com/docs/en/rsm/7.5.0?topic=models-uml-diagrams&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;br&gt;raywenderlich - Design Patterns by Tutorials&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/75</guid>
      <comments>https://doing-programming.tistory.com/entry/UMLUnified-Modeling-Language-feat-Class-Diaphragm#entry75comment</comments>
      <pubDate>Tue, 15 Nov 2022 21:48:17 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI - SwiftUI 환경에서 ViewController Life Cycle 사용하기</title>
      <link>https://doing-programming.tistory.com/entry/SwiftUI-SwiftUI-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-ViewController-Life-Cycle-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI 프레임워크로 넘어오면서, 가장 불편했던 것 중 하나는 기존 UIViewController가 제공했던 ViewController Life Cycle이 SwiftUI View에는 없다는 것입니다.&lt;br&gt;View Modifier에도 onAppear와 onDisappear가 존재하지만 기존 LifeCycle을 대체하기에는 부정확한 동작이 많았죠&lt;br&gt;&lt;br&gt;특히나 didAppear와 didDisappear의 빈자리가 비교적 크게 느껴집니다.&lt;br&gt;&lt;br&gt;SwiftUI를 프로젝트에 적용하는 방법중에는 UIKit을 기반으로 HostingController를 활용하는 법과&lt;br&gt;순수 SwiftUI 베이스로 시작하는 방법이 있는데요!&lt;br&gt;&lt;br&gt;이번에 설명드릴 방법은 SwiftUI 베이스에서 UIViewController의 Life Cycle 사용하는 방법입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Concept&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8DYs/btrPRcYy6nG/5MxZMQaBE09lB7SV0tUhA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8DYs/btrPRcYy6nG/5MxZMQaBE09lB7SV0tUhA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8DYs/btrPRcYy6nG/5MxZMQaBE09lB7SV0tUhA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8DYs%2FbtrPRcYy6nG%2F5MxZMQaBE09lB7SV0tUhA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1534&quot; height=&quot;1016&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;우선 컨셉부터 보여드리면, 위 그림과 같이 View가 ViewModel에 의존해서 Presentation Logic을 처리합니다. 이때 ViewController도 동시에 존재하는데요, ViewController 또한 View가 가진 동일한 ViewModel에 의존합니다. 그리고 ViewController에서는 LifeCycle을 ViewModel로 보내면, ViewModel은 그에 따른 동작을 수행해서 상태를 View로 방출하면 됩니다.&lt;br&gt;&lt;br&gt;UIKit환경과는 달리 View Controller가 많은 일을 담당하지 않습니다. 단지 Life Cycle 신호를 ViewModel로 보낼 뿐이죠&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Implemenation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현은 어떻게 하면 될까요?&lt;br&gt;우선 &lt;b&gt;LifeCycleController&lt;/b&gt;를 선언하겠습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;enum LifeCycle {
&amp;nbsp;&amp;nbsp;case viewDidLoad
&amp;nbsp;&amp;nbsp;case viewWillAppaer
&amp;nbsp;&amp;nbsp;case viewDidAppear
&amp;nbsp;&amp;nbsp;case viewWillDisappear
&amp;nbsp;&amp;nbsp;case viewDidDisappear
}

protocol LifeCycleHandlerProtocol: AnyObject {
&amp;nbsp;&amp;nbsp;var lifeCycle: PassthroughSubject&amp;lt;LifeCycle, Never&amp;gt; { get }
}

final class LifeCycleController: NiblessViewController {
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private weak var handler: LifeCycleHandlerProtocol?
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;init(handler: LifeCycleHandlerProtocol) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.handler = handler
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.init(nibName: nil, bundle: nil)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;override func viewDidLoad() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewDidLoad()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler?.lifeCycle.send(.viewDidLoad)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;override func viewWillAppear(_ animated: Bool) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewWillAppear(animated)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler?.lifeCycle.send(.viewWillAppaer)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;override func viewDidAppear(_ animated: Bool) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewDidAppear(animated)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler?.lifeCycle.send(.viewDidAppear)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;override func viewWillDisappear(_ animated: Bool) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewWillDisappear(animated)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler?.lifeCycle.send(.viewWillDisappear)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;override func viewDidDisappear(_ animated: Bool) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.viewDidDisappear(animated)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;handler?.lifeCycle.send(.viewDidDisappear)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;deinit {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(self, #function)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;struct Representable: UIViewControllerRepresentable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;typealias UIViewControllerType = LifeCycleController
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private let handler: LifeCycleHandlerProtocol
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init(handler: LifeCycleHandlerProtocol) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.handler = handler
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;func makeUIViewController(context: Context) -&amp;gt; LifeCycleController {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LifeCycleController(handler: handler)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;func updateUIViewController(_ uiViewController: LifeCycleController, context: Context) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LifeCycleController의 역할은 Handler에게 액션을 보내주기만 하면 되는 형태입니다.&lt;br&gt;지금은 Combine을 써서 넘겨주고 있는데 어떤 방식이든 크게 상관없습니다.&lt;br&gt;&lt;br&gt;그 다음은 ViewModel 입니다. 앞에서 컨셉에서 설명드렸듯 ViewModel은 LifeCycle을 핸들링 하면 됩니다.&lt;br&gt;기존 ViewModel이 &lt;b&gt;LifeCycleHandlerProtocol&lt;/b&gt;을 채용합니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;extension ContentView {
&amp;nbsp;&amp;nbsp;final class ViewModel: ObservableObject, LifeCycleHandlerProtocol {

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private var cancellables: Set&amp;lt;AnyCancellable&amp;gt; = []
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let lifeCycle = PassthroughSubject&amp;lt;LifeCycle, Never&amp;gt;()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bind()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private func bind() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lifeCycle
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.sink(receiveValue: { [weak self] lifeCylce in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self?.lifeCycleHandling(lifeCylce)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.store(in: &amp;amp;cancellables)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private func lifeCycleHandling(_ lifeCycle: LifeCycle) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(lifeCycle)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch lifeCycle {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .viewDidLoad:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .viewWillAppaer:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .viewDidAppear:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .viewWillDisappear:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .viewDidDisappear:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;deinit {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(self, #function)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로요, 지금은 블럭들이 비어있지만 실제로 사용할 때 알맞는 코드를 넣어주면 되겠죠?&lt;br&gt;그리고 이를 화면에 배치해야 비로서 ViewController의 LifeCycle 이 시작되는데요, View에 어떻게 배치하면 좋을까요?&lt;br&gt;&lt;br&gt;Modifier를 이용하겠습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct LifeCycleModifier: ViewModifier {
&amp;nbsp;&amp;nbsp;let handler: LifeCycleHandlerProtocol
&amp;nbsp;&amp;nbsp;func body(content: Content) -&amp;gt; some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.overlay(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LifeCycleController.Representable(handler: handler)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	.frame(width: .zero, height, .zero)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;}
}

extension View {
&amp;nbsp;&amp;nbsp;func lifeCycle(handler: LifeCycleHandlerProtocol) -&amp;gt; some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier(LifeCycleModifier(handler: handler))
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Modifier로 content에 overlay로 작성하게 되면 화면 배치에 영향도 없고, 아무것도 보이지 않는 Empty View가 완성됩니다.&lt;br&gt;이제 View Extension으로 lifeCycle을 호출한 다음 handler만 전달해주면 되죠!&lt;br&gt;&lt;br&gt;View Content에서는 아래처럼 선언하면 이제 완성됩니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct ContentView: View {
&amp;nbsp;&amp;nbsp;@ObservedObject private var viewModel: ViewModel
&amp;nbsp;&amp;nbsp;var body: some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VStack(spacing: 0) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Image(systemName: &quot;globe&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.imageScale(.large)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.foregroundColor(.accentColor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;Hello, world!&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.padding()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.lifeCycle(handler: viewModel)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;init(viewModel: ViewModel) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.viewModel = viewModel
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 SwiftUI 베이스 보다는 UIKit 베이스로 SwiftUI를 더 많이 작성하긴 하는데&lt;br&gt;순수하게 해당 뷰의 ViewLifeCycle만 받고, 재사용 가능한 코드로 구성할때는 이 방법이 깔끔하고 좋은 것 같습니다.&lt;br&gt;애플에서는 onAppear왜 onDisappear 그리고 ObservableObject가 뷰의 생명주기를 컨트롤한다고 얘기하지만, 정말 단순 명료하게 VC에서 제공하던 LifeCycle이 필요할 때가 정말 많아서 생각하게 된 방법입니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sample Proejct.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/SwiftUIViewLifeCycle&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/urijan44/SwiftUIViewLifeCycle&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/74</guid>
      <comments>https://doing-programming.tistory.com/entry/SwiftUI-SwiftUI-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-ViewController-Life-Cycle-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0#entry74comment</comments>
      <pubDate>Wed, 26 Oct 2022 02:46:35 +0900</pubDate>
    </item>
    <item>
      <title>Lukas Graham - 7 years (lyrics)</title>
      <link>https://doing-programming.tistory.com/entry/Lukas-Graham-7-years-lyrics</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;Lukas Graham - 7 Years [Official Music Video]&quot; href=&quot;http://&amp;lt;iframe width=&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/LHCob76kigA&quot; width=&quot;723&quot; height=&quot;407&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://%3Ciframe%20width%3D/&quot;&gt;&amp;nbsp; &lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was seven years old my momma told me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go make yourself some friends or you'll be lonely&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was seven years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;It was a big big world, but we thought we were bigger&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pushing each other to the limits, we were learning quicker&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;By eleven smoking herb and drinking burning liquor&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Never rich so we were out to make that steady figure&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was eleven years old my daddy told me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go get yourself a wife or you'll be lonely&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was eleven years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I always had that dream like my daddy before me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So I started writing songs, I started writing stories&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;someting about that glory just always seemed to bore me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cause only those I really love will ever really know me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was twenty years old, my story got told&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Before the morning sun, when life was lonely&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was twenty years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I only see my goals, I don't believe in failure&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cause I know the smallest voices, they can make it major&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I got my boys with me at least those in favor&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;and if we don't meet before I leave, I hope I'll see you later&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was twenty years old my story got told&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I was writing about everthing, I saw before me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was twenty years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon we'll be thirty years old, our songs have been sold&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We've traveled around the world and we're still roaming&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon we'll be thirty years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I'm still learning about life, my woman brought children for me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;So I can sing them all my songs and I can tell them stories&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Most of my boys are with me, some are still out seeking glory&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;And some I had to leave behind, my brother, I',m still sorry&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon I'll be sixty years old my daddy got sixty-one&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Remember life and then your life becomes a better one&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I made a man so happy when I wrote a letter once&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I hope my children come and visit, once or twice a month&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon I'll be sixty years old, will I think the world is cold&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Or will I have a lot of children who can warm me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon I'll be sixty years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon I'll be sixty years old, will I think the world is cold&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Or will I have a lot of children who can warm me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Soon I'll be sixty years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was seven years old my momma told me&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go make yourself some freinds or you'll be lonely&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was seven years old&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Once I was seven years old&lt;/p&gt;</description>
      <category>English</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/73</guid>
      <comments>https://doing-programming.tistory.com/entry/Lukas-Graham-7-years-lyrics#entry73comment</comments>
      <pubDate>Mon, 10 Oct 2022 23:44:29 +0900</pubDate>
    </item>
    <item>
      <title>Suzanne Vega  - Luka (lyrics)</title>
      <link>https://doing-programming.tistory.com/entry/Suzanne-Vega-Luka-lyrics</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;Suzanne Vega - Luka (Official Video)&quot; href=&quot;http://&amp;lt;iframe width=&quot;&gt;&lt;br /&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/VZt7J0iaUD0&quot; width=&quot;800&quot; height=&quot;450&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;br /&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;My name is Luka&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I live on the second floor&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I live upstairs from you&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Yes, I think you've seen me before&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;If you hear someting late at night&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Some kind of trouble, some kind of fight&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I think it's cause I'm clumsy&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I try not to talk too loud&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Maybe it's because I'm crazy&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I try not to act too proud&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;They only hit until you cry&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;After that yout don't ask why&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;You just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Yes, I think I'm okay&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I walked into the door again&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;If you ask that's what I'll say&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;It's not your business anywhere&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I guess i'd like to be alone&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;With notting broken, nothing thrown&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me how I am&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me how I am&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me how I am&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;My nams' is Luka&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I live on the second floor&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;I live upstairs on you&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Yes, I think you've seen me before&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;If your hear someting late at night&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Some kind of trouble, some kind of fight&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Just don't ask me what it was&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;And they only hit until you cry&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;After that you don't ask why&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;You just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;You just don't argue anymore&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>English</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/72</guid>
      <comments>https://doing-programming.tistory.com/entry/Suzanne-Vega-Luka-lyrics#entry72comment</comments>
      <pubDate>Thu, 6 Oct 2022 23:42:46 +0900</pubDate>
    </item>
    <item>
      <title>Architecture - Single Responsibility Principle(SRP)</title>
      <link>https://doing-programming.tistory.com/entry/Architecture-Single-Responsibility-PrincipleSRP</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRP는 SOLID라 불리는 아키텍처 원칙 중 첫번째 글자에 해당하는 원칙입니다.&lt;br&gt;용어는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-responsibility_principle#:~:text=The%20term%20was,reason%20for%20change.%22&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;로버트 C. 마틴이 2003년 저서 &lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Single-responsibility_principle#:~:text=The%20term%20was,reason%20for%20change.%22&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;i&gt;Agile Software Development, Principles, Patterns, and Practices&lt;/i&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Single-responsibility_principle#:~:text=The%20term%20was,reason%20for%20change.%22&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;에 소개한 개념으로 OOD의 원리&lt;/span&gt;&lt;/a&gt; 라는 기사에서 소개했다고 합니다. 도서 클린 아키텍처(로버트 C. 마틴 저)에 따르면 일부 정의된 아키텍처 원칙 중 몇몇을 마이클 페더스라는 사람이 그것들을 재배열해서 나오게 된 이름이라고 하네요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Single Responsibility Principle&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SRP는 직역하면, &lt;b&gt;단일 책임 원칙 &lt;/b&gt;이라고 해석할 수 있는데, SOLID 원칙 중 가장 의미가 제대로 전달되지 못한 원칙 중 하나라고 합니다. 그 이유는 이름 때문이라고...(당신이 지은거 아님..?)&lt;br&gt;여기서 말하는 SRP의 설명은 &lt;b&gt;단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다. &lt;/b&gt;라고 설명을 하고 있습니다. 동시에 &lt;b&gt;모든 모듈이 하나의 일만 해야 한다. &lt;/b&gt;라는 것은 잘못된 전달이라고 합니다. 그렇게 알고있는 것은 함수는 단 하나의 일만 해야 한다, &lt;b&gt;(커다란 함수를 작은 함수들로 리팩터링 하는 저수준 원칙) &lt;/b&gt;이며 이는 SOLID 원칙, 즉 SRP가 아니라고 설명합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZwquS/btrL5QXtgN6/JhsUsj4FrIZBHspsKPsW5K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZwquS/btrL5QXtgN6/JhsUsj4FrIZBHspsKPsW5K/img.jpg&quot; data-alt=&quot; 그러게 이름을 왜 그렇게 지어서.. &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZwquS/btrL5QXtgN6/JhsUsj4FrIZBHspsKPsW5K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZwquS%2FbtrL5QXtgN6%2FJhsUsj4FrIZBHspsKPsW5K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 그러게 이름을 왜 그렇게 지어서.. &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 SRP 의 수준은 &quot;&lt;b&gt;모듈&lt;/b&gt;&quot; 로 보입니다. 모듈이란 가장 단순한 정의로는 소스파일 그 자체 라고 볼 수도 있습니다. (아닌 경우도 있다고 함?) 좁게는 클래스, 넓게는 하나의 응집된 컴포넌트 그 자체로 볼 수 있을 것 같습니다. 절대 함수와 같은 저수준 원칙을 얘기하는 것이 아닙니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 뭔데...?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 이해하기로, 책에서는 &lt;b&gt;Actor&lt;/b&gt; 라는 개념을 중요하게 얘기합니다. 모듈은 하나의 &lt;b&gt;Actor&lt;/b&gt;에 의해서만 변경될 수 있다는 얘기입니다. 이게 무슨 말일까요?&lt;br&gt; &lt;br&gt;저는 실제로 경험한 것 중 가장 적합한 예시는 바로 &lt;b&gt;병합 &lt;/b&gt;이라고 생각합니다.&lt;br&gt;프로그램을 만들다 보면, 우리는 중복 코드를 발견하고 이를 &lt;b&gt;병합 &lt;/b&gt;하려는 행동을 보입니다. 리팩터링 중 가장 흔하게 할 수 있는 것 중 하나죠?&lt;br&gt;그런데 병합을 할 때 가끔 우리는 잘못된 &lt;b&gt;병합&lt;/b&gt;을 하는 것 같습니다. 잘못된 병합을 시도하고, 중복코드를 줄이고 뿌듯해 하죠! SRP 위배는 바로 여기서 발생합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PwEqJ/btrL3RbZTnW/SKu27t4N4KYYswNVCFmRKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PwEqJ/btrL3RbZTnW/SKu27t4N4KYYswNVCFmRKK/img.png&quot; data-alt=&quot; 대체 왜 합쳐놨냐고!!!! &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PwEqJ/btrL3RbZTnW/SKu27t4N4KYYswNVCFmRKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPwEqJ%2FbtrL3RbZTnW%2FSKu27t4N4KYYswNVCFmRKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;259&quot; height=&quot;194&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 대체 왜 합쳐놨냐고!!!! &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;네, 코드를 수정하다가 무언가 수정했는데, 그로 인해서 다른 곳에서 기능이 변하는 경우죠, 이걸 대체 왜 합쳐놨지? 라고 생각이 들때가 바로 SRP를 위배한 현장을 목격한 것입니다.&lt;br&gt; &lt;br&gt;이해하기 편하도록 예시 케이스를 하나 만들어 봤습니다. 실제 사건을 모티브로 각색해 보았어요!&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgZmrO/btrL2mXXdli/9rkinEMcQlGW3LG0iBKqoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgZmrO/btrL2mXXdli/9rkinEMcQlGW3LG0iBKqoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgZmrO/btrL2mXXdli/9rkinEMcQlGW3LG0iBKqoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgZmrO%2FbtrL2mXXdli%2F9rkinEMcQlGW3LG0iBKqoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;216&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;커뮤니티 어플리케이션이 있습니다.&lt;br&gt;이 커뮤니티는 글을 쓰고 포인트를 받을 수 있는데, 회원 구분에 따라 포인트가 차등 지급 되고 있습니다.&lt;br&gt;인증회원의 경우 기본 포인트5에 활동기간 및 레벨 등의 가중치를 얻고&lt;br&gt;미인증 회원의 경우 기본 포인트만5 만 지급받는 형식입니다.&lt;br&gt;개발 초창기에는 그것 말고는 다른 케이스가 없어서 &lt;b&gt;글쓰기 &lt;/b&gt;라는 하나의 모델에 인증/미인증 회원 두 액터가 모두 의존하고 있는 상황입니다.&lt;br&gt; &lt;br&gt;여기서 포인트지급 정책이 바뀌기로 했습니다. 미인증 회원의 글쓰기 포인트가 너무 짜서, 기본 포인트를 증가 시키기로 했습니다.&lt;br&gt;그런데 여기서 한가지 문제가 발생하죠? 기본 포인트를 수정하게 되면 인증 회원의 포인트까지 증가하게 됩니다.&lt;br&gt;코드로 보면 이렇습니다.&lt;br&gt; &lt;br&gt;유저 클래스&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;final class User {
&amp;nbsp;&amp;nbsp;private(set) var certification = false
&amp;nbsp;&amp;nbsp;private(set) var signUpDate = Date()
&amp;nbsp;&amp;nbsp;private(set) var level = 0
&amp;nbsp;&amp;nbsp;private(set) var point = 0
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func increasePoint(point: Int) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.point += point
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판 모델&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct WriteBoard {
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private let pointCalculator = PointCalculator()
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func write(user: User) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user.increasePoint(point: pointCalculator.point(user: user))
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인트 지급 정책 모델&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct PointCalculator {
&amp;nbsp;&amp;nbsp;private let basePoint = 5
&amp;nbsp;&amp;nbsp;private let levelPointWeight = 1.25
&amp;nbsp;&amp;nbsp;private let signUpDatePointWeight = 2
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func point(user: User) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if user.certification {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return basePoint * (signUpDatePoint(signUpDate: user.signUpDate) + levelPoint(level: user.level))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return basePoint
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private func signUpDatePoint(signUpDate: Date) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;signUpDatePointWeight * Int(Date().timeIntervalSinceNow - signUpDate.timeIntervalSinceNow)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private func levelPoint(level: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Int(levelPointWeight * Double(level))
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 문제가 되는 부분은 basePoint가 미인증 유저와, 인증 유저 2개의 액터에 걸쳐 있다는 것입니다. 2개의 액터 즉 SRP 위반입니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하는 방법은 다양한데, POP를 통해서 한번 해결해보도록 하겠습니다.&lt;br&gt;기존 포인트 지급 정책을 기본 포인트, 추가 포인트 정책 프로토콜로 쪼개서 가집니다.&lt;br&gt;그리고 포인트를 지급할 수 있는 인터페이스를 추가합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;1126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY2NDx/btrLZx6JPqL/d5QHkGZGsEX9ElE2B8b7eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY2NDx/btrLZx6JPqL/d5QHkGZGsEX9ElE2B8b7eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY2NDx/btrLZx6JPqL/d5QHkGZGsEX9ElE2B8b7eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY2NDx%2FbtrLZx6JPqL%2Fd5QHkGZGsEX9ElE2B8b7eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;296&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;1126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;즉 기존 포인트 계산기는 새로 생긴 프로토콜에 의존합니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;protocol BasePointPolicy {
&amp;nbsp;&amp;nbsp;var basePoint: Int { get }
}

protocol WeightPointPolicy {
&amp;nbsp;&amp;nbsp;var levelPointWeight: Double { get }
&amp;nbsp;&amp;nbsp;var signUpDatePointWeight: Double { get }
}

protocol PointGarentInterface {
&amp;nbsp;&amp;nbsp;func point(user: User) -&amp;gt; Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 3개를 조합해서 2개의 포인트 계산기 구현을 만들 수 있습니다.&lt;br&gt; &lt;br&gt;미인증회원 포인트 지급 정책&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct InvalidateUserPointCalculator: BasePointPolicy, PointGarentInterface {
&amp;nbsp;&amp;nbsp;let basePoint = 10
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func point(user: User) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;basePoint
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증회원 포인트 지급 정책&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct CertifiedUserPointCalculator: BasePointPolicy, WeightPointPolicy, PointGarentInterface {
&amp;nbsp;&amp;nbsp;let basePoint = 5
&amp;nbsp;&amp;nbsp;let levelPointWeight = 1.25
&amp;nbsp;&amp;nbsp;let signUpDatePointWeight: Double = 2
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func point(user: User) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;basePoint * (signUpDatePoint(signUpDate: user.signUpDate) + levelPoint(level: user.level))
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private func signUpDatePoint(signUpDate: Date) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Int(signUpDatePointWeight * Date().timeIntervalSinceNow - signUpDate.timeIntervalSinceNow)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;private func levelPoint(level: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Int(levelPointWeight * Double(level))
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;그래서 게시판이 하나의 포인트 계산기를 의존하지 않고 포인트 계산기 인터페이스를 의존합니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct WriteBoard {
&amp;nbsp;&amp;nbsp;private let pointCalculator: PointGarentInterface
&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;func write(user: User) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user.increasePoint(point: pointCalculator.point(user: user))
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 인증회원은 CertifiedUserPointCalculator에 미인증회원은 InvalidateUserPointCalculator에 각각 1:1로 의존하게 됩니다. 다르게 말하면 각 포인트 계산기는 자신이 담당하는 엑터에 의해서만 수정될 수 있습니다. 프로토콜에 의해서 포인트 지급 정책은 수정될일이 없는거죠!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SRP는 클래스의 기능, 함수의 기능이 하나이어야 된다는 뜻이 아니라 &lt;b&gt;변경의 이유가 하나 이어야 한다. &lt;/b&gt;즉 단 하나의 액터만 상대 해야 한다는 뜻입니다. &lt;br&gt;- SRP 위반은 주로 병합에서 나타나는 것으로 볼 수 있습니다. 무언가 &lt;b&gt;병합&lt;/b&gt;해야 한다면 혹시 다른 액터를 가진 것을 &lt;b&gt;병합&lt;/b&gt;하고 있지는 않는지 고민해볼 필요가 있을 것 같습니다.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로버트 C. 마틴: 클린 아키텍처&lt;br&gt; &lt;/p&gt;</description>
      <category>General Dev</category>
      <category>cleanarchitecture</category>
      <category>Solid</category>
      <category>SRP</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/70</guid>
      <comments>https://doing-programming.tistory.com/entry/Architecture-Single-Responsibility-PrincipleSRP#entry70comment</comments>
      <pubDate>Wed, 14 Sep 2022 01:33:41 +0900</pubDate>
    </item>
    <item>
      <title>Swift - 지금 보는 뷰 이름 알기</title>
      <link>https://doing-programming.tistory.com/entry/iOS-%EC%A7%80%EA%B8%88-%EB%B3%B4%EB%8A%94-%EB%B7%B0-%EC%9D%B4%EB%A6%84-%EC%95%8C%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Introduce&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 업무로 코딩하다 보면 정말 많이 화가날 때가 있는데, 뷰가 너무너무너무 많아서 도대체 내가 지금 보고 있는 뷰의 이름이 뭔지 모를때가 있다. 비슷하게 생긴 뷰도 워낙 많고 하니, 단순히 피그마 디자인만 보고 뷰의 이름이 추측이 안될때가 있다. 기록이나 정의해 둔 문서가 따로 있다면 좋겠지만, 아마 대부분의 회사는 없을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보통 쓰는 방법이, 뷰 계층 디버거를 이용해서 이름을 알아 내는 방법인데, 이게 또 프로젝트가 너무 커서 그런가, 켜지는 데 시간도 오래 걸리고, 심지어 어떨 때는 계층 디버거가 켜지지도 않는다. 이럴때 진짜 매우 섬뜩하게 화가난다. 코딩할 시간도 부족한데, 뷰 이름이나 찾고 있어야 한다는게...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떤 방법을 이용하면 좀더 쉽고 빠르게 찾을 수 있을까 하다가, 배운적은 있지만 한번도 실용적으로 사용해본 적은 없는 &lt;b&gt;Method Swizzling&lt;/b&gt;이 생각났다. 개념을 모르시는 분들께 간단히 설명하자면, 런타임 중에 실행되는 메서드를 바꿔치기 하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 모든 &lt;b&gt;UIViewController&lt;/b&gt;는 로드될 때 필연적으로 &lt;b&gt;viewDidLoad()&lt;/b&gt; 를 호출하게 되는데, 이걸 내가 만든 별도의 메서드가 호출되도록 할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uMtl1/btrLiBne1bD/CdN0kSxXgO9KOBVZOodia1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uMtl1/btrLiBne1bD/CdN0kSxXgO9KOBVZOodia1/img.jpg&quot; data-alt=&quot;놀라워라!!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uMtl1/btrLiBne1bD/CdN0kSxXgO9KOBVZOodia1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuMtl1%2FbtrLiBne1bD%2FCdN0kSxXgO9KOBVZOodia1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;159&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;놀라워라!!!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;딱기다려&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 하느냐?&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension UIViewController {

  @objc func whatTheThisView() {
    print(&quot;What The This View: \(self)&quot;)
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 @objc 딱지가 붙은 메서드를 하나 만들 건데, UIViewController에서 이용할 예정이므로 extension으로 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 지금 viewDidLoad와 교체할 메서드 이므로, 내용은 취향껏 채우자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 메서드를 스위즐링을 수행할 메서드를 만들 건데, 이 기능을 AppDelegate 시점에 DEBUG 일 때만 수행되도록 하고 싶다. 그래서 UIViewController의 타입 메서드로 만들꺼다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;static func startWhatTheThisView(mirror: Bool = false) {
    let target = #selector(viewDidLoad)
    let source = mirror ? #selector(whatTheThisViewMirrir) : #selector(whatTheThisView)

    guard
      let targetMethod = class_getInstanceMethod(UIViewController.self, target),
      let sourceMethod = class_getInstanceMethod(UIViewController.self, source)
    else { fatalError(&quot;What The This View Start Failure, Can't Find Method&quot;) }

    method_exchangeImplementations(targetMethod, sourceMethod)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;addTarget 할때처럼 selector를 이용해 메서드를 가리켜 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 class_getInstanceMetohd 를 통해 뭔갈 가져오면 되는데 신기하게도 &lt;b&gt;Method&lt;/b&gt; 라는 타입이다. 신기해서 문서를 보니 &lt;b&gt;OpaquePointer&lt;/b&gt; 라는 구조체의 별칭이었다. OpaquePointer는 C Pointer의 래핑이라고 문서에 써져있더라.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rJk7W/btrLh8yXFCQ/QVcXPkn2Nri2qrVRfkFmX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rJk7W/btrLh8yXFCQ/QVcXPkn2Nri2qrVRfkFmX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rJk7W/btrLh8yXFCQ/QVcXPkn2Nri2qrVRfkFmX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrJk7W%2FbtrLh8yXFCQ%2FQVcXPkn2Nri2qrVRfkFmX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1540&quot; height=&quot;658&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 그렇단 말은 Method의 참조 포인터를 가져온 다는 뜻! 즉 런타임 중에 &lt;b&gt;viewDidLoad&lt;/b&gt; 가 올라가 있는 주소가 있고, 내가 만든 메서드 주소가 있는데, viewDidLoad로 향하게 안하고 내가 만든 주소로 향하게 해서 메서드 스위즐링 이란 것을 수행하는 것으로 추측된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 수행 하는 코드는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;method_exchangeImplementations(targetMethod, sourceMethod)&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름만 봐도 메서드 바꿔줄 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AppDelegate에서 방금 만든 타입 메서드를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1662224406957&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#if DEBUG
    UIViewController.startWhatTheThisView(mirror: true)
#endif&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 앱을 실행시켜서 화면을 돌아다니면 내가 만든 메서드가 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 같은 경우는 ViewController가 가지고 있는 (가증스러운) 멤버 까지 모두 호출 시키도록 했다. 가끔 화면에 매치된 뷰 들이랑, 뷰의 이름이 뭔지 모를 경우가 많기 때문이다,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWcjoe/btrLmyDcl6Y/CUs8PfaR21jQCQlgZAqvw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWcjoe/btrLmyDcl6Y/CUs8PfaR21jQCQlgZAqvw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWcjoe/btrLmyDcl6Y/CUs8PfaR21jQCQlgZAqvw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWcjoe%2FbtrLmyDcl6Y%2FCUs8PfaR21jQCQlgZAqvw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1478&quot; height=&quot;338&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;933&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zydFO/btrLhXxuEej/YWlKyGGzJuEJMyUYn0wDg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zydFO/btrLhXxuEej/YWlKyGGzJuEJMyUYn0wDg0/img.jpg&quot; data-alt=&quot;니네 이제 다 뒤졌다?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zydFO/btrLhXxuEej/YWlKyGGzJuEJMyUYn0wDg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzydFO%2FbtrLhXxuEej%2FYWlKyGGzJuEJMyUYn0wDg0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;252&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;933&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;니네 이제 다 뒤졌다?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 회사에 오래 근무하신 분들은 이미 코드들을 속속 알고 계셔서 금방 찾으시겠지만, 회사를 옮길 때 마다 이 뷰가 무슨 뷰인가 찾는 시간이 너무 아깝다고 생각했다. 그렇다고 뭘 특별히 하기에는 좀 그렇고, 간단한 코드 한 페이지 작성해서 실행 시킬 수 있으니 훨씬 작업시간이 빨라지는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(로그 창이 지옥으로 변하는 건 어쩔 수 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>IOS</category>
      <category>MethodSwilzzling</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/69</guid>
      <comments>https://doing-programming.tistory.com/entry/iOS-%EC%A7%80%EA%B8%88-%EB%B3%B4%EB%8A%94-%EB%B7%B0-%EC%9D%B4%EB%A6%84-%EC%95%8C%EA%B8%B0#entry69comment</comments>
      <pubDate>Sun, 4 Sep 2022 02:04:26 +0900</pubDate>
    </item>
    <item>
      <title>Swift - Result Type 을 반환값으로 써보자</title>
      <link>https://doing-programming.tistory.com/entry/Swift-Result-Type-%EC%9D%84-%EB%B0%98%ED%99%98%EA%B0%92%EC%9C%BC%EB%A1%9C-%EC%8D%A8%EB%B3%B4%EC%9E%90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Introduce&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Result Type은 Swift 5에서 도입되어 SSL(Swift Standart Library)에 포함된 문법입니다. 동작의 수행 결과를 Success 인지 Failure인지 구분해서 나은 가독성과 코드 분기를 제공해줍니다.&lt;br&gt;(개인 의견: 테스트 코드를 작성해본 사람은 알겠지만, try-catch에서 catch 아래는 코드 커버리지에 포함이 안되어서 미묘하게 킹받는데, 그런 사소한 부분을 해결해 주기도 한다.)&lt;br&gt;본 글은 Result Type을 반환값으로 쓰는 방법으로 Result Type 자체에 대한 내용은 다루지 않겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 Return 값으로 Result Type을 쓰고싶었을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Layer를 작성하다보면, 해당 비즈니스 모델에는 iOS 버전이나 라이브러리를 의존하고 싶지 않은 경우가 있습니다. 기껏 Layer를 구분했는데, 라이브러리를 포함시키게 되면 유지보수나 빌드 속도에 악영향을 미칠 가능성이 높습니다.&lt;br&gt;비즈니스 모델이 iOS 버전이나 라이브러리에 의존하는 것 만큼 안좋은 선택은 없을 것 같습니다.&lt;br&gt;따라서 최소한의 의존성을 가지도록 구성하고 싶습니다.&lt;br&gt;이 얘기는 왜 하냐면, iOS13 타겟 이상이라면 사실 Combine의 Future를 사용할 수 있습니다.&lt;br&gt;또는 라이브러리에 의존하자면 PromiseKit이나 FutureKit 또는 RxSwift의 Single을 이용할 수 있겠죠.&lt;br&gt;(2022년에 네이티브한 Future-Promise 문법이 Swift에 없다는 것 자체가 좀 불만 스럽네요)&lt;br&gt;하지만 앞서 얘기했 듯 Data Layer는 아주 가볍고 민첩해야 합니다. 의존성도 낮아야 하고 말입니다.&lt;br&gt;그래서 Swift에는 이미 Result Type이 도입되었고 이를 이용해 Combine의 Future처럼 쓰고 싶다는 생각을 했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그냥 파라미터에서 escaping closure로 쓰면 안될까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 도서에서 소개된 대로 Result Type은 escaping closure를 활용해서 잘 사용할 수 있습니다. 아래처럼요&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;func fetchImage(completionHandler: @escaping (Result&amp;lt;Data, Error&amp;gt;) -&amp;gt; Void) {
  urlSession.dataTask(with: URL(string: &quot;https://image.tmdb.org/t/p/w500/kAVRgw7GgK1CfYEJq8ME6EvRIgU.jpg&quot;)!) { data, response, error in
    if let error = error {
      completionHandler(.failure(error))
      return
    }

    if let data = data {
      completionHandler(.success(data))
      return
    }
  }
  .resume()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession과 같은 비동기 기능에서 Result Type을 꺼내려면 escaping 을 사용하는 수 밖에는 없습니다.&lt;br&gt;이는 Swift 도서에 소개된 방법 그대로 이지만, 조금 아쉽습니다.&lt;br&gt;&lt;b&gt;completionHandler&lt;/b&gt; 자체를 매번 파라미터로 전달하는 것도 그렇습니다. 이게 생각보다 귀찮거든요 수정에도 그렇고 결과를 받을 때도 조금 불편합니다.&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;configuration.fetchImage { result in
  switch result {
    case .success(let success):
      ()
    case .failure(let failure):
      ()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetchImage를 호출하고 바로 수행 코드를 작성해줘야 합니다. 물론 이게 편할때도 많습니다. 하지만 그렇게 쓰고 싶지 않을때도 존재합니다.&lt;br&gt;결론은 그렇게 쓰고 싶지 않을 때도 있는데 자유가 없다 정도입니다. (웃음)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서 Return으로 Result Type을 쓰면 어떻게 생겼는데?&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;configuration
  .fetchImage(name: &quot;영화사진&quot;)
  .map { data in
    UIImage(data: data)
  }
  .sink { [weak self] result in
    switch result {
      case .success(let image):
        Task {
          self?.imageView.image = image
        }
      case .failure(let error):
        print(&quot;ERROR:\(error)&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요렇게 쓰고 싶습니다. (Single이랑 Combine.Future랑 똑같이 생겼움)&lt;br&gt;지금은 이렇게 썼지만&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let fetchResult = configuration.fetchImage(name: &quot;영화사진&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;completionHandler를 썼을 때와 다르게 바로 호출하지 않고 이렇게 저장해둘 수 있습니다. 필요한 시점에 sink를 호출해야지 비로서 네트워크 요청을 하게 됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 했냐가 중요할 것 같습니다.&lt;br&gt;사실 이게 맞게 구현한 건가 잘 모르겠습니다. 이미 promise kit 이나 future kit등 좋은 예제가 있지만 우선 구현은 스스로의 힘으로 해본 뒤 라이브러리를 참고하고 싶었습니다. (밑밥 깔기)&lt;br&gt;우선 코드를 아래처럼 사용할 수 있어야 합니다.&lt;/p&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;func fetchImage(name: String) -&amp;gt; Future&amp;lt;Data, Error&amp;gt; {
  return Future&amp;lt;Data, Error&amp;gt; { [unowned self] promise in
    let url = imageService.makeURL(name: name)
    urlSession.dataTask(with: URL(url: url) { data, response, error in
      if let error = error {
        promise(.failure(error))
        return
      }

      if let data = data {
        promise(.success(data))
        return
      }
    }
    .resume()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 이런 느낌인데, 여기서 눈 여겨 보셔야 할 부분은&lt;br&gt;1. 파라미터에 escaping closure 대신 아니라 return 이 있습니다.&lt;br&gt;2. URLSession completion handler 내부에서 그 결과를 리턴값으로 전달해야 합니다.&lt;br&gt;Future&amp;lt;Data, Error&amp;gt; 뒤에 클로져로 promise가 붙습니다. promise에 succee와 failure를 전달하면 URLSession 블록 바깥으로 값을 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 promise에 promise(.success(value)) 와 같은 식으로 값을 전달하기 위해서 promise의 타입은 &lt;code&gt;(Result&amp;lt;Data, Error&amp;gt;) -&amp;gt; Void&lt;/code&gt; 와 같은 형태일 필요가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 생성자를 다음과 같이 만들어 줬습니다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;typealias Promise&amp;lt;T, E: Error&amp;gt; = (Result&amp;lt;T, E&amp;gt;) -&amp;gt; Void

private var result: (@escaping Promise&amp;lt;T, E&amp;gt;) -&amp;gt; Void

/// Future 생성자
/// - Parameter promise: promise 클로져 안에서 promise를 호출하고 success 또는 failure를 작성할 수 있습니다.
init(promise: @escaping (@escaping Promise&amp;lt;T, E&amp;gt;) -&amp;gt; Void) {
  result = promise
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자를 열고 클로져 인자로 Result&amp;lt;T, E&amp;gt; -&amp;gt; Void 를 내보내야 하는데 풀어쓰면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;init(promise: @escaping (@escaping (Result&amp;lt;T, E&amp;gt;) -&amp;gt; Void) -&amp;gt; Void)&lt;/code&gt; 과 같은 형태라 typealias로 감싸줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 아래처럼 Result Type 자체를 반환값으로 사용할 수 있고, 그 안에서 어떤 값이 전달될 것인지 구성할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;return Future&amp;lt;Data, Error&amp;gt; { promise in 
    // success
    promise(.success(data))

    // error
    promise(.failure(error))

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 생성자와 result 멤버 변수가 &lt;code&gt;@escaping (@escaping Promise&amp;lt;T, E&amp;gt;) -&amp;gt; Void&lt;/code&gt; 와 같이 escaping이 두개나 붙는 기형적인 형태에 대해서 의아하실 텐데요, 저도 처음에 많이 고민했던 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;URLSession의 completion handler는 마찬가지로 escaping clousre 이기 때문에 해당 블록 안에서 값을 탈출 시키려면 탈출되는 코드도 escaping 이어야 하는데요,&lt;br&gt;첫번째 escaping은 생성자 블록 자체를 Future의 result 멤버 변수로 전달하는데 한번 사용했고, 남는건 (Result&amp;lt;T, E&amp;gt;) -&amp;gt; Void 가 남는데 이건 escaping closure가 아니라서 URLsession의 escaping closure 바깥으로 가져갈 수 없습니다.&lt;br&gt;그렇기 때문에 escaping 타입 속성이 하나 더 있는거죠&lt;br&gt; &lt;br&gt;이제 여기까지만 해도 Result Type을 반환값으로 사용하는 것은 목표 달성입니다만, 한번 해보고 싶었던 부분은&lt;br&gt;transform 연산입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Additional&lt;/h3&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;fetchResult
&amp;nbsp;&amp;nbsp;.map { data in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;UIImage(data: data)
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;.sink { [unowned imageView] result in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch result {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .success(let image):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Task {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;imageView.image = image
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .failure(let error):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&quot;ERROR:&quot; , error.localizedDescription)
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map과 같은 변환 오퍼레이터를 이용하고 싶은 것이죠&lt;br&gt;map 까지는 타입이 Result&amp;lt;Data, Error&amp;gt; 이고 이후에는 Result&amp;lt;UIImage?, Error&amp;gt; 입니다. &lt;br&gt; &lt;br&gt;방법을 생각해보면&lt;br&gt;1. map 이후에는 새로운 Future 타입을 생성&lt;br&gt;2. 외부에서 transform을 하는 블럭을 받아서 그를 통해 Future 타입 생성&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func map&amp;lt;N&amp;gt;(_ transform: @escaping (T) -&amp;gt; N) -&amp;gt; Future&amp;lt;N, E&amp;gt; {
&amp;nbsp;&amp;nbsp;Future&amp;lt;N, E&amp;gt; { newPromise in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result { newResult in
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch newResult {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .success(let t):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;newPromise(.success(transform(t)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case .failure(let error):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;newPromise(.failure(error))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런식으로 새로운 Future를 작성해볼 수 있을 것 같습니다. 요번 포스팅의 목적 자체가 Return 에 Result Type을 사용해보고 싶은 것이었는데 해당 부분을 해결했기 때문에 이런것도 가능한 것 같습니다.&lt;br&gt;같은 요령으로 tryMap, compactMap도 만들어 볼 수 있을 것 같습니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Sink&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sink 구현을 누락했는데요! 별거없습니다.&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;/// 저장된 값을 sink를 통해서 가져올 수 있습니다.
&amp;nbsp;&amp;nbsp;/// - Parameter result: result는 success, failure 두 타입이 있습니다.
&amp;nbsp;&amp;nbsp;func sink(result: @escaping Promise&amp;lt;T, E&amp;gt;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.result(result)
&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Repository&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/ReturnResultKit&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/urijan44/ReturnResultKit&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Refernece&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Futures_and_promises&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://en.wikipedia.org/wiki/Futures_and_promises&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/result&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/swift/result&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/67</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-Result-Type-%EC%9D%84-%EB%B0%98%ED%99%98%EA%B0%92%EC%9C%BC%EB%A1%9C-%EC%8D%A8%EB%B3%B4%EC%9E%90#entry67comment</comments>
      <pubDate>Sun, 28 Aug 2022 16:51:55 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 에서 MVVM 을 멈춰야 하는가?</title>
      <link>https://doing-programming.tistory.com/entry/SwiftUI-%EC%97%90%EC%84%9C-MVVM-%EC%9D%84-%EB%A9%88%EC%B6%B0%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 자극적인 주제죠?&lt;br&gt;네 오늘은 SwiftUI에서 MVVM을 멈춰야 하는가에 대한 제 생각을 써보려고 합니다. &lt;br&gt; &lt;br&gt;결론은 &lt;b&gt;쓰세요 &lt;/b&gt;입니다. 반박시 여러분 말이 맞습니다.  &lt;br&gt; &lt;br&gt;본문에 들어가기 앞서, 민감한 주제라고 생각하기 때문에, 누가 그러한 주장을 했는지는 모두 생략하도록 하겠습니다.&lt;br&gt;본 글에서 작성하는 '여론'의 근거는 주로 iOS Developer Slack 채널 raywenderlich 웨비나, 블로그 글 등을 참고했습니다. &lt;br&gt; &lt;br&gt;이제 왜 이러한 결론에 도달했는지 설명 드리겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwiftUI의 State와 Binding은 View Model 인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근들어 iOS Developer Slack 채널이나, 온갖 뉴스레터에서 SwiftUI Framework에서는 MVVM을 쓰지 말자는 논지의 아티클을 정말 많이 본 것 같습니다. raywenderlich에서 진행한 웨비나도 있었습니다.&lt;br&gt; &lt;br&gt;SwiftUI에서 MVVM을 쓰지 말자고 하시는 분들의 의견을 정리하면 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
  SwiftUI에서는 View에 View Model이 통합되었습니다. (State가 ViewModel이다.) 따라서 View Model을 통한 바인딩이 필요없어졌기 때문에 ViewModel은 불필요한 레이어입니다. 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 의견 말고는 다른 의견은 본적이 없습니다. 있다면 언제든 댓글/이메일 등으로 알려주시면 정정하도록 하겠습니다.&lt;br&gt; &lt;br&gt;그리고 위 의견에 반대하는 의견도 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
  SwiftUI View에 View Model이 통합되면, Model이 View에 보여지기 위해서는 &lt;b&gt;보여주기 위한 &lt;/b&gt;형태로 변환하는 과정이 필요한데, View Model은 그 역할을 겸하고 있다. 그렇다면 이 &lt;b&gt;보여주기 위한(Presentation)&lt;/b&gt; 형태로 변환하는 작업 또한 View에 포함되야 하는데, 너무 무거워 진다. 
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 두 의견이 대세인 것으로 보입니다. &lt;br&gt; &lt;br&gt;두 의견의 핵심을 추려서 다시 파악해 보면&lt;br&gt;SwiftUI의 &lt;b&gt;State&lt;/b&gt;와 &lt;b&gt;Binding&lt;/b&gt;이 ViewModel로 볼 것이냐 아니냐 에 대한 차이로 보입니다.&lt;br&gt; &lt;br&gt;State, Binding의 특징은 값이 변경되면 body를 재 호출 해서 body 라는 뷰를 통째로 새로 그리게 됩니다. 즉 값이 변경되면 뷰가 즉시 반응하는 반응형 프로그래밍인 셈이죠.&lt;br&gt; &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewModel이 뭔데?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM 디자인 패턴은 2005년 Microsoft의 WPF, Sliverlight의 설계자 중 한명인 &lt;b&gt;John Gossman이 &lt;/b&gt;개발하고 블로그를 통해 공개했습니다.(1) 해당 패턴은 Martin Fowler의 PM의 아이디어를 차용했다고 밝혔는데요, 해당 블로그 글에 따르면 핵심은 ViewModel과 View의 의존 방향이며, View가 ViewModel의 값을 얻는 특이한 방법입니다.&lt;br&gt; &lt;br&gt;쉽게 설명하자면, 다들 아시겠지만 MVC의 구조는 유저가 View를 보고 터치 동작과 같은 반응을 수행하면, Controller에 의존해서 이를 수행하고, Controller과 Model과 통신해서 유저 동작 수행 결과를 View에 업데이트 시킵니다. 이 과정에서 View는 Controller에 의존하면서 Controller는 또 View를 알고있는 상태가 됩니다. MVC는 이 방법을 View와 Controller의 상호 참조를 통해서 쉽게 해결합니다. 네 그렇습니다. 양방향 패턴입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgI9H1/btrJmxfBvMV/LKr1EdVDWZ9rnKcoWa1eQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgI9H1/btrJmxfBvMV/LKr1EdVDWZ9rnKcoWa1eQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgI9H1/btrJmxfBvMV/LKr1EdVDWZ9rnKcoWa1eQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgI9H1%2FbtrJmxfBvMV%2FLKr1EdVDWZ9rnKcoWa1eQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;129&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보니 View와 Controller가 너무 의존적이기 때문에 재사용성이 떨어지는 결과를 초래했습니다, 통제 흐름이 양방향이다 보니 리팩터링할 때 코드 찾아다니는 것도 쉽지 않습니다.&lt;br&gt; &lt;br&gt; &lt;br&gt;MVVM은 이러한 문제를 단방향 참조와 Binding과 같은 개념으로 민첩하게 설계할 수 있게 해줍니다.&lt;br&gt;View는 유저가 발생하는 어떤 액션을 ViewModel에 보내면, ViewModel은 해당 액션을 분석하고 Model에 의존해서 그 결과를 단순히 방출만 합니다. View를 직접 업데이트 하지 않습니다. 이 덕분에 통제 흐름이 단방향으로 흐르게 됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biAyRz/btrJikB3uS1/Ls5wLk4VvjwFHsV9v6Tc50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biAyRz/btrJikB3uS1/Ls5wLk4VvjwFHsV9v6Tc50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biAyRz/btrJikB3uS1/Ls5wLk4VvjwFHsV9v6Tc50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiAyRz%2FbtrJikB3uS1%2FLs5wLk4VvjwFHsV9v6Tc50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;127&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;View는 업데이트를 어떻게 하냐 하면, 딱히 특정 ViewModel이 아니더라도 인터페이스를 통해 방출되고 있는 값을 감시하고 하고 있다가 값이 업데이트 되면 뷰 스스로를 변경하면 됩니다. &lt;br&gt; &lt;br&gt;이 부분이 ViewModel이 존재하는 핵심 이유입니다. 상호 참조로 해결하냐, 값을 방출만 하냐 그 차이지만 이 차이가 생각보다 굉장히 중요합니다. 저는 경험적으로 양방향 아키텍처를 분석하는 것 보다 단방향 아키텍처를 분석하는 것이 훨씬 쉽고, 나중에 분리하기도 쉬웠습니다.&lt;br&gt;(이 부분이 사바사 일지는 잘 모르겠습니다. 의견 주시면 감사하겠습니다.)&lt;br&gt; &lt;br&gt; ViewModel의 핵심을 설명하느라 좀 많이 돌아왔는데요!&lt;br&gt;그래서 State/Binding이 ViewModel을 대체할 수 있을까요? &lt;br&gt;&lt;b&gt;아니요 &lt;/b&gt;그럴 수 없습니다.&lt;br&gt; &lt;br&gt; 저는 두가지 핵심 이유가 있다고 생각합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
 &lt;li&gt;State와 Binding PropertyWrapper는 모델을 사용하기에 불편하다.&lt;/li&gt;
 &lt;li&gt;수명주기가 View에 종속된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 설명 드리겠습니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;State와 Binding PropertyWrapper는 객체를 사용하기에 불편하다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 State와 Binding은 Class 모델을 사용하기에 조금 번거롭습니다. 무슨 뜻일까요? 애플의 설계는 State, Binding으로 감싼 객체가 변경이 되면 항상 body를 새로 그리기를 원합니다. SwiftUI에서 View는 값의 상태와 동일한 개념입니다. 값이 변경되면 (일반적으로) 항상 뷰가 변경이 됩니다. UIKit과 달리 일일이 업데이트 코드를 사용하지 않아도 되는 것이 SwiftUI의 장점이자 편리한 점이죠.&lt;br&gt; &lt;br&gt;그런데 State와 Binding으로 감싼 값이 '구조체'가 아니라 '클래스'가 되면 이 규칙은 깨집니다. 구조체일 경우 mutate가 발생하고 body는 그것을 인지하고 뷰를 업데이트 합니다. 그런데 클래스의 경우 멤버가 변경되는 것을 mutate로 보지 않죠 간단합니다.&lt;br&gt; &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Book {
&amp;nbsp;&amp;nbsp;var title: String
&amp;nbsp;&amp;nbsp;var content: String

&amp;nbsp;&amp;nbsp;init(title: String, content: String) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.title = title
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.content = content
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 Book 클래스를 만들겠습니다. &lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct ContentView: View {
&amp;nbsp;&amp;nbsp;@State var book = Book(title: &quot;곰세마리&quot;, content: &quot;&quot;)
&amp;nbsp;&amp;nbsp;var body: some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VStack {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Image(systemName: &quot;book&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.imageScale(.large)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.foregroundColor(.accentColor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;내용을 입력하세요&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text: $book.content,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;axis: .vertical
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.padding()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.background {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RoundedRectangle(cornerRadius: 8)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.foregroundColor(.gray.opacity(0.3))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.padding()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;RedrawMonitor(count: book.content.count)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}

struct RedrawMonitor: View {
&amp;nbsp;&amp;nbsp;let count: Int
&amp;nbsp;&amp;nbsp;var body: some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&quot;\(count)&quot;)
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;init(count: Int) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.count = count
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 간단하게 Book의 내용을 편집할 수 있는 뷰를 마련했습니다. 여기서 TextField로 Book의 내용을 수정할 수 있고, RedrawMontior를 통해 책 content의 길이를 표시하려고 합니다. 당연히 TextField를 수정하게 되면 Book은 mutate가 발생하게 되고 뷰를 새로 그리면서 RedrawMonitor는 content 길이를 업데이트 하고 표시합니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5iK1Y/btrK3Z3OBps/EjxVOTMeieii1U4HKa98Pk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5iK1Y/btrK3Z3OBps/EjxVOTMeieii1U4HKa98Pk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5iK1Y/btrK3Z3OBps/EjxVOTMeieii1U4HKa98Pk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b5iK1Y/btrK3Z3OBps/EjxVOTMeieii1U4HKa98Pk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;432&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 여기서 Book이 구조체가 아니고 클래스로 변경되면 어떻게 될까요?&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;final class Book {
&amp;nbsp;&amp;nbsp;var title: String
&amp;nbsp;&amp;nbsp;var content: String

&amp;nbsp;&amp;nbsp;init(title: String, content: String) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.title = title
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.content = content
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그닥 놀라운 일은 아닙니다. RedrawMonitor가 업데이트 되지 않습니다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lIiVc/btrK56gnQ2A/NtGSDxQKu8MtW6V5QqmGP1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lIiVc/btrK56gnQ2A/NtGSDxQKu8MtW6V5QqmGP1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lIiVc/btrK56gnQ2A/NtGSDxQKu8MtW6V5QqmGP1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lIiVc/btrK56gnQ2A/NtGSDxQKu8MtW6V5QqmGP1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;432&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;물론 여기서 왜 굳이 Book이 클래스 이어야 하냐고 반문 하실 수도 있습니다. 하지만 앱을 만들다 보면 꼭 구조체만 사용할 수 없는 상황은 오기 마련입니다.   또 클래스여도 업데이트 할 방법은 있다고 하실 수 있습니다. 의도적으로 mutating을 발생시키는 State를 하나 만들면 됩니다. 하지만 그것은 값의 변화에 따라 뷰 업데이트를 신경쓰지 않겠다는 SwiftUI의 근본 개념 자체를 부정하는 행위입니다.&lt;br&gt; &lt;br&gt;그래서 SwiftUI는 이러한 불편함을 해소 할 수 있는 도구가 하나 더 있습니다. 바로 ObservableObject 입니다. ObservableObject는 내부에서 objectWillChange가 호출되면 body를 다시 그리도록 하는 특성이 있습니다.&lt;br&gt;Published Property Wrapper를 사용하면 자동으로 objectWillChange를 호출하죠.&lt;br&gt; &lt;br&gt;물론 Published에 클래스를 쓰게되면 자동으로 objectWillChange를 발생시키지 않습니다. State와 같은 이유 이지요.&lt;br&gt;하지만 강제적으로 뷰를 업데이트 하기 위해 State은 더미를 만들어야 하지만 ObservableObject는 의도적으로 통제할 수 있죠.&lt;br&gt; &lt;br&gt;결론은 State와 Binding에는 구조체만 넣는것이 합당? 해 보이고 이는 State와 Binding을 ViewModel로 보기에는 수많은 제약이 따릅니다. &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수명주기가 View에 종속된다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 View는 Data의 파생입니다. 데이터가 변경이 되면 View는 자동으로 상태를 따라갑니다. 그런데 WWDC 내용에 따르면 View는 지역적 특성을 가집니다, View에 종속되죠. 실제로 SwiftUI 뷰를 구성하다 보면 State, Binding은 값이라는 느낌보다는 UILabel, UIButton 같은 느낌입니다. 값 보다는 뷰에 가까운 것이죠. API통신과 같은 외부의 값을 가져오는데는 많은 불편함이 있습니다.&lt;br&gt;View쪽 코드에서 어떤 값을 가져와 State에 대입해야 하죠.&lt;br&gt;코드로 보면&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;final class Repository {
&amp;nbsp;&amp;nbsp;private let cached = Book(title: &quot;Swift&quot;, content: &quot;어렵다~&quot;)

&amp;nbsp;&amp;nbsp;func loadBook() -&amp;gt; Book {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cached
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 Repository 모델이 있다고 가정 합니다. 아주 심플하게요&lt;/p&gt;
&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct ContentView: View {
&amp;nbsp;&amp;nbsp;let repository: Repository
&amp;nbsp;&amp;nbsp;@State var update = false
&amp;nbsp;&amp;nbsp;@State var book = Book(title: &quot;곰세마리&quot;, content: &quot;&quot;)
&amp;nbsp;&amp;nbsp;var body: some View {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VStack { ... } 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.onAppear {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let initBook = repository.loadBook()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.book = initBook
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 가져온 값을 @State book으로 전달할려면 View의 특정 시점이나 트리거에서 가져와서 대입해야 합니다. 그런데 여기에 한술 더떠 지금은 Entity를 날것 그대로 가져오고 있는데, 만약 여기서 데이터를 가공해서 보여줘야 할때는 어떻게 해야 하나요? 데이터 가공 코드가 뷰에 들어가야 하나요? 그러지 않길 바랍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결책은 많다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말한 케이스의 해결방법은 엄청 많습니다. Realm에서 밀고가는 MVI도 있고, TCA도 있습니다. 그런데 그거아세요?&lt;br&gt;Realm에서 말한 MVI는 Realm에 의존합니다. 웨비나에 따르면 다른 Persistent Data Base에 대한 질문이 나왔을 때 Realm 개발자 분께서는 답변을 주시지 않으셨습니다.&lt;br&gt;TCA는? TCA 그 자체라는 거대한 라이브러리에 의존합니다.&lt;br&gt; &lt;br&gt;그런데 ObservableObject를 ViewModel로 쓰면 별도의 프레임워크, 라이브러리 없이 너무나 쉽게 해결 가능합니다.&lt;br&gt;물론 SwiftUI에 알맞게 사용하기 위해 많은 코드를 연구하다 보면 이건 더이상 ViewModel이 아닌 것 같은데~ 그런 생각이 들기도 하고, 어쨌든 정답은 없으니 기존 UIKit 프레임워크 위에서 쓰던 MVVM을 그대로 가져오면 또 SwiftUI의 특성을 100% 살리지 못하는 것도 사실입니다.&lt;br&gt; &lt;br&gt;하지만 애초에 MVVM은 ViewModel과 View의 통신방법 (바인딩)과 의존방향(단방향) 이 핵심이라는 것을 잊지 마세요! ObservableObject는 바인딩을 하기에 매우 유용한 도구입니다.&lt;br&gt; &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결론&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI라고 해서 MVVM 개념이 부적절 하다는 생각은 저는 별로 좋지 않다고 생각합니다. 그렇다고 MVVM을 맹목적으로 사용하는 것도 주의해야 합니다. 하지만 MVVM을 포기하고(정확히는 MVVM의 개념)을 포기하고 굳이 다른 서드파티 프레임워크 라는 길로 돌아가지 않았으면 합니다. 중요한 것은 민첩하고 유연한 코드를 구성하는 개념 그 자체이지 어떤 디자인 패턴, 아키텍처 패턴을 선택하는 것이 아니라는 것입니다. &lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) &lt;a href=&quot;https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://docs.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern&lt;/span&gt;&lt;/a&gt;&lt;br&gt;(2) 클린 아키텍처, 로버트 마틴&lt;br&gt;(3) raywenderlich Realm 웨비나&lt;br&gt;(4) SwiftUI 관련 WWDC&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/66</guid>
      <comments>https://doing-programming.tistory.com/entry/SwiftUI-%EC%97%90%EC%84%9C-MVVM-%EC%9D%84-%EB%A9%88%EC%B6%B0%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80#entry66comment</comments>
      <pubDate>Wed, 10 Aug 2022 02:11:14 +0900</pubDate>
    </item>
    <item>
      <title>Singleton Pattern vs Dependency Injection</title>
      <link>https://doing-programming.tistory.com/entry/Singleton-Pattern-vs-Dependency-Injection</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Intro&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하다 보면, 특정 기능을 다른 객체에 의존해야 하는 상황이 생깁니다. 간단한 예로 TIL 앱을 만들었다고 가정했을 때, 데이터를 저장하는 객체를 DataManager 와 같은 이름으로 만들어서 쓸 수 있겠죠.&lt;br&gt;이 의존성을 받는 객체는 유저의 액션에 따라, 로직 흐름에 따라 호출이 이루어지는데요. 호출을 하는 대표적인 두 가지 방법이 바로 Singleton Pattern과 Dependancy Injection입니다.&lt;br&gt;각 개념에 대해서는 별도로 소개하지 않겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Singleton&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념은 서로 반대 진영에 있는 것으로 볼 수 있는데요, 우선 Singleton은 인스턴스를 전역에 올려두고, 어디에서든 쉽고 빠르게 접근할 수 있다는 장점이 있습니다.&lt;br&gt;아래 코드는 어떤 DataSource 클래스를 간단하게 Singleton으로 구현한 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct DataSource {

  static let shared = DataSource()

  private init() {}

  func fetchId() -&amp;gt; Int {
    0
  }

  func requestRegisterCoupon() {
    // some network request
  }

  func fetchSomeData(userId: String) -&amp;gt; Result&amp;lt;Data, Error&amp;gt; {
    return .success(Data())
  }

  func save(with data: Data) throws {
    // save...
  }

  func edit(with data: Data) throws {
    // edit...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Singleton은 하나의 인스턴스를 전역에 올려두고 관리하는 것이기 때문에, 일반적인 생성자는 &lt;code&gt;private&lt;/code&gt;로 막아두고 흔히 하는 &lt;code&gt;shared&lt;/code&gt;를 통해서 접근할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제로 사용할 때는 아래와 같이 호출합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let result = DataSource.shared.fetchSomeData(userId: id)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Singleton Pattern은 위 코드에서 보았듯 구현하기도 쉽고, 어디서든 빠르고 쉽게 접근 가능하기 때문에, 자주 선호됩니다. 그리고 핵심은 하나의 인스턴스로 관리하기 때문에, 인스턴스가 둘 이상 생성되어서는 안되는 상황을 보장할 수 있습니다.&lt;br&gt;하지만 너무 쉬운 코드, 사용의 자유는 다른 불편함을 낳기도 합니다.&lt;br&gt;Singleton의 대표적인 단점은 유지보수가 어려워 진다는 점입니다. 이 부분이 사람에 따라서는 크게 와 닿지 않을 수도 있는데요.&lt;br&gt;실제 서비스에서는 코드의 관리자는 빠르게 변합니다. 신규입사자가 들어오기도 하고, 관리한지 오래된 코드를 오랜만에 다시 열어볼 수도 있습니다.&lt;br&gt;그러니까 기존 코드에 대해서 모르는 상황에서 이 Singleton 객체는 굉장히 불친절합니다.&lt;br&gt;우선 Singleton객체가 굉장히 큰 책임을 가지고 있는 경우를 생각해 볼까요?&lt;br&gt;아주 많은 메서드가 있고 그것이 접근에 제한이 없다면 (다양한 UseCase나 View에서 Singleton을 호출하기 때문에, private로 접근 제한이 되어 있지 않을 것입니다)&lt;br&gt;DataSource.shared를 입력하고 한번 더 .을 누르면 일차적으로 멘탈에 금이갑니다.&lt;br&gt;대체 어느 메서드를 호출해야 원하는 결과를 얻을 수 있을까요? 모릅니다. 알아내기 위해서는 DataSource 클래스를 열어서 코드 한줄 한줄 파악해가며 해당 메서드가 무슨 역할을 하는지 알아내야 합니다&lt;br&gt;왜냐하면 Singleton 객체는 작성자에게 너무 큰 자유를 주었기 때문에, 담당하는 기능을 만들면서 손쉽게 Singleton에 기능을 추가했을 것이고, 비슷하거나 겹치는 이름을 피해 메서드 명이 점점 기괴해지거나 길어졌기 때문입니다.&lt;br&gt;이를 해결하기 위해 Facade 패턴을 이용해 너무 큰 Singleton 객체를 분리하는 방법 도 있습니다.&lt;br&gt;그리고 기능을 개발하기 위해 Singleton 객체가 이미 존재해야 하는 불편함이 있습니다. 다른 말로 Singleton 객체와 기능이 따로 개발 될 수 없습니다. 한 사람이 한번에 개발을 해야 하죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dependency Injection&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dependency Injection은 어떤 기능이 의존해야 하는 객체를 외부에서 주입하는 방법을 말합니다. 이름 그대로 의존성 주입이죠. 보통 줄여서 DI라고 부릅니다.&lt;br&gt;//some feature&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  private let provider: DataSource

  init(provider: DataSource) {
    self.provider = provider
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI는 위와 같이 some feature 기능에서 DataSource를 멤버로 가지게 되고, 이를 생성자를 통해서 외부에서 주입 받습니다.&lt;br&gt;이 덕분에, some feature는 DataSource에 대해서 '그다지' 자세히 알 필요가 없습니다. 외부에서 주입되기 때문에 코드 작성자가 어느 인스턴스를 호출해야 할지 '자유롭게' 생각하지 않아도 됩니다. some feature가 데이터를 가져올 떄 부를 수 있는 코드는 provider로 제한되기 때문입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let result = provider.fetchSomeData(userId: userId)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI 덕분에 의존 관계가 명확해 졌네요. 지금은 단순한 예제를 보았지만, 대형 서비스 코드들을 보고, 해당 Feature가 의존하고 있는 경우를 명백하게 분석할 수 있습니다.&lt;br&gt;한가지 더 장점은 DI의 경우 interface처리가 훨씬 편하다는 것입니다. Singleton이 기능이 비대해 져서 접근할 수 있는 범위를 제한하기 위해 Facade를 사용해서 implement를 만든다면&lt;br&gt;DI는 필요한 기능을 protocol로 간단하게 처리해 버릴 수 있습니다.&lt;br&gt;지금은 some feature가 someData를 가져올 때 DataSource라는 Concrete한 객체에 의존하지만, 나중에 DataSource 객체가 교체가 될 수도 있습니다.&lt;br&gt;또는 some feature를 작성하는 도중에 DataSource가 완성이 되지 않았을 수도 있죠.&lt;br&gt;그래서 우리는 protocol을 이용합니다.&lt;br&gt;fetchSomeData(userId:) 를 제공해주는 어떤 객체든 상관없도록 만드는 것이죠&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol SomeViewProvider {
  func fetchSomeData(userId: String) -&amp;gt; Result&amp;lt;Data, Error&amp;gt;
  func saveSomeData(with data: Data)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜을 만들고&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private let provider: SomeViewProvider

init(provider: SomeViewProvider) {
  self.provider = provider
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 Concrete 타입이 아니라 프로토콜을 타입으로 받습니다.&lt;br&gt;이렇게 해서 some feature를 작성하는 동안은 DataSource 작업도에 영향을 받지 않을 뿐더러, DataSource가 다른 객체로 변경되더라도 SomrViewProvider만 채용 해달라고 부탁하면 되는 것이죠&lt;br&gt;또 접근할 수 있는 메서드가 (지금의 겨웅) fetchSomeData,와 saveSomeData로 제한되기 때문에, 해당 feature에서 어떤 기능을 사용하는지 더 명백해 집니다.&lt;br&gt;그리고 위와 같은 코드 패턴은 Testable한 코드도 겸사겸사 만들게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Singleton VS Dependency Injection&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 중에 뭘 쓰면 더 좋을까 하는 고민은, 큰 의미는 없습니다. 왜냐하면 각각의 장단이 있기 때문인데요, Singleton은 어쨌거나 단 하나의 인스턴스를 만들어야 하는 경우는 DI보다 더 낫습니다. DI는 주입하는 과정에서 서도 다른 인스턴스를 만들 수 있는 가능성이 충분하기 때문이죠. 만약 local에 특정 user 정보를 보관하고 있다면 이는 따로 관리 되어서는 안될 것입니다.&lt;br&gt;하지만 디바이스 성능이 갈 수록 좋아지고, 기본적인 서비스 크기가 대폭 늘어남에 따라 Singleton이 주는 편리함은 치가 떨리는 재사용성을 낳습니다. 후임자에게 의존관계 분석 지옥을 선사하죠. 그래서 사견으로는 작은 서비스, 작은 책임에서는 Singleton은 좋은 선택이지만 유지보수가 필요한 상용 서비스에서는 Singleton을 멀리하고 Dependency Injection을 가까이 하는게 낫습니다. &lt;/p&gt;</description>
      <category>General Dev</category>
      <category>DependencyInjection</category>
      <category>singleton</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/65</guid>
      <comments>https://doing-programming.tistory.com/entry/Singleton-Pattern-vs-Dependency-Injection#entry65comment</comments>
      <pubDate>Tue, 28 Jun 2022 12:09:46 +0900</pubDate>
    </item>
    <item>
      <title>Swift - Dynamic member lookup</title>
      <link>https://doing-programming.tistory.com/entry/Swift-Dynamic-member-lookup</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift로 된 라이브러리를 구경하다 보면, Class의 @Attribute로 &lt;code&gt;dynamicMemberLookup&lt;/code&gt;이라는 것이 붙은 것을 본 적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dynamicMemberLookup&lt;/code&gt;이 무엇인지, 어떻게 쓰이는지 알아보자&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;What is Dynamic member lookup&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 이것이 무엇인지만 간략하게 설명하자면 '실제 객체에 존재하지 않는 프로퍼티(멤버 변수)를 Dot Syntex로 접근하는 문법` 으로 몬 쌉소린가 싶습니다. 하지만 이 개념을 알고 난 뒤에는 똑같이 설명하게 되실 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 당연한 예제를 하나 볼게요&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Developer {
  let languages: [String: Int]
}

var henry = Developer(languages: [
  &quot;swift&quot;: 5,
  &quot;cpp&quot;: 3
])

henry.languages[&quot;swift&quot;] // Optional 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Developer 객체는 사용할줄 아는 언어와 그 경력이 Dictionary로 정의되어 있습니다. 만약 swift 언어 경력을 알고싶다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`henry.languages[&quot;swift&quot;]` 와 같이 작성해서 languages에 접근해볼 수 있곘고 결과는 Optional 5 일겁니다. 그렇죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하지만&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FByl6/btrCqnSpWwf/xD5Xr6K4dek7LCdhHQvlY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FByl6/btrCqnSpWwf/xD5Xr6K4dek7LCdhHQvlY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FByl6/btrCqnSpWwf/xD5Xr6K4dek7LCdhHQvlY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFByl6%2FbtrCqnSpWwf%2FxD5Xr6K4dek7LCdhHQvlY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;951&quot; height=&quot;53&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게는 못가져 올겁니다. Developer 구조체는 swift라는 멤버 변수를 가지고 있지 않기 때문이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Dynamic Member Lookup&lt;/code&gt; 문법은 이걸 가능하게 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Implementation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 &lt;code&gt;Developer&lt;/code&gt;를 아래와 같이 고쳐볼게요&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@dynamicMemberLookup
struct Developer {
  var languages: [String: Int]

  subscript(dynamicMember member: String) -&amp;gt; Int {
    get {
      languages[member] ?? 0
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 차근차근 보겠습니다.&lt;br /&gt;우선 @Attribute로 &lt;code&gt;@dynamicMemberLookup&lt;/code&gt; 이란게 붙었습니다. 해당 객체가 dynamicMemberLookup를 지원하겠다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 뜬금없이 subscript 문법이 들어와 있는데요, subscript문법을 통해서 languages에 바로 접근하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamicMemberLookup 을 잠깐만 잊어볼게요, subscript문법을 작성했으니 다음과 같이 swift 경력을 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;henry[dynamicMember: &quot;swift&quot;]&lt;/code&gt;&amp;nbsp;그렇죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscript 에서 외부 접근 레이블인 dynamicMember는 사실 dynamicMemberLookup의 구현 사항입니다. subscript문법만 놓고 생각하면 외부 레이블이 뭐든 상관 없을 겁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dynamicMemberLookup은 지금까지 작성한 내용을 바탕으로 dot syntex로 swift에 접근할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;51&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk4VBr/btrCoWBj04f/ZsRoGMPdBNswR35skmCg5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk4VBr/btrCoWBj04f/ZsRoGMPdBNswR35skmCg5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk4VBr/btrCoWBj04f/ZsRoGMPdBNswR35skmCg5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk4VBr%2FbtrCoWBj04f%2FZsRoGMPdBNswR35skmCg5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;991&quot; height=&quot;51&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;51&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와는 달리 컴파일이 되면서 5라는 정수가 튀어나오죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;henry라는 Developer의 멤버 변수에는 swift는 없지만, 멤버 변수처럼 접근 할 수 있습니다. 바로 subscript 문법을 이용해서요! 이제 아시겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Point&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 실제로 존재하지 않는 속성을 dot syntex로 접근하는 것은 굉장히 편리하지만, 런타임에 결과를 확인할 수 있기 때문에 신중해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 javascript에 경력에 대해서 접근하려고 하는데, js인가요 javascript인가요 javaScript인가요 아니면 JavaScript인가요?&lt;/p&gt;
&lt;pre id=&quot;code_1652801093275&quot; class=&quot;yaml&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let henry = Developer(languages: [
  &quot;swift&quot;: 5,
  &quot;cpp&quot;: 3,
  &quot;javascript&quot;: 1
])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGDWd/btrCqaSYfnw/pA5coIwHNoC8IAukgEboqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGDWd/btrCqaSYfnw/pA5coIwHNoC8IAukgEboqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGDWd/btrCqaSYfnw/pA5coIwHNoC8IAukgEboqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGDWd%2FbtrCqaSYfnw%2FpA5coIwHNoC8IAukgEboqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;99&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 javascript의 경력이 있더라도 조금만 잘못 입력하면, 원하는 값이 나오지 않을 수 있습니다. 컴파일 타임에 확인이 불가능 하니 휴먼 에러가 발생할 확률이 높을 뿐더러, 어떤 식으로 프로퍼티가 존재할지 직관적이지도 않고, 자동완성 기능도 기대할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이를 보완하기 위한 방법이 있는데요&lt;/p&gt;
&lt;pre id=&quot;code_1652801291742&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Point { var x: CGFloat, y: CGFloat }

@dynamicMemberLookup
struct PassthroughWrapper&amp;lt;Value&amp;gt; {
  var value: Value
  subscript&amp;lt;T&amp;gt;(dynamicMember member: KeyPath&amp;lt;Value, T&amp;gt;) -&amp;gt; T {
    get {
      return value[keyPath: member]
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 래퍼를 만들어서 KeyPath를 사용하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA0SQJ/btrCp5R6YAd/HPpibJr5Jx7vVjkfR5ZpVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA0SQJ/btrCp5R6YAd/HPpibJr5Jx7vVjkfR5ZpVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA0SQJ/btrCp5R6YAd/HPpibJr5Jx7vVjkfR5ZpVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA0SQJ%2FbtrCp5R6YAd%2FHPpibJr5Jx7vVjkfR5ZpVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;331&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사진 처럼 자동완성의 도움도 받을 수 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재하지 않는 프로퍼티에 접근하면 컴파일 타임에 이를 확인 할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSedSI/btrCp6KeFed/ZkizYp0oWkj5CI858IgAU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSedSI/btrCp6KeFed/ZkizYp0oWkj5CI858IgAU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSedSI/btrCp6KeFed/ZkizYp0oWkj5CI858IgAU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSedSI%2FbtrCp6KeFed%2FZkizYp0oWkj5CI858IgAU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1318&quot; height=&quot;61&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우에 따라서는 편리하게 사용할 수 있지만, 래퍼를 통하더라도 남용하는 것은 주의하는게 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://books.apple.com/kr/book/the-swift-programming-language-swift-5-6/id881256329&quot;&gt;https://books.apple.com/kr/book/the-swift-programming-language-swift-5-6/id881256329&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>DynamicMemberLookup</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/61</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-Dynamic-member-lookup#entry61comment</comments>
      <pubDate>Wed, 18 May 2022 00:32:06 +0900</pubDate>
    </item>
    <item>
      <title>Combine - 여러 요청 결과를 결합해야 하는 경우</title>
      <link>https://doing-programming.tistory.com/entry/Combine-%EC%97%AC%EB%9F%AC-%EC%9A%94%EC%B2%AD-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EA%B2%B0%ED%95%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Issue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 A.Request의 요청 결과를 받고 A의 결과에 따라 B.Request를 수행해야 하는 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Combine으로는 이를 어떻게 처리를 해야 할까 고민했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652762983425&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    A_Request.sink { _ in
      
    } receiveValue: { data in
      
      //processing
      
      B_Request.sink { _ in
        
      } receiveValue: { data2 in
        //processing + data2 processing
      }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무식한 방법으로는 위와 같이, A_Request를 받고 내부에서 B_Request를 또 호출하는 방법이지만, 괜찮은 코드는 아닙니다. 코드 자체가 불필요한 뎁스를 가지기도 하고 가독성도 떨어지며, 두 기능을 분리하기가 쉽지 않네요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소개 드릴 기능은 CombineLastest로 Rx를 많이들 써보셨다면 잘 아시는 결합 오퍼레이터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 결합 연산자 중에 &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;CombineLastest&lt;/span&gt;를 써야 하는 이유가 있는데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 결합 연산자는 각 퍼블리셔의 스트림을 합쳐주는 역할을 하기는 하지만, 기존 데이터를 받고, 그거에 대해서 또 처리를 하기는 용이 하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bti2NK/btrCqb4CDi3/NuHhM8Sdepxzk7yZUEjml0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bti2NK/btrCqb4CDi3/NuHhM8Sdepxzk7yZUEjml0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bti2NK/btrCqb4CDi3/NuHhM8Sdepxzk7yZUEjml0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbti2NK%2FbtrCqb4CDi3%2FNuHhM8Sdepxzk7yZUEjml0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;785&quot; height=&quot;381&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge, Append, Prepend등은 위와 같은 도식 형태로 Publisher를 결합하는 반면(오퍼레이터 마다 조금 다릅니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWTPoV/btrCpVVfna1/z74Dvx7FwJSVpmAKg3X9C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWTPoV/btrCpVVfna1/z74Dvx7FwJSVpmAKg3X9C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWTPoV/btrCpVVfna1/z74Dvx7FwJSVpmAKg3X9C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWTPoV%2FbtrCpVVfna1%2Fz74Dvx7FwJSVpmAKg3X9C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;451&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLastest는 퍼블리셔를 결합에 튜플로 뱉습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652766408815&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A_Request.combineLatest(B_Request)
      .sink(receiveCompletion: { _ in }
        receiveValue: { (a, b) in
        let c = C(a: a, b: b)
        print(c)
      })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CombineLastest는 또 좋은점이 순서를 보장 해준다는 것인데요&lt;/p&gt;
&lt;pre id=&quot;code_1652767905553&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      Timer.publish(every: 3.0, on: .main, in: .common)
        .autoconnect()
        .sink { [unowned self] _ in
          DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.publisher2.send(&quot;7&quot;)
          }
          DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.publisher1.send(&quot;5&quot;)
          }

        }
        .store(in: &amp;amp;subscriptions)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 처럼 명백하게 publisher2에 값이 먼저 도착하고 publisher1에 값이 도착을 해도 결과물은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5다음 7이 들어오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 처음에 combineLastest를 할 때 publisher1뒤에 publisher2를 결합했기 때문입니다. 만약 순서를 바꾸고 싶다면, 결합하는 코드를 바꾸면 되겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 즐거운 컴바인이었습니다.&lt;/p&gt;</description>
      <category>iOS</category>
      <category>combine</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/60</guid>
      <comments>https://doing-programming.tistory.com/entry/Combine-%EC%97%AC%EB%9F%AC-%EC%9A%94%EC%B2%AD-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EA%B2%B0%ED%95%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0#entry60comment</comments>
      <pubDate>Tue, 17 May 2022 15:18:59 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI - 애니메이션 사라질 때 동작하지 않는 경우(SwiftUI Disappear transition not animated)</title>
      <link>https://doing-programming.tistory.com/entry/SwiftUI-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%82%AC%EB%9D%BC%EC%A7%88-%EB%95%8C-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%BD%EC%9A%B0SwiftUI-Disappear-transition-not-animated</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 withAnimation으로 간단하게 애니메이션을 할 수 있는데, 종종 사라질 때 이 애니메이션이 작용하지 않는 듯한 모습을 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.06.59.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzw19z/btrBMh6krGw/smILeV4wFCT35lxOsVaVtk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzw19z/btrBMh6krGw/smILeV4wFCT35lxOsVaVtk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzw19z/btrBMh6krGw/smILeV4wFCT35lxOsVaVtk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dzw19z/btrBMh6krGw/smILeV4wFCT35lxOsVaVtk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.06.59.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tap을 하면 Circle은 자연스럽게 opacity Transition이 동작하는데, 글자는 나타날 때는 애니메이션이 잘 되는거 같은데, 사라질 때는 애니메이션이 동작하지 않는 것 처럼 뿅하고 사라집니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1652191823491&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
  @State var state: Bool = false
  var body: some View {
    VStack {
      Button {
        withAnimation {
          state.toggle()
        }
      } label: {
        Text(&quot;Tap \(state ? &quot;On&quot; : &quot;Off&quot;)&quot;)
      }
      ZStack {
        Circle()
          .foregroundColor(state ? .blue : .clear)
        if state {
          Text(&quot;Hello Swift&quot;)
          .foregroundColor(state ? .white : .black)
          .font(.system(size: 55))
          .transition(.opacity.animation(.easeIn(duration: 1).delay(0.5)))
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션이 되지 않는 것일까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.23.33.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5SJ9/btrBHroq6rz/KQCg7hoiRAFEK0FQOcIYv0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5SJ9/btrBHroq6rz/KQCg7hoiRAFEK0FQOcIYv0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5SJ9/btrBHroq6rz/KQCg7hoiRAFEK0FQOcIYv0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/k5SJ9/btrBHroq6rz/KQCg7hoiRAFEK0FQOcIYv0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.23.33.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 백그라운드에 Rectable을 검게 반만 깔아보았습니다. 검은 배경이 있는 곳은 애니메이션이 정상적으로 보이는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하얀 배경쪽에서는 바로 사라지는 것 처럼 보입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 ZIndex로 Text가 Transition 될 때 ZIndex가 원 보다 아래로 내려가면서 한번에 사라지는 것 처럼 보입니다. 아마 이는 명백한 버그 일 것입니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결법은 간단한데, Text가 Transition 될 때 강제로 ZIndex가 0이 되는 것이 문제이므로, ZIndex를 명시적으로 1로 정하거나 원의 ZIndex를 0보다 낮은 -1 등으로 정하면 해결 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.27.48.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ58Rp/btrBLFNuIfK/k9Z2nIudAQvBnMK3sfWZKk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ58Rp/btrBLFNuIfK/k9Z2nIudAQvBnMK3sfWZKk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ58Rp/btrBLFNuIfK/k9Z2nIudAQvBnMK3sfWZKk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cJ58Rp/btrBLFNuIfK/k9Z2nIudAQvBnMK3sfWZKk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone SE (3rd generation) - 2022-05-10 at 23.27.48.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1652192917374&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
  @State var state: Bool = false
  var body: some View {
    VStack {
      Button {
        withAnimation {
          state.toggle()
        }
      } label: {
        Text(&quot;Tap \(state ? &quot;On&quot; : &quot;Off&quot;)&quot;)
      }
      ZStack {
        Circle()
          .foregroundColor(state ? .blue : .clear)
        if state {
          Text(&quot;Hello Swift&quot;)
          .foregroundColor(state ? .white : .black)
          .font(.system(size: 55))
          .zIndex(1)
        }
      }
      .background(Rectangle().foregroundColor(.black).frame(width: UIScreen.main.bounds.width / 2), alignment: .leading)
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sarunw.com/posts/how-to-fix-zstack-transition-animation-in-swiftui/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sarunw.com/posts/how-to-fix-zstack-transition-animation-in-swiftui/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/57730074/transition-animation-not-working-in-swiftui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/57730074/transition-animation-not-working-in-swiftui&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>Animation</category>
      <category>SwiftUI</category>
      <category>TRANSITION</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/59</guid>
      <comments>https://doing-programming.tistory.com/entry/SwiftUI-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%82%AC%EB%9D%BC%EC%A7%88-%EB%95%8C-%EB%8F%99%EC%9E%91%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%BD%EC%9A%B0SwiftUI-Disappear-transition-not-animated#entry59comment</comments>
      <pubDate>Tue, 10 May 2022 23:29:28 +0900</pubDate>
    </item>
    <item>
      <title>Git - 작업하던 내용 브렌치 옮기기! Git Stash</title>
      <link>https://doing-programming.tistory.com/entry/Git-%EC%9E%91%EC%97%85%ED%95%98%EB%8D%98-%EB%82%B4%EC%9A%A9-%EB%B8%8C%EB%A0%8C%EC%B9%98-%EC%98%AE%EA%B8%B0%EA%B8%B0-Git-Stash</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 막 현업에서 일을 하게 되었는데, Git-flow가 아직 습관이 안되어서, Feature 브랜치에 작성 할 내용을 실수로 Develope 브랜치에서 작성 해버렸습니다. 작성 내용이 생각보다 많아서, 이를 복사해놓고, 초기화 하고 브랜치를 또 새로 파서 거기에 붙여넣고 하자니 시간이 너무 오래걸릴 것 같더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 찾아보니 Stash를 통해 간단히 해결할 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stashes&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Stash는 은닉이라는 뜻입니다. 커밋하기 전 내용을 로컬 저장소에 잠깐 임시저장 할 수 있는 기능이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 전 내용을 왜 잠시 저장해 두어야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 특정 기능을 개발하고 있는데, 해당 기능 동작이 아직 완성 되지 않았는데, 다른 기능을 작성해야 한다고 합니다. 이때 다른 브랜치로 이동하거나 해야 하는데, 그렇다고 커밋을 치자니 기능이 완성이 되지 않아 커밋 룰에 어긋납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 저장을 할 필요가 있죠, 물론 수동으로 할 수 있습니다. 작성한 내용을 따로 빼 두고, 내용을 초기화 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 만약, 그래야 하는 내용이 너무나 많다면? 또 그 과정에서 실수가 없을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Git은 Stash라는 기능을 제공해서 편하게 이 고민을 해결해 줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 작업하던 내용은 저장하고 나오세요. 그리고 커밋은 하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git stash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 terminal에 위와 같이 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래 처럼 메시지가 출력되는데, 메시지가 아래처럼 출력이 되고 나면, Modified 이어야 할 작성 하던 파일이, 편집되기 전 상태로 돌아갑니다.&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;Saved working directory and index state WIP on master: 780e259 git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 작업물이 사라져서 당황할 수 있지만, 다행히 작업물은 사라지지 않고, 로컬 디렉토리 어딘가에 저장되어 있는 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디 있을까요?&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;cd .git/refs/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 디렉토리로 이동해 보면&lt;br /&gt;stash 라는 파일이 있을 겁니다. stash 파일을 읽으보면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;9001f55f2bacf55a47505ae4329cef5c9669e87f&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commit id 처럼 해시 값이 하나 존재하고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용을 읽어보면&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;git cat-file -p 9001f55&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;tree c05ce9e072776b110d4c2b5c6e0a1bc80b899581
parent 780e259d5d9ae0c524095a2156f50efcc265abf9
parent 84396b3b0c28fe16cf6915d85b0ca5b1ddbd0e7f
author urijan44 &amp;lt;teamsva360@gmail.com&amp;gt; 1649688251 +0900
committer urijan44 &amp;lt;teamsva360@gmail.com&amp;gt; 1649688251 +0900&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 내용이 많네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tree 해시값을 다시 봐보면&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;git cat-file -p c05ce9e&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100644 blob ab983e11d9f77aee6ffc1f1317fa7832dc0e1ee7    README.md&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 커밋에서 작업 된 내용을 볼 수 있습니다.&lt;br /&gt;여기서 해시값을 또 읽어보면 stash했을 때 내용을 볼 수가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stash한 기록은&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;git stash list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 통해 읽어 볼 수 있는데요&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;stash@{0}: WIP on master: 780e259 git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태로 나타납니다.&lt;br /&gt;여기서 stash@{0}은 stash의 이름을 나타낸다고 보면 됩니다. culry brace 안에 숫자는 stash의 index를 나타내는데, 새로 들어오는 stash는 가장 빠른 Index 즉 0이 됩니다. stack 형태로 저장이 되는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 위 내용만 보고서는 어떤 내용을 stash했는지 알기가 어렵죠. 해시값 하나하나 읽어가며 내용을 파악하는 것은 시간 낭비가 큽니다.&lt;br /&gt;따라서 Commit 할때 메시지를 같이 입력하듯, stash도 메시지를 함께 입력할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;git stash push -m &quot;main.swift write&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 입력하면 stash 할때 메시지를 같이 전달할 수 있고, stash list를 통해 보면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;stash@{0}: On master: main.swift write
stash@{1}: WIP on master: 780e259 git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message에 쓴 내용을 볼 수가 있습니다.&lt;/p&gt;
&lt;h1&gt;Stash Apply&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Stash한 내용을 가져와서 적용해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 제가 Feature Branch에 작업할 내용을 실수로 Develope 브랜치에다가 작업을 했다고 했죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Develope 브랜치에서 작업한 내용을 우선 Stash 했다면 Feature Branch로 Checkout 합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;git stash apply stash@{0}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 apply 명령어와 stash 이름으로 현재 브랜치에 stash 내용을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 현재 브랜치의 상태가 stash에 저장한 작업하던 내용을 그대로 불러올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해서 다른 브랜치 커밋을 어지럽히거나!, 수고스럽게 파일을 초기화 했다 붙여넣을 필요가 없어졌습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Stash Clear&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stash를 apply를 통해 작업이 끝났으면, stash를 지워주는게 좋을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stash list를 통째로 날려버리는 명령어는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;git stash clear
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 특정 stash만 지우고 싶다면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;git stash -q drop stash@{0}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0에다 stash를 인덱스만 갈아 끼워넣어주면, 특정 stash를 삭제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raywenderlich: Advanced Git&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://hyuna-tech.tistory.com/entry/github-일종의-임시저장-git-stash-사용법&quot;&gt;https://hyuna-tech.tistory.com/entry/github-일종의-임시저장-git-stash-사용법&lt;/a&gt;&lt;/p&gt;</description>
      <category>General Dev</category>
      <category>branch</category>
      <category>GIT</category>
      <category>gitflow</category>
      <category>stash</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/58</guid>
      <comments>https://doing-programming.tistory.com/entry/Git-%EC%9E%91%EC%97%85%ED%95%98%EB%8D%98-%EB%82%B4%EC%9A%A9-%EB%B8%8C%EB%A0%8C%EC%B9%98-%EC%98%AE%EA%B8%B0%EA%B8%B0-Git-Stash#entry58comment</comments>
      <pubDate>Tue, 12 Apr 2022 00:35:39 +0900</pubDate>
    </item>
    <item>
      <title>SeSAC(청년취업사관학교) iOS 앱 개발자 데뷔 과정 수료+비전공자 취업 후기</title>
      <link>https://doing-programming.tistory.com/entry/SeSAC%EC%B2%AD%EB%85%84%EC%B7%A8%EC%97%85%EC%82%AC%EA%B4%80%ED%95%99%EA%B5%90-iOS-%EC%95%B1-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8D%B0%EB%B7%94-%EA%B3%BC%EC%A0%95-%EC%88%98%EB%A3%8C%EC%B7%A8%EC%97%85-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SeSAC&lt;/b&gt;(청년취업사관학교, 이하 SeSAC)에서 진행한, iOS 앱 개발자 데뷔 과정 수료 및 취업 후기를 써보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 얘기하자면 수료 후 한달만에 꽤 규모있는 회사에 취업했고, 원래 있던 업계에서는 상상도 못할 대우를 받으며 회사를 다니고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우선 본인은 예술대학교에서 4년제를 취득한 예술학사로 배경이 개발과는 완전무관합니다. (다만 전공 특수성 때문에 이과랑 거리가 없지는 않습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;졸업 후 행정 사무직으로 2년, 전공 관련 업무로 1년의 회사생활을 하면서, 비생산적인 일에 실증이 나 있었습니다. 업무를 더 잘하고 싶어서, 사무자동화와 파이썬, C/C++를 공부해서 업무에 자동화를 도입했지만, 본인의 성장과는 거리가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2주 가까이 걸리던 작업을 1~2일로 시간 단축을 해냈지만, 업무 속도가 빠를 뿐 그게 제 커리어가 되지는 못했습니다. 결국은 업계에 대한 불만이지만 자세한 내용은 생략하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;조금이나마 사용했던 개발이 가져다 주었던 성취감과 결과물은 그간 제 고민들의 출구가 되었습니다. 생산적이고, 제가 노력하는 만큼 결과와 커리어를 가져다 줄 수 있는 그런 일이요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 퇴사를 결심했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무턱대로 퇴사를 결정한 것은 아닙니다. 개발자로 취업하는데 걸리는 시간과 비용, 학습, 취업 플랜을 (나름) 철저히 준비한 다음의 퇴사입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;퇴사 후&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 분명한 목표가 중요했는데, 첫번째는 어떤 개발자가 될 것인지 입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발자 분류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 웹 프론트엔드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 백엔드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 안드로이드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- iOS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게임 클라이언트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 게임 서버 개발자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에 나열한 개발자 보다, 훨씬 더 많은 개발자 종류가 있습니다. 어떤 개발자가 있는지는 원티드에서 자세히 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 비전공자 이면서, 빠르게 취업할 수 있는 직군은, 웹 프론트엔드와 안드로이드 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 쪽이 쉬워서 비전공자가 빠르게 취업할 수 있는 것은 아니고, 웹 프론트엔드와 안드로이드는 강의와 레퍼런스가 정말 많아서 배우기가 쉽고, 신입 채용도 비교적 많은 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;저는 그 중에서 iOS 개발자 신입을 선택했는데, 숨고에서 개발자 취업 커리어 컨설팅을 돈주고 받은 적이 있는데, iOS 개발자는 비추하시더라구요... 아무렴 앞서 웹 프론트엔드와 안드로이드를 추천한 이유와 딱 정반대의 이유를 가지고 있습니다. 강의와 레퍼런스가 부족하고, 주변에서도 잘 모를 뿐더러, 신입 수요도 진짜 없는 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 개발자 직군 선택은 여건에 맞게 선택하시면 될 것 같습니다. 주변에서 어렵다고 해도 본인이 정말 그 직군이 하고싶고, 자신이 있으시면 선택 하셔도 전혀 문제 없을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;왜냐하면 여기서 다시 &lt;b&gt;SeSAC&lt;/b&gt; 얘기가 나오는데요, 들어가서 보시면 아시겠지만, 웹 프론트부터 안드로이드, iOS(백엔드는 안보이네요) 등 양질의 교육과정이 준비 되어있습니다. 지금은 작년 사업을 종료해서 표시가 안되는 교육과정이 있는데, 관심있는 분야의 교육과정을 열릴 계획이 있는지 기관에 문의 해보는게 좋을 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;iOS 앱 개발자 데뷔 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;제가 SeSAC에서 진행한 교육과정은 iOS 앱 개발자 데뷔 과정으로, 9월 27일 부터 3월 8일 까지 총 6개월 간 교육을 실시 했습니다. 지금은 교육이 종료되어서 홈페이지에서 교육과정을 볼 수 없는데, 제가 볼 때 여타 유료 교육과정과 비교해도 취업하기에 필요한 역량을 충분히 갖출 수 있는 교육과정이라고 생각했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;교육 과정은 다른 사설 교육 기관(국비지원 아닌 교육기관!) 과 비교해서 보면, 얼마나 실무적인지 파악할 수 있을 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;iOS앱 개발 교육 같은 경우는, 사설 교육비가 굉장히 비싸고, (6개월에 700정도 합니다..) 기회도 많지 않습니다. 하지만 &lt;b&gt;iOS 앱 개발자 데뷔 과정&lt;/b&gt;은 '무료' 교육과정입니다. (처음에 15만원인가? 돈을 내고 수료하면 100% 돌려받습니다!) (대신 맥북이라는 투자금이 별도로 든다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교육과정 합격 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신청은 자기소개서 제출과 코딩 테스트를 보았던 것으로 기억합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서류 심사 -&amp;gt; 코딩 테스트 -&amp;gt; 면접 심사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 총 3단계에 걸쳐 심사를 하고, 최종적으로 선발되면 교육에 참여하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;서류 합격 후 코딩 테스트 까지 시간이 꽤 되므로 Swift에 대해서 전혀 몰라도 충분히 합격할 수 있을 만큼 공부할 시간이 됩니다! 저는 해당 교육과정 이전에 독학으로 6개월간 iOS와 Swift를 공부했기 때문에 크게 어렵지는 않았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트에 합격하면 면접을 실시합니다. 저는 코로나 때문에 면접을 온라인 미팅으로 보았네요! 굉장히 무서웠습니다. 면접 준비도 꼭 따로 하시는게 좋다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;교육&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교육은 Swift, iOS, CS 를 주차 교육 계획에 맞게 실시했습니다. 코로나 때문에 Zoom을 통해서 수업을 진행했습니다. 오전은 Zoom으로 수업을 듣고 오후에는 당일 과제를 수행했는데 여기부터는 개인이 하는만큼 공부를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대학 교수가 학생들에게 수업을 가르치는 탑-다운 방식이 아니라, 스스로 공부한 만큼 멘토링을 받을 수 있는 바텀-업 방식의 교육이기 때문입니다.(교육 용어가 있었는데 기억이 안나네요!) 플립드 러닝이기도 한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;만약 강사님들이 단순한 티쳐 였다면, 불가능 했을 얘기 인 것 같습니다. 저는 6개월의 독학 기간 중 가장 힘들었던 것이, 제가 지금 바른 방향으로 가고 있는지 스스로 점검할 수 없다는 점이었습니다. iOS가 아무리 강의랑 레퍼런스가 부족하다고 하지만, 사실 영어로 검색하면 다 나옵니다. 애초에 Apple에서 자기내 OS앱 만들어서 내라고 A to Z부터 다 알려줍니다. 그러니까 결국 중요한 것은 '하는 법'이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;9시부터 12시 까지 수업을 듣고, 점심먹고 나면 밤새도록 그날 배운거 정리하고, 디벨롭 하고, 과제를 수행합니다. 그러다 보면 이미 해 뜰 시간이고 컴퓨터 앞에서 기절하고 일어나서 수업듣고, 그렇게 공부 지옥을 6개월을 지냈네요. 어떻게 공부하고 기록했는지는 제 깃헙에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/SeSAC-Assignments&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/urijan44/SeSAC-Assignments&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1649262032396&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - urijan44/SeSAC-Assignments: SeSAC과정 Assignmnets&quot; data-og-description=&quot;SeSAC과정 Assignmnets. Contribute to urijan44/SeSAC-Assignments development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/urijan44/SeSAC-Assignments&quot; data-og-url=&quot;https://github.com/urijan44/SeSAC-Assignments&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bqgq9q/hyNVIK4q5k/kRpJCDnG6Hp9985ZoU8NWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/SeSAC-Assignments&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/urijan44/SeSAC-Assignments&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bqgq9q/hyNVIK4q5k/kRpJCDnG6Hp9985ZoU8NWk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - urijan44/SeSAC-Assignments: SeSAC과정 Assignmnets&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SeSAC과정 Assignmnets. Contribute to urijan44/SeSAC-Assignments development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(지금 보면 진짜 쉬운 것 밖에 없네요! 그만큼 왠만한 개발은 다 할 수 있다는 자신감이 생겼습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 같은 수강생 끼리 별도의 코테 스터디를 진행 하기도 했고, 나중에는 팀 단위로 코드 리뷰를 하거나 협업을 하기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS, Swift 멘토링 외에도 공부가 너무 힘들 때는 공부 멘토링을 받기도 했고 자소서, 포폴 첨삭, 모의 면접도 수차례나 받았습니다. 힘들었지만, 사설 학원에서 돈주고 받기도 힘든걸 진짜 엄청 많이 받아서, 멘토님들 한테 너무너무 감사하다는 말 전하고싶네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취업까지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 취업은 10월 쯤에 이미 최종합격을 두 군데 성공 했습니다. 교육과정을 통해서 배운 것대로 면접에서 쪽집게 처럼 나온 것도 아주 큰 역할을 한 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이때는, 아직 스스로 실력에 자신이 없었고 더 공부하고 싶다는 생각이 커서 입사를 거절하고 3월 최종 수료까지 진행하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2월 부터 본격적으로 취업 활동을 시작한 것 같은데, 시기가 시기인 만큼 경쟁자가 꽤 많았습니다. 당장 대학 졸업 후 취업전선에 뛰어드는 컴공생 전공자들과, 같은 수료생!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토님들의 도움 + 운 덕분에 추가로 4군데 더 최종 합격해서 그 중에서 가장 큰 회사로 취업 하게 되었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 준비할 때 주변에서 iOS도 비추천 했고, 본 교육과정도 비추천 했습니다. 하지만 너무 좋은 기회였고, 잘했다는 생각이 듭니다. 최고의 6개월 이었던 것 같습니다. 비전공자 이지만 개발자로 취업 예정이 있다면 진짜 좋은 선택지라고 생각합니다. 조금이라도 생각이 있다면 망설이지 말고 꼭 도전해보셨으면 좋겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 비전공자 인데, 개발자 취업을 준비하고 있고 고민이 있다면 댓글 남겨주세요!&lt;/p&gt;</description>
      <category>General Dev</category>
      <category>IOS</category>
      <category>SeSAC</category>
      <category>개발자</category>
      <category>비전공자</category>
      <category>청년취업사관학교</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/57</guid>
      <comments>https://doing-programming.tistory.com/entry/SeSAC%EC%B2%AD%EB%85%84%EC%B7%A8%EC%97%85%EC%82%AC%EA%B4%80%ED%95%99%EA%B5%90-iOS-%EC%95%B1-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%8D%B0%EB%B7%94-%EA%B3%BC%EC%A0%95-%EC%88%98%EB%A3%8C%EC%B7%A8%EC%97%85-%ED%9B%84%EA%B8%B0#entry57comment</comments>
      <pubDate>Wed, 6 Apr 2022 23:20:43 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode - Merge Two Sorted Lists, Swift</title>
      <link>https://doing-programming.tistory.com/entry/LeetCode-Merge-Two-Sorted-Lists-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/merge-two-sorted-lists/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://leetcode.com/problems/merge-two-sorted-lists/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648295962806&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Merge Two Sorted Lists - LeetCode&quot; data-og-description=&quot;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&quot; data-og-host=&quot;leetcode.com&quot; data-og-source-url=&quot;https://leetcode.com/problems/merge-two-sorted-lists/&quot; data-og-url=&quot;https://leetcode.com/problems/merge-two-sorted-lists/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/j3Oz0/hyNPJ9v4N4/Q8nQa8a2N9uakU9ZADjU2K/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/dGO6rR/hyNPJBEPm8/Yq9Z8SHjYMTALDrk0VenM1/img.jpg?width=662&amp;amp;height=302&amp;amp;face=0_0_662_302&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/merge-two-sorted-lists/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leetcode.com/problems/merge-two-sorted-lists/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/j3Oz0/hyNPJ9v4N4/Q8nQa8a2N9uakU9ZADjU2K/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/dGO6rR/hyNPJBEPm8/Yq9Z8SHjYMTALDrk0VenM1/img.jpg?width=662&amp;amp;height=302&amp;amp;face=0_0_662_302');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Merge Two Sorted Lists - LeetCode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;leetcode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 리트코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 두개의 `정렬된` 링크드 리스트를 하나의 정렬된 리스트로 만들어 내는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본 알고리즘은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OtMAH/btrxrcHBhm0/IbdaFvGUiBOZfjFuKtRc80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OtMAH/btrxrcHBhm0/IbdaFvGUiBOZfjFuKtRc80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OtMAH/btrxrcHBhm0/IbdaFvGUiBOZfjFuKtRc80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOtMAH%2FbtrxrcHBhm0%2FIbdaFvGUiBOZfjFuKtRc80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;334&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 두개의 링크드 리스트가 있으면 각 리스트를 노드를 가리키는 참조 인스턴스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Left와 Right의 CurrentNode의 Val중 누가 더 작은지 대소 비교를 해서 더 작은 노드를 Head의 next Node로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCaJXK/btrxlrlsvJK/cSOK7IBzJjzrT1hqJrYBFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCaJXK/btrxlrlsvJK/cSOK7IBzJjzrT1hqJrYBFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCaJXK/btrxlrlsvJK/cSOK7IBzJjzrT1hqJrYBFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCaJXK%2FbtrxlrlsvJK%2FcSOK7IBzJjzrT1hqJrYBFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;334&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 Left의 CurrentNode는 1이고, Right의 CurrentNode 3이므로 또 3과 1을 대소 비교 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NGuIC/btrxmAoVkoS/sXBQDDdlhaWgx6HTf9VL60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NGuIC/btrxmAoVkoS/sXBQDDdlhaWgx6HTf9VL60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NGuIC/btrxmAoVkoS/sXBQDDdlhaWgx6HTf9VL60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNGuIC%2FbtrxmAoVkoS%2FsXBQDDdlhaWgx6HTf9VL60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;324&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 다음 노드가 없을 때 까지 반복 해주면 된다. 아주 간단!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1648296727964&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     public var val: Int
 *     public var next: ListNode?
 *     public init() { self.val = 0; self.next = nil; }
 *     public init(_ val: Int) { self.val = val; self.next = nil; }
 *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
 * }
 */
class Solution {
    func mergeTwoLists(_ list1: ListNode?, _ list2: ListNode?) -&amp;gt; ListNode? {
        
        var leftNode = list1
        var rightNode = list2

        var head: ListNode? = ListNode(101)
        var currentNode: ListNode? = head
        while let node = currentNode {
            if leftNode != nil, rightNode != nil {
                if leftNode!.val &amp;lt; rightNode!.val {
                    node.next = leftNode
                    leftNode = leftNode?.next
                } else {
                    node.next = rightNode
                    rightNode = rightNode?.next
                }
            } else {
                node.next = leftNode != nil ? leftNode : rightNode
                break
            }
            currentNode = node.next
        }
        return head?.next
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>leetcode</category>
      <category>LinkedList</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/56</guid>
      <comments>https://doing-programming.tistory.com/entry/LeetCode-Merge-Two-Sorted-Lists-Swift#entry56comment</comments>
      <pubDate>Sat, 26 Mar 2022 21:12:24 +0900</pubDate>
    </item>
    <item>
      <title>BOJ1158 - 요세푸스 문제, Swift</title>
      <link>https://doing-programming.tistory.com/entry/BOJ1158-%EC%9A%94%EC%84%B8%ED%91%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9C-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1158&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/1158&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648218059825&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;1158번: 요세푸스 문제&quot; data-og-description=&quot;첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 &amp;le; K &amp;le; N &amp;le; 5,000)&quot; data-og-host=&quot;www.acmicpc.net&quot; data-og-source-url=&quot;https://www.acmicpc.net/problem/1158&quot; data-og-url=&quot;https://www.acmicpc.net/problem/1158&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/O7ePF/hyNPEfzKqF/IcsC7PmS3X0nlNbxhKPTnk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1158&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.acmicpc.net/problem/1158&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/O7ePF/hyNPEfzKqF/IcsC7PmS3X0nlNbxhKPTnk/img.png?width=2834&amp;amp;height=1480&amp;amp;face=0_0_2834_1480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;1158번: 요세푸스 문제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 &amp;le; K &amp;le; N &amp;le; 5,000)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.acmicpc.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수가 작아서, remove(at:)을 써도 되는 문제, 만약 n이 커진다면, 링크드리스트나, 다른 접근 방법 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648218761203&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func boj1158() {
  let input = readLine()!.split(separator: &quot; &quot;).map{ Int(String($0))! }
  let (n, k) = (input[0], input[1])

  var arr: [Int] = .init(1...n)
  var count = k - 1

  var result = &quot;&amp;lt;&quot;

  while arr.count &amp;gt; 0 {
    count = count &amp;gt;= arr.count ? count % arr.count : count

    result.write(&quot;\(arr.remove(at: count)), &quot;)
    count += (k - 1)
  }

  result.removeLast(2)
  result.write(&quot;&amp;gt;&quot;)
  print(result)
}

boj1158()&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/55</guid>
      <comments>https://doing-programming.tistory.com/entry/BOJ1158-%EC%9A%94%EC%84%B8%ED%91%B8%EC%8A%A4-%EB%AC%B8%EC%A0%9C-Swift#entry55comment</comments>
      <pubDate>Fri, 25 Mar 2022 23:33:34 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 등굣길, Cpp</title>
      <link>https://doing-programming.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%93%B1%EA%B5%A3%EA%B8%B8-Cpp</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/42898&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/42898&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1647879153763&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 등굣길&quot; data-og-description=&quot;계속되는 폭우로 일부 지역이 물에 잠겼습니다. 물에 잠기지 않은 지역을 통해 학교를 가려고 합니다. 집에서 학교까지 가는 길은 m x n 크기의 격자모양으로 나타낼 수 있습니다. 아래 그림은 m =&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42898&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42898&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dclQ47/hyNNjP4K4c/ciwoiWyDvGrs9KY4gg4S70/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/oapLz/hyNM6DcpwE/OwhRTiePbVMysZaI1qlmdk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/42898&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42898&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dclQ47/hyNNjP4K4c/ciwoiWyDvGrs9KY4gg4S70/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/oapLz/hyNM6DcpwE/OwhRTiePbVMysZaI1qlmdk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 등굣길&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;계속되는 폭우로 일부 지역이 물에 잠겼습니다. 물에 잠기지 않은 지역을 통해 학교를 가려고 합니다. 집에서 학교까지 가는 길은 m x n 크기의 격자모양으로 나타낼 수 있습니다. 아래 그림은 m =&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목표 지점까지 가장 빠르게 도착하는 패스가 몇 개인지 를 물어보는 문제로, 문제 유형이 DP로 이미 공개되어있어서 쉽게 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선 m x n을 row x col 좌표로 두고, 시작점 0,0을 1로 초기화 나머지를 0으로 초기화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. puddles이 있는 경우는 -1로 초기화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 문제가 오른쪽과 아래쪽으로만 움직일 수 있기 때문에, 패턴을 단순화 할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 25.2732%; height: 142px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 41.7143%;&quot;&gt;row/col&lt;/td&gt;
&lt;td style=&quot;width: 22.5714%;&quot;&gt;m1&lt;/td&gt;
&lt;td style=&quot;width: 24.5714%;&quot;&gt;m2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 41.7143%;&quot;&gt;n1&lt;/td&gt;
&lt;td style=&quot;width: 22.5714%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 24.5714%;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 41.7143%;&quot;&gt;n2&lt;/td&gt;
&lt;td style=&quot;width: 22.5714%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 24.5714%;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP의 특성상 큰 문제를 작은 문제로 나누어서 생각해 볼 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;row를 바깥 루프, col을 안쪽 루프로 이중 for 문을 돌면서 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 (0,0)에서 다음 좌표는 오른쪽 또는 아래로 (1,0) 또는 (0,1)이다. 각 좌표로 갈 수 있는 방법은 방금 0,0에서 접근한 한가지 방법 뿐으로 아래처럼 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;row:0, col:0&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 25.2732%; height: 142px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;row/col&lt;/td&gt;
&lt;td&gt;m1&lt;/td&gt;
&lt;td&gt;m2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 col인 (0,1)에서 다시 시작해서 다음 좌표를 보면, (1, 1) 이 유일한데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 여기서 (0,1)에서 (1,1)로 접근했으로 (1,1)도 1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;row:0, col:1&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 25.2732%; height: 142px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;row/col&lt;/td&gt;
&lt;td&gt;m1&lt;/td&gt;
&lt;td&gt;m2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 col이 끝났으므로 col을 다시 0으로 초기화 하고 row를 1증가 시키면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;row:1, col:0&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 25.2732%; height: 142px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;row/col&lt;/td&gt;
&lt;td&gt;m1&lt;/td&gt;
&lt;td&gt;m2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1,0)에서 다음 좌표는 (1,1)로 갈 수 있는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 (0,1)에서 접근해서 이미 (1,1)은 1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 더해서 (1,0)에서 접근할 수 있기 때문에 (1,1)은 최종적으로 2가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한가지 의문점은, 모든 방법이 그럼 가장 빠른 방법일까 하는 것인데? 본 문제에서 아래-오른쪽 으로만 이동해보면, 도착 거리가 다 똑같다는 것을 알 수 있다. 따라서 앞서 한 작은 문제를 확장시켜 적용하기만 하면 문제는 풀린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647879964522&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int solution(int m, int n, vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; puddles) {
    const int mod = 1000000007;
    int dp[n][m];
    for (int r = 0; r &amp;lt; n; r++) {
        for (int c = 0; c &amp;lt; m; c++) {
            dp[r][c] = 0;
        }
    }
    for (int i = 0; i &amp;lt; puddles.size(); i++) {
        dp[puddles[i][1] - 1][puddles[i][0] - 1] = -1;
    }
    
    dp[0][0] = 1;
    int dx[2] = {0, 1};
    int dy[2] = {1, 0};
    
    for (int row = 0; row &amp;lt; n; row++) {
        for (int col = 0; col &amp;lt; m; col++) {
            if (dp[row][col] == -1) { continue; }
            for (int index = 0; index &amp;lt; 2; index++) {
                int nx = row + dx[index];
                int ny = col + dy[index];
                if(nx &amp;gt;= n || ny &amp;gt;= m || (dp[nx][ny] == -1)) { continue; }
                dp[nx][ny] = (dp[row][col] + dp[nx][ny]) % mod;
            }
                
        }
    }
    return dp[n-1][m-1];
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <category>cpp</category>
      <category>등굣길</category>
      <category>코테</category>
      <category>프로그래머스</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/53</guid>
      <comments>https://doing-programming.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%93%B1%EA%B5%A3%EA%B8%B8-Cpp#entry53comment</comments>
      <pubDate>Tue, 22 Mar 2022 01:26:25 +0900</pubDate>
    </item>
    <item>
      <title>iOS - URLCache</title>
      <link>https://doing-programming.tistory.com/entry/iOS-URLCache</link>
      <description>&lt;h1&gt;소개&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cache(캐시)는 컴퓨팅에서 데이터를 일시적으로 저장하는 것을 뜻한다.&lt;br&gt;어플리케이션에서 캐시는 요긴하게 쓰이는데, 예를 들어 네트워크 요청을 할 때, 한 번 요청한 결과에 대해서 캐시를 해두고, 다음에 해당 요청을 한번 더 할 때 네트워크 요청을 하지 않고 캐시에서 결과를 가져 올 수 있다. 정리하자면 아래와 같은 장점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
 &lt;li&gt;클라이언트에서는 요청 결과를 훨씬 더 빨리 화면에 보여줄 수 있다.&lt;/li&gt;
 &lt;li&gt;서버에서는 동일한 요청에 대해 동작을 중복 수행하지 않아도 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;URLCache&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서 네트워크 요청을 하기 위해서는 URLSession이라는 API를 사용하는데, URLSession의 Task에는 대표적으로 몇가지 종류가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;DataTask&lt;/li&gt;
 &lt;li&gt;DownloadTask&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Steam이나, Socket, Upload는 예외하겠다.&lt;br&gt;두 Task는 모두 요청 결과를 받아오는 동작을 수행할 수 있는데, 조금 다른 결과를 가져온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;DataTask: Cache정책에 따라 memory와 disk에 동시에 cache한다. 시간이 지나면 memory에서는 해제된다.&lt;/li&gt;
 &lt;li&gt;DownloadTask: 다운로드 한 파일을 메모리에 Cache하고, disk에는 cache하지 않는다. (Tep Directory에 저장하고 필요가 없어지면 지운다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession은 고맙게도 default 구성이 cache를 자동으로 실시한다.&lt;br&gt;Cache된 데이터는 Library/Caches~ 에 저장되며, 클라이언트에서 같은 네트워크 요청이 있을 때, 별도의 코드없이 자동으로 Cache를 확인하고, 디스크에서 불러온다.&lt;br&gt;하지만 이때, 모든 데이터가 Cache되는 것은 아닌데, URLSession는 URLCache에 의존하고 있는데, URLCache가 default로 가지고 있는 메모리, 디스크 용량 제한이 있다.&lt;br&gt;이때 용량 제한은 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;Memory Capacity: 4MB&lt;/li&gt;
 &lt;li&gt;Disk Capacity: 20MB&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도로 지정해 주지 않으면, 큰 파일은 자동으로 Cache되지 않는다.&lt;br&gt;이를 해결하는 방법은 URLCache 인스턴스를 새로 생성해 URLSession 프로퍼티에 할당하거나, URLCache에 store메서드를 통해 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let ownCache = URLCache(memoryCapacity: 20*1024*1024*1024, diskCapacity: 80*1024*1024*1024, directory: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent(&quot;DownloadImage&quot;))

private lazy var session: URLSession = {
    let configuration = URLSessionConfiguration.default
    configuration.urlCache = ownCache
    return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
  }()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSessionConfiguration을 통해 URLCache 인스턴스를 전달할 수 있다.&lt;br&gt;이 방법은 정해진 용량 아래서, session의 데이터 받아오기가 끝나면 자동으로 Cache가 된다.&lt;br&gt;혹은 데이터 다운로드가 완료된 시점에서&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;ownCache.storeCachedResponse(.init(response: task.response!, data: self.data!), for: task.currentRequest!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storeCachedResponse를 통해서 데이터를 저장할 수 있다.&lt;br&gt;만약 별도의 URLCache 인스턴스를 URLSession으로 전달하지 않았다면 URLCache는 싱글톤 인스턴스를 제공하기 때문에 이를 이용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;URLCache.shared.storeCachedResponse(.init(response: task.response!, data: self.data!), for: task.currentRequest!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DownloadTask는 앞서 Cache 하지 않는다고 얘기했는데, 위와 같이 동일한 방법으로 Cache 할 수 있다. 다른 방법이라면 DownloadTask는 데이터를 디스크에 쓴 다음 Location URL을 제공하는 것만 다를 뿐이다.&lt;/p&gt;
&lt;h1&gt;DataTask vs DownloadTask&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataTask와 DownloadTask는 각각 언제 사용하는 것이 좋을까?&lt;br&gt;Cache는 언제 Cache하고, 삭제하고, 업데이트 하는지에 대한 내용은 Cache Policy를 따른다.&lt;br&gt;이게 왜 중요하냐면&lt;br&gt;URLSessionDataTask는 자동으로 Data를 Cache한다고 앞서 얘기했는데, 예를들어 Daum 검색 API를 URLSessionDataTask로 요청을 보냈다고 한다면, API는 검색 결과를 Data로 보낼 것이다.&lt;br&gt;이때 Cache 덕분에 동일한 요청 결과에 대해서는 API에 추가적으로 요청하지 않고, 클라이언트에서 빠르게 보여줄 수 있지만, 만약 잠깐 사이에 검색 결과가 변경이 되었을 가능성이 있는데, 클라이언트에서는 Cache 때문에 해당 요청에 대해 업데이트 Data를 받아오지 않고, Cache한 Data를 보여줄 수 있다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOVMqK/btrwDPtzgHB/E8BrVmmshcK2UkPGugbpKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOVMqK/btrwDPtzgHB/E8BrVmmshcK2UkPGugbpKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOVMqK/btrwDPtzgHB/E8BrVmmshcK2UkPGugbpKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOVMqK%2FbtrwDPtzgHB%2FE8BrVmmshcK2UkPGugbpKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;773&quot; height=&quot;418&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 결과가 나타내는 바는, Cache는 특정 시점을 선택하고, 시점에 따라 삭제하고 업데이트 해야 한다는 것이다. 그 시점은 특정 기간일 수도 있고, 애플리케이션이 종료되는 시점일 수도 있다. 클라이언트 입장에서 Cache는 언제 삭제되어도 이상하지 않은 기록 방법인 것이다.&lt;br&gt;반면 DownloadTask는 그 기능을 보면 알지만 더 큰 Data를 받는데 특화되어 있다. 예를들어 Background 다운로드, Data 이어서 받기와 같은...&lt;br&gt;이러한 큰 Data는 다운받은데 큰 네트워크 리소스를 사용하므로 클라이언트가 종료되었다고 삭제할 수는 없다.&lt;br&gt;또 다운로드 한 Data의 목적에 따라 저장되어야 하는 Directory가 다르기 때문에, 마찬가지로 구분 되어야 한다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c29pPN/btrwA5Kc0J5/bbJx1WiqL1dF7Dx1vhskjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c29pPN/btrwA5Kc0J5/bbJx1WiqL1dF7Dx1vhskjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c29pPN/btrwA5Kc0J5/bbJx1WiqL1dF7Dx1vhskjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc29pPN%2FbtrwA5Kc0J5%2FbbJx1WiqL1dF7Dx1vhskjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1067&quot; height=&quot;600&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
 &lt;li&gt;&lt;a href=&quot;https://melod-it.gitbook.io/sagwa/app-frameworks/foundation/url-loading-system/accessing-cached-data&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://melod-it.gitbook.io/sagwa/app-frameworks/foundation/url-loading-system/accessing-cached-data&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlcache&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/foundation/urlcache&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/foundation/url_loading_system/accessing_cached_data&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://pspdfkit.com/blog/2020/downloading-large-files-with-urlsession/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://pspdfkit.com/blog/2020/downloading-large-files-with-urlsession/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://nshipster.com/nsurlcache/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://nshipster.com/nsurlcache/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://ichi.pro/ko/apple-ui-urlrequest-kaesi-jeongchaeg-e-daehae-jasehi-al-abogi-272417068332696&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://ichi.pro/ko/apple-ui-urlrequest-kaesi-jeongchaeg-e-daehae-jasehi-al-abogi-272417068332696&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href=&quot;https://web.dev/http-cache/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://web.dev/http-cache/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;HTTP Cache로 불필요한 네트워크 요청 방지&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;불필요한 네트워크 요청을 어떻게 피할 수 있습니까? 브라우저의 HTTP 캐시는 첫 번째 방어선입니다. 이 방법이 반드시 가장 강력하고 유연한 방법은 아니며 캐시된 응답의 수명을 제한적으로 제&quot; data-og-host=&quot;web.dev&quot; data-og-source-url=&quot;https://web.dev/http-cache/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/N3A9Q/hyNNdCzxQ7/Y9Ip3xwVVQ4Drl4byR0jKK/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot; data-og-url=&quot;https://web.dev/i18n/ko/http-cache/&quot;&gt;
 &lt;a href=&quot;https://web.dev/i18n/ko/http-cache/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://web.dev/http-cache/&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/N3A9Q/hyNNdCzxQ7/Y9Ip3xwVVQ4Drl4byR0jKK/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;HTTP Cache로 불필요한 네트워크 요청 방지&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;불필요한 네트워크 요청을 어떻게 피할 수 있습니까? 브라우저의 HTTP 캐시는 첫 번째 방어선입니다. 이 방법이 반드시 가장 강력하고 유연한 방법은 아니며 캐시된 응답의 수명을 제한적으로 제&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;web.dev&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Cache Handling with Moya&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;We have implemented Moya, RxSwift And Alamofire as pods in our project. Does anyone know how you gain control of the cache policies per url request using this tech? I have read through quite a ...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b5KGjh/hyNNo5fftE/h35EgxcT4Dlj9vahCbcD80/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot; data-og-url=&quot;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&quot;&gt;
 &lt;a href=&quot;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&quot; target=&quot;_blank&quot; data-source-url=&quot;https://stackoverflow.com/questions/48516806/cache-handling-with-moya&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b5KGjh/hyNNo5fftE/h35EgxcT4Dlj9vahCbcD80/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Cache Handling with Moya&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;We have implemented Moya, RxSwift And Alamofire as pods in our project. Does anyone know how you gain control of the cache policies per url request using this tech? I have read through quite a ...&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;stackoverflow.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nshipster.com/nsurlcache/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://nshipster.com/nsurlcache/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/52</guid>
      <comments>https://doing-programming.tistory.com/entry/iOS-URLCache#entry52comment</comments>
      <pubDate>Mon, 21 Mar 2022 16:45:44 +0900</pubDate>
    </item>
    <item>
      <title>2022 KAKAO BLIND RECRUITMENT - 양궁대회</title>
      <link>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%EC%96%91%EA%B6%81%EB%8C%80%ED%9A%8C</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92342&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/92342&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647571389364&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 양궁대회&quot; data-og-description=&quot;문제 설명 카카오배 양궁대회가 열렸습니다. 라이언은 저번 카카오배 양궁대회 우승자이고 이번 대회에도 결승전까지 올라왔습니다. 결승전 상대는 어피치입니다. 카카오배 양궁대회 운영위원&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92342&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92342&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eNuDn/hyNJ5xNOw5/p5E9fuTeOBaOdTl2srUGc1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/vjgRN/hyNI3hjIrx/EdZbaZrHEqKQvgLAnQaqs1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92342&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92342&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eNuDn/hyNJ5xNOw5/p5E9fuTeOBaOdTl2srUGc1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/vjgRN/hyNI3hjIrx/EdZbaZrHEqKQvgLAnQaqs1/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 양궁대회&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 설명 카카오배 양궁대회가 열렸습니다. 라이언은 저번 카카오배 양궁대회 우승자이고 이번 대회에도 결승전까지 올라왔습니다. 결승전 상대는 어피치입니다. 카카오배 양궁대회 운영위원&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 너무 무식하게 접근했는데, 다른 사람 풀이보고 공부해야 겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 기본적으로 bfs 접근 방식이니, 라이언의 info를 set형태로 visited를 체크하면 속도는 좀 올라갈듯.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647571382658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
let scoreBoard = [10,9,8,7,6,5,4,3,2,1,0]

struct Queue {
  var leftStack: [Game] = []
  var rightStack: [Game] = []
  var appeachInfo: [Int] = []
  var maxScoreGame: [Int] = [-1]
  var maxScore: Int = -1

  var count: Int {
    leftStack.count + rightStack.count
  }

  mutating func enqueue(_ element: Game) {
    leftStack.append(element)
    maxScoreCalculating(element.info)
  }

  mutating private func maxScoreCalculating(_ lyonGame: [Int]) {
    let (appeahScore, lyonScore) = scoreCalculating(appeachInfo, lyonGame)
    let scoreDifference = lyonScore - appeahScore
    if scoreDifference &amp;gt; 0,
        maxScore &amp;lt; scoreDifference {
      maxScore = scoreDifference
      maxScoreGame = lyonGame
    } else if scoreDifference &amp;gt; 0, maxScore == scoreDifference {

      for (previous, new) in zip(maxScoreGame, lyonGame).reversed() {
        if new &amp;gt; previous {
          maxScoreGame = lyonGame
          return
        } else if new &amp;lt; previous {
          return
        }
      }
    }
  }

  mutating func dequeue() -&amp;gt; Game? {
    if rightStack.isEmpty {
      rightStack = leftStack.reversed()
      leftStack.removeAll()
    }
    return rightStack.popLast()
  }

  func scoreCalculating(_ appeach: [Int], _ lyon: [Int]) -&amp;gt; (appeach: Int, lyon: Int) {
    var appeachTotalScore = 0
    var lyonTotalScore = 0

    for (index, (appeachArrow, lyonArrow)) in zip(appeach, lyon).enumerated() {
      if appeachArrow != 0 &amp;amp;&amp;amp; appeachArrow &amp;gt;= lyonArrow {
        appeachTotalScore += scoreBoard[index]
      } else if lyonArrow != 0 {
        lyonTotalScore += scoreBoard[index]
      }
    }
    return (appeachTotalScore, lyonTotalScore)
  }
}

struct Game {

  var remainArrow: Int
  var info: [Int] = .init(repeating: 0, count: 11)
  var index: Int = 0

  mutating func shot(_ amountOfArrow: Int) {
    defer {
      index += 1
    }

    guard remainArrow &amp;gt;= amountOfArrow,
            amountOfArrow &amp;gt; 0 else {
      info[index] = index == 10 ? remainArrow : 0
      return
    }
    info[index] = amountOfArrow
    remainArrow -= amountOfArrow
    return
  }

  init(remainArrow: Int) {
    self.remainArrow = remainArrow
  }

  init(game: Game) {
    self.remainArrow = game.remainArrow
    self.info = game.info
    self.index = game.index
  }
}

func solution(_ n:Int, _ info:[Int]) -&amp;gt; [Int] {
  var queue = Queue()
  queue.appeachInfo = info
  queue.enqueue(Game(remainArrow: n))
  for arrow in info {
    for _ in 0..&amp;lt;queue.count {
      guard let element = queue.dequeue() else { break }

      var shot = Game(game: element)
      var dontShot = Game(game: element)

      shot.shot(arrow + 1)
      dontShot.shot(0)

      queue.enqueue(shot)
      queue.enqueue(dontShot)
    }
  }
  return queue.maxScoreGame
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/51</guid>
      <comments>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%EC%96%91%EA%B6%81%EB%8C%80%ED%9A%8C#entry51comment</comments>
      <pubDate>Fri, 18 Mar 2022 11:44:20 +0900</pubDate>
    </item>
    <item>
      <title>2022 KAKAO BLIND RECRUITMENT -  k진수에서 소수 개수 구하기, Swift</title>
      <link>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%08k%EC%A7%84%EC%88%98%EC%97%90%EC%84%9C-%EC%86%8C%EC%88%98-%EA%B0%9C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92335&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/92335&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647352769632&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - k진수에서 소수 개수 구하기&quot; data-og-description=&quot;문제 설명 양의 정수 n이 주어집니다. 이 숫자를 k진수로 바꿨을 때, 변환된 수 안에 아래 조건에 맞는 소수(Prime number)가 몇 개인지 알아보려 합니다. 0P0처럼 소수 양쪽에 0이 있는 경우 P0처럼 소&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92335&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92335&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HXCQZ/hyNJgFTXw9/IWuuZ4IFDUx1N4uRkLC8S0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/9oHWc/hyNI9z0k0y/ZCwyw8xiMphq7Puk9ZIv3k/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92335&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92335&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HXCQZ/hyNJgFTXw9/IWuuZ4IFDUx1N4uRkLC8S0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/9oHWc/hyNI9z0k0y/ZCwyw8xiMphq7Puk9ZIv3k/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - k진수에서 소수 개수 구하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 설명 양의 정수 n이 주어집니다. 이 숫자를 k진수로 바꿨을 때, 변환된 수 안에 아래 조건에 맞는 소수(Prime number)가 몇 개인지 알아보려 합니다. 0P0처럼 소수 양쪽에 0이 있는 경우 P0처럼 소&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 입력받은 숫자를 k진수로 바꾸고, 변환된 정수에서 조건에 맞는 소수를 찾아내는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 0P0 조건을 4줄이나 설명되어 있는데, 잘 생각해보면 그냥 0을 기점으로 수를 나눠버리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 소수를 구하는 가장 빠른 방법을 생각해보면 문제는 쉽게 풀린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647352852636&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

func solution(_ n:Int, _ k:Int) -&amp;gt; Int {
  var result = 0
  var primeSet: Set&amp;lt;String&amp;gt; = []
  let kradix = String(n, radix: k, uppercase: true)
  var stack: String = &quot;&quot;

  func stackCheck(_ stack: inout String) {
    guard !stack.isEmpty else { return }
    if primeSet.contains(stack) {
      result += 1
    } else if isPrime(stack) {
      result += 1
      primeSet.insert(stack)
    }
    stack = &quot;&quot;
  }

  for number in kradix {
    if number != &quot;0&quot; {
      stack.write(String(number))
    } else {
      stackCheck(&amp;amp;stack)
    }
  }
  stackCheck(&amp;amp;stack)
  return result
}

func isPrime(_ number: String) -&amp;gt; Bool {
  let number = Int(number)!
  return number.isPrime
}

extension Int {
  var isPrime: Bool {
    guard self &amp;gt;= 2 else { return false }
    guard self != 2 else { return true  }
    guard self % 2 != 0 else { return false }
    return !stride(from: 3, through: Int(sqrt(Double(self))), by: 2).contains { self % $0 == 0 }
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/50</guid>
      <comments>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%08k%EC%A7%84%EC%88%98%EC%97%90%EC%84%9C-%EC%86%8C%EC%88%98-%EA%B0%9C%EC%88%98-%EA%B5%AC%ED%95%98%EA%B8%B0-Swift#entry50comment</comments>
      <pubDate>Tue, 15 Mar 2022 23:01:18 +0900</pubDate>
    </item>
    <item>
      <title>2022 KAKAO BLIND RECRUITMENT -  주차요금 계산하기, Swift</title>
      <link>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%08%EC%A3%BC%EC%B0%A8%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1647256799900&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 주차 요금 계산&quot; data-og-description=&quot;[180, 5000, 10, 600] [&amp;quot;05:34 5961 IN&amp;quot;, &amp;quot;06:00 0000 IN&amp;quot;, &amp;quot;06:34 0000 OUT&amp;quot;, &amp;quot;07:59 5961 OUT&amp;quot;, &amp;quot;07:59 0148 IN&amp;quot;, &amp;quot;18:59 0000 IN&amp;quot;, &amp;quot;19:09 0148 OUT&amp;quot;, &amp;quot;22:59 5961 IN&amp;quot;, &amp;quot;23:00 5961 OUT&amp;quot;] [14600, 34400, 5000]&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bPNYsR/hyNGHFBuNK/fcSwNRKr46PjNIyKE5Nt80/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Uwoz9/hyNHVPN4lo/CnJSqEk4BmfZmXZG6Mk8cK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/92341?language=swift&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bPNYsR/hyNGHFBuNK/fcSwNRKr46PjNIyKE5Nt80/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/Uwoz9/hyNHVPN4lo/CnJSqEk4BmfZmXZG6Mk8cK/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 주차 요금 계산&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[180, 5000, 10, 600] [&quot;05:34 5961 IN&quot;, &quot;06:00 0000 IN&quot;, &quot;06:34 0000 OUT&quot;, &quot;07:59 5961 OUT&quot;, &quot;07:59 0148 IN&quot;, &quot;18:59 0000 IN&quot;, &quot;19:09 0148 OUT&quot;, &quot;22:59 5961 IN&quot;, &quot;23:00 5961 OUT&quot;] [14600, 34400, 5000]&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 차량 출입과 그 시간을 계산하는게 핵심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해당 차량의 총 주차장 이용 시간은 PlainTime(시:분 이 아니라 분으로 총 환산)만 계산해주면 어렵지 않다. 예를들어 6시 40분에 출입해서 7시 30분에 나갔다면 총 이용시간은 50분인데, 6시 40분을 PlainTime으로 환산하면 400분 과 같고, 7시 30분은 450분으로 결국 두 시각의 차이는 50분으로 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647264339749&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

final class ParkingTime: CustomStringConvertible {
  enum InOut {
    case `in`
    case out
  }

  private var currentInTime: Int = 0
  var totalUsedTime: Int = 0

  func setTime(_ time: String, type: InOut) {
    let plainTime = timeConvert(time)
    switch type {
      case .in:
        currentInTime = plainTime
      case .out:
        totalUsedTime += plainTime - currentInTime
        currentInTime = 0
    }
  }

  private func timeConvert(_ time: String) -&amp;gt; Int {
    let convert = time.components(separatedBy: &quot;:&quot;)
    let hour = Int(convert[0])! * 60
    let minute = Int(convert[1])!
    return hour + minute
  }

  var description: String {
    totalUsedTime.description
  }
}

final class Parking {
  private var baseTime: Int = 0
  private var baseFee: Int = 0
  private var unitTime: Int = 0
  private var unitFee: Int = 0

  private var currentCarList: [String: ParkingTime] = [:]
  private var remain: Set&amp;lt;String&amp;gt; = []

  init() { }

  var feeList: [Int] {
    let sorted = currentCarList.sorted { lhs, rhs in
      lhs.key &amp;lt; rhs.key
    }

    return sorted.map {
      let totalTime = $0.value.totalUsedTime
      return feeCaculate(totalTime)
    }
  }

  private func feeCaculate(_ time: Int) -&amp;gt; Int {
    if time &amp;gt; baseTime {
      return baseFee + Int(ceil(Double(time - baseTime) / Double(unitTime))) * unitFee
    } else {
      return baseFee
    }
  }

  func setRecored(_ records: [String]) {
    records.forEach {
      let (time, carNumber, type) = recoredDecorder($0)
      if type == &quot;IN&quot; {
        remain.insert(carNumber)
      } else {
        remain.remove(carNumber)
      }
      if let car = currentCarList[carNumber] {
        car.setTime(time, type: type == &quot;IN&quot; ? .in : .out)
      } else {
        let parkingTime = ParkingTime()
        parkingTime.setTime(time, type: type == &quot;IN&quot; ? .in : .out)
        currentCarList[carNumber] = parkingTime
      }
    }

    remain.forEach { carNumber in
      guard let car = currentCarList[carNumber] else { return }
      car.setTime(&quot;23:59&quot;, type: .out)
    }
  }

  func setFees(_ fees: [Int]) {
    baseTime = fees[0]
    baseFee = fees[1]
    unitTime = fees[2]
    unitFee = fees[3]
  }

  private func recoredDecorder(_ record: String) -&amp;gt; (String, String, String) {
    let line = record.components(separatedBy: &quot; &quot;)
    return (line[0], line[1], line[2])
  }
}

func solution(_ fees:[Int], _ records:[String]) -&amp;gt; [Int] {

  let parking = Parking()
  parking.setFees(fees)
  parking.setRecored(records)

  return parking.feeList
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/49</guid>
      <comments>https://doing-programming.tistory.com/entry/2022-KAKAO-BLIND-RECRUITMENT-%08%EC%A3%BC%EC%B0%A8%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0%ED%95%98%EA%B8%B0-Swift#entry49comment</comments>
      <pubDate>Mon, 14 Mar 2022 22:33:21 +0900</pubDate>
    </item>
    <item>
      <title>Run Loop vs DispatchQueue</title>
      <link>https://doing-programming.tistory.com/entry/Run-Loop-vs-DispatchQueue</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Combine을 사용할 때, debounce와 같은 오퍼레이터를 사용하면 scheduler를 지정해 주어야 한다. (그 외에도 scheduler를 지정해주는 상황)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 지정해 줄 수 있는 것은 &lt;code&gt;RunLoop.main&lt;/code&gt; 과 &lt;code&gt;DispatchQueue.main&lt;/code&gt; 인데 이 둘은 무슨 차이일까?&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;query.debounce(for: .milliseconds(800), scheduler: RunLoop.main)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;query.debounce(for: .milliseconds(800), scheduler: DispatchQueue.main)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 실행해 보면 얼핏 차이가 없는 것처럼 보이지만 실제로는 중요한 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;RunLoop.main&lt;/code&gt; 를 스케줄러로 사용하면 사용자의 터치 반응 등의 이벤트가 전달되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;따라서 스크롤 중에는 debounce가 제대로 되지 않는 다는 뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &lt;code&gt;DispatchQueue.main&lt;/code&gt; 터치 반응 등의 이벤트가 동작 하는 도중에도 이벤트가 전달이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;RunLoop.main&lt;/code&gt;은 &lt;code&gt;RunLoop.main.perform&lt;/code&gt; 을 호출하고 &lt;code&gt;DispatchQueue.main&lt;/code&gt;은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DispatchQueue.main.async&lt;/code&gt;를 호출하기 때문!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처: &lt;a href=&quot;https://stackoverflow.com/questions/56724566/runloop-vs-dispatchqueue-as-scheduler/58849015#58849015&quot;&gt;https://stackoverflow.com/questions/56724566/runloop-vs-dispatchqueue-as-scheduler/58849015#58849015&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/48</guid>
      <comments>https://doing-programming.tistory.com/entry/Run-Loop-vs-DispatchQueue#entry48comment</comments>
      <pubDate>Sun, 13 Mar 2022 18:25:37 +0900</pubDate>
    </item>
    <item>
      <title>2019 KAKAO BLIND RECRUITMENT - 오픈 채팅방, Swift</title>
      <link>https://doing-programming.tistory.com/entry/2019-KAKAO-BLIND-RECRUITMENT-%EC%98%A4%ED%94%88-%EC%B1%84%ED%8C%85%EB%B0%A9-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/42888&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/42888&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647097212457&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 오픈채팅방&quot; data-og-description=&quot;오픈채팅방 카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다. 신입사원인 김크루는 카카오톡 오&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42888&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42888&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKCIge/hyNGNYLqMn/SH1Suht22AOPwwuLEwmGn0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/OpUmw/hyNGBxidNo/VSx6W6QaXcKS9lH6G3LgW0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/42888&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/42888&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKCIge/hyNGNYLqMn/SH1Suht22AOPwwuLEwmGn0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/OpUmw/hyNGBxidNo/VSx6W6QaXcKS9lH6G3LgW0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 오픈채팅방&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오픈채팅방 카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다. 신입사원인 김크루는 카카오톡 오&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 핵심은 출입로그 메시지에서 이름이 변경된 유저의 로그 내용을 변경해야 한다는 뜻이다. 일일이 서치해서 지우기에는 입력 받는데 O(N), 출력문으로 컨버팅 하는데 O(N) 으로 O(N^2)이므로 여유가 없다. 따라서 User를 클래스로 정의하고, 참조를 통해 해결해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. User는 id로 구분 가능한 Hashable 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Chat 클래스는 Log 형태로 User를 소유하고 있고, Enter, Leave, Change 명령어를 수행할 때 이름을 참조해서 바꾸기 때문에, 별도의 시간 소모 없이 수행이 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1647097708525&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

final class User: Equatable, Hashable {
  let id: String
  var name: String

  init(id: String, name: String) {
    self.id = id
    self.name = name
  }

  static func == (lhs: User, rhs: User) -&amp;gt; Bool {
    lhs === rhs
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }
}

final class Chat {

  enum Log: CustomStringConvertible {
    case enter(User)
    case leave(User)

    private func messageFormatter(log: Log) -&amp;gt; String {
      switch log {
        case .enter(let user):
          return &quot;\(user.name)님이 들어왔습니다.&quot;
        case .leave(let user):
          return &quot;\(user.name)님이 나갔습니다.&quot;
      }
    }

    var description: String {
      messageFormatter(log: self)
    }
  }

  private var log: [Log] = []

  private var userList: [String: User] = [:]

  var message: [String] {
    log.map{$0.description}
  }

  func enter(user: User) {
    if let alreadyUser = userList[user.id] {
      log.append(.enter(alreadyUser))
      alreadyUser.name = user.name
    } else {
      log.append(.enter(user))
      userList.updateValue(user, forKey: user.id)
    }
  }

  func leave(userId: String) {
    guard let user = userList[userId] else { return}
    log.append(.leave(user))
  }

  func change(userId: String, changeName: String) {
    guard let target = userList[userId] else { return }
    target.name = changeName
  }
}

func solution(_ record:[String]) -&amp;gt; [String] {

  let chat = Chat()

  record.forEach { packet in
    let command = packet.components(separatedBy: &quot; &quot;)
    switch command[0] {
      case &quot;Enter&quot;:
        let user = User(id: command[1], name: command[2])
        chat.enter(user: user)
      case &quot;Leave&quot;:
        chat.leave(userId: command[1])
      case &quot;Change&quot;:
        chat.change(userId: command[1], changeName: command[2])
      default:
        return
    }
  }
  return chat.message
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <category>오픈채팅방</category>
      <category>프로그래머스</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/47</guid>
      <comments>https://doing-programming.tistory.com/entry/2019-KAKAO-BLIND-RECRUITMENT-%EC%98%A4%ED%94%88-%EC%B1%84%ED%8C%85%EB%B0%A9-Swift#entry47comment</comments>
      <pubDate>Sun, 13 Mar 2022 00:21:53 +0900</pubDate>
    </item>
    <item>
      <title>2020 KAKAO BLIND RECRUITMET - 문자열 압축, Swift</title>
      <link>https://doing-programming.tistory.com/entry/2020-KAKAO-BLIND-RECRUITMET-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%95%95%EC%B6%95-Swift</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/60057&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://programmers.co.kr/learn/courses/30/lessons/60057&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647015509264&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;코딩테스트 연습 - 문자열 압축&quot; data-og-description=&quot;데이터 처리 전문가가 되고 싶은 &amp;quot;어피치&amp;quot;는 문자열을 압축하는 방법에 대해 공부를 하고 있습니다. 최근에 대량의 데이터 처리를 위한 간단한 비손실 압축 방법에 대해 공부를 하고 있는데, 문&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/60057&quot; data-og-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/60057&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ClICo/hyNGBjaEhw/hT4duGBgwkZemm19vjUaOk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/cuokPb/hyNGPobDRT/dZ6fjoKXsCmCQf1Vp4N5Y0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626&quot;&gt;&lt;a href=&quot;https://programmers.co.kr/learn/courses/30/lessons/60057&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://programmers.co.kr/learn/courses/30/lessons/60057&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ClICo/hyNGBjaEhw/hT4duGBgwkZemm19vjUaOk/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626,https://scrap.kakaocdn.net/dn/cuokPb/hyNGPobDRT/dZ6fjoKXsCmCQf1Vp4N5Y0/img.jpg?width=626&amp;amp;height=626&amp;amp;face=0_0_626_626');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;코딩테스트 연습 - 문자열 압축&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터 처리 전문가가 되고 싶은 &quot;어피치&quot;는 문자열을 압축하는 방법에 대해 공부를 하고 있습니다. 최근에 대량의 데이터 처리를 위한 간단한 비손실 압축 방법에 대해 공부를 하고 있는데, 문&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기본적인 알고리즘은, 1칸씩 잘라보고, 그 다음 2칸씩 잘라보는 행위를 계속 반복하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 문자열이 1,000 자 라면 500자 까지만 잘라보면 되는데, 왜냐하면 501자를 자르게 되면 남은 길이가 499자 이므로 앞서 자른 문자와 무조건 일치하지 않는다. 따라서 전체길이 S 일 때&amp;nbsp; S/2 까지만 자르는 동작을 반복하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 문자열의 길이가 1일때 예외처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 문자열을 자를 때 자른 문자열이 이전 문자열과 일치 할 때, 나온 회수를 +1 해준다., 아니라면 배열에 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 압축한 문자열 길이를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 자르는 범위 오른쪽 경계가 nil이거나 초과하면 반복문을 빠져나오고 다음 자르는 단위로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 반복된 회수가 1일때는 별도로 표시하지 않으므로 예외처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산 횟수는 최악의 경우 500 * 500 * 500 으로 125,000,000&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647015490294&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

final class CompressedString: CustomStringConvertible {
  let word: String
  var count: Int = 1

  init(word: String) {
    self.word = word
  }
  var countAnnotation: String {
    count &amp;gt; 1 ? String(count) : &quot;&quot;
  }

  var description: String {
    &quot;\(countAnnotation)\(word)&quot;
  }
}

func solution(_ s: String) -&amp;gt; Int {
  let wholeLenth = s as NSString
  let bound = wholeLenth.length / 2
  var shortestStringLenth = 1001
  if wholeLenth.length == 1 { return 1 }
  for unit in 0..&amp;lt;bound {

    var compressedStrings: [CompressedString] = []
    var lbound = s.startIndex
    var rbound = s.index(lbound, offsetBy: unit, limitedBy: s.index(before: s.endIndex))
    var previousString = CompressedString(word: &quot;&quot;)
    while true {
      if rbound == nil || rbound! &amp;gt;= s.endIndex {
        let substring = CompressedString(word: String(s[lbound..&amp;lt;s.endIndex]))
        if !substring.word.isEmpty {
          compressedStrings.append(substring)
        }
        break
      }
      let substring = CompressedString(word: String(s[lbound...rbound!]))

      if substring.word == previousString.word {
        previousString.count += 1
      } else {
        compressedStrings.append(substring)
        previousString = substring
      }

      lbound = s.index(after: rbound!)
      rbound = s.index(lbound, offsetBy: unit, limitedBy: s.endIndex)
    }

    var legnth = 0
    compressedStrings.forEach {
      legnth += ($0.description as NSString).length
    }
    shortestStringLenth = min(shortestStringLenth, legnth)

  }
  return shortestStringLenth
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/46</guid>
      <comments>https://doing-programming.tistory.com/entry/2020-KAKAO-BLIND-RECRUITMET-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%95%95%EC%B6%95-Swift#entry46comment</comments>
      <pubDate>Sat, 12 Mar 2022 01:34:32 +0900</pubDate>
    </item>
    <item>
      <title>DiffableDataSource와 Realm</title>
      <link>https://doing-programming.tistory.com/entry/DiffableDataSource%EC%99%80-Realm</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Issue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DiffableDataSource와 Realm을 함께 썼을 때 발생했던 문제와 해결법&lt;/b&gt;&lt;br /&gt;DiffableDataSource는 Diff을 위해서 일종의 뷰의 상태를 가지고 있는데, 데이터가 변하면, 이전에 가지고 있던 상태와 비교해서 뷰를 갱신합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV47r1/btruVLT2m53/ziSZThYHKZuBY7dKPk37X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV47r1/btruVLT2m53/ziSZThYHKZuBY7dKPk37X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV47r1/btruVLT2m53/ziSZThYHKZuBY7dKPk37X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV47r1%2FbtruVLT2m53%2FziSZThYHKZuBY7dKPk37X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;136&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Storage에서 Object3이 삭제가 되고, snapshot을 재 구성해서 dataSource에 apply를 실행하게 되면, 이전 DataSource Snapshot과 비교해서, 화면을 갱신하는 상황입니다.&lt;br /&gt;그런데 해당 과정에서 Realm 예외처리 오류가 발생합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;   RealmManager.shared.deleteTodo(todo: todo)
    print(dataSource.snapshot().itemIdentifiers(inSection: .Task).map{($0 as! ToDo).task})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realm에서 해당 데이터를 지운 후 dataSource에서는 아직 갱신이 되지 않았기 때문에, 해당 객체를 참조해볼 수 있는데, 프린트만 해도 에러가 납니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
terminating with uncaught exception of type NSException&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Realm Object로 생성한 객체는 Realm DB에서 삭제한 후에는 참조할 수 없도록, 예외처리가 들어가 있는 것 같습니다.&lt;br /&gt;DiffableDataSource에서는 Diff을 위해, 삭제된 객체를 들고있어야 하는데, Realm에서 못들고 있게 하니까 snapshot을 apply 할 수 없게 되는 현상이 발생합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Solution&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 DiffableDataSource 순서는&lt;br /&gt;1. 스토리지에서 삭제&lt;br /&gt;2. Snapshot 갱신&lt;br /&gt;으로 볼 수 있는데, 이를 반대로 해볼 수 있습니다.&lt;br /&gt;1. Snapshot 갱신&lt;br /&gt;2. 스토리지에서 삭제&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;guard !todolist.isEmpty else { fatalError() }
    var snapshot = self.dataSource.snapshot()

    for todo in todolist {
      guard let _ = dataSource.indexPath(for: todo) else {
        print(&quot;delete error&quot;)
        return
      }
    }

 snapshot.deleteItems(todolist)
 dataSource.apply(snapshot, animatingDifferences: true)

 RealmManager.shared.deleteTodo(todo: todo)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이러면 DiffableDataSource를 쓰는 의미가 없죠, DiffableDataSource의 장점은, 언제든 ConfigureSnapshot만 호출하면 되는 부분이 중요한데 말이죠!&lt;br /&gt;그래서 다른 방법!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.의 솔루션을 조금 다르게 생각해서&lt;br /&gt;DiffableDataSource에서 Diff 연산을 하고 난 뒤에 스토리지에서 삭제하는 컨셉은 똑같이 생각합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbGX2k/btruWiYqZGx/I496BKjxoJsNc38YvAjvyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbGX2k/btruWiYqZGx/I496BKjxoJsNc38YvAjvyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbGX2k/btruWiYqZGx/I496BKjxoJsNc38YvAjvyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbGX2k%2FbtruWiYqZGx%2FI496BKjxoJsNc38YvAjvyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;228&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 Realm DB에서 삭제를 이렇게 해버렸다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rRSyu/btruVLs1aUD/DD8OHmKZ2gLCTaj9PucJOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rRSyu/btruVLs1aUD/DD8OHmKZ2gLCTaj9PucJOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rRSyu/btruVLs1aUD/DD8OHmKZ2gLCTaj9PucJOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrRSyu%2FbtruVLs1aUD%2FDD8OHmKZ2gLCTaj9PucJOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;150&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 삭제하지 않고, 삭제한 데이터를 관리하는 섹션으로 따로 관리하는 거죠&lt;br /&gt;Realm 내부에서 삭제된 데이터를 관리하는 별도의 모델을 만들거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 모델에 &lt;code&gt;isDelete: Bool&lt;/code&gt; 를 추가해서 필터링 해서 가져오는 방법도 있죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;More&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 사용자 경험 관점에서, 삭제를 바로 해버리는 것 보다는 안전장치를 두는게 더 좋은 선택일 수도 있다고 생각이 드네요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yJLGU/btruZPt9FBL/F7mZcbyjuNzpOl4ZF5CYN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yJLGU/btruZPt9FBL/F7mZcbyjuNzpOl4ZF5CYN0/img.png&quot; data-alt=&quot;Realm Delete를 설명하는 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yJLGU/btruZPt9FBL/F7mZcbyjuNzpOl4ZF5CYN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyJLGU%2FbtruZPt9FBL%2FF7mZcbyjuNzpOl4ZF5CYN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;811&quot; height=&quot;178&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Realm Delete를 설명하는 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 읽읍시다. ^^&lt;/p&gt;</description>
      <category>iOS</category>
      <category>DiffableDataSource</category>
      <category>IOS</category>
      <category>Realm</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/45</guid>
      <comments>https://doing-programming.tistory.com/entry/DiffableDataSource%EC%99%80-Realm#entry45comment</comments>
      <pubDate>Thu, 3 Mar 2022 11:42:39 +0900</pubDate>
    </item>
    <item>
      <title>CAF파일 이란? (번역)</title>
      <link>https://doing-programming.tistory.com/entry/CAF%ED%8C%8C%EC%9D%BC-%EC%9D%B4%EB%9E%80-%EB%B2%88%EC%97%AD</link>
      <description>&lt;h1&gt;CAF란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C&lt;/b&gt;ore &lt;b&gt;A&lt;/b&gt;udio &lt;b&gt;F&lt;/b&gt;ormat(이하 CAF)는 애플에서 개발한, 오디오 포맷으로 기존의 AIFF, WAV등의 한계점을 개선하기 위한 포맷이다.&lt;/p&gt;
&lt;h1&gt;Advantages&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무제한 파일 사이즈
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AIFF, AIFF-C, WAVE는 기존 오디오 포맷은 4기가바이트의 용량 제한이 있었고, 대략 15분 정도의 길이의 오디오만 담을 수 있었지만, CAF는 용량에 제한이 없기 때문에, 이론적으로 수백년 길이의 오디오도 저장 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;안전하고 효율적인 녹음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AIFF와 WAV는 녹음이 끝난 뒤 파일의 사이즈를 나타내는 헤더를 업데이트 해야하는데, 헤더 저장이 정상적으로 완료되지 않으면 파일을 못쓰게 되는 경우가 있다. CAF는 헤더없이 파일의 사이즈를 결정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;호환성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CAF는 다양한 오디오 포맷의 래퍼 역할로, 메타데이터, 멀티채널에 대한 호환이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다양한 보조 데이터의 취급
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오디오 편집에 필요한 추가 데이터나 채널 레이아웃, 텍스트 어노테이션 등도 함께 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 의존성 지원
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CAF의 특정 메타데이터는 편집 카운트 값으로 오디오 데이터에 연결되어있어, 이를 이용해 오디오 데이터가 변경된 경우를 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;CAF File Structure&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAF는 버전을 표기하는 헤더를 포함하고, 그 뒤로는 일련의 청크가 위치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크는 또 그 안에서, 청크의 유형을 정의하고, 데이터 섹션의 크기를 나타내는 청크 헤더와 청크 데이터로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAF파일에 필요한 청크 유형은 오디오 데이터 청크와 오디오 데이터 형식을 지정하는 오디오 Description 청크를 필요로 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Chunk Structure&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;청크의 타입을 나타내는 4자리 코드&lt;/li&gt;
&lt;li&gt;청크 크기를 바이트 단위로 나타내는 숫자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Packet, Frame, Sample&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sample: 디지털화한 오디오 데이터의 한 채널에 대한 하나의 숫자 (44100샘플이라고 하면 1 샘플이 44100개)&lt;/li&gt;
&lt;li&gt;Frame: 각 채널에 대해 하나의 샘플을 타나내는 &lt;b&gt;샘플 세트,&lt;/b&gt; 프레임의 샘플은 동시에 재생되도록 되어있다. *비디오에서 설명하는 프레임과는 다른 개념! 착각하지 말자&lt;/li&gt;
&lt;li&gt;Packet: 가장 최소 단위의 데이터 블록으로 Linear PCM 데이터의 경우 각 패킷에 정확히 하나의 프레임이 위치한다. 압축된 오디오 데이터 형식의 경우는 패킷의 프레임 수는 인코딩에 따라 다르다. 예를 들어 AAC 패킷은 1024프레임의 PCM을 나타낸다.&lt;/li&gt;
&lt;li&gt;SmapleRate: 압축/압축해제 된 데이터의 전체 프레임 수 대 초당 샘플&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/MusicAudio/Reference/CAFSpec/CAF_intro/CAF_intro.html#//apple_ref/doc/uid/TP40001862-CH203-TPXREF101&quot;&gt;https://developer.apple.com/library/archive/documentation/MusicAudio/Reference/CAFSpec/CAF_intro/CAF_intro.html#//apple_ref/doc/uid/TP40001862-CH203-TPXREF101&lt;/a&gt;&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/44</guid>
      <comments>https://doing-programming.tistory.com/entry/CAF%ED%8C%8C%EC%9D%BC-%EC%9D%B4%EB%9E%80-%EB%B2%88%EC%97%AD#entry44comment</comments>
      <pubDate>Wed, 2 Mar 2022 02:43:33 +0900</pubDate>
    </item>
    <item>
      <title>Swift- CoreBluetooth 찍먹</title>
      <link>https://doing-programming.tistory.com/entry/CoreBluetooth-%EC%B0%8D%EB%A8%B9</link>
      <description>&lt;h1&gt;Bluetooth&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS에서는 BLE(Bluetooth Low Energy) 무선 기술을 CoreBluetooth 프레임워크를 통해 연결 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Central&lt;/b&gt;: Bluetooth 장치에서 데이터를 수신하는 개체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Peripheral&lt;/b&gt;: 다른 장치에서 사용할 데이터를 게시할 Bluetooth 장치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Advertising Packets&lt;/b&gt;: 블루투스는 Advertising Packets 형태로 가지고 있는 데이터 중 일부를 브로드캐스트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷에는 주변 장치의 이름, 기능 등과 같은 정보가 포함될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Central의 역할은 이러한 AdvertisingPackets 을 스캔하고 관련이 있는 주변 장치를 식별하고 개별 장치에 연결하고 추가 정보를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주변 장치의 특정 기능 또는 특징을 설명하는 데이터 및 관련 동작의 모음, 예를들어 심박 센서에는 심박수 서비스가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;Characteristics&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 정보를 제공한다. 예를 들어, 심박수 서비스에는 분방 비트 수 데이터가 포함된 심박수 측정 특성이 있다. 심박수 서비스가 가질 수 있는 또 다른 특징은 바디 센서 로케이션으로, 의도된 신체 위치를 설명하는 문자열이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스 및 특성은, 16bit or 128bit이 될 수 있는 UUID로 표시된다.&lt;/p&gt;
&lt;h1&gt;CBCentralManager&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CBCentralManager는 코어 블루투스를 관리하는 모델로, 델리게이트를 통해서 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 컨트롤러 등에서 다음과 같이 프로퍼티와, 초기화를 실시한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;var centralManager: CBCentralManager!
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;centralManager = CBCentralManager(delegate: self, queue: nil)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension ViewController: CBCentralManagerDelegate {
  func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {

      case .unknown:
        print(&quot;central.state is .unknown&quot;)
      case .resetting:
        print(&quot;central.state is .resetting&quot;)
      case .unsupported:
        print(&quot;central.state is .unsupprted&quot;)
      case .unauthorized:
        print(&quot;central.state is .unauthorized&quot;)
      case .poweredOff:
        print(&quot;central.state is .poweredOff&quot;)
      case .poweredOn:
        print(&quot;central.state is .poerwedOn&quot;)
      @unknown default:
        print(&quot;central.state is .unknown default&quot;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CBCentralManagerDelegate를 채용하게 되면, centralManagerDidUpdateState(_:) 를 필수적으로 구현하게 되는데, 말그대로 central의 상태로, 이상태에서 빌드를 하게 되면, 디바이스의 블루투스가 켜져있다면 poweredOn상태 블록을 실행하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블루투스를 이용하기 위해서, 다음과 같은 동의를 얻어야 함 Privacy - Bluetooth Always Usage Description&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scanning for Peripherals&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 주변 기기를 검색할 차례다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주변 기기 검색은 CBCentralManager의 **&lt;b&gt;scanForPeripherals(withServices:options:)&lt;/b&gt; 메소드를 통해 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withServices 인수는 CBUUID를 통해, 앱에서 관심있는 블루투스 기기의 advertise만 확인하는데, 추천하는 값은 기본적으로 nil이다. 예를들면, 싸이클 관련 어플리케이션인 Strava를 보면, 싸이클 컴퓨터를 블루투스로 연결하는데, 관련기기가 아니면 서치 자체가 되지 안흔다. 아마 이러한 부분을 이용한 것으로 생각된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options인수는 말그대로 검색을 커스터마이징 하는 방법으로, 이건 우선은 패스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 해당 메서드를 호출해보자.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;case .poweredOn:
  print(&quot;central.state is .poerwedOn&quot;)
  centralManager.scanForPeripherals(withServices: nil)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 보면 당연한 이야기 인데, 해당 메서드는 poweredOn상태에서만 호출할 수 있다. 그 외에 호출하게 되면 API MISUSE 메시지가 날아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scanForperipherals는 검색을 시작하라는 명령어인데 그럼 검색된, 블루투스 기기는 어떻게 확인 할 수 있을까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CBCentralManagerDelegate 에서 didDiscover로 제공되는 메서드가 있다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;print(peripheral)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 프린트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드해보면 맥북 블루투스도 잡히고, 에어팟도 잡히고, 우리집에는 없는 기기도 잡힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡처 화면에는 일부만 보이지만, 실제로 해보면 꽤 많은 검색 결과가 나오기 때문에, 여기서 필요한 서비스만 걸러 내보고자 한다. 앞서 얘기했던 CBUUID를 이용해 볼 것이다. 에어팟과 같이 오디오 재생 장치만 걸러볼려고 한다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블루투스 시방 확인 &lt;a href=&quot;https://www.bluetooth.com/specifications/assigned-numbers/&quot;&gt;https://www.bluetooth.com/specifications/assigned-numbers/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시방서에 들어가면 16-bit UUID pdf를 받을 수 있는데, 여기서 AllocationType을 찾아보면 GATT Service가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 찾아보니 이거는 자전거에 들어가는 스피드, 케이던스 센서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인데, 내가 찾고싶은언 에어팟과 같이 오디오 관련도니 서비스 인데, 못찼겠다. 다음에 찾아보겠ㅇㅁ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 필터링 하는건 못했으니, 그냥 이름으로 기기를 잡아 보려고 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;var airpods: CBPeripheral!
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    if peripheral.name ?? &quot;&quot; == &quot;둥둥팟&quot; {
      print(peripheral)
      airpods = peripheral
      centralManager.stopScan()
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에어팟의 이름이 &amp;ldquo;둥둥팟&quot;이기 때문에, peripheral.name이 &amp;ldquo;둥둥팟&amp;rdquo;과 같을때 CBPeripheral 인스턴스로 전달해준뒤 centramManager를 stopScan하면 더이상 스캔을 계속하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Connecting to a Peripheral&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 찾아서 인스턴스로 만들었기 때문에 이를 연결하는 방법은 centralManager에 **&lt;b&gt;connect(_:options:)&lt;/b&gt; 메서드를 호출하고, 첫번째 인자에 Perpiheral 인스턴스를 전달하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 스캔을 정지하기 전에 호출해주자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블루투스 기기가 연결 되었다는 것을 어디서 알 수 있냐하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 델리게이트 메서드에서 확인 가능하다., 만약 성공적으로 연결 되었다면, 이 부분이 호출 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주변 기기를 연결 한 다음에는, 주변기기가 제공하는 서비스를 다시 검색해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 연결된 기기의 Delegate에서 확인 가능하다. 따라서 우선 기기를 연결해준 다음&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;airpods.delegate = self
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;델리게이트 self&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인된 서비스 목록을 보아야 하는데, 이는 CBPeripheral 메서드로 호출 가능하다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    airpods.discoverServices(nil)
    print(&quot;Connect to: \\(peripheral)&quot;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 didConnect에서 호출 하면&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;extension ViewController: CBPeripheralDelegate {
  func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    guard let services = peripheral.services else {
      print(&quot;didDiscoverServices Error \\(error?.localizedDescription)&quot;)
      return
    }

    for service in services {
      print(service)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;peripheral에서 service를 배열 형태로 받으 ㄹ수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 해서 받은 서비스는 위 사진처럼 나타내는데, 아직은 이게 뭘 의미하는지 모르겠다. 콩나물이 2개라 2개가 출력되나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니면 오디오 입/출력 2개로 나뉘어서 2개 서비스인지 모르겠다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 보면, UUID 부분이 정의가 되어있으면 우리가 아는 영단어로 떨어지는데, 에어팟이라 그런가..저게 128Bit UUID인가 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어, 아까 GAPP Service에서 확인한 것 처럼, 심박이면 &amp;ldquo;UUID = Heart Rate&amp;rdquo;, &amp;ldquo;UUID = Cycle Cencer&amp;rdquo; 이런식으로 나온다는 것&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Discovering a Service&amp;rsquo;s Characteristics&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 처음 설명했든 주변기기에는, Characteristics가 있는데, 주변기기가 가지는 특성을 전달한다. 케이던스 센서면, 분당 회전수를 특성으로 전달해줄 것이다. 에어팟은, 사운드 기기니까, 오디오 정보, 볼륨이나, 주파수를 전달하지 않을까?&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;for service in services {
      print(service)
      print(&quot;Characteristic: \\(service.characteristics)&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;CBService: 0x283400a00, isPrimary = YES, UUID = 9BD708D7-64C7-4E9F-9DED-F6B6C4551967&amp;gt;
Characteristic: nil
&amp;lt;CBService: 0x283400cc0, isPrimary = YES, UUID = 7798082B-B7B7-45A6-9933-563492EFE04E&amp;gt;
Characteristic: nil
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 아무것도 엄슴.이 아니라 방법이 잘못 되었는데, Characteristic을 찾는 방법은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;didDiscoverCharacteristicsFor 를 통해서 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;service에서는&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;print(peripheral.discoverCharacteristics(nil, for: service))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 호출 해 주고&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    guard let characteristics = service.characteristics else {
      print(&quot;didfail didscover characteristics with error: \\(error)&quot;)
      return
    }

    for characteristic in characteristics {
      print(characteristic)
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;델리게이트 메서드를 호출해서, 특성을 확인한다&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;lt;CBCharacteristic: 0x2826204e0, UUID = F195B8FB-A9E2-4401-858B-2F87A06928A8, properties = 0x2, value = {length = 8, bytes = 0x0101000407020101}, notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x282620540, UUID = E1F9B835-7E47-413D-AF94-C68E574B8F7E, properties = 0x4, value = (null), notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x282620480, UUID = A08CE5EF-698A-42A2-B980-7F3AC00B3845, properties = 0x14, value = (null), notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x2826203c0, UUID = 6288EA2D-7B89-47AD-890B-9FA6BF3CFC58, properties = 0x14, value = (null), notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x282620420, UUID = 3F1C161D-6473-4746-91F5-6D27610780C6, properties = 0x10, value = {length = 91, bytes = 0x01003856 0001d041 0151b83d fabea0b8 ... 962aac7f 17a0757f }, notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x2826205a0, UUID = C7C6947D-3165-4BCB-8EAF-B328896CB531, properties = 0x14, value = {length = 4, bytes = 0x01000300}, notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x282620720, UUID = 82F6BC0A-1BCA-4873-AFC9-EC5890E3469A, properties = 0x2, value = (null), notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x2826206c0, UUID = D5F96AFA-2F2C-41BB-A7E6-F54ABE6235B4, properties = 0x2, value = (null), notifying = NO&amp;gt;
&amp;lt;CBCharacteristic: 0x282620660, UUID = D5621CC1-F7AB-45DB-9403-9EAF744D5390, properties = 0x6, value = (null), notifying = NO&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 뭔가 잔뜩 나왔는데, 뭐가 뭔지는 모르겠고, 어떤 거는 value를 가지고 있는 것도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID에 따라서 어떤 내용인지 파악하면 되는데, 에어팟을 내가 생각한게 아니고, 블루투스 스펙에도 나와있지 않다. 애플이 임의로 정의 해 둔 것이라는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 실제로 개발하는 입장이라면 아마&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;D5621CC1-F7AB-45DB-9403-9EAF744D5390&amp;rdquo; 이 ID는 현재 배터리 상태를 보내는 거고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;D5F96AFA-2F2C-41BB-A7E6-F54ABE6235B4&amp;rdquo;는 현재 착용중인지 나타내는 값이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 아마 차트를 정리 해 두었을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 각 특성에 따라서 Properties를 가지는데, 가상으로 생각해보면, 현재 배터리 상태를 나타내는 것은 에어팟에서 값을 가져올 것이기 때문에 read를 가지고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/corebluetooth/cbcharacteristicproperties&quot;&gt;https://developer.apple.com/documentation/corebluetooth/cbcharacteristicproperties&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기부터는 가상의 코딩.ㅎ&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;peripheral.readValue(for: characteristic)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특성을 readValue하게 되면 이 결과가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 델리게이트 메서드로 넘어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래 처럼 상태 처리를 해서&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    if characteristic.uuid == CBUUID(string: &quot;배터리상태&quot;) {
      print(batteryState(from: characteristic))
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;battryState를 계산해주는 메서드를 호출하면 되고&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private func batteryState(from characteristic: CBCharacteristic) -&amp;gt; Int {
    //byte 처리
    return -1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;batteryState 메서드에서는 characteristic으로 전달된 value값(Data)를 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분도 실제로 해보고 싶은데, 에어팟 블루투스 시방서가 없어서.. 가상으로 해보자면&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56, 224, 1, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 50, 46, 49, 49, 56, 55, 48, 54, 48, 48, 48, 52, 48, 48, 48, 48, 48, 48, 46, 57, 124, 99, 115, 118, 49, 58, 50, 46, 48, 46, 53, 124, 99, 115, 118, 50, 58, 49, 46, 57, 46, 52, 124, 100, 97, 116, 101, 58, 50, 48, 50, 49, 32, 49, 49, 32, 49, 49, 124, 117, 112, 100, 118, 58, 49, 54, 53]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 바이트 쪼가리가 데이터로 들어오는데, 이를 시방서를 보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 뭐 12번째 자리가, uint8 형식으로, 배터리를 0~100까지 나타낸다고 그러면, 알아서 잘 변환해서 처리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집에 범용적인 블루투스 기계가 없어서 실제로 해보지 못하는게 너무 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체코드보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/CoreBluetoothPractice/blob/main/CoreBlueToothPractice/ViewController.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/urijan44/CoreBluetoothPractice/blob/main/CoreBlueToothPractice/ViewController.swift&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>Bluetooth</category>
      <category>CoreBluetooth</category>
      <category>swift</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/43</guid>
      <comments>https://doing-programming.tistory.com/entry/CoreBluetooth-%EC%B0%8D%EB%A8%B9#entry43comment</comments>
      <pubDate>Tue, 1 Mar 2022 21:40:38 +0900</pubDate>
    </item>
    <item>
      <title>NMapsMap M1 Build Error</title>
      <link>https://doing-programming.tistory.com/entry/NMapsMap-M1-Build-Error</link>
      <description>&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;하드웨어 환경&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-02-25 오후 10.53.13.png&quot; data-origin-width=&quot;220&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPoCVS/btruqF0ivcM/9VK60D85tdERpt7hToPuS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPoCVS/btruqF0ivcM/9VK60D85tdERpt7hToPuS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPoCVS/btruqF0ivcM/9VK60D85tdERpt7hToPuS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPoCVS%2FbtruqF0ivcM%2F9VK60D85tdERpt7hToPuS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;113&quot; data-filename=&quot;스크린샷 2022-02-25 오후 10.53.13.png&quot; data-origin-width=&quot;220&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 로제타로 실행하세요  &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;NMapsMap을 사용해야 하는데, 해당 프레임워크는 cocoapods 설치밖에 지원하지 않습니다. 거기다 잘~ 설치하고나서 보니, SPM으로 설치된 것들이 빌드 오류가 발생습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbTwTr/btrul4tedMy/lu11pyJXpMZNtgnn96KyK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbTwTr/btrul4tedMy/lu11pyJXpMZNtgnn96KyK1/img.png&quot; data-alt=&quot;대빡치는 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbTwTr/btrul4tedMy/lu11pyJXpMZNtgnn96KyK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbTwTr%2Fbtrul4tedMy%2Flu11pyJXpMZNtgnn96KyK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;616&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대빡치는 상황&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/42</guid>
      <comments>https://doing-programming.tistory.com/entry/NMapsMap-M1-Build-Error#entry42comment</comments>
      <pubDate>Sat, 26 Feb 2022 17:40:12 +0900</pubDate>
    </item>
    <item>
      <title>Git 잘못 올라간 파일 및 기록 삭제</title>
      <link>https://doing-programming.tistory.com/entry/Git-%EC%9E%98%EB%AA%BB-%EC%98%AC%EB%9D%BC%EA%B0%84-%ED%8C%8C%EC%9D%BC-%EB%B0%8F-%EA%B8%B0%EB%A1%9D-%EC%82%AD%EC%A0%9C</link>
      <description>&lt;h1&gt;Intro&lt;/h1&gt;
&lt;p&gt;출시한 프로젝트에 GoogleCrashlytics랑 Analytics 달아놓고, 평생 비공개 할꺼라, 신경을 못썼는데, 프로젝트를 공개로 돌리려고 봤더니, GoogleService-Info.plist 가 노출이 되는걸, 뒤늦게 눈치챘다.&lt;br&gt;Gitignore에 넣는것도 깜빡했고, Commit 로그에도 남아있는 관계로... 깃에서 파일을 삭제하고 커밋 히스토리에서 해당 파일을 삭제하고자 했다.&lt;/p&gt;
&lt;h1&gt;파일 삭제&lt;/h1&gt;
&lt;p&gt;우선 git에 잘못 등록된 파일은 아래 명령어를 사용하자&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rm --cached -r &amp;lt;DIRECTROY&amp;gt;
git rm --cached &amp;lt;FILE_NAME&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;cached 옵션은, remote에서 이를 삭제한다는 뜻으로 해당 옵션이 없으면 local에서도 삭제해버리니, 주의하자&lt;/p&gt;
&lt;p&gt;삭제한 후, gitignore에 추가하고, commit, push하자&lt;/p&gt;
&lt;h1&gt;커밋 히스토리 삭제&lt;/h1&gt;
&lt;p&gt;지금 당장 올라간 파일은 삭제했지만, 깃은, 해당 커밋에 파일내용이 그대로 들어간다. 따라서, 히스토리에서도 해당 파일에 대한 내용을 삭제할 필요가 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git filter-branch -f --index-filter &amp;#39;git rm --cached --ignore-unmatch &amp;lt;PATH_FILE_NAME&amp;gt;&amp;#39; --prune-empty -- --all&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;입력한 파일 이름을 가지고 필터링 해서, 해당 커밋에서 내용을 삭제해준다.&lt;/p&gt;
&lt;p&gt;좀 기다리면 커밋 아이디가 줄줄이 뜨면서 rewirte되었다고 뭐가 많이 나온다.&lt;br&gt;만약 Rewirte 내용이 안나온다면, 파일 이름을 맞게 입력했는지 패스는 맞는지 확인하자!&lt;/p&gt;
&lt;h1&gt;마치며.&lt;/h1&gt;
&lt;p&gt;애시당초 민감한 파일은, 비공개로 하더라고 애초에 리모트에 올리지 말자!&lt;/p&gt;</description>
      <category>General Dev</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/41</guid>
      <comments>https://doing-programming.tistory.com/entry/Git-%EC%9E%98%EB%AA%BB-%EC%98%AC%EB%9D%BC%EA%B0%84-%ED%8C%8C%EC%9D%BC-%EB%B0%8F-%EA%B8%B0%EB%A1%9D-%EC%82%AD%EC%A0%9C#entry41comment</comments>
      <pubDate>Mon, 21 Feb 2022 15:06:51 +0900</pubDate>
    </item>
    <item>
      <title>명품 C++ Programming - 1장 연습문제(이론) 짝수 정답</title>
      <link>https://doing-programming.tistory.com/entry/%EB%AA%85%ED%92%88-C-Programming-1%EC%9E%A5-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C%EC%9D%B4%EB%A1%A0-%EC%A7%9D%EC%88%98-%EC%A0%95%EB%8B%B5</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(네이버 블로그에서 이전 작성한 글입니다)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. ④&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 기계어로 프로그램을 작성하는 것은, 그만큼 사이드이펙트를 줄일 수 있지만, 생산성 측면에서 완전히 낭비입니다. 좋은 개발자가 되는 지름길로는 보기 힘듭니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;4. ②&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- C언어는 절차 지향 프로그래밍 언어입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;6. ②&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;객체 지향의 특성은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Encapsulation&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Inheritance&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Polymorphism&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Abstraction&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#OOP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#OOP&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645203248168&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Introduction to Object Oriented Programming Concepts (OOP) and More&quot; data-og-description=&quot;This article helps to understand OOP concepts, focusing on .NET/ C#. This is written in the form of asking questions and writing answers to them, making it easy to understand.&quot; data-og-host=&quot;www.codeproject.com&quot; data-og-source-url=&quot;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#OOP&quot; data-og-url=&quot;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/guJWy/hyNrBef1Ag/5YGIPoGjW9VTfhCUUfNdVK/img.jpg?width=100&amp;amp;height=67&amp;amp;face=0_0_100_67,https://scrap.kakaocdn.net/dn/cbSAmy/hyNsRNi4Va/7cOgs1AeqBfrO8U3eRA1K0/img.jpg?width=100&amp;amp;height=67&amp;amp;face=0_0_100_67&quot;&gt;&lt;a href=&quot;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#OOP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#OOP&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/guJWy/hyNrBef1Ag/5YGIPoGjW9VTfhCUUfNdVK/img.jpg?width=100&amp;amp;height=67&amp;amp;face=0_0_100_67,https://scrap.kakaocdn.net/dn/cbSAmy/hyNsRNi4Va/7cOgs1AeqBfrO8U3eRA1K0/img.jpg?width=100&amp;amp;height=67&amp;amp;face=0_0_100_67');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Introduction to Object Oriented Programming Concepts (OOP) and More&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This article helps to understand OOP concepts, focusing on .NET/ C#. This is written in the form of asking questions and writing answers to them, making it easy to understand.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.codeproject.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. ④&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++는 클래스 외부에서 변수를 선언할 수 있기 때문에 캡슐화 원칙이 무너져 순수 객체지향 언어로 보기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 C++언어와 모던한 언어들 모두 멀티패러다임이기 때문에 크게 문제되는 부분은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. ②&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 컴파일러가 하나가 아닌데, 표준 C++ 로 작성하게 되면, 컴파일러가 달라도 문제없이 동일하게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캡슐화: TV를 예로 들자면, TV에는 다양한 객체의 집합인데, 전원 버튼, 외부 연결 컴포넌트, 디스플레이, 스피커 등으로 구성되어있다. 각 컴포넌트 들은 각자의 역할만 수행할 뿐, 다른 컴포넌트들이 무슨 일을 하는지는 알 수도 없고, 관심도 없습니다. 디스플레이가 안나와도 스피커는 나오거든요. 스피커 컴포넌트에 내부에는 다른 컴포넌트로 부터 받은 오디오 신호를 내부적으로 코딩하고 아마 play()등의 메서드로 출력할겁니다. 내부적으로 코딩하는 부분을 processing() 메서드라고 한다면 해당 processing 메서드는 스피커 안에서만 동작하지, 전원 컴포넌트가 speaker.processing() 같은 형식으로 호출을 할 수는 없습니다.&lt;/li&gt;
&lt;li&gt;다형성: 오퍼레이터를 예를 들어 설명하라고 되어있는데, 수라는 모델이 있다고 가정했을 때 수는 기본적으로 사칙연산이 가능할 겁니다. 마찬가지로 대소관계 비교도 가능 할 것이구요, 이 수라는 모델을 서브클래싱 하면, &lt;b&gt;실수&lt;/b&gt;, &lt;b&gt;정수&lt;/b&gt; 등이 될 수 있습니다. 실수와 정수는 '수' 개념을 서브클래싱 한 것으로, '수'에서 가능했던 사칙연산과 대소비교가 가능하게 되는 것입니다. 실제로 구하는 방법은 무슨 수인가에 따라서 조금씩 달라집니다. 프로그래밍에서는 상속에 의해서 나타나고, 메서드 오버라이딩, 오퍼레이터 오버라이딩 으로 활용됩니다.&lt;/li&gt;
&lt;li&gt;상속성: 좀더 쉬운 예시를 들면, 사람이라는 부모 클래스가 있고, 사람을 상속한 학생 클래스가 있다고 가정하면, 사람은 통상적으로 이름이 있고, 나이, 생일 등이 있습니다. 이를 상속한 학생 클래스는, 이름, 나이 생일을 포함한 학교, 학년, 시험 성적등이 있겠죠&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. ④&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type Conversion은 C도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16. ①&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 소스파일은 텍스트 파일이다. 따라서 메모장으로도 편집이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림은 링킹을 설명하는 그림으로, 링킹이란 여러 실행 코드들을 컴파일 하여 나온 objㅇ파일과 라이브러리 등을 결합하는 과정으로, 이를 한데 묶어 실행파일을 만들기 위해 필요한 작업이다. 만약 링킹 하는 과정에서 참조하는 코드를 찾을 수 없다면, 링크 오류가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링킹 과정에서 실행파일로 같이 묶지 않고, 실행파일 시점에 라이브러리를 참조하도록 하는 것이 바로 Dynamic Linking Library이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c++ 20 기준으로도 netowrk library를 포함하고 있지 않습니다. c++ 23에 network를 의제가 포함되어 있긴 하지만, 확정된 사항이 아닌 것으로 알고있습니다. c++ 20 때도 포함된다는 얘기도 있었으니까요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 c++를 공부할 때, 이부분이 상상히 궁금했는데, 참고할만한 블로그 글이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645206157937&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Why doesn&amp;rsquo;t C++ have networking support?&quot; data-og-description=&quot;Recently, in a discussion of the Networking TS on one of WG21&amp;rsquo;s internal &amp;ldquo;reflector&amp;rdquo; mailing lists, Bjarne Stroustrup wrote:&quot; data-og-host=&quot;quuxplusone.github.io&quot; data-og-source-url=&quot;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&quot; data-og-url=&quot;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://quuxplusone.github.io/blog/2019/10/09/why-no-networking/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Why doesn&amp;rsquo;t C++ have networking support?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Recently, in a discussion of the Networking TS on one of WG21&amp;rsquo;s internal &amp;ldquo;reflector&amp;rdquo; mailing lists, Bjarne Stroustrup wrote:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;quuxplusone.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE라고 줄여말하는 이것은, 프로그램을 개발하는데 필요한 기능을 모아놓은 프로그램입니다. 코드를 작성하는데에는 메모장만 있으면 되지만, 생산성을 높이기 위해, 자동완성, 편리한 빌드세팅, 컴파일러 등 다양한 기능을 모아놓은것이 IDE이죠. IDE마다 목적이 다르기 때문에 개발하고자 하는 목적에 따라 IDE선택을 하시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows플랫폼에서는 Visual Studio가 강력할 것이고, 윈도우 GUI를 작성하는데에는, qt도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple 플롯폼은 Xcode가 독보적이고, 안드로이드 앱 개발에는 안드로이드 스튜디오, Java진영은 이클립스와 InteliJ가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>명품 C++ Programming(2018판)</category>
      <category>명품cppProgramming c++</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/40</guid>
      <comments>https://doing-programming.tistory.com/entry/%EB%AA%85%ED%92%88-C-Programming-1%EC%9E%A5-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C%EC%9D%B4%EB%A1%A0-%EC%A7%9D%EC%88%98-%EC%A0%95%EB%8B%B5#entry40comment</comments>
      <pubDate>Sat, 19 Feb 2022 02:47:39 +0900</pubDate>
    </item>
    <item>
      <title>Diffable Data Source</title>
      <link>https://doing-programming.tistory.com/entry/Diffable-Data-Source</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;(safari에서는 gif를 이미지를 불러올 수 없습니다. 곰탱이로 보여집니다. chrome로 봐요)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DiffableDataSource&lt;/b&gt;는 wwdc2019에서 Advances in UI Data Sources라는 이름으로 발표된 API입니다.&lt;br&gt;DiffableDataSource는 기존 DataSource를 구성하는 새로운 방법으로, 복잡한 코드를 간편하고 빠르게 작성할 수 있게 해줍니다.&lt;br&gt;CollectionView와 TableView에서 애니메이션 효과는 꼭 필요한 유저 경험을 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출현배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CollectionView, TableView 모두 동시에 적용 되므로, CollectionView로 앞으로는 통일하겠습니다.&lt;br&gt;간단한 CollectionView 써보셨다면, 사실 크게 복잡하지 않은 앱에서는 딱히 문제가 없으셨을 것입니다.&lt;br&gt;셀을 삭제하고, 추가할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;collectionView.deleteItems&lt;/code&gt; 또는 &lt;code&gt;insertItems&lt;/code&gt;을 통하면 애니메이션 효과를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HIG에 따르면 애니메이션은 사용자에게 조작감을 향상 시키고, 앱을 컨트롤하고 있다는 느낌을 들게해주는 중요한 요소입니다.&lt;br&gt;앱이 조금 복잡해져&lt;br&gt;Item을 삭제, 추가, 이동 한다고 했을 때는 &lt;b&gt;batch block&lt;/b&gt;을 열어, 안에서 &lt;b&gt;IndexPath&lt;/b&gt;를 계산해주어&lt;br&gt;앞서 애니메이션을 주는 메서드들을 작성할 수 있습니다.&lt;br&gt;그런데 앱은 이것보다 더 복잡해질 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCdLJJ%2FbtrtKDIBJ2f%2FPX9c992RuzLWEeGt9bjwt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;279&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 대량의 데이터가 오갈 수 있는 외부 서비스나, DB , 혹은 아까 말했던 것처럼 좀더 복잡하게 동작하는 DataSource의 경우 한번에 데이터가 여러개 추가, 삭제, 편집 등 다양하게 동작할 수 있습니다.&lt;br&gt;유저가 인터페이스를 통해서 조작하는거 까지는 처리가 쉬운데, 외부에서 들어오는 경우, batch 업데이트를 어떻게 해야 할까요?&lt;br&gt;방법이 없는 것은 아니지만, 꽤 코드가 복잡하고 길어질 것은 확실합니다.&lt;br&gt;여기서 한번쯤은 다들 경험 해보셨을&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dTsT8x/btrtIdRjq5W/KSzdkwDo1kJeO3yk5UZB1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dTsT8x/btrtIdRjq5W/KSzdkwDo1kJeO3yk5UZB1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dTsT8x/btrtIdRjq5W/KSzdkwDo1kJeO3yk5UZB1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdTsT8x%2FbtrtIdRjq5W%2FKSzdkwDo1kJeO3yk5UZB1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3352&quot; height=&quot;694&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;batch update 크래시 에러를 볼 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 크레시가 발생하기 때문에 여간 위험한게 아니기 때문에 애니메이션 없이 &lt;code&gt;reloadData()&lt;/code&gt;나 &lt;code&gt;reloadSection()&lt;/code&gt;을 한다고 가정해봅시다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;1040&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v05Ar/btrtLzFYqJr/wSpmWtuEMdYHN79WOfOaJK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v05Ar/btrtLzFYqJr/wSpmWtuEMdYHN79WOfOaJK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v05Ar/btrtLzFYqJr/wSpmWtuEMdYHN79WOfOaJK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/v05Ar/btrtLzFYqJr/wSpmWtuEMdYHN79WOfOaJK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;520&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;1040&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;임의의 앱을 만들어 보았는데요&lt;br&gt;개발자가 이력서 보이기를 설정하면, 화면에 표시되는 구조입니다.(가상으로)&lt;br&gt;애니메이션 없이 처리되니까 뭐가 어떻게 변하고 있는지, 알 수가 없고 꽤 정신 없습니다.&lt;br&gt;이렇게 경우에 따라 애니메이션이 없는 앱은 나쁜 유저 경험을 발생시킵니다.&lt;br&gt;이를 해결하는 것이 바로 &lt;b&gt;Diffable Data Source&lt;/b&gt; 인것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Diffable Data Source&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Diffable Data Source가 위에서 말한 어떠한 문제점들을 해결할까요?&lt;br&gt;1. No more batch update&lt;br&gt;2. No more IndexPath&lt;br&gt;3. Easy Animation&lt;br&gt;Diffable Data Source는 diff 연산과, View 업데이트의 두 가지고 생각할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eeUZBQ/btrtEwKUYn6/kqgdTiHoNavElJXkhT1CLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eeUZBQ/btrtEwKUYn6/kqgdTiHoNavElJXkhT1CLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eeUZBQ/btrtEwKUYn6/kqgdTiHoNavElJXkhT1CLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeeUZBQ%2FbtrtEwKUYn6%2FkqgdTiHoNavElJXkhT1CLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;370&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;DiffableDataSource는 현재 뷰의 상태를 &lt;code&gt;Current Snapshot&lt;/code&gt;이라는 형태로 관리를 하게 됩니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CdLJJ/btrtKDIBJ2f/PX9c992RuzLWEeGt9bjwt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCdLJJ%2FbtrtKDIBJ2f%2FPX9c992RuzLWEeGt9bjwt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;279&quot; data-origin-width=&quot;3352&quot; data-origin-height=&quot;1558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아까처럼 데이터의 변동 사항이 생기면 이를 &lt;code&gt;New Snapshot&lt;/code&gt;으로 봅니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2994&quot; data-origin-height=&quot;1642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coqYJD/btrtIdKzsJD/nLAVq3tK5oo1skwlKRy5I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coqYJD/btrtIdKzsJD/nLAVq3tK5oo1skwlKRy5I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coqYJD/btrtIdKzsJD/nLAVq3tK5oo1skwlKRy5I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoqYJD%2FbtrtIdKzsJD%2FnLAVq3tK5oo1skwlKRy5I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2994&quot; height=&quot;1642&quot; data-origin-width=&quot;2994&quot; data-origin-height=&quot;1642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;New Snapshot&lt;/b&gt;을 생성하고 이를 &lt;b&gt;Current Snapshot&lt;/b&gt;으로 적용하기 위해 &lt;code&gt;apply()&lt;/code&gt; 메서드를 호출하게 되는게 이게 DiffableDataSource에서 가장 매력적인 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply()메서드는 원래 Data Source에서 우리가 일일이 배치 블럭 열고, 인덱스 패스 계산하고, deleteRows등을 호출 하는 과정을 한번에 해결해줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;498&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;DiffableDataSource를 사용한 상태입니다. 이 모든게 DiffableDataSource를 적용하고, apply() 메서드만 호출한 결과물인거죠.&lt;br&gt;그러면 DiffableDataSource 는 어떻게 사용할 수 있나요?&lt;br&gt;아래 3가지를 구현하면 됩니다.&lt;br&gt;1. Hashable&lt;br&gt;2. configureDataSource()&lt;br&gt;3. configureSnapshot()&lt;br&gt;하나씩 천천히 봅시다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hashable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DiffableDataSource에 들어가는 인스턴스는 Hashable을 채용한 모델이여야 합니다. Diff 연산을 하기위해, 고유한 ID를 가질 필요가 있는 것이죠&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;final class Developer: Hashable {
  static func == (lhs: Developer, rhs: Developer) -&amp;gt; Bool {
    lhs.id == rhs.id
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }

  var id = UUID().uuidString
  var name: String
  var major: Major
  var language: [Language]
  var carrer: String
  var searcable: Bool = true

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Configure Data Source&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ConfigureDataSource는 특별한게 아니고, 기존 DataSource에서 하던 &lt;b&gt;cellForRowAt&lt;/b&gt; 같은 것이라고 생각하시면 됩니다.&lt;br&gt;RxDataSource를 사용해셨던 분들은 구조가 한번에 이해가 되실 겁니다.&lt;br&gt;우선 클래스의 Property에 DataSource 선언이 필요합니다.&lt;/p&gt;
&lt;pre class=&quot;nsis&quot;&gt;&lt;code&gt;var dataSource: UICollectionViewDiffableDataSource&amp;lt;Section, Developer&amp;gt;!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt;(TableView도 &lt;code&gt;UITableViewDiffableDataSource&lt;/code&gt;로 똑같습니다.)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVZ0F/btrtHUqX3bP/GjLEMhUbcBnGn1N2J4bOi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVZ0F/btrtHUqX3bP/GjLEMhUbcBnGn1N2J4bOi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVZ0F/btrtHUqX3bP/GjLEMhUbcBnGn1N2J4bOi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVZ0F%2FbtrtHUqX3bP%2FGjLEMhUbcBnGn1N2J4bOi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;771&quot; height=&quot;117&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이는 제네릭 타입 클래스로 Section Hashable Type과 Item Hashable Type을 받습니다. Item Hashable Type은 앞서 Hashable을 적용한 모델이 들어가면 되는것이고, Section에는 임의로 만드시면 됩니다.&lt;br&gt;예를들어 보여주고자 하는 CollectionView에 Main섹션, Body섹션 이 있다면&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum Section {
  case main
  case body
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 만든 후 Section Type에 전달해 주면 됩니다. 어렵지 않죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;configureDataSource()&lt;/code&gt;를 만들어서 호출해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private func configureDataSource() {
    dataSource = UICollectionViewDiffableDataSource&amp;lt;Section, Developer&amp;gt;(collectionView: collectionView) { collectionView, indexPath, developer in
      let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ProfileCardCell.identifier, for: indexPath) as! ProfileCardCell
      cell.configure(developer)
      return cell
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드는 &lt;code&gt;viewDidLoad()&lt;/code&gt; 등에서 호출해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UICollectionViewDiffableDataSource&lt;/code&gt;는 초기화로 &lt;code&gt;collectionView&lt;/code&gt;와 &lt;code&gt;cellProvider&lt;/code&gt; 클로져를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cellProvider&lt;/code&gt;를 보면 &lt;code&gt;collectionView&lt;/code&gt;, &lt;code&gt;indexPath&lt;/code&gt;, &lt;code&gt;itemIdentifier&lt;/code&gt; 가 전달되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;itemIdentifier&lt;/code&gt;는 제네릭으로 전달한 Hashable 모델입니다. 그러니 지금은 developer를 그대로 전달받는 것이죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 원래 데이터 리스트에서 indexPath로 찾을 필요 없이 바로 cell에 전달하시면 되는겁니다.&lt;br&gt;그런데 내용을 기존 data Source의 &lt;b&gt;cellForRowAt&lt;/b&gt; 와 똑같죠? 심지어 numberOfRowInSection, NumberOfSection 는 호출하지 않아도 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Configure Snapshot&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 dataSource와 cell구성은 되었으니 snapshot구성을 해봅시다. snapshot도 마찬가지로, &lt;code&gt;viewDidLoad()&lt;/code&gt;등에서 호출하는데, &lt;code&gt;configureDataSource()&lt;/code&gt;다음으로 호출해주시면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func configureSnapshot(animatingDifferences: Bool = true) {
    var snapshot = NSDiffableDataSourceSnapshot&amp;lt;Section, Developer&amp;gt;()
    snapshot.appendSections([.main])
    snapshot.appendItems(storage.filteredDevelopers, toSection: .main)
    dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;snapshot 구성은 &lt;code&gt;NSDIffableDataSourceSnapshot&lt;/code&gt; 클래스를 이용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스를 생성하고, &lt;code&gt;append Section&lt;/code&gt;과 &lt;code&gt;append Item&lt;/code&gt;을 전달해 준뒤&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dataSource.apply()&lt;/code&gt; 해주시면 끝입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body 섹션까지 전달하고 싶다면 &lt;code&gt;appendSections&lt;/code&gt;에 &lt;code&gt;[.main, .body]&lt;/code&gt; 와 같은 형태로 작성하면 되겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 끝입니다. 앞으로 데이터가 변경되면 &lt;code&gt;configureSnapshot()&lt;/code&gt;만 호출해주시면, 알아서 CollectionView가 애니메이션 과 함께 동작할 것입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cOiHAN/btrtHVwDxDl/Z0fYk1OP93DKn1IfTBSZQk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;498&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;아까의 화면이 지금까지 한 것의 결과물인거죠. 데이터 변경 시점만 Notification이나 Rx등을 통해 옵저버를 통해 구독하고 있다가 configureSnapshot() 코드만 호출하면 끝인겁니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Performance&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쉬운 DiffableDataSource 성능은 어떨까요?&lt;br&gt;diff 연산은 결국 데이터를 하나씩 비교하는 작업이기 때문에 O(N)의 시간 복잡도를 가집니다. 그러면 데이터가 커지면, 일일이 백그라운드 큐에서 apply()를 해야하나요?&lt;br&gt;&lt;b&gt;아니요!&lt;/b&gt;&lt;br&gt;DiffableDataSource는 diff연산은 알아서 백그라운드 큐에서, UI update는 알아서 메인 큐에서 실행해줍니다. 맞습니다. 스레드 관리가 필요가 없습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 기존 indexPath로 Item을 얻고, 원래 데이터에서 해당 아이템을 찾고 하는 과정을 &lt;code&gt;dataSource.itemIdentifier(for:)&lt;/code&gt;메서드를 통해 O(1) 시간으로 얻어올 수 있습니다. 모델이 Hashable이기 때문입니다. 멋지죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKqjB8/btrtGhUDhqJ/eAKECd9hotQaI6yDNO3YFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKqjB8/btrtGhUDhqJ/eAKECd9hotQaI6yDNO3YFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKqjB8/btrtGhUDhqJ/eAKECd9hotQaI6yDNO3YFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKqjB8%2FbtrtGhUDhqJ%2FeAKECd9hotQaI6yDNO3YFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;242&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;1102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;Trade-off&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 좋은 DiffableDataSource 문제는 없을까요?&lt;br&gt;아쉽게도 있습니다.&lt;br&gt; &lt;br&gt;&lt;b&gt;1. iOS 13.0이 최소버전&lt;/b&gt;&lt;br&gt;WWDC2019 (iOS 13.0)으로 발표되었기 때문에 미만의 OS에서는 사용할 수가 없습니다.&lt;br&gt;출시한 앱에 적용해보았는데, OS가 13.0, 14.0, 15.0에서 똑같은 코드로 각기다른 에러가 발생하더라구요, 제가 잘 사용하지 못한 것도 있지만, 바르게 사용하기 위해서는 숙달하는 시간도 필요할 것 같습니다.&lt;br&gt; &lt;br&gt;&lt;b&gt;2. RxDataSource&lt;/b&gt;&lt;br&gt;프로젝트에서 RxSwift를 사용한다면 RxDataSource라는 대체제도 있습니다. RxDataSource도 한계점이 있지만, 굳이 RxDataSource부분을 DiffableDataSource로 바꿀래? 라고 그러면 거기까지는 아니라는 거죠&lt;br&gt; &lt;br&gt;&lt;b&gt;3. Bug&lt;/b&gt;&lt;br&gt;DiffableDataSource를 쓰면 원래 dataSource = self가 완전히 무력화 되는데, &lt;br&gt;제가 겪었던 버그는 tableView에 &lt;b&gt;Drop, Drag Delegate&lt;/b&gt;를 사용해야 하는데 전혀 호출되지 않았던 적이 있습니다.&lt;br&gt; &lt;br&gt;&lt;a href=&quot;https://gookbobhenry.notion.site/DiffableDataSource-DropDelegate-ISSUE-52d6e52640bb41cf8172c58d490fe37b&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://gookbobhenry.notion.site/DiffableDataSource-DropDelegate-ISSUE-52d6e52640bb41cf8172c58d490fe37b&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;DiffableDataSource DropDelegate ISSUE&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;TableView에서 Drag, Drop Delegate를 이용해서 드래그하여 셀 재정렬이 가능한데, DiffableDataSource에서 Drop Delegate 메서드들이 호출이 안 되던 문제.&quot; data-og-host=&quot;gookbobhenry.notion.site&quot; data-og-source-url=&quot;https://gookbobhenry.notion.site/DiffableDataSource-DropDelegate-ISSUE-52d6e52640bb41cf8172c58d490fe37b&quot; data-og-url=&quot;https://gookbobhenry.notion.site/52d6e52640bb41cf8172c58d490fe37b&quot;&gt;
 &lt;a href=&quot;https://gookbobhenry.notion.site/52d6e52640bb41cf8172c58d490fe37b&quot; target=&quot;_blank&quot; data-source-url=&quot;https://gookbobhenry.notion.site/DiffableDataSource-DropDelegate-ISSUE-52d6e52640bb41cf8172c58d490fe37b&quot;&gt;
  &lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;DiffableDataSource DropDelegate ISSUE&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;TableView에서 Drag, Drop Delegate를 이용해서 드래그하여 셀 재정렬이 가능한데, DiffableDataSource에서 Drop Delegate 메서드들이 호출이 안 되던 문제.&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;gookbobhenry.notion.site&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;delegate = self 순서 변경으로 해결된 문제였지만, 얼마나 삽질했는지 생각하면 지금도 화가납니다.&lt;br&gt; &lt;br&gt;그럼에도, 기존 DataSource에 비해 애니메이션 까지 구현하기가 굉장히 쉽고, 편리합니다. 그래서 저는 간단한 앱에도 DiffableDataSource를 씁니다. 이쪽이 작성이 더 빠르거든요.&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DiffableDataSource는 iOS 14.0에서 한단계 더 진화하는데 이부분은 아직 저도 공부중으로, 관심있으시면 WWDC2020에 Advances in diffable data sources 영상을 보시는 걸 추천드립니다.&lt;br&gt;&lt;b&gt;Section Snapshot과 Reordering Support 가 &lt;/b&gt;새로 생기거든요&lt;br&gt; &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References / Resources&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/220&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;WWDC 2019 Advances in UI Data Sources&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/implementing_modern_collection_views&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Reference Project&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;br&gt;&lt;a href=&quot;https://github.com/urijan44/DiffableAnimation&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;Sample Project&lt;/span&gt;&lt;/a&gt;&lt;br&gt; &lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/39</guid>
      <comments>https://doing-programming.tistory.com/entry/Diffable-Data-Source#entry39comment</comments>
      <pubDate>Fri, 18 Feb 2022 23:30:51 +0900</pubDate>
    </item>
    <item>
      <title>Design Pattern - Coordinator Part1</title>
      <link>https://doing-programming.tistory.com/entry/Design-Pattern-Coordinator-Part1</link>
      <description>&lt;h1&gt;Coordinator Pattern&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator Pattern은 Structure Design Pattern으로 View Controller간의 로직 흐름을 조직하기 위한 디자인 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 얘기하자면, 뷰간 화면 전환 Coordinator로 한번에 관리하겠다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5개의 컴포넌트로 이루어져 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코디네이터 프로토콜: View Present, Dismiss 메소드를 정의&lt;/li&gt;
&lt;li&gt;코디네이터 인스턴스: 코디네이터 프로로콜을 채용한 인스턴스, 뷰 컨트롤러를 어떻게 만들 것인지 알고있다.&lt;/li&gt;
&lt;li&gt;라우터 프로토콜: View Present, Dismiss 메소드를 정의&lt;/li&gt;
&lt;li&gt;라우터 인스턴스: 라우터 프로토콜을 채용한 인스턴스, 코디네이터와 다른 점이라면, 어디서 무엇을 보여줄 것인지가 아니라, 어떻게 보여줄 것인지를 정의한다.&lt;/li&gt;
&lt;li&gt;뷰 컨트롤러 인스턴스 들: 뷰 컨트롤러는 서로, 어디서 어떻게 표시될지는 모른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/ed5b2142-22a2-4027-ad08-b123c10c5261/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코디네이터 패턴 클래스 다이어그램 srouce: &lt;i&gt;raywenderlich&lt;/i&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용목적은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰 컨트롤러간의 종속성을 때어내고 싶을 때&lt;/li&gt;
&lt;li&gt;뷰 컨트롤러의 재사용성을 높이고 싶을 때&lt;/li&gt;
&lt;li&gt;런타임에 뷰 컨트롤러 시퀀스가 결정될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Router&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Router는 Coordinator Pattern에서 어떻게 뷰의 Present, Dismiss의 방법을 정의한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

public protocol Router: AnyObject {
  func present(_ viewController: UIViewController, animated: Bool)
  func present(_ viewController: UIViewController, animated: Bool, onDismissed: (() -&amp;gt; Void)?)
  func dismiss(animated: Bool)
}

extension Router {
  public func present(_ viewController: UIViewController, animated: Bool) {
    present(viewController, animated: animated, onDismissed: nil)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 에서는 present와 dismiss 메소드를 정의한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NavigationRouter&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UINavigationController를 사용할 때 Router가 어떻게 동작하는지 만든다&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

public class NavigationRouter: NSObject {
  private let navigationController: UINavigationController
  private let routerRootController: UIViewController?
  private var onDismissForViewController: [UIViewController: (()-&amp;gt;Void)] = [:]

  public init(navigationController: UINavigationController) {
    self.navigationController = navigationController
    self.routerRootController = navigationController.viewControllers.first
    super.init()
    self.navigationController.delegate = self
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NavigationRouter&lt;/code&gt;는 &lt;code&gt;UINavigationController&lt;/code&gt;와 &lt;code&gt;RootViewController&lt;/code&gt;, 그리고 View가 Dismiss됐을 때 동작을 저장하는 Dictionary로 &lt;code&gt;[UIViewController: (() -&amp;gt; Void)?)]&lt;/code&gt;&lt;br /&gt;정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;code&gt;NavigationRouter&lt;/code&gt;는 Router 프로토콜을 채용한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension NavigationRouter: Router {
  public func present(_ viewController: UIViewController, animated: Bool, onDismissed: (() -&amp;gt; Void)?) {
    onDismissForViewController[viewController] = onDismissed
    navigationController.pushViewController(viewController, animated: animated)
  }

  public func dismiss(animated: Bool) {
    guard let routerRootController = routerRootController else {
      navigationController.popToRootViewController(animated: animated)
      return
    }
    performOnDismissed(for: routerRootController)
    navigationController.popToViewController(routerRootController, animated: animated)
  }

  private func performOnDismissed(for viewController: UIViewController) {
    guard let onDismiss = onDismissForViewController[viewController] else {
      return
    }
    onDismiss()
    onDismissForViewController[viewController] = nil
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;present&lt;/code&gt;시에는 present할 viewController를 파라메터로 가진다. 전달받은 viewController는 navigationController에 push되는데, 이때 onDismissForViewController에 onDismissed 클로져와 함께 등록된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;dismiss(animated:)&lt;/code&gt;메소드는 뷰가 dismiss될 때를 정의하는 것으로,&lt;br /&gt;&lt;code&gt;routerRootViewController&lt;/code&gt;가 없으면 가장 아래 뷰 컨트롤러까지 pop한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;있을 경우는 평범하게 해당 뷰 컨트롤러까지 pop하는데 이 전에 &lt;code&gt;performOnDismissed&lt;/code&gt;를 호출한다. 해당 메서드는 뷰를 present할때 정의해 두었던 dismiss 클로져를 실행하고, 딕셔너리에서 정리하기 위함&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Coordinator&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코디네이터 프로토콜은 각 뷰 컨트롤러간의 계층 구조를 나타내기 위함이다.&lt;br /&gt;코디네이터 프로토콜은 아래와 같다&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;public protocol Coordinator: AnyObject {
  var children: [Coordinator] { get set }
  var router: Router { get }

  func present(animated: Bool, onDismissed: (()-&amp;gt;Void)?)
  func dismiss(animated: Bool)

  func presentChild(_ child: Coordinator, animated: Bool)
  func presentChild(_ child: Coordinator, animated: Bool, onDismissed: (()-&amp;gt;Void)?)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator 프로토콜을 준수하는 객체를 children으로 가지고&lt;br /&gt;router를 가진다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension Coordinator {
  public func dismiss(animated: Bool) {
    router.dismiss(animated: animated)
  }

  public func presentChild(_ child: Coordinator, animated: Bool) {
    presentChild(child, animated: animated, onDismissed: nil)
  }

  public func presentChild(_ child: Coordinator, animated: Bool, onDismissed: (()-&amp;gt;Void)?) {
    children.append(child)
    child.present(animated: animated, onDismissed: {[weak self, weak child] in
      guard let self = self, let child = child else { return }
      self.removeChild(child)
      onDismissed?()
    })
  }

  public func removeChild(_ child: Coordinator) {
    guard let index = children.firstIndex(where: { $0 === child }) else { return }
    children.remove(at: index)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;child를 present 할 때는 children에 해당 child View Controller를 등록한다. onDismissed 클로져에는 실행 시에 스스로를 제거하는 클로져이다.&lt;/p&gt;
&lt;h1&gt;Simple Example&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 실제 예제로 Coordinator 패턴을 좀더 들여다보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/fae802f7-8665-4f7b-b9b0-42e805faa5a4/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 각기다른 3개의 뷰 컨트롤러가 있다고 가정하자.&lt;br /&gt;Coordinator 패턴이 아닌 일반적인 방법으로 계층을 보면&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ViewController1에서 ViewController2를 인스턴스화 하고 push한다(네비게이션 기준, present일 수도 있다.)&lt;/li&gt;
&lt;li&gt;ViewController2에서 ViewController3를 인스턴스화 하고 push한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이고 각 뷰컨트롤러 내용에는 화면 전환에 대한 코드가 존재할 것이다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;//View Controller1
func buttonAction() {
    navigationController?.pushViewController(ViewController2, animated: true)
    //or
    present(ViewController2, animated: true)

}

//View Controller2
func buttonAction() {
    navigationController?.pushViewController(ViewController3,     animated: true)
    //or
    present(ViewController3, animated: true)
}


//View Controller3
func buttonAction() {
    navigationController?.popViewController(animated: true)
    //or
    dismiss(animated: true))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator 패턴에서는 이 부분이 뷰 컨트롤러에서 빠지고, 두 컴포넌트, Coordinator와 Router로 분리되어 작성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 뷰컨트롤러에서는 Coordinator에게 나 &lt;i&gt;버튼 눌렸으니 화면 이동해줘!&lt;/i&gt; 라고 핸들러나 델리게이트로 알려주기만 하면 된다. 어떻게 전환할지(push, present)는 Router에서,&lt;br /&gt;어디로 이동할지(ViewController1 -&amp;gt; ViewController2)는 Coordinator에서 관리하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/acdb98f7-c510-46c0-a25c-9917d8ce8e91/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Concrete Coordinator에서는 ViewController의 계층을 알고있고, View Controller에서 델리게이트 또는 핸들러 등으로 화면 전환이 필요하다고 요청하면, Router를 통해 어떻게 전환할 것인지 결정한다.&lt;/p&gt;
&lt;h1&gt;장점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator Pattern의 장점은 위 처럼 하여, 여기저기 흩어져 있는 뷰 계층을 Coordinator에서 한눈에 파악이 가능하고, 뷰 전환에 관련된 코드가 뷰 컨트롤러에서 빠지므로, 뷰가 좀더 정말 보여주는 거에만 집중할 수 있다.&lt;br /&gt;다만 너무 작은 시스템에서는 오히려, 오버킬이 될 수 있다.&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/38</guid>
      <comments>https://doing-programming.tistory.com/entry/Design-Pattern-Coordinator-Part1#entry38comment</comments>
      <pubDate>Wed, 9 Feb 2022 18:00:06 +0900</pubDate>
    </item>
    <item>
      <title>Access Control</title>
      <link>https://doing-programming.tistory.com/entry/Access-Control</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; font-size: 16px;&quot;&gt;Access Controler(접근제어)&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reference by Swift Apprentice&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍시 프로퍼티나 메소드, 이니셜라이저 기타 유형 등을 이용해서 Swift 타입을 선언하고 이러한 요소들을 합쳐 API 인터페이스를 구성한다. 코드가 복잡해지고 인터페이스를 제어하는 과정에서 일명 도우미 메소드를 인터페이스 내부에서 사용하는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 이 인터페이스를 사용할 때는 이러한 도우미 메소드는 숨기는 것이 구현의 복잡성을 숨길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이 숨겨진 내부 상태는 공개 인터페이스가 항상 유지해야 하는 불변성을 유지하게 하는 일명 캡슐화로 알려진 기본적인 소프트웨어 설계 개념이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근 제어가 없다면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은행 라이브러리를 작성하고 있다고 가정하자, 해당 라이브러리는 다른 은행이 뱅킹 소프트웨어를 작성할 수 있는 기반을 하는데 도움이 되는 라이브 러리이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;protocol Account {
  associatedtype Currency

  var balance: Currency { get }
  func deposit(amount: Currency)
  func withdraw(amount: Currency)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 Account로 정의 된 것은 잔액을 확인하는 프로퍼티와, 입출금을 하는 메소드가 정의 되어 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;typealias Wons = Int

class BasicAccount: Account {

  var balance: Wons = 0

  func deposit(amount: Wons) {
    balance += amount
  }

  func withdraw(amount: Wons) {
    if amount &amp;lt;= balance {
      balance -= amount
    } else {
      balance = 0
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국은행에서 이 프로토콜을 가져와 위와 같이 프로토콜을 준수해서 클래스를 만들었다고 친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Account 프로토콜에서 잔고는 읽기를 요구사항으로 정의 했지 쓰기 기능에 대해서 제한은 걸어두지 않았다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;//계좌를 개설한다.
let account = BasicAccount()

//계좌에 입 출금한다.
account.deposit(amount: 10_000)
account.withdraw(amount: 5_000)

//돈을 1백만원으로 변경한다?
account.balance = 1_000_000

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계좌의 잔고 변동은 입출금 메소드를 통해서만 변경이 가능해야 하는데 이 경우 아무런 제약사항이 없기 때문에 외부에서 내부 상태를 직접적으로 변경할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 예시로 접근 제어가 보안 기능과 연관이 있다고 착각할 수 있다. 접근 제어의 목적은 보안이 아니라 객체의 불변성과 정확성을 유지하는 것에 있다는 것을 명심하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근 제어 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 제어는 속성이나 메소드 앞에 형식 선언 앞에 modifier 키워드를 추가로 선언한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 위와 같은 BasicAccount에 접근제어를 설정하자면&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;typealias Wons = Int

class BasicAccount: Account {

  private(set) var balance: Wons = 0

  func deposit(amount: Wons) {
    balance += amount
  }

  func withdraw(amount: Wons) {
    if amount &amp;lt;= balance {
      balance -= amount
    } else {
      balance = 0
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;balance를 설정하는 set은 private로 외부에서 이를 접근하는 것을 제한한다. 아까와 같은 실행 코드를 작성하게 되면 balance 메소드는 접근이 불가하다며 컴파일 에러를 발생시킨다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;private : 동일한 소스 파일 내에서 타입 정의, 모든 내부 타입, 해당 타입의 확장자에만 액세스 할 수 있다.&lt;/li&gt;
&lt;li&gt;fileprivate : 정의된 소스 파일 내 어디에서나 액세스 할 수 있다.&lt;/li&gt;
&lt;li&gt;internal : 정의된 모듈 내 어디에서나 액세스 할 수 있다. 이 수준은 Swift에서 기본으로 설정되어 있다.&lt;/li&gt;
&lt;li&gt;public : 모듈을 가져오는 모든 곳에서 액세스 할 수 있다.&lt;/li&gt;
&lt;li&gt;open : public과 동일하며 모듈의 코드를 재정의 할 수 있는 추가 기능이 부여가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Private&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Private 접근 제어는 정의된 엔티티와 그 안에 있는 모든 Nested type에 대한 접근을 제한한다(이를 Lexical scope라고도 함). 동일한 소스 파일 내의 extension에 대한 엔티티에는 액세스 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class CheckingAccount: BasicAccount {
  private let accountNumber = UUID().uuidString

  class Check {
    let account: String
    var amount: Wons
    private(set) var cashed = false

    func cash() {
      cashed = true
    }

    init(amount: Wons, from account: CheckingAccount) {
      self.amount = amount
      self.account = account.accountNumber
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accountNumber의 경우 고유한 id이므로 상수이면서 외부에서 접근을 제한하며 Check Nested 타입의 경우 여기까지 접근을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 예금 계좌는 수표를 쓸 수도 있고 이를 현금화 할수도 있어야 하기 때문에 아래 메소드를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func writeCheck(amount: Wons) -&amp;gt; Check? {
  //출금을 하는데 출금 금액보다 잔고수가 많아야 한다.
  guard balance &amp;gt; amount else {
    return nil
  }

  //출금 목표 금액 만큼의 수표를 작성한다.
  let check = Check(amount: amount, from: self)
  //출금
  withdraw(amount: check.amount)
  return check
}

func deposit(_ check: Check) {
  //수표가 아직 현금화 되지 않았을 때만 진해앟ㄴ다.
  guard !check.cashed else {
    return
  }

  //계좌에 입금한다.
  deposit(amount: check.amount)
  //수표를 현금화 했다고 알린다.
  check.cash()
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;철수가 영희에게 수표를 쓴다고 가정하자&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//철수의 계좌를 만들고 3백만원을 입금한다.
let 철수Checking = CheckingAccount()
철수Checking.deposit(amount: 3_000_000)

//철수 계좌로부터 2백만원의 수표를 작성한다.
let check = 철수Checking.writeCheck(amount: 2_000_000)!

//영희의 계좌를 만들고 수표를 철수가 쓴 수표를 입금한다.
let 영희Checking = CheckingAccount()
영희Checking.deposit(check)
영희Checking.balance // 2_000_000

//이미 사용한 수표를 한번 더 사용하지만
//계좌는 변도잉 없다.
영희Checking.deposit(check)
영희Checking.balance // 2_000_000

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 작성을 해보면 알겠지만 영희Checking.까지 입력했을 때 자동 완성이 표시하는 인터페이스를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 철수가 작성한 수표의 경우 한번 사용하고 현금화가 되었는데 우리는 이를 private(set)으로 작성하였기 때문에 외부에서 직접적으로 현금화된 상태를 변경할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 check의 경우 Basic Account의 Nested 유형인데 private로 선언된 accountNumber직접적으로 접근할 수 없지만 우리는 초기화때 이를 account로 넘겨 받았기 때문에 이를 통해 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 작성된 코드 중 Account protocol과 Wons typealies BasicAccount를 플레이그라운드에서 Sources 폴더로 Account.swift 라는 파일을 만들고 옮겨보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 CheckingAccount 클래스도 Sources 폴더에 CheckingAccount.swift 파일을 만들고 옮기자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 남은 철수가 영희에게 수표를 작성하고 옮기는 코드가 CheckingAccount를 찾지 못해 컴파일 에러가 발생한다. 이는 나중에 해결할 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fileprivate&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fileprivate는 private가 제공하는 동일한 파일 내에서 동일한 lexical scope와 확장자 대신 엔티티와 동일한 파일에 작성된 모든 코드에 대한 접근을 허용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 당장은 아까 작성한 Check에 대해 코더가 접근하고 수정하는 것을 막을 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 코드의 안전성을 위해서 CheckingAccount에서만 Check가 작성되기를 원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Check의 생성자를 private로 선언해버리면 어떻게 될까?&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private init(amount: Wons, from account: CheckingAccount) { //...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 Check를 선언하는 것을 막을 수 있지만 CheckingAccount에서 접근하는 것도 막아버리니 원하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에 사용할 수 있는 것이 바로 fileprivate이다. Check의 생성자를 fileprivate로 바꾸어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 오로지 CheckingAccount에서만 Check를 작성할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Internal, Public, Open&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private와 fileprivate는 다른 타입의 파일에 접근하는 코드를 보호할 수 있다. 이러한 Access Modifier는 Internal 디폴트 액세스 레벨에서 엑세스룰 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Internal 엑세스 레벨은 엔터티가 정의된 소프트웨어 모듈 내의 모든 위치에서 엔터티에 액세스 할 수 있음을 의미한다. 지금까지 모든 코드를 하나의 플레이그라운드 파일을 작성했는데 이는 모두 동일한 모듈에 있음을 의미한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 액세스 수준은 엔터티가 정의된 소프트웨어 모듈 내의 모든 위치에서 엔터티에 액세스할 수 있음을 의미합니다. 지금까지 모든 코드를 하나의 플레이그라운드 파일에 작성했습니다. 이는 모두 동일한 모듈에 있음을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이그라운드의 Sources 디렉토리에 코드를 추가하면 플레이그라운드에서 사용하는 모듈을 효과적으로 생성한 것이다. Xcode에서 플레이그라운드가 작동하는 방식으로 Sources 디렉토리의 모든 파일은 한 모듈의 일부이며 플레이그라운듸 모든 것은 Sources 폴더의 모듈을 사용하는 또 다른 모듈이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Internal&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이그라운드로 돌아가서 아까, private가 끝나고 Sources폴더로 파일을 나눈 뒤 코드가 작동하지 않는 것을 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount를 찾을 수 없다는 이유로 컴파일 에러가 발생하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에대해서 더 잘 설명하려면 Public과 Open에 대해서도 알아야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Internal은 별도로 사용하지 않았지만 이는 Swift에서 기본 액세스 수준이므로 명시적으로 선언할 필요가 없다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Public&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount를 플레이그라운드에 정상적으로 표시되게 하려면 액세스 수준을 Internal에서 Public으로 변경해야 한다. Public은 정의된 모듈 외부의 코드에서 보고 접근할 수 있는 엔터티이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount 클래스에 Public modifier를 추가하자 또한 CheckingAccountrk Account 모듈에서 BasicAccount를 불러올 수 있도록 BasicAccount에도 public을 추가하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 CheckingAccount에 대한 접근은 가능하지만 여전히 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount' initializer is inaccessible due to 'internal' protection level&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount의 생성자가 internal 보호 수준으로 접근할수가 없다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 자체는 Public으로 공개되었지만 해당 멤버는 여전히 Internal이므로 모듈 외부에서 사용할 수가 없다. 이제 덕지 덕지 public을 붙일 차례이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BasicAccount와 CheckingAccount에 기본 생성자를 public으로 해서 새로 만들어 주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BasicAccount의 경우 balance, deposit, siwthdraw 그리고 typealias를 모두 public으로 선언한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount에서는 writeCheck(amount:), deposit(_:) Check을 모두 public으로 선언한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BasicAccount에서 balance는 private(set)으로 선언 되어 있는데 이경우는&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public private(set) var balance: Wons = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 작성해 주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Open&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 CheckingAccount 및 해당 멤버들이 공개되어 뱅킹 인터페이스를 설계된 대로 사용할 수 있다. 하지만 한가지 기존 계좌 형태가 아니라 다른 특별한 형태, 예를들어 이자가 붙는 계좌를 만든다고 했을 때를 보자&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class SavingsAccount: BasicAccount {
  var interestRate: Double

  init(interestRate: Double) {
    self.interestRate = interestRate
  }

  func processInterest() {
    let interest = Double(balance) * interestRate
    deposit(amount: Int(interest))
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 BasicAccount가 Open이 아니라서 재정의 할 수가 없다는 에러가 발생한다. 이제 BasicAccount를 Public에서 Open으로 수정해서 해결 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BasicAccount 가 open으로 공개되었다고 해서 그 안에 정의된 출금, 입금 메소드를 재정의 할 수는 없다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;override func deposit(amount: Wons) {

    super.deposit(amount: 1_000_000)
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 이런 코드는 동작하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Extension 코드 조직화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 제어의 주제는 코드가 느슨하게 결합되고 응집력이 높아야 한다는 내용이다. 느슨하게 결합된 코드는 한 엔터티가 다른 엔터티에 대해 아는 정도를 제한하므로 코드의 다른 부분이 또 다른 부분에 덜 의존하게 된다. 앞에서 나온 내용처럼 응집력이 높은 코드는 밀접하게 관련된 코드가 함게 작업하여 작업을 수행하는데 도움이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;행동(Behavior)에 의한 Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 효과적인 전략은 코드를 행동에 따라 extension을 구성하는 것이다. extension 자체에도 접근 제어를 적용할 수 있다. 그러면 전체적인 코드 섹션을 public, internal, private로 분류하는데 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 CheckingAccount에서 사기 방지 기능을 추가하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CheckingAccount에 다음과 같은 속성을 추가한다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;private var issuedChecks: [Int] = []
private var currentCheck = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 private extension을 추가한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private extension CheckingAccount {
  func inspectForFraud(with checkNumber: Int) -&amp;gt; Bool {
    issuedChecks.contains(checkNumber)
  }

  func nextNumber() -&amp;gt; Int {
    let next = currentCheck
    currentCheck += 1
    return next
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 메소드와 프로퍼티로 수표 발행의 사기 행각을 추적을 계획이다. 이 확장자는 private로 선언했는데 암시적으로 해당 멤버 전부를 private로 보게 된다. 이렇게 하면 해당 기능은 오로지 CheckingAccount에서만 컨트롤 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로토콜에 따른 Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 분류법은 프로토콜 준수를 기반으로 확장하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;extension CheckingAccount: CustomStringConvertible {
  public var description: String {
    &quot;Checking Balance: $\\\\(balance)&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 확장은 CustomStringConvertible을 구현하는데 더 중요한 것은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;description이 CustomStringConvertible의 일부분임을 분명히 한다.&lt;/li&gt;
&lt;li&gt;다른 프로토콜을 준수하는데 영향을 주지 않는다.&lt;/li&gt;
&lt;li&gt;CheckingAccount의 나머지 에 대해 영향을 끼치지 않고 손쉽게 제거하고 추가할 수 있다. 이해하기도 쉽다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;available()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SavingsAccount를 보면 이자를 증가시키는 메소드를 여러번 호출해서 반복적으로 동작해서 이자 추가를 남용할 수도 있다. 이 기능을 좀더 안전하게 만들기 위해 계정에 PIN을 추가할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class SavingsAccount: BasicAccount {
  var interestRate: Double
  private let pin: Int

  init(interestRate: Double, pin: Int) {
    self.interestRate = interestRate
    self.pin = pin
  }

  func processInterest(pin: Int) {
    if pin == self.pin {
      let interest = balance * interestRate
      deposit(amount: interest)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SavingsAccount에 pin을 추가하고 이자 증가 메소드가 이 PIN을 매개변수로 사용하는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이자 증가 메소드를 함부로 사용할 수 없도록 했지만 기존에 이 SavingAccount클래스를 사용하던 은행 클라이언트는 업데이트된 코드로 인해서 기존에 쓰던 코드가 컴파일이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 구현을 사용하는 코드가 깨지는 것을 방지하려면 코드를 교체하는 대신 더 이상 사용하지 않아야 한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;class SavingsAccount: BasicAccount {
  var interestRate: Double
  private let pin: Int

  @available(*, deprecated, message: &quot;Use init(interestRate:pin:) instead&quot;)
  init(interestRate: Double) {
    self.interestRate = interestRate
    pin = 0;
  }

  init(interestRate: Double, pin: Int) {
    self.interestRate = interestRate
    self.pin = pin
  }

  @available(*, deprecated, message: &quot;Use processInterest(pin:) instead&quot;)
  func processInterest() {
    let interest = balance * interestRate
    deposit(amount: interest)
  }

  func processInterest(pin: Int) {
    if pin == self.pin {
      let interest = balance * interestRate
      deposit(amount: interest)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 이전버전을 사용하려고 하는 유저에게 경고 메시지를 남겨줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별표 매개변수는 영향을 받는 플랫폼을 나타내고, 두번째 매개변수는 deprecated, renamed, unavailable을 나타낸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Opaque return types&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은행 라이브러리 사용자를 위한 공개 API를 만들어야 된다고 하자 createAccount라는 새 계정을 만드는 함수를 만들어야 한다. 이 API의 요구 사항 중 하나는 구현 세부 정보를 숨겨 클라이언트가 일반 코드를 작성하도록 권장하는 것이다. 이는 생성 중인 계정 유형(BasicAccount, CheckingAccoung 또는 SavingsAccount)를 노출해서는 안된다는 의미이다. 대신 프로토콜 Account를 준수하는 일부 인스턴스만 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 활성화하려면 우선 Accouns 프로토콜을 public으로 두어야 한다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;func createAccount() -&amp;gt; Account {
  CheckingAccount()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 이제 코드를 작성하면 에러가 발생하는데 프로토콜 Account는 Self 또는 연관 타입 요구사항이 있기 때문에 일반 제약 조건으로만 사용할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해서는 아래 처럼 쓰면 된다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;func createAccount() -&amp;gt; some Account {
  CheckingAccount()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;some 키워드를 붙였다. 이게 불투명한 반환 타입이며 Account 클래스 타입을 노출하지 않고 함수가 반환하려는 타입을 결정할 수 있도록 한다.&lt;/p&gt;
&lt;h1&gt;Swift 최적화&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서 접근 제어자의 역할은 기본적으로 접근 범위를 제한함으로써, 객체지향의 원칙을 지키는데 있으나, 동시에 컴파일러에서 빌드 속도를 최적화 하는데 이용될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 .swift 파일을 빌드할 때, 각 객체(클래스나 구조체)관의 의존성(누가 누구를 호출하는지)을 확인하는데 이때 접근제어자로 이를 명시적으로 지정하게 되면 컴파일러는, 해당 클래스의 재정의 여부나 의존 여부를 쉽게 판단하게 된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;private class E {
  func doSomething() { ... }
}

class F {
  fileprivate var myPrivateVar: Int
}

func usingE(_ e: E) {
  e.doSomething() // There is no sub class in the file that declares this class.
                  // The compiler can remove virtual calls to doSomething()
                  // and directly call E's doSomething method.
}

func usingF(_ f: F) -&amp;gt; Int {
  return f.myPrivateVar
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#reducing-dynamic-dispatch&quot;&gt;swift/OptimizationTips.rst at main &amp;middot; apple/swift&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에 여러 모듈이 있는 경우, 다른 모듈에서 참조할 필요가 없는 경우는 기본인 internal로 선언되는 것도 중요하다! 모든것을 다 public 선언 해버리면 컴파일러가 전부 관계 조사를 해야할 것이다.&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/37</guid>
      <comments>https://doing-programming.tistory.com/entry/Access-Control#entry37comment</comments>
      <pubDate>Wed, 9 Feb 2022 17:59:53 +0900</pubDate>
    </item>
    <item>
      <title>SwiftGen 사용기 (Homebrew)</title>
      <link>https://doing-programming.tistory.com/entry/SwiftGen-%EC%82%AC%EC%9A%A9%EA%B8%B0-Homebrew</link>
      <description>&lt;h1&gt;소개&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftGen을 간단하게 소개하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 쉽게 시나리오로 설명을 해보려고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Assets 카탈로그에 Icons, Image, Color등을 정의하고 이를 프로젝트에서 사용하기 위해서는 각각의 생성자 (Color로 예를들자면 &lt;code&gt;UIColor(named:)&lt;/code&gt; 를 통해 Asset에 지정한 문자열 상수를 넣어 불러와야 한다.
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let customColor = UIColor(named: &quot;customRed&quot;)!&lt;/code&gt;&lt;/pre&gt;
해당 방법은 문자열을 하드코딩 한다는 점에서, 실수가 발생할 여지가 있고, 또 런타임에 에러가 결정되기 때문에 컴파일 타임에 해당 실수를 잡아내기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보완책으로 아래 처럼 사용해볼 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum Constans: String {
  let customRed = &quot;customRed&quot;
}

extension UIColor {
  static var customRed: UIColor {
    UIColor(named: Constans.customRed.rawValue) ?? UIColor()
  }
}

let customRed: UIColor = .customRed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가한 에셋이 많을 경우는 매크로 코드를 짜서 일괄로 생성하는 방법도 썼었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 간편하게 해주도록 나온 것이 바로 SwiftGen이다.&lt;br /&gt;SwiftGen은 위 과정을 템플릿을 통해 일괄적으로 작업할 수 있게 해주고,&lt;br /&gt;하드코딩에 의한 실수, 또 컴파일 타임에 에러를 잡을 수 있도록 도와준다.&lt;br /&gt;어떻게 사용하는지 알아보자.&lt;/p&gt;
&lt;h1&gt;설치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 전에 나의 작업 환경은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;m1 macbook pro 16g&lt;/li&gt;
&lt;li&gt;Xcode 13.2&lt;/li&gt;
&lt;li&gt;Monterey 12.1&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/SwiftGen/SwiftGen#installation&quot;&gt;설치 방법&lt;/a&gt;&lt;br /&gt;github에 잘 소개 되어있기도 한데 여러 방법마다 장단이 있는 것 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Download the ZIP&lt;/li&gt;
&lt;li&gt;CocoaPods&lt;/li&gt;
&lt;li&gt;Homebrew&lt;/li&gt;
&lt;li&gt;Mint&lt;br /&gt;Zip과 CocoaPods는 프로젝트 별로 따로 적용하는데 이용하기 편하고&lt;br /&gt;Homebrew와 Mint는 시스템 전체적으로 사용할 수 있기 때문에,프로젝트 마다 일일이 설정하기 싫은 경우 선택할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 Homebrew를 선택했고, Homebrew로 설치했지만, 프로젝트마다 다르게 swiftgen.yml을 다르게 적용해보려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Homebrew 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;terminal에서 brew로 swiftgen을 설치한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew update
brew install swiftgen&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 hoem directory에 기본적으로 swiftgen.yml이 위치하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;swiftgen 기본 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swiftgen의 기본 사용 방법은 swiftgen.yml을 통해 템플릿을 작성하고&lt;br /&gt;swiftgen으로 템플릿을 파싱하는 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swiftgen.yml에서는 번들의 &lt;code&gt;Assets.xcassets&lt;/code&gt;를 파싱해서&lt;br /&gt;&lt;code&gt;Asset+Generated.swift&lt;/code&gt;(예시 이름)를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Asset+Generated.swift&lt;/code&gt;에는 열거형으로 Asset에 정의한 것들을 접근할 수 있게 한다.&lt;br /&gt;예를들어&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let customRed: UIColor = Asset.Color.customRed.color&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 쓸 수 있다. 타입 어노테이션은 참고하라고 써둔 것이고 필수가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 저게 어떻게 돌아가는지 간단하게 테스트 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 다음과 같이 입력한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cd ~/Desktop
swiftgen config init&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여기서 에러 났으면 brew로 swiftgen이 설치가 안된거다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 사용하기 편하게 데스크톱 디렉토리로 이동 한 뒤 작업한다.&lt;br /&gt;위 명령어는 swiftgen.yml을 생성하는 명령어이다.&lt;br /&gt;Dekstop 디렉토리로 가면 swiftgen.yml이 생성되어 있을 것이다.&lt;br /&gt;아마 &lt;code&gt;swiftgen config init&lt;/code&gt; 명령어를 입력하면 swiftgen.yml 파일이 자동으로 편집기로 열릴 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;swiftgen.yml&lt;/code&gt;내용을 보면 주석으로 사용법이 자세하게 적혀있는게 나중에 디테일 하게 사용하고 싶다면 꼭 읽어보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 우선 asset에만 집중해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트에서 방금 만든 &lt;code&gt;swiftgen.yml&lt;/code&gt;을 가져오자.&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/fd92d3d3-d6c8-47ba-996f-3ae2d727744c/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴리즈 하고 나면 swiftgen이 실행 될 일이 없기 때문에, 따로 가져올 필요가 없지만, 설명에 편의를 위해 가져왔다. 프로젝트에 따로 추가하지 않아도 되고 필요한 디렉토리 위치시켜 두고 위치만 지정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yml내용을 보면&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# xcassets:
#   inputs:
#     - Main.xcassets
#     - ProFeatures.xcassets
#   outputs:
#     - templateName: swift5
#       params:
#         forceProvidesNamespaces: true
#       output: XCAssets+Generated.swift&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 Asset을 파싱 후 컨버팅 해주는 부분으로, 조리해서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해보려고 하는 부분은 Color Asset을 별도로 만들어 볼려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Asset Catalog&lt;/code&gt;를 하나 새로 만든다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/ae19f0f6-61ec-4282-9f16-37a68119d755/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 Color 로&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/4a0fb82f-93a1-4f45-86d1-d81ec21e49c6/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Color Asset안에 커스텀 컬러 몇개 저장 후&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/1bd3527e-e0e2-479d-a8e1-b7352a4e4d1c/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swiftgen.yml에 다음과 같이 내용을 추가한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;xcassets:
  inputs:
    - Color.xcassets
  outputs:
    - templateName: swift5
      params:
        enumName: Colors
      output: Color+Generated.swift&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용을 찬찬히 뜯어보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;xcassets: xcassets 형태를 파싱하기 위한 명령어로 문서보면 xcassets말고도 string, ib 등등이 있다.&lt;/li&gt;
&lt;li&gt;inputs: 입력할 파일로 우리가 생성한 Asset Catalog를 전달&lt;/li&gt;
&lt;li&gt;outputs: 출력할 영역
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;templateName: swift5를 쓰자, 특별히 swift4를 쓸일이 있으면 해당 내용을 전달하면 된다. 자세한 내용은 문서&lt;/li&gt;
&lt;li&gt;params: 파일을 출력할 때 몇가지 옵션을 줄 수 있다.&lt;/li&gt;
&lt;li&gt;enumName: 따로 설정하지 않으면 기본 열거형의 이름이 Asset로 정해진다.&lt;/li&gt;
&lt;li&gt;output: 출력할 파일 이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성이 끝났으면 terminal로 해당 yml이 있는 디렉토리로 찾아간다.&lt;br /&gt;그리고 아래 명령어를 입력&lt;br /&gt;&lt;code&gt;swiftgen config run&lt;/code&gt;&lt;br /&gt;성공적으로 명령어가 동작했다면&lt;br /&gt;&lt;code&gt;File written: Color+Generated.swift&lt;/code&gt;&lt;br /&gt;이라고 표시된다.&lt;br /&gt;지금은 아무것도 보이지 않을텐데 해당 디렉토리로 가 보면&lt;br /&gt;&lt;code&gt;Color+Generated.swift&lt;/code&gt;파일이 생성 되어 있을 것이다.&lt;br /&gt;이를 프로젝트에 추가한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct ContentView: View {
  var body: some View {
    Text(&quot;Hello, world!&quot;)
      .padding()
      .foregroundColor(Colors.customRed.color)
  }
}
extension View {
  func foregroundColor(_ uicolor: UIColor) -&amp;gt; some View {
    self.foregroundColor(Color(uicolor))
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;extension은 foregroundColor에서 UIColor로 바로 생성할 수 있도록 작성 된것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 컬러는&lt;br /&gt;&lt;code&gt;Color.customRed.color&lt;/code&gt;로 접근할 수 있다. 형태는 &lt;code&gt;Color&lt;/code&gt;는 yml에서 작성한 enumName이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 기본적인 작업 플로우로, 정리해보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;swiftgen.yml 을 생성&lt;/li&gt;
&lt;li&gt;swiftgen.yml 템플릿 내용을 작성&lt;/li&gt;
&lt;li&gt;terminal에서 &lt;code&gt;swiftgen config run&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;생성된 swift파일 프로젝트에 추가&lt;br /&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 좀더 생각해보면, Asset Catalog에 새 Asset이 추가될 때 마다 terminal 키고 &lt;code&gt;swiftgen config run&lt;/code&gt; 하면 너무 귀찮은데? 라는 생각이 들 수 있다. 이부분을 자동화 시켜줄 수 있는것이 바로 &lt;code&gt;Run Script&lt;/code&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RunScript&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 -&amp;gt; 타겟 -&amp;gt; 빌드 페이즈(Build Phases)로 이동한 뒤&lt;br /&gt;Run Script를 하나 추가하자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/46f9b54d-175f-4509-94a8-55d801e2f5e4/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;run script는 빌드 할 때 자동으로 터미널 켜서 스크립트 안에 적힌 내용을 실행시켜 주는 기능이라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/63a4173e-3fc0-460e-adb4-d3dcbcf499d7/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;요 Shell 안에 내용을 작성할 것인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 앞서 우리가 터미널에서 했던&lt;br /&gt;&lt;code&gt;swiftgen config run&lt;/code&gt; 을 작성해주면&lt;br /&gt;빌드 될 때 마다 파싱해서 &lt;code&gt;Generated.swift&lt;/code&gt;를 생성해 줄 것이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/c8f77a66-cc7d-4b93-97ed-4580ee290c5f/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 내용을 넣고 빌드해보면~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/0ce61a07-7a6e-42f1-8892-23cca8174173/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;에러가 난다.&lt;br /&gt;별 내용은 아니고 terminal에서 swiftgen 실행시키려고 하는데 없어서 그러는 거다.&lt;br /&gt;brew에 설치된 swiftgen을 실행시켜야 해서 패스를 지정해 준다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;PATH=/opt/homebrew/bin:$PATH
swiftgen config run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/bbe96a70-2478-4b84-873d-ba999bd51c35/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 실행 시켜보면 또 에러가 난다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/cf6fba00-b58a-4045-b1e3-686354c65246/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 swiftgen.yml을 찾을 수가 없다고 나온다. 엥?&lt;br /&gt;그래서 해당 명령어를 실행하기 전에 위치한 디렉터리가 어딘지 보자&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;PATH=/opt/homebrew/bin:$PATH
pwd
ls
swiftgen config run&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트에 위 내용으로 변경후 발생하는 에러를 보면&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/5b9cf937-73c0-41c9-848b-825762c5e5ce/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로젝트 폴더에는 가있는데 타겟 폴더에 안들어가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타겟 폴더 안으로 들어가면 되겠다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;PATH=/opt/homebrew/bin:$PATH
cd SwiftGenPractice
swiftgen config run&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 빌드해보면 에러가 발생하지 않고 정상 빌드 될 것이다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인라인?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 swfitgen.yml을 찾고 내용대로 파싱해서 파일을 생성하는 방식인데 사실 예제처럼 파싱할 내용이 많지 않으면 yml없이 인라인으로 작성할 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;swiftgen xcassets Resources/Images.xcassets --templateName swift5 --output &quot;Constants/Assets+Generated.swift&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;swiftgen은 자동화 도구로 사용만 잘하면 굉장히 유용하다.&lt;/li&gt;
&lt;li&gt;굳이굳이 homebrew로 설치하고, 프로젝트 마다 다르게 사용하려면 위 처럼 고생한다. 잘 동작은 하는데 이게 맞는 방법인지는 모르겠다.&lt;/li&gt;
&lt;li&gt;템플릿을 커스텀하거나, 파싱하는데 더 다양한 옵션을 제공하므로 &lt;a href=&quot;https://github.com/SwiftGen/SwiftGen&quot;&gt;문서&lt;/a&gt;를 꼭 확인하자.&lt;br /&gt;&lt;a href=&quot;https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/SwiftGenPractice&quot;&gt;전체코드&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/36</guid>
      <comments>https://doing-programming.tistory.com/entry/SwiftGen-%EC%82%AC%EC%9A%A9%EA%B8%B0-Homebrew#entry36comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:47:47 +0900</pubDate>
    </item>
    <item>
      <title>Firebase Auth 전화번호 회원가입</title>
      <link>https://doing-programming.tistory.com/entry/Firebase-Auth-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase Auth에서 제공하는 전화번호 인증해보기&lt;/p&gt;
&lt;h1&gt;1. 사전작업&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Firebase Auth 전화번호 인증은, Slient Push Notification을 이용해 토큰을 보내기 때문에 Push Notification을 사용하는 것과 동일하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래의 준비가 필요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유료 애플 개발자 계정&lt;/li&gt;
&lt;li&gt;Xcode Project Setup&lt;/li&gt;
&lt;li&gt;Apple Developer Member Center Identifier&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;유료 애플 개발자 계정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 말했지만, 유료 애플 개발자 계정을 필요로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Xcode Project Setup&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Push Notification을 받는 것이기 때문에 Xcode 설정이 필요하다.&lt;/li&gt;
&lt;li&gt;Project - Target - Signing &amp;amp; Capabilites - Push Notifications&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/9c7b75e8-b2e0-4c44-8c07-06719b98e952/1.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Apple Developer Member Center Identifier&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증토큰은 Firebase project에 등록해야할 토큰으로 아래 과정을 통해서 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Developer Apple의 Member Center로 이동 후, Keys탭으로 간다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/4c12b406-c0fb-4366-a206-9e2ec4530c57/2.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/650b10d9-e97c-4a22-85de-35115254761b/3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당히 알아볼 만한 이름을 작성하고, APNs를 체크 한 후 Continue - Register&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 Download가 나오는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고문을 찬찬히 읽어보면, 보안을 위해 다운로드 후에는 서버에서 해당 키를 삭제한다고 한다. 그러니까 다운받은 그것이 유일본이기 때문에 잃어버리지 않도록 안전한 곳에 보관해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키를 다운받아 보면 AuthKey_ABCDEFGH~ 이런식으로 되어있는데 언더바 뒤의 알파벳,숫자 조합이 바로 키 아이디가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 디바이스 토큰, 인증 토큰(키), 그리고 하나 더 필요한 것은 TeamID인데 이것은 화면 우측 상단 계정에 이름 아래에 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/005f89f5-d3c1-4953-bb0a-ef957c226621/4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 토큰을 이제 Firebase에 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 파이어베이스 전화번호 인증 문서 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/auth/ios/phone-auth?authuser=0&quot;&gt;Firebase 인증 문서&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;2. 프로젝트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이어베이스에 프로젝트에 앱을 등록했으면, 이제 프로젝트에 작성할 일만 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 본인은 SwiftUI로 했는데 SwiftUI 처음 진입하면 AppDelegate가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 작성해준다. (프로젝트명App.swift)&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import SwiftUI
import Firebase

class AppDelegate: NSObject, UIApplicationDelegate {

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&amp;gt; Bool {

    FirebaseApp.configure()
    return true
  }

  func application(_ application: UIApplication,
                   didReceiveRemoteNotification notification: [AnyHashable : Any],
                   fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -&amp;gt; Void) {

  }
}

@main
struct FirebasePhoneAuthPracticeApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노티피케이션의 기본적인 플로우는 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;application(_:didFinishLaunchingWithOptions:)&lt;/code&gt; 에서 &lt;code&gt;registerForRemoteNotifications&lt;/code&gt;를 통해 APN으로 디바이스 토큰을 전송한다&lt;/li&gt;
&lt;li&gt;APN은 앱에 디바이스 토큰을 반환하고 이때 &lt;code&gt;application(_:didRegisterForRemoteNotificationsWithDeviceToken:)&lt;/code&gt; 호출하거나 토큰 등록에 실패했을 시 &lt;code&gt;application(_:didFailToRegisterForRemoteNotificationsWithError:)&lt;/code&gt; 를 호출하여 알린다.&lt;/li&gt;
&lt;li&gt;디바이스는 토큰을 binary 또는 hexadecimal 포맷으로 provider(서버)로 토큰을 보낸다. provider가 이제 토큰을 추적하기 시작한다.&lt;/li&gt;
&lt;li&gt;provider가 notification 요청을 다수의 토큰과 함께 APNs로 보낸다.&lt;/li&gt;
&lt;li&gt;마지막으로 APNs가 notification을 유효한 토큰이 제공된 각 디바이스에 알림을 보낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Firebase 전화번호 Auth는 Slient Push Notification이기 때문에 &lt;code&gt;application(_:didFinishLaunchingWithOptions:)&lt;/code&gt; 는 FirebaseApp초기화 때문에 필요하고 나머지는 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Slient Push를 이용하기 위한 사전 작업이 따로 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Background Modes&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slient Push Notification은 사용자에게 보이지 않는 기능이고 백그라운드에서 동작한다. 따라서 Sining &amp;amp; Capabilites에 Background Modes를 추가하고 거기서 Remote Notifications을 체크 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/e202d16a-d68d-4ad6-bdf5-2611d8bd81bc/5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;App Delegate&lt;/h2&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;func application(
  _ application: UIApplication,
  didReceiveRemoteNotification userInfo: [AnyHashable: Any],
  fetchCompletionHandler completionHandler:
  @escaping (UIBackgroundFetchResult) -&amp;gt; Void
) {
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;application(_:didReceiveRemoteNotification:fetchCompletionHandler)&lt;/code&gt; 델리게이트 메서드가 있어야 한다. 해당 코드는 이미 작성했으므로 Slient Push Notification을 사용할 준비가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 Slient Push Notification은 유저에게 알람이 발생하지 않으므로 알람에 대한 동의를 받을 필요가 없다.&lt;/p&gt;
&lt;h1&gt;인증하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증하는 작업 플로우는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전화번호를 검증하고 요청한다.&lt;/li&gt;
&lt;li&gt;1번 과정이 성공적으로 진행되면 해당 번호로 인증번호가 온다.&lt;/li&gt;
&lt;li&gt;유저가 작성한 인증번호가, 실제로 서버에서 보낸 인증번호가 일치하는 작업을 한다.&lt;/li&gt;
&lt;li&gt;3번이 성공적일 경우 로그인 루틴을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 전화번호 검증&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func requestVerifyCode() {
    buttonDisabled = true
    let validPhoneNumber = &quot;+82\(phoneNumber)&quot;
    PhoneAuthProvider.provider().verifyPhoneNumber(validPhoneNumber, uiDelegate: nil) { verification, error in
      if let error = error {
        print(&quot;error: \(error)&quot;)
        buttonDisabled = false
      } else {
        self.verificationId = verification
        withAnimation {
          watingVeryfiId = true
        }
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트필드에 입력된 전화번호를 검증하고 Firebase서버에 요청하는 코드로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;verifyPhoneNumber(_:uiDelegate:completion:) 메서드를 통해 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 인자에 유저가 입력한 전화번호가 들어가면 된다. 입력은 통상적으로 입력하는 010~ 패턴이면 되는데 앞에 국가코드를 포함해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전화번호 검증이 성공했을 때 &lt;b&gt;인증코드&lt;/b&gt;가 전화번호로 날아오게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 인증번호 검증&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;func verifyLogin() {
    isVerifying = true
    let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationId ?? &quot;&quot;, verificationCode: userVerify)
    Auth.auth().signIn(with: credential) { (success, error) in
      if let error = error {
        print(&quot;error: \(error.localizedDescription)&quot;)

      } else {
        print(&quot;success&quot;)
        showMainView = true
      }
      isVerifying = false
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 유저가 받은 인증코드를 다른 텍스트필드에 입력하고, 1에서 받은 인증코드와 비교하는 메서드를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;credential(withVerificationID:verificationCode)&lt;/code&gt; 메소드에 첫번째 인자는 verifyPhoneNumber메서드로 받은 인증코드, 그리고 두번째 인자에는 유저가 입력한 인증코드가 된다. 두 개가 일치하게 되면 인증이 성공하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 코드만 작성 한 것으로 토큰 관리나, 로그인 상태 관리 등은 꼭 별도로 해야한다!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/aa9b177b-9569-47eb-a82b-9f38d45970ce/FirebaseAuth.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/urijan44/TIL-Store/tree/master/Firebase/FirebasePhoneAuthPractice&quot;&gt;전체 코드 보기&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/35</guid>
      <comments>https://doing-programming.tistory.com/entry/Firebase-Auth-%EC%A0%84%ED%99%94%EB%B2%88%ED%98%B8-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85#entry35comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:46:45 +0900</pubDate>
    </item>
    <item>
      <title>iOS 15.0 UIButton</title>
      <link>https://doing-programming.tistory.com/entry/iOS-150-UIButton</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC2021가 발표한지는 한참 지났지만, 정리해보는 UIButton,&lt;br /&gt;iOS15.0의 버튼이 특별히 달라지는 것은 아니지만, 기존에 서브클래싱 등으로 해결해야 했던것을 &lt;code&gt;Configuration&lt;/code&gt;을 통해 제공해주는 것이 핵심이다.&lt;/p&gt;
&lt;h1&gt;Basic Style&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS15.0 UIButton은 기본 4가지 스타일을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Plain&lt;/li&gt;
&lt;li&gt;Gray&lt;/li&gt;
&lt;li&gt;Tinted&lt;/li&gt;
&lt;li&gt;Filed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/e2284c7a-b654-48f0-950b-bf34b663e3a7/image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 UIButton은 다음과 같을 것이다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let button = UIButton(type: .system)
button.setTitle(&quot;Sign In&quot;, for: [])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/e7f546e3-18e6-4c4d-8b29-38422b174490/image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 Basic Style을 적용하는 방법은 아래와 같다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let button = UIButton(type: .system)
button.setTitle(&quot;Sign In&quot;, for: [])
button.configuration = .filled()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/29d81bbd-c09a-4938-ac73-a7b383b75c2a/image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic Style을 적용해서 &lt;code&gt;BackgroundColor&lt;/code&gt; Tint와&lt;code&gt;CornerRadius&lt;/code&gt;가 있는 버튼을 빠르게 얻을 수 있다.&lt;br /&gt;바닐라 스타일을 사용하는 사람들에게는 편리한 기능으로 보인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UIButton.Configuration&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIButton 인스턴스를 생성 한 후에, 프로퍼티들을 바꾸는 것이 아니라, UIButton.Configuration을 미리 만들고, UIButton 인스턴스를 만들 때 configuration을 생성자에 전달할 수 있다.&lt;br /&gt;이 방법은 굳이 UIButton을 서브클래싱 하는 것보다 더 나은 재사용성을 누릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서는 button을 생성 한 뒤에, 타이틀을 입력했지만 configuration을 사용하면 아래와 같이 할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;var config = UIButton.Configuration.tinted()
config.title = &quot;Hello, Swift!&quot;
config.image = UIImage(systemName: &quot;swift&quot;)

let button = UIButton(
  configuration: config,
  primaryAction: UIAction(handler: { _ in
    print(&quot;Hello, Swift!&quot;)
  }))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/6f47304b-cf66-4094-a7dd-55c0af699b46/image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;configuration에는 다양한 옵션이 있기 때문에 입맛대로 사용하기도 편하다.&lt;br /&gt;예를들어서 이미지 위치가 왼쪽이 아니라 오른쪽에 위치했으면 좋겠다고 한다면&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;config.imagePlacement = .trailing&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한출 추가하는 것으로 이미지는 오른쪽으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/2dcf9871-b7ab-44bf-8c8c-c588de9b4008/image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 외에도 타이틀과 이미지의 패딩, 타이틀의 정렬 기준 등등 다양하기 때문에 문서를 보고 사용해 보길 권장&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ConfigurationUpdateHandler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Button을 사용하다보면, 항상 똑같은 모습을 보여주는 정적인 버튼 말고도, 상태에 따라 변하는 동적인 버튼을 많이 사용한다.&lt;br /&gt;기존에는 버튼 상태를 나타내는 모델에서 모델이 변경되면 버튼을 업데이트 하도록 구성할 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;   button.configurationUpdateHandler =  { [unowned self] button in
      var config = button.configuration
      config?.title = &quot;오늘의 할일&quot;
      config?.image = self.done
      ? UIImage(systemName: &quot;checkmark.square.fill&quot;)
      : UIImage(systemName: &quot;square&quot;)
      config?.subtitle = self.done
      ? &quot;01/16 Done!&quot;
      : &quot;01/16&quot;
      button.configuration = config
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;button.setNeedsUpdateConfiguration()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;setNeedsUpdateConfiguration()&lt;/code&gt;을 호출해서, 버튼 UI의 갱신상태를 변경 요청할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Other Features&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Button.Configuration&lt;/code&gt;은 이외에도 다양하고 유용한 기능을 제공한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Activity Indicator: UIButton에 별도로 서브클래싱 없이 Activity Indicator를 넣을 수 있다.&lt;/li&gt;
&lt;li&gt;Metrics adjustments: 타이틀, 이미지, 서브 타이틀간의 위치를 설정할 수 있다.&lt;/li&gt;
&lt;li&gt;Semantic styling: 버튼을 만들기가 더 쉬워졌다.&lt;/li&gt;
&lt;li&gt;Customization: Color, TextAttribute 등 커스텀에 필요한 기능을 Configuration을 통해 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/5643bcb8-753c-40b4-8db4-c7f85a3454eb/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 UIButton에 Activity Indicator를 이용하려면 서브클래싱을 통해서 해야했는데 Button.Configuration에 이 기능이 들어갔다.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;
var loggin: Bool = false {
    didSet {
      button.setNeedsUpdateConfiguration()
      button.isEnabled = !loggin
    }
  }

button.configurationUpdateHandler =  { [unowned self] button in
      var config = button.configuration
      config?.title = &quot;Sign In&quot;
      config?.showsActivityIndicator = loggin
      config?.image = UIImage(systemName: &quot;externaldrive.connected.to.line.below&quot;)
      button.configuration = config
    }

func requestLoggin() {
  loggin = true
  //Network Request Code
  DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
     self.loggin = false
  }
}   
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;configurationUpdateHandler()&lt;/code&gt;에 &lt;code&gt;showActivityIndicator&lt;/code&gt;만 설정해 주면 된다.&lt;br /&gt;예를들어서 로그인을 요청하면 서버에서 응답이 올때까지 버튼에 인디케이터가 표시 되어야 하는 경우, 다음 과 같이 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/73d8f66f-f1f6-4151-9b61-2063f05813d4/%E1%84%92%E1%85%AA%E1%84%86%E1%85%A7%E1%86%AB%20%E1%84%80%E1%85%B5%E1%84%85%E1%85%A9%E1%86%A8%202022-01-16%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2010.38.08.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Toggle Button&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼의 On/Off 상태를 좀더 편리하게 사용해주는 새 기능으로 해당 프로퍼티를 true로 설정하게 되면 버튼 선택에 따른 상태를 얻을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;button.changesSelectionAsPrimaryAction = true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;changeSelectionAsPrimaryAction&lt;/code&gt;을 true로 하면 버튼 토글 상태가 &lt;code&gt;button.isSelected&lt;/code&gt;로 전달된다.&lt;br /&gt;맨 위에서 했던 예제로 다시 돌아가자면&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;button.changesSelectionAsPrimaryAction = true

button.configurationUpdateHandler =  { button in
  var config = button.configuration
  config?.title = &quot;오늘의 할일&quot;
  config?.image = button.isSelected
  ? UIImage(systemName: &quot;checkmark.square.fill&quot;)
  : UIImage(systemName: &quot;square&quot;)
  config?.subtitle = button.isSelected
  ? &quot;01/16 Done!&quot;
  : &quot;01/16&quot;
  button.configuration = config
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼의 상태를 isSelected로 선택해볼 수 있다.&lt;/p&gt;
&lt;h1&gt;Pop-up button&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 구성할 때 팝업 버튼 형식으로 쓰고 싶을 때는 &lt;code&gt;showMenuAsPrimaryAction&lt;/code&gt; 을 &lt;code&gt;true&lt;/code&gt;로 두기만 하면 된다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(children: [
  UIAction(title: &quot;삭제하기&quot;, attributes: .destructive, handler: deleteComment),
  UIAction(title: &quot;수정하기&quot;, handler: editComment)
])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/541abb56-6f43-4aec-a7a8-bf2e0a85ad3f/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-16%20at%2023.28.52.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 팝업 버튼 안에서 토글 방식이 필요하다면 &lt;code&gt;changeSelectionAsPrimaryAction&lt;/code&gt;을 true로 두면 팝업 안에서 토글 형식으로 쓸 수 있다&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;button.showsMenuAsPrimaryAction = true
button.menu = UIMenu(children: [
  UIAction(title: &quot;매일반복&quot;, handler: repeatEveryday),
  UIAction(title: &quot;요일반복&quot;, handler: repeatDay)
])
button.changesSelectionAsPrimaryAction = true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/917781d2-627c-431d-a1f7-bd2160d9b18a/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-16%20at%2023.30.35.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 예제 프로젝트는 TIL에서 보실 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/UIKit/UIButtonConfiguration.playground&quot;&gt;https://github.com/urijan44/TIL-Store/tree/master/iOS_Swift/UIKit/UIButtonConfiguration.playground&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/34</guid>
      <comments>https://doing-programming.tistory.com/entry/iOS-150-UIButton#entry34comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:45:20 +0900</pubDate>
    </item>
    <item>
      <title>Swift, Concurrency</title>
      <link>https://doing-programming.tistory.com/entry/Swift-Concurrency</link>
      <description>&lt;h1&gt;Concurrency(동시성)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 프로그래밍이라고도 한다. &lt;code&gt;Concurrency&lt;/code&gt;는 여러 작업을 나누어서 처리하는 것으로, 우리가 사용하는 아이폰이 노래도 재생하면서, 유저의 입력에 따라 이메일도 작성할 수 있고, 중간에 전화도 받을 수 있는 것이 이 동시성이다.&lt;br /&gt;만약 노래를 재생하는 동안은 화면을 터치할 수 없고, 인터넷 검색도 불가능하다면, 굉장히 불편할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 실제로 어떻게 되는지 보자&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;//1
func calculatePrimes() {
    for number in 0...1_000_000 {
      let isPrimeNumber = isPrime(number: number)
      print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
    }
  }

//2
func isPrime(number: Int) -&amp;gt; Bool {
    if number &amp;lt;= 1 {
      return false
    }
    if number &amp;lt;= 3 {
      return true
    }

    var i = 2
    while i * i &amp;lt;= number {
      if number % i == 0 {
        return false
      }
      i = i + 2
    }
    return true
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;calculatePrimes()&lt;/code&gt;는 1에서 1백만 까지의 자연수 중에 &lt;i&gt;소수&lt;/i&gt; 를 구하는 메서드로, 해본 사람은 알겠지만 꽤 오랜 시간이 걸리는 작업이다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;var body: some View {
    VStack {
      Spacer()
      DatePicker(selection: .constant(Date())) {
        Text(&quot;Date&quot;)
      }
      .datePickerStyle(.wheel)
      .labelsHidden()
      Button {
        calculatePrimes()
      } label: {
        Text(&quot;Calculate Primes&quot;)
      }
      Spacer()
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면에는 DatePicker UI가 있고, 버튼을 누르면 소수를 구하기 시작한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/281eb1a2-8fd5-4b09-a61d-783b40908059/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-13%20at%2001.48.44.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;소수 구하기 버튼을 누르면 해당 계산을 하느라 DatePicker의 UI가 동작하지 않는다.&lt;br /&gt;그 이유는 DatePicker의 UserInteraction과 &lt;code&gt;calculatePrimes()&lt;/code&gt;를 둘다 메인 쓰레드에서 수행하고 있기 때문이다. &lt;code&gt;calculatePrimes()&lt;/code&gt;가 쓰레드를 점유하고 있어서 다른 메인 쓰레드에서 작업해야 하는 User Interaction이 동작하지 않는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thread/Multithread&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티쓰레드 라는 얘기 많이 들어보았는데, 그럼 쓰레드는 뭘까,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.semanticscholar.org/paper/How-to-Make-a-Multiprocessor-Computer-That-Executes-Lamport/bdacc240ee817e6565f27f40f8658c037a457a13&quot;&gt;컴퓨팅에서 쓰레드는 OS 스케줄러에 의해 독립적으로 관리될 수 있는 프로그래밍된 명령의 가장 작은 시퀀스다.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 CPU는 다수의 코어와 그 이상의 쓰레드를 가지고 있어, 물리적으로 작업을 동시에 처리할 수 있다. 이게 멀티 쓰레딩이다.&lt;br /&gt;이 부분이 &lt;code&gt;Parallelism&lt;/code&gt;과 차이점이다. 병렬프로그래밍이라고 하는 &lt;code&gt;Parallelism&lt;/code&gt;는 작업의 단위가 스레드가 아니라, CPU가 된다. 앞서 &lt;code&gt;caculatePrimes()&lt;/code&gt;메서드를 하나의 스레드에서 점유해서 계산하지 않고, 분할해서 계산해서 작업의 속도를 높이는 것이 &lt;code&gt;Parallelism&lt;/code&gt;이고&lt;br /&gt;&lt;code&gt;Concurrency&lt;/code&gt;의 핵심은 스레드이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context Swiching&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Concurrency&lt;/code&gt;의 또 하나의 핵심은 Context Switching으로, 하나의 코어는 Time Slicing이라는 방법을 통해 Concurrency하게 움직인다. 쉽게 이해해보면, 내가(아이폰) 커피를 만드는데 이 커피를 만드는 동작안에는 원두를 분쇄하고, 물을 끓이고, 컵을 준비하는 일련의 동작이고, 이를 아주 빠르게 한다, 다른 사람(사용자) 입장에서 보면 거의 동시에 일어나는 것 처럼 보인다.&lt;/p&gt;
&lt;h1&gt;Use Concurrency&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다시 맨 처음 어플리케이션으로 돌아와서, iOS는 기본적으로 멀티쓰레드이다. 소수를 구하는 것과 동시에 UI작업을 처리할 수 있는 것이 당연한 것이다. 그런데 실제로는 계산을 하느라 UI작업을 하지 못했다. 우리는 소수를 구하는 작업을 다른 쓰레드에서 하도록 명시할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플은 고맙게도? 쓰레드를 편리하게 사용할 수 있는 프레임워크를 제공한다. 굉장히 Low하게 보면 NSThread와 더 내려가서 Unix POSIX 쓰레드를 통해서 이 쓰레드를 사용할 수 있지만 우리에게는 &lt;b&gt;Grand Central Dispatch&lt;/b&gt;(GCD) 가 있다. GCD 또한 Concurrency 작업을 위한 프레임워크로 꽤 LowLevel 측면으로 디자인 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또다른 옵션은 Operation Queue로 GCD위에 만들어져 있고, 더 쉽고 간결한 코드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 2019년에 나온 Combine으로 이는 백그라운드에서 작업을 선언형으로 관리한다. 오퍼레이터를 통해 스레드 간의 쉬운 전환이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또! 그다음은 Swift Concurrency가 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GCD&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD는 스레드 위에 구축되며, 공유 스레드 풀을 관리한다. 이를 사용해서 &lt;b&gt;DispatchQueue&lt;/b&gt; 에 코드 블록을 처리하고 GCD는 이를 실행할 쓰레드를 결정하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Queue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 FIFO구조로 먼저 들어온 작업을 먼저 내보낸다는 특징이 있다. GCD는 같은 방식으로 작업 순서를 보장한다. 이때 Queue는 Seiral이거나 Concurrent일 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/9ca1c73a-4844-4bcb-9039-3303d03dcf61/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이처럼 Serial은 선형 시간동안 하나의 작업만 실행된다. Task1이 끝나면 Task2가 실행되는 식이다. 쓰레드를 사용해서 이를 Concurrent하게 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/99454a07-6acb-40a0-a905-b6a2491a6361/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Concurrent는 Task순서대로 일단 작업을 할당하긴 했지만 Serial과는 다르게 Task2보다 Task3과 Task4가 훨씬 더 빨리 끝나서 작업물을 반환한다. 그래서 작업 순서를 보장할 수 없다.&lt;br /&gt;커피를 다 볶은 다음에 컵에 넣고 물을 부어야 하는데, 물이 먼저 끓었다고 물부터 냅다 부어버린 것&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Operation Queue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GCD를 사용하기 위해서는 Operation을 상속해서 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;class CaculatePrimeOperation: Operation {


  override func main() {

    for number in 0...1_000_000 {
      let isPrimeNumber = isPrime(number: number)
      print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
    }
  }

  func isPrime(number: Int) -&amp;gt; Bool {
    if number &amp;lt;= 1 {
      return false
    }
    if number &amp;lt;= 3 {
      return true
    }

    var i = 2
    while i * i &amp;lt;= number {
      if number % i == 0 {
        return false
      }
      i = i + 2
    }
    return true
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;main()&lt;/code&gt;에서 앞에서 작성한 소수 구하기 메서드를 이식한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;let operation = CaculatePrimeOperation()
func calculatePrimes() {

  let queue = OperationQueue()
  queue.addOperation(operation)

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 해당 클래스의 인스턴스를 만든 뒤, OperationQueue의 인스턴스에&lt;code&gt;addOperation&lt;/code&gt;으로 넘겨준다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;  func calculatePrimes() {
    let operation = CalculatePrimeOperation()
    let queue = OperationQueue()
    queue.addOperation(operation)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 꼭 Operation을 상속하는 클래스를 만들 필요는 없고 addOperation이 클로져를 제공하기 때문에 메서드를 해당 클로져 안에 작성해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;OperationQueue&lt;/code&gt;는 인스턴스를 만들 때 자동으로 남는 쓰레드를 할당해 준다. 만약 메인 쓰레드를 사용하라고 명시적으로 알리고 싶다면&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let mainQueue = OpearationQueue.main&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;OperationQueue&lt;/code&gt;말고도 더 쉽고 간편한 방법은 &lt;code&gt;DispatchQueue&lt;/code&gt;가 있다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DispatchQueue&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;DispatchQueue.global(qos: .userInitiated).async {
  for number in 0...1_000_000 {
    let isPrimeNumber = number.isPrime
    print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드를 global로 보낼 수 있다. qos설정은 애플 문서에서 자세히 확인할 수 있다.&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass&quot;&gt;https://developer.apple.com/documentation/dispatch/dispatchqos/qosclass&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이제 앞에서 소수를 구하느라 반응하지 않던 UI를 구조할 수 있게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hey_hen/post/cf7eeaa4-d89c-4fa1-9278-939e79b4dcfb/Simulator%20Screen%20Recording%20-%20iPhone%2013%20mini%20-%202022-01-13%20at%2002.41.53.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Swift Concurrency&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업을 Swift Concurrency를 이용하면 코드 한줄? 추가하는 것으로 가능하다.&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;  func calculatePrimes() {
    doneLabel = &quot;Calculating!&quot;
    Task {
      for number in 0...1_000_000 {
        let isPrimeNumber = number.isPrime
        print(&quot;\(number) is prime: \(isPrimeNumber)&quot;)
      }
    }
    doneLabel = &quot;Done!&quot;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Refereces&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2&quot;&gt;raywenderlich Grand Cental Dispatch Tutorial for Swift4 by Evan Dekhayser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/dp/1950325520?tag=raywend-20&quot;&gt;Swift Apprentice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Monocoding&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/33</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-Concurrency#entry33comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:41:46 +0900</pubDate>
    </item>
    <item>
      <title>Swift - Function Notation</title>
      <link>https://doing-programming.tistory.com/entry/Swift-Function-Notation</link>
      <description>&lt;div id=&quot;SE-3dbaee2e-c6b1-4835-b856-a13a886c9a4e&quot; data-compid=&quot;SE-3dbaee2e-c6b1-4835-b856-a13a886c9a4e&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-3dbaee2e-c6b1-4835-b856-a13a886c9a4e&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-d7584a08-fa75-4a77-adda-ac4cf0230ca9&quot;&gt;
&lt;p id=&quot;SE-004a9ba7-fecc-4c65-a407-0a2d3a3c21f7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Function Notation은 함수를 표시하는 법을 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-d396e5ca-1db7-414b-a5d7-a98c1e0a43ea&quot; data-compid=&quot;SE-d396e5ca-1db7-414b-a5d7-a98c1e0a43ea&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-d396e5ca-1db7-414b-a5d7-a98c1e0a43ea&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;func viewDidLoad()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-ff62b8bc-bf1c-40f3-a63a-3ce4437b6fe7&quot; data-compid=&quot;SE-ff62b8bc-bf1c-40f3-a63a-3ce4437b6fe7&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-ff62b8bc-bf1c-40f3-a63a-3ce4437b6fe7&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-5fdb87ab-6828-404d-9c19-d3364e5abd9b&quot;&gt;
&lt;p id=&quot;SE-d8472514-b580-459d-8be6-234d9be58520&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뷰 컨트롤러에 viewdidLoad라는 메서드가 있다는 것은 다들 알 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-05b6e287-ec17-482c-a734-916a145a5425&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 표기할때는 `viewDidLoad()` 로 표시하면 된다. 인자가 있는 함수의 경우는 어떨까?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-25b1e5bd-6995-47bf-80d8-86a442ff413a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-37701e1a-7d99-47fd-a295-5961759594f4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 SceneDelegate에 있는 메서드 중 하나이다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-5487211f-0acd-422c-a3a3-b04185cf2ab0&quot; data-compid=&quot;SE-5487211f-0acd-422c-a3a3-b04185cf2ab0&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-5487211f-0acd-422c-a3a3-b04185cf2ab0&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-5046b3b0-1a32-4509-bcb2-a57e74ec6b2e&quot; data-compid=&quot;SE-5046b3b0-1a32-4509-bcb2-a57e74ec6b2e&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-5046b3b0-1a32-4509-bcb2-a57e74ec6b2e&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-a81adc18-78fc-4fb9-b758-357511615010&quot;&gt;
&lt;p id=&quot;SE-8d9bca4c-2b25-4a95-97c9-6a986a99bcf2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 경우는 어떻게 써야할까? 복사 붙여넣기 해서 길에 늘여써야 할까?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-06edd145-43e9-4ecf-86e3-dc2dcb8222ef&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아니다&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-bbfceb32-b612-480e-a914-d0ab347bcd2e&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-8453d802-2a1f-4505-9d90-367ad1f70bf9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;swift 에서는 이를&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-8009adb9-84c0-48d6-b8b9-dc78cbb88ff3&quot; data-compid=&quot;SE-8009adb9-84c0-48d6-b8b9-dc78cbb88ff3&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-8009adb9-84c0-48d6-b8b9-dc78cbb88ff3&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;scene(_:willConnectTo:options:)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-16ef2456-42fc-4057-8a1f-0da6129e72f9&quot; data-compid=&quot;SE-16ef2456-42fc-4057-8a1f-0da6129e72f9&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-16ef2456-42fc-4057-8a1f-0da6129e72f9&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-844a25c7-661e-4d03-a7f8-d4f5761f725d&quot;&gt;
&lt;p id=&quot;SE-bf3b9f19-2ee5-4e78-b251-574696473dc2&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로 표시한다. 표시할 때는 외부에 보여지는 레이블 이름을 붙이며, 없는 경우는 언더바로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-9de841e6-8cc8-47d7-b2f6-83faa7e4315f&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 뒤에 타입을 명시하지 않고 콜론으로 구분한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-f4906445-f370-4584-95d4-4671f4f0549c&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-f2516069-db3c-4ce7-a8dc-bd4c642d699b&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/32</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-Function-Notation#entry32comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:39:37 +0900</pubDate>
    </item>
    <item>
      <title>Swift - Any vs AnyObject</title>
      <link>https://doing-programming.tistory.com/entry/Swift-Any-vs-AnyObject</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;Swift쓰다보면 Any와 AnyObject 둘 다 쓰이는 걸 볼 수 있는데,&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;SE-856a0c0e-3d43-4336-a41a-8230dd0b4101&quot; data-compid=&quot;SE-856a0c0e-3d43-4336-a41a-8230dd0b4101&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-856a0c0e-3d43-4336-a41a-8230dd0b4101&quot; data-direction=&quot;top&quot;&gt;
&lt;div id=&quot;SE-4a1233b9-8c78-4ff2-8793-edaf76296808&quot;&gt;
&lt;p id=&quot;SE-3006b347-b71d-4f70-ad5a-5877ce9d3660&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Any는 함수 타입을 포함해서 모든 타입의 인스턴스를 나타낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-3cc3db82-19c9-41d3-8e4f-f9eaeb183772&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반면 AnyObject는 모든 클래스 타입의 인스턴스를 나타낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-666a6a63-1479-41f5-a8c1-500fcdbfd838&quot; data-compid=&quot;SE-666a6a63-1479-41f5-a8c1-500fcdbfd838&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-666a6a63-1479-41f5-a8c1-500fcdbfd838&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;henry&quot;,
  &quot;age&quot;: 27
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-79999f8a-6ae2-47f1-b5c5-f2f00aa3ba96&quot; data-compid=&quot;SE-79999f8a-6ae2-47f1-b5c5-f2f00aa3ba96&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-79999f8a-6ae2-47f1-b5c5-f2f00aa3ba96&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-93a0f38b-6d13-4462-af4f-7c0eadba68db&quot;&gt;
&lt;p id=&quot;SE-2ee1d857-172f-42eb-a29b-a96485c9e970&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같은 json 형태로 데이터가 들어올 때, Decode용 모델을 만들어서 Decodable을 해도 되지만&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-bb761160-daef-4795-9b07-be61e0aa810c&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JSONSerialization을 이용해서 간편하게 쓸 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-f56956c5-0cdd-4c5f-b154-f5c6bf8e9701&quot; data-compid=&quot;SE-f56956c5-0cdd-4c5f-b154-f5c6bf8e9701&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-f56956c5-0cdd-4c5f-b154-f5c6bf8e9701&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let fetchedData = try! JSONSerialization.jsonObject(with: json)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-2a837fcc-037b-4776-b215-08a3a3427976&quot; data-compid=&quot;SE-2a837fcc-037b-4776-b215-08a3a3427976&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-2a837fcc-037b-4776-b215-08a3a3427976&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-cc1786b8-f98c-40ab-8a94-a872cdc824ef&quot;&gt;
&lt;p id=&quot;SE-3266a7ef-0e21-41b1-a5cb-25267059912a&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;jsonObject(with:) Any를 리턴한다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-d4d151d4-4e37-44a3-902c-313dd9fcc650&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 어떻게 접근할 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-033668f3-d4c2-4c5a-9151-17b32d6ffa0a&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-de07765b-9e4e-4696-9429-61de2872b57c&quot; data-compid=&quot;SE-de07765b-9e4e-4696-9429-61de2872b57c&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-de07765b-9e4e-4696-9429-61de2872b57c&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let fetchedData = try! JSONSerialization.jsonObject(with: json) as! [String: Any]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-cb876091-44e4-4a05-aca6-784e54c4770f&quot; data-compid=&quot;SE-cb876091-44e4-4a05-aca6-784e54c4770f&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-cb876091-44e4-4a05-aca6-784e54c4770f&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-8b879cd4-9c59-44fb-ae0d-3143a0b4bb7d&quot;&gt;
&lt;p id=&quot;SE-a125e63a-98cd-4b43-92e6-cc8323ce6503&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;[String: Any]로 타입캐스팅이 가능하고, json으로 들어오는 키 값이 name하고age 인걸 알고 있기 떄문에&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-4a58ab3e-67a1-42a8-89eb-cbddbd3bec6c&quot; data-compid=&quot;SE-4a58ab3e-67a1-42a8-89eb-cbddbd3bec6c&quot; data-a11y-title=&quot;코드&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-4a58ab3e-67a1-42a8-89eb-cbddbd3bec6c&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;fetchedData[&quot;name&quot;] as! String // henry
fetchedData[&quot;age&quot;] as! Int // 27&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-88896fc8-356a-4674-8f51-57830048a79e&quot; data-compid=&quot;SE-88896fc8-356a-4674-8f51-57830048a79e&quot; data-a11y-title=&quot;본문&quot;&gt;
&lt;div&gt;
&lt;div data-unitid=&quot;&quot; data-compid=&quot;SE-88896fc8-356a-4674-8f51-57830048a79e&quot; data-direction=&quot;top&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;SE-c3c3304c-e221-4628-a8e2-a37e45ee6629&quot;&gt;
&lt;p id=&quot;SE-7d013dba-b23d-40d5-9024-3cdd48f8725e&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-6d44644a-6c57-4dba-9598-cbf73fcdb60e&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-2cf77814-acfc-49e0-8eb3-5315e159e948&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AnyClass는 모든 클래스가 암시적으로 채용하는 최상위 프로토콜로, 클래스에만 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-bf656ede-537a-4c3d-a3c2-a58508d0bd39&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Any, AnyClass는 런타임에 타입이 결정된다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-85ad38b5-3374-41dc-80c5-4ef1b05a0521&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-af7058e1-2394-4cd2-8c4a-a642b10ecf9b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-href=&quot;https://github.com/urijan44/SeSAC-Task/blob/master/220110/Checklist.md&quot;&gt;전체코드&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <author>Hey_Hen</author>
      <guid isPermaLink="true">https://doing-programming.tistory.com/31</guid>
      <comments>https://doing-programming.tistory.com/entry/Swift-Any-vs-AnyObject#entry31comment</comments>
      <pubDate>Wed, 9 Feb 2022 16:38:55 +0900</pubDate>
    </item>
  </channel>
</rss>