<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bom's_daddy.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 07 Mar 2025 06:56:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bom's_daddy.log</title>
            <url>https://velog.velcdn.com/images/bom_daddy/profile/df119898-0e77-4ad5-8a5c-c1a25f779fc7/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bom's_daddy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bom_daddy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CleanCode]]></title>
            <link>https://velog.io/@bom_daddy/CleanCode</link>
            <guid>https://velog.io/@bom_daddy/CleanCode</guid>
            <pubDate>Fri, 07 Mar 2025 06:56:08 GMT</pubDate>
            <description><![CDATA[<h6 id="책을-요약한-걸-다시-요약해-적는-글이기에-본-책과-내용이-상이할-수-있습니다"><em>책을 요약한 걸 다시 요약해 적는 글이기에 본 책과 내용이 상이할 수 있습니다.</em></h6>
<h2 id="요약">요약</h2>
<p>나쁜 코드와 좋은 코드를 구별하고 더 나아가 좋은 코드로 개선할 방법을 떠올릴 수 있는 코드감각이 필요하다.
프로그래밍 과정에서 기억할 두 가지 규칙은</p>
<blockquote>
<p>처음 이곳에 왔을 때보다 더 깨끗하게 만들고 떠날 것
나중에 해야지라고 할 때 나중은 결코 오지 않는다</p>
</blockquote>
<p>이름을 지을 때 중요한 것은 명확한 의미를 담고 있어야 한다는 것.
이름 간의 일정한 규칙과 맥락을 가져야 한다.
또 이미 널리 사용되는 이름을 혼자 다르게 사용하거나 잘못된 추측을 하게 만드는 축약이나 개성있는 이름도 피하자.</p>
<hr>
<p>주석보다 코드로 의도를 표현할 수 있도록 연습하자.
다양한 상황에서 주석으로 설명하는 대신 코드로 표현할 수 있다.</p>
<pre><code class="language-swift">// 플레이어가 이벤트 대상인지 체크
if (player.lever &gt;&gt; 30 &amp;&amp; player.isPromotioned) { ... }

----

if player.isEligibleForEvent() { ... }</code></pre>
<pre><code class="language-swift">// smodule이 우리 시스템에 의존하는가??
if (smodule.getDepedencyList().contain(subSystem.getSystem())) { ... }

-----

let moduleDependencies = smodule.getDependencyList()
let ourSystem = subSystem.getSystem()

if moduleDependecies.contain(ourSystem)</code></pre>
<p>주석을 주의해야 하는 이유는 코드의 변화를 주석이 따라가지 못하는 경우가 많기 때문이다.
그 결과 코드를 정확히 설명하지 못하고 부적절한 정보를 주는 주석이 될 가능성이 생긴다.
또 부적절한 주석이 많아지면 주석을 무시하게 되는 습관을 낳는다.</p>
<p>코드의 의도를 알리거나 결과에 대한 경고를 알리는 주석 등은 좋다.</p>
<p>간혹 아래 코드처럼 닫는 괄호( <code>}</code> )에 어떤 스코프에 해당하는지 설명을 달고 싶어진다면 크기를 줄일 방법을 고민해보는 것이 우선이다. </p>
<pre><code class="language-swift">var body: some View {
    ScrollView {
        List {
            ForEach(arr) { item in
                if isPresented {
                    do {
                        let foo = try bar()
                        VStack {
                            SomeView {
                                HStack {
                                    AnotherView {
                                        Text(&quot;Nested&quot;)
                                    }
                                }
                                if condition {
                                    ZStack {
                                        Text(&quot;Layered View&quot;)
                                    }
                                }
                            }
                            Text(foo.description)
                        }
                    } catch {
                        VStack {
                            Text(&quot;Error occurred&quot;)
                            Button(&quot;Retry&quot;) {
                                retryAction()
                            }
                        }
                    }
                } else {
                    HStack {
                        if shouldShow {
                            VStack {
                                Text(&quot;Alternative View&quot;)
                                Spacer()
                                SomeOtherView()
                            }
                        } else {
                            Text(&quot;Fallback&quot;)
                        }
                    }
                }
            }
        }
    }
}</code></pre>
<p>마지막으로 지역 메서드에서 전역에 관한 주석을 달거나 너무 많은 정보 / 너무 모호한 정보 등을 주석을 다는 것도 조심할 것.</p>
<hr>
<p>함수는 최대한 작게 만든다. 들여쓰기가 2단을 넘어가지 않도록 신경써볼 것.
특히 단 한가지의 일만 하도록 주의해야 하는데,
지정된 함수 이름 아래로 추상화 수준이 하나인 단계만 수행하는지 체크할 것.
단순히 다른 표현이 아닌 의미있는 다른 이름으로 분리해낼 수 있다면 여러 일을 하고 있는 것이라 볼 수 있음.
또는 함수 내에 섹션이 자연스럽게 나누어져도 여러 책임을 가진 것이라 볼 수 있다.</p>
<p>함수가 한 가지 작업만 하려면 함수 내의 모든 추상화 수준이 동일해야 한다. 
동일한 추상화 수준이란 근본 개념과 세부 구현 사항이 혼재되어있지 않음을 의미한다. </p>
<pre><code class="language-swift">func fetchUserData() -&gt; User {
    let url = URL(string: &quot;https://api.example.com/user&quot;)! // (저수준)
    var request = URLRequest(url: url) // (저수준)
    request.httpMethod = &quot;GET&quot; // (저수준)

    let data = try! URLSession.shared.data(for: request).0 // (저수준)
    let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] // (저수준)

    return User(id: json[&quot;id&quot;] as! Int, name: json[&quot;name&quot;] as! String) // (고수준)
}

---

func fetchUserData() -&gt; User {
    let data = fetchUserDataFromAPI() // (고수준)
    return parseUserData(data) // (고수준)
}

private func fetchUserDataFromAPI() -&gt; Data {
    let url = URL(string: &quot;https://api.example.com/user&quot;)!
    var request = URLRequest(url: url)
    request.httpMethod = &quot;GET&quot;

    return try! URLSession.shared.data(for: request).0
}

private func parseUserData(_ data: Data) -&gt; User {
    let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
    return User(id: json[&quot;id&quot;] as! Int, name: json[&quot;name&quot;] as! String)
}</code></pre>
<p>추상화 수준을 구분하는 것은 아직 어렵지만 크게 3가지로 나눈다면 다음과 같을 수 있겠다.</p>
<ul>
<li>구체적인 세부 구현을 가지는 저수준</li>
<li>저수준의 함수를 조합하여 보다 의미있는 단위를 만드는 중간수준</li>
<li>특정 도메인의 개념을 처리하는 추상적인 흐름을 가지는 고수준</li>
</ul>
<p>더 자세하게는</p>
<ol>
<li>하나의 개념적 단위를 형성하는지?<ol>
<li>저수준: 개별적인 시스템 동작을 직접 다룸</li>
<li>중간수준: 특정 기능을 담당하는 역할을 함</li>
<li>고수준: 위 둘을 조합하여 더 큰 개념을 의미하게 됨</li>
</ol>
</li>
<li>동사 기준으로 추상화를 나누기( 무엇을 한다(동작) vs 어떻게 한다(세부구현) )<ol>
<li>고수준: fetchUserData() -&gt; 사용자 데이터를 가져온다 -&gt; 추상적 개념</li>
<li>중간수준: fetchUserDataFromAPI() -&gt; API에서 데이터를 가져온다 -&gt; 특정 작업 수행</li>
<li>저수준: performHTTPRequest() -&gt; HTTP요청을 실행한다 -&gt; 시스템 동작</li>
</ol>
</li>
</ol>
<p>위와 같이 구분해보도록 연습해보고 추상화 수준이 동일한 함수를 구현해볼 것.</p>
<p>또 위에서 아래로 내려갈수록 추상화 수준이 낮아지는 게 자연스럽다.</p>
<pre><code class="language-swift">배를 채운다
밥을 먹는다
요리를 한다
재료 준비를 한다, 재료가 없다면 주문한다</code></pre>
<hr>
<p>switch는 태생적으로 여러가지 일을 한다.
즉 앞서 말한 한 가지 일만 한다는 걸 위반하기 쉽다는 것인데 저자는 아래처럼 추상 팩토리에 숨기는 방식을 선호했다.
또 다형적 코드를 생성하는 곳에서만 사용할 것을 권장한다.</p>
<pre><code class="language-swift">func foo(type: SomeType) {
    switch type {
        case foo: 
            ...
        case bar:
            ...
        ...
    }
}

func foo(type: SomeType) {
    let someValue = SomeFactory(type)
    someValue...
    someValue...
    ...
}</code></pre>
<p>이름은 최대한 서술적으로 짓기를 추천한다.</p>
<p>인수 또한 없는 게 가장 좋고 그 뒤로 1개, 2개이다.
3개는 주의해야 하고 4개는 안 쓰는 것이 좋다고 한다.</p>
<p>특히 출력인수(inout이나 &amp;등을 통해 참조하여 입력받은 인수를 직접적으로 수정하는 인수)의 사용은 결과를 예측하기 힘들게 하므로 최대한 지양할 것.</p>
<p>플래그를 인수로 받을 것이라면 차라리 두 개의 함수로 분리하고(flag에 따라 내부에서 두 가지 일을 할 것이 분명한 것도 이유에 포함될 듯)
단항 함수라면 함수 이름과 인수가 동사와 명사로 쌍을 이룰 수 이뤄야 좋다.
이항 함수라면 두 인수가 하나의 값을 가리키거나(point x, y처럼) 자연적인 순서가 명확한 경우에 사용할 것
자연적인 순서가 없다면 어떤 인수를 먼저 넣을지 헷갈릴수밖에 없다. 이 경우 함수 이름에 키워드를 넣어줘도 좋다고 한다.
가능하다면 두 인수를 하나의 구조체로 묶어 전달하는 것도 좋다.</p>
<pre><code class="language-swift">func write(name) {} // 동사 - 명사 쌍
func assertEqual(expected, actual) {}
-&gt; func assertExpectedEqualActual(expected, actual) {}</code></pre>
<hr>
<p>부수효과(사이드 이펙트)가 없어야 한다.</p>
<hr>
<p>명령과 조회가 분리되어야 한다. 
&#39;동작을 시도&#39;하고 &#39;성공 여부&#39;를 반환하는 경우가 없어야 하는 것.
예외, 오류 처리 또한 하나의 책임으로 do try를 쓰되 try catch를 분리하는 것이 좋다고 한다.</p>
<hr>
<p>적절한 행길이와 빈 행 사용에 주의하기
변수는 사용하는 곳과 가장 가까이, 인스턴스 변수는 가장 상단에 두기
그리고 종속 함수는 호출하는 함수 뒤에 둘 것
들여쓰기를 무시하고자 하는 유혹에 넘어가지 말기</p>
<p>개인의 선호가 아닌 팀 규칙을 항상 우선시 할 것</p>
<h2 id="중요-포인트">중요 포인트</h2>
<blockquote>
<p>**이름에는 의도와 맥락이 드러나고 잘못된 정보나 맥락을 가지지 않도록 하자.
검색과 발음이 쉬울수록 더욱 좋다.</p>
</blockquote>
<blockquote>
<p><strong>주석보다 코드로 의도를 표현할 수 있는 방법을 최우선적으로 고민하고 정말 코드 이해에 대한 정보인 경우나 결과에 대한 경고를 알려야 할 경우에만 제공할 것.</strong></p>
</blockquote>
<blockquote>
<p><strong>전체적으로 FP를 설명하는 듯한 느낌이 많이 들었다.
SRP를 잘 지키는 순수함수를 위주로 추상화 수준을 생각하며 설계할 것.</strong></p>
</blockquote>
<blockquote>
<p><strong>한 문서에 사용된 양식이 다른 소스 파일에서도 유지될 것이란 믿음을 깨지 않도록 만들 것.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Instruments]]></title>
            <link>https://velog.io/@bom_daddy/Instruments</link>
            <guid>https://velog.io/@bom_daddy/Instruments</guid>
            <pubDate>Fri, 27 Dec 2024 02:47:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b3d8f8de-9bbe-4ec0-b8dd-9da995452cad/image.png" alt=""></p>
<h2 id="instruments">Instruments</h2>
<p>큰 프로젝트를 하다 보면 신경을 썼음에도 어디선가 메모리 누수가 발생할 수 있다.</p>
<p>직접 코드를 찾아다니지 않고 xcode에서 지원해주는 기능을 활용하여 이런 누수를 찾아낼 수 있다.</p>
<p>cmd+I를 누르거나 상단의 Product에서 Profile을 누르자.
<img src="https://velog.velcdn.com/images/bom_daddy/post/610c1c8d-4d37-42ae-9fc4-883962155f61/image.png" alt=""></p>
<p>그럼 프로파일링을 할 것들을 선택할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/ba05650c-a028-41ff-b7e5-41b5a4a55c4e/image.png" alt=""></p>
<p>그 뒤에 동그라미 친 record 버튼을 클릭하면 녹화를 시작하면서 앱 실행 상황을 보여준다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/676b2891-f089-4898-a3d7-aaca06b0e0ab/image.png" alt=""></p>
<p>그리고 메모리 릭이 발생하게 된다면 저렇게 x표시로 메모리 릭 발생 사실을 알려준다.</p>
<hr>
<p>메모리 릭을 일부러 발생시키려 했을 때 첫 코드는 생각대로 메모리 누수가 발생하지 않았었다.
이는 내가 함수 내부에서 변수를 선언하고 참조했기 때문이었는데,
이런 부분을 캐치 못했다는 건 반대로 메모리 릭이 발생하는 순간도 제대로 캐치하지 못할 거란 생각이 들었다.</p>
<p>xcode에서 지원해주는 도구를 사용하여 쉽게 잡아낼 수 있다곤 하지만 좀 더 메모리 릭이 발생할 수 있는 상황들을 이론적으로만 생각해보는 것이 아닌 프로젝트 과정 중에서 깊게 생각해보는 습관을 가지도록 노력해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LuckVII_3(Cuncurrency)]]></title>
            <link>https://velog.io/@bom_daddy/LuckVII3Cuncurrency</link>
            <guid>https://velog.io/@bom_daddy/LuckVII3Cuncurrency</guid>
            <pubDate>Mon, 23 Dec 2024 12:15:09 GMT</pubDate>
            <description><![CDATA[<h2 id="singleton">Singleton</h2>
<p>싱글톤 패턴은 의존성 주입에 비해 너무나 간편하고 간결해서 자꾸만 중독적으로 쓰게 된다.
하지만 정확히 어디서 봤는지 모르는 어떤 글이 계속 생각나 사용할 때 고민을 하게 해준다.</p>
<blockquote>
<p>파일 10개 중 단 2~3곳에서만 사용되는 객체를 앱의 시작부터 종료시점까지 메모리에 올려두어야 할 필요가 정말 있는지 확인해라.</p>
</blockquote>
<p>이번 프로젝트에서 로그인과 관련된 객체를 만들며 이러한 문제에 대해 팀원들과 고민했었다.</p>
<p>로그인과 관련하여 로그인 페이지를 모달로 띄워줄 객체인 LoginManager와
로그인 페이지에 미리 아이디와 비밀번호가 입력되어있도록 도와줄 UserDefaultsManager가 필요했다.</p>
<p>이 두 객체를 처음엔 별 생각없이 싱글톤으로 구현했으나 곰곰히 생각해보니 로그인 페이지가 나타나는 곳은 딱 3곳이었다.</p>
<p>앱을 처음 실행할 때 1번만 메인 탭바 뷰, 
예매하기 버튼을 누르는 디테일뷰, 
회원정보를 확인하는 마이페이지 뷰.</p>
<p>여러 화면 중에서 딱 3곳에서만 필요하고 로그인 이후 로그인에 대한 정보면 몰라도 로그인 화면에 대해서는 동일한 상태를 유지할 필요도 없다고 판단하여 의존성 주입 방식으로 사용하기로 변경했다.</p>
<p>대신 UserDefaultsManager에는 현재 로그인 상태에 대한 정보도 담아두었는데,
현재 로그인 상태를 앱의 실행 중 계속해서 동일하게 유지해야 한다는 점과 로그인 매니저, 로그인 창을 열지 결정해야 하는 뷰 등 다양한 곳에서 해당 객체를 필요로 하여 싱글톤으로 구현하기로 결정했다.</p>
<h2 id="concurrency-safe">Concurrency-safe</h2>
<p>이번 프로젝트의 첫 글 하단에서 sending을 알아보며 잠시 나왔던 Sendable과 관련하여 팀원이 경고를 발견한 부분에 대해 추가로 적으려 한다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b9991174-b979-4940-b899-0988f7bc446a/image.png" alt=""></p>
<p>static을 이용해 싱글톤 패턴을 구현하는 중에 발견된 경고인데 동시성 안전성이 보장되지 않는다는 뜻같아보인다. 
그 아래로 Sendable 프로토콜을 준수하지 않는다.
@MainActor를 사용해 메인스레드에서만 동작하는 것을 보장해봐라
동시성 안전성 체크하는 기능을 끄라는 등의 추천 동작이 이어졌다.
게다가 Swift6에서는 경고가 아닌 에러로 간주된다고 한다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/8e8390f8-c5ab-47ea-9133-016ec6ced912/image.png" alt=""></p>
<p>실제로 이 경고는 내가 BuildSetting에서 Strict Concurrency Checking의 옵션을 complete로 바꾸고 나서야 발생했다.</p>
<hr>
<h2 id="vs-thread-safe">vs Thread-safe</h2>
<p>아무튼 해당 경고는 결국 동시성 안전성이 보장되지 않는다는 말인데 그렇다면 Concurrency-safe가 뭘까? Thread-safe와 비슷하게 생겼는데 비교하면서 알아보겠다.</p>
<p>일단 동시성이란 CPU의 성능을 최대화 하기 위해 여러 작업을 동시에 실행하는 것을 말한다.
하지만 동시에 같은 자원에 접근하여 읽고 쓰는 경우 실행 순서에 따라 결과가 달라지는 일이 발생한다.(Race Condition)
또는 서로 자원을 기다리기만 하고 쓰는 스레드가 없어 영원히 멈추는 경우(Deadlock)나 데이터가 불일치하는 상황이 발생할 수 있다.</p>
<p>이를 해결하기 위해 Swift에는 몇 가지 방법이 존재한다.</p>
<ol>
<li>Lock</li>
<li>Queue-Based Concurrency(GCD나 OperationQueue)</li>
<li>Immutability</li>
<li>Actor</li>
<li>Sendable</li>
</ol>
<p>이러한 방법을 통해 Concurrency-safe를 보장받을 수 있다.</p>
<p>그렇다면 Thread-safe와는 무엇이 다를까?
먼저 스레드 안전이란 공유 자원이 여러 스레드에서 동시에 접근되더라도 데이터 무결성이 보장되는 상태를 의미한다.
이 또한 경쟁조건이나 데이터 손상, 데드락을 방지하기 위해 필요하다.</p>
<p>스레드 안전성을 보장하기 위해선 다음과 같은 방법을 사용한다.</p>
<ol>
<li>Lock</li>
<li>GCD(Grand Central Dispatch)</li>
<li>Immutability</li>
<li>Actor</li>
<li>Atomic Operation</li>
</ol>
<p>뭔가 방지하고자 하는 목적도 비슷하고 해결법도 굉장히 비슷하다.
그렇다면 정확히 어느 지점에서 구분이 되는 걸까??</p>
<p>스레드 안전성은 공유 자원 자체에 초점이 맞춰져있다.
동시성 안전성은 작업의 단위와 시스템 전체 동작의 안전성에 초점이 맞춰져있다.
결국 데이터를 보호하는 것이 핵심이고 여러 작업이 충돌하지 않도록 방지하기 위함이기에 위에서처럼 해결방법이 대부분 겹치는 것이다.</p>
<p>내가 이애한 바로는 Thread-safe가 좀 더 작은 단위(변수나 객체)를 보호하기 위함이라면 Concurrency-safe는 좀 더 큰 단위(시스템이나 작업)를 보호하는 수준이라고 생각된다.</p>
<p>동시성 안전성을 해결한다면 스레드 안전성도 어느정도 따라오는 느낌이다.</p>
<hr>
<h2 id="해결법">해결법</h2>
<p>결국 static이 붙어있는 공유객체는 여러 작업, 스레드에서 동시에 접근할 가능성이 존재하므로 이를 좀 더 안전하게 사용할 수 있도록 보장하라는 의미의 경고로 보인다.</p>
<p>이를 위한 해결법을 아주 간단히 하나씩 알아보겠다.
그래야 강의를 들을 수 있다..</p>
<h3 id="nslock">NSLock</h3>
<p>lock은 이름 그대로 자원에 락을 걸어두어 해제 전까지 다른 스레드에서의 접근을 막아버리는 방법이다.
그렇다면? 당연히 관리를 잘못했을 때 Deadlock에 걸릴 것이다.
또 경합(contention)이 발생하여 대기 시간이 길어지고 성능이 저하될 가능성도 존재한다.
때문에 아래에서 알아볼 다른 방법을 더 적극적으로 사용하자.</p>
<h3 id="actor">Actor</h3>
<p>1편에서 알아봤던 Actor다. 
복습을 하자면 actor 내부에 정의된 변수와 메서드는 기본적으로 Thread-safe하다.
또 상태에 접근하기 위해 반드시 await이 필요하고 격리되었음을 더 확실히 보증하기 위해 isolated라는 키워드를 사용한다.</p>
<p>그리고 내부 상태에 대한 접근이 모두 직렬화 되기에 동시성 안전성이 보장된다!
새벽에 밤새가며 공부한 보람이 있다.</p>
<h3 id="gcdgrand-central-dispatch">GCD(Grand Central Dispatch)</h3>
<p>이건 1편에서 그냥 넘겼던 부분이다..
가장 일반적으로 사용되는 DispatchQueue를 활용한 직렬화, 동시 실행 방법이다.</p>
<pre><code class="language-swift">let serialQueue = DispatchQueue(label: &quot;com.example.serialQueue&quot;)

serialQueue.async {
    print(&quot;작업 1 시작&quot;)
    print(&quot;작업 1 완료&quot;)
}

serialQueue.async {
    print(&quot;작업 2 시작&quot;)
    print(&quot;작업 2 완료&quot;)
}
// 작업 프린트문이 순서대로 출력된다.
</code></pre>
<p>직렬 큐를 사용하면 순차적으로 작업이 실행되니 자연스레 동시성 문제가 발생하지 않는다.
또 동시 실행도 지원하는데 대신 적절한 동기화 처리를 해주어야 문제가 발생하지 않는다.
그러나 동시 작업이 복잡할 경우 코드가 어려워질 수 있다.</p>
<h3 id="operation-queue">Operation Queue</h3>
<p>Operation Queue는 GCD보다 상위 API로 작업의 우선순위와 의존성 그리고 취소 가능성까지 추가로 관리 가능하다.
대신 그만큼 약간의 오버헤드가 따를 수 있다.</p>
<p>사용 예시는 다음과 같다.</p>
<pre><code class="language-swift">let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1 // 직렬 실행
// 숫자를 늘리면 그만큼 동시 실행이 가능해짐

operationQueue.addOperation {
    print(&quot;Task 1 실행&quot;)
}

operationQueue.addOperation {
    print(&quot;Task 2 실행&quot;)
}</code></pre>
<p>의존성 주입의 예시는 다음과 같다.</p>
<pre><code class="language-swift">let operationQueue = OperationQueue()

let task1 = BlockOperation {
    print(&quot;Task 1 실행&quot;)
}
// BlockOperation은 클로저 기반의 작업을 생성하고 실행할 수 있게 돕는 서브클래스.
let task2 = BlockOperation {
    print(&quot;Task 2 실행&quot;)
}

task2.addDependency(task1) // 의존성을 설정하여 Task 1이 완료된 후에 Task 2 실행되도록 함

operationQueue.addOperations([task1, task2], waitUntilFinished: false)// 작업을 추가하고 작업 완료를 기다리지 않고 즉시 반환하도록 설정함. 즉 비동기적으로 실행되도록 함.</code></pre>
<h3 id="immutability">Immutability</h3>
<p>가장 간단하고 안전하지만 수정이 잦을 경우 성능저하가 발생하는 불변성 활용이다.
모든 데이터가 불변이라면 동시성 문제가 원칙적으로 발생하지 않을 것이다.
대신 새로운 상태를 설정할 때마다 값을 복사하여 새로 넣어주어야 하니 성능저하가 생길 수 있다.</p>
<pre><code class="language-swift">struct Immutability {
    let value = 1

    func addValue(_ num: Int) -&gt; Immutability {
        return Immutability(value = self.value + num)
    }</code></pre>
<h3 id="sendable">Sendable</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/9e0d9815-4487-4961-8a63-e487bdb572ee/image.png" alt=""></p>
<p>마지막으로 알아볼 것은 Sendable이다.
이건 애초에 Concurrency-safe를 보장하는 프로토콜로 객체나 값이 여러 스레드 간에 안전하게 전달될 수 있는지를 컴파일 타임에 검사받는다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/3cdf48bc-76af-482a-adf0-d619de3dff51/image.png" alt="">
Sendable은 자동으로 준수하게 되는 경우가 있다.
즉 명시하지 않더라도 암묵적으로 준수하는 코드를 작성할 수 있는데 그 예가 위와 같다.</p>
<ul>
<li>값타입</li>
<li>가변 저장소가 없는 참조타입</li>
<li>내부적으로 상태 접근을 관리하는 참조 타입</li>
<li>함수와 클로저(클로저에 @Sendable을 추가하여 표시)</li>
</ul>
<p>이 프로토콜에는 필수 메서드느 프로퍼티는 없으나 컴파일 타임에 검증되는 의미적 요구사항이 존재한다.</p>
<p>내가 느끼기엔 순서의 차이일 뿐 Sendable을 준수한다 = 동시성 안전성을 보장한다.
동시성 안전을 보장하는 코드 = Sendable을 준수하는 코드 같다.
구현은 개발자가 도구를 선택하여 직접 구현하고 구현했을을 컴파일러에게 알리는 도구가 Sendable인 느낌.</p>
<hr>
<p>Sendable의 요구사항을 충족하려면 열거형이나 구조체는 Sednable멤버와 관련된 값만 가져야 한다.</p>
<ul>
<li>Frozen인 구조체와 열거형.</li>
<li>public이 아니고 @usableFromInline이 마크되지 않은 구조체와 열거형</li>
</ul>
<p>위 두 가지 경우엔 Sendable을 명시적으로 선언할 필요가 없다.
그 외의 경우엔 Sendable을 명시적으로 선언해야 하고,
코드 작성 시 컴파일러는 동시성 안전성을 확인하지 못하지만 개발자가 동시성 안전성을 보장하려면 <code>@unchecked Sendable</code>을 사용하여 
컴파일러에게 안전하니까 보장해달라고 하는 방법이 있다.</p>
<p>Frozen은 아마 첫 글에서 잠깐 봤던 @frozen을 붙여 확장이 불가능함을 명시적으로 지정한 구조체나 열거형을 뜻하는 게 아닐까 싶다.
(원문: Frozen structures and enumerations)</p>
<p>그리고 @usableFromInline은 internal로 선언된 심볼을 모듈외부에서도 인라인될 수 있도록 허용하여 성능 최적화를 하는 속성이라고 하는데
아마 내가 쓸 일은 없지 않을까.. 그냥 쓰게 되면 명시적으로 Sendable을 준수하자.</p>
<pre><code class="language-swift">class Foo: Sendable {
    let value: Int = 0
} // Int는 Sendable을 준수하므로 Bar도 Sendable을 자동으로 준수한다.

struct Bar: Sendable {
    let bar: Int = 0 
} // Int는 Sendable을 준수하므로 Bar도 Sendable을 자동으로 준수한다.

struct Baz: Sendable {
    let userDefaults = UserDefalts
} // UserDefaults는 Sendable을 준수하지 않기 때문에 수동으로 준수하도록 구현해야 함.
</code></pre>
<hr>
<p>모든 actor는 암시적으로 Sendable을 준수한다. 모든 접근을 순차적으로 처리하니까.</p>
<hr>
<p>Sendable을 준수하는 클래스는 다음 사항을 만족해야 한다.</p>
<ul>
<li>final로 표시될 것</li>
<li>Immutable하고 Sendable한 저장프로퍼티만 포함할 것</li>
<li>상위 클래스가 없거나 NSObject일 것</li>
</ul>
<p>그리고 @MainActor는 암시적으로 Sendable을 준수한다. 
메인스레드에서만 동작한다고 보장받으니 가변 프로퍼티나 Sendable하지 않은 프로퍼티도 가질 수 있다.</p>
<hr>
<p>함수와 클로저는 프로토콜 대신 @Sendable attribute로 표시한다.
특히 @Sendable Closure는 다음을 충족해야 한다.</p>
<ul>
<li>캡처한 값이 모두 Sendable일 것</li>
<li>캡처한 값은 값 복사 방식으로만 사용할 것</li>
</ul>
<p>만약 Sendable 클로저를 요구하는 컨텍스트에서 위 요구사항을 충족한다면 암시적으로 Sendable로 간주해준다.</p>
<p>명시적으로 작성하고 싶다면 다음과 같이 작성하면 된다.</p>
<pre><code class="language-swift">let closure = { @Sendable (num: Int) -&gt; Int in
    return num * 2
}</code></pre>
<hr>
<p>앞서 살펴본 Sendable이 자동으로 준수되는 경우가 아닌 경우 수동으로 동시성 안전성을 보장해줘야 하한다.
그 방법은 다시 앞서 알아본 NSLock, DispatchQueue, actor 등의 동기화 도구 활용으로 이어진다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/7a0de5c6-7e4d-4306-b171-91c50e4524dc/image.png" alt=""></p>
<p>이번에 만난 싱글톤 객체의 경고를 해결해보자.
일단 class에 Sendable을 사용해보자.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/1bab8f22-1cb8-419a-88b8-4e65171141ae/image.png" alt=""></p>
<p>그러니 내부 프로퍼티 중 하나인 userDefaults의 타입이 Sendable을 준수하지 않는 타입이라고 경고가 떴다.</p>
<p>어떻게 해결할까?
actor는 class처럼 사용할 수 있는 키워드인데
앞서 말했듯 actor는 내부 상태에 대한 모든 접근을 직렬화 하고 동시성 안전을 보장하기 때문에 UserDefaults같은 스레드안전하지 않은 객체도 안전하게 관리할 수 있다.</p>
<p>대신 비동기적으로 호출되니 final class에서 actor로 변경 시 호출되는 부분을 모두 await을 사용하도록 변경해주어야 한다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/33bf187c-a18c-409b-b2e8-daeca14633e2/image.png" alt=""></p>
<p>UserDefaults에 대한 경고가 사라졌다!
 <img src="https://velog.velcdn.com/images/bom_daddy/post/e84cc2ec-f40e-4233-8f73-1f7b455d75c9/image.png" alt="">
대신 에러를 받았다!!
일단 actor로만 바꾸고 함수에 대해 async 처리를 안 해줘서 난 오류였다.
함수에 async를 붙이고 오니 다른 에러가 발생했다.</p>
<blockquote>
<p>&#39;async&#39; call in a function that does not support concurrency</p>
</blockquote>
<p>이 에러는 직역하면 동시성을 지원하지 않는 함수에서 호출되었다는 건데
즉 비동기 함수(async)로 선언되지 않은 함수에서 호출받았다는 뜻이다.</p>
<p>동기함수에서는 비동기 호출이 허용되지 않기 때문이며 함수 자체를 다시 async로 정의하거나 get async로 속성을 비동기로 정의하여 반환해버릴 수도 있다.</p>
<p>함수 자체가 비동기일 필요가 아닌 반환 속성이 비동기이길 원하기 때문에 get async{}로 묶어주어서 반환을 해줬다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/4c08e4e1-6dd0-4514-b353-112173c9b584/image.png" alt="">
오늘도 글을 끝내고 싶은데 끝낼 수가 없다.</p>
<p>왜냐면 다시 해당 함수를 호출하는 곳을 비동기처리해야 하니까..
아 근데 이렇게 하나가 비동기가 되면서 비동기가 비동기를 낳고 비동기를 낳는 이런 전파가 맞는 걸까..?
actor로 바꿀 문제가 아닌 걸까 흑흑</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LuckVII_2(Swiftlint)]]></title>
            <link>https://velog.io/@bom_daddy/LuckVII2Swiftlint</link>
            <guid>https://velog.io/@bom_daddy/LuckVII2Swiftlint</guid>
            <pubDate>Mon, 23 Dec 2024 06:28:24 GMT</pubDate>
            <description><![CDATA[<h2 id="swiftlint">Swiftlint</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/8c6a9820-bdd7-420d-baa5-61301cf5c297/image.png" alt=""></p>
<p>이번 프로젝트를 진행하며 밤을 새던 새벽에 문득 코드 컨벤션이 하나도 안 지켜지고 있단 생각이 들었다.
그러나 이번 프로젝트의 swift파일은 약 50개,,</p>
<p>일일이 수정하지 않으면서 가독성과 코드 일관성을 높이고 과도하게 긴 객체나 메서드를 찾고자 수업시간에 배운 swiftlint를 활용해보고자 했다.</p>
<p>swiftlint란 코드 스타일 가이드와 규칙을 기반으로 문제가 되는 코드를 탐지하고 경고해주며 수정가능한 간단한 사항들은 자동으로 수정까지 해주는 linting툴이다.</p>
<p>주로 띄어쓰기, 네이밍 규칙, 줄 길이 제한, 들여쓰기 등에 대해 검사한다.
또 빌드 과정에 스크립트로 통합시킬 수 있어 자동으로 규칙 위반을 감지할 수도 있다.
어떤 분은 TODO주석으로 할 일들을 기록해두어 놓치지 않기 위해 TODO주석에 대해 경고를 남기도록 설정해두시기도 한다고.</p>
<p>그외에도 강제 언래핑을 막거나 치명적인 에러가 발생할 수 있는 코드 등도 지적해주는 편리한 도구이다.</p>
<p>유명한 스타일 가이드는 Airbnb, RayWenderlich가 있고 팀의 스타일에 따라 .yaml파일을 만들어 규칙을 커스텀할 수도 있다.
특정파일을 제외하거나 규칙을 비활성화하거나 한 줄의 최대 길이를 지정하는 등.</p>
<p>그리고 이러한 과정을 통해 얻을 수 있는 장점은 코드 품질을 자동으로 관리해주어 리뷰 시간을 단축해주고 프로젝트 코드 스타일의 일관성을 유지하니 협업에도 도움이 된다.
아니면 강제언래핑을 막는 것처럼 실수나 에러 발생 가능성을 감소시키는 데도 도움이 된다. 또 상속하지 않는다면 final을 붙이도록 하는 등 성능 최적화에도 도움을 받을 수 있다.</p>
<h2 id="install">Install</h2>
<p>swiftlint는 홈브루로 설치하거나 xcode에서 스냅킷을 추가하듯 추가할 수 있다.
근데 난 스냅킷처럼 엑스코드 파일에 포함시켰을 때 엄청난 에러를 보고 그냥 얌전히 홈브루로 설치했다.</p>
<pre><code>brew install swiftlint</code></pre><p>혹시나 xcode에 통합하고 싶다면 build Phases에서 New Run Script Pahse를 선택하고 아래 스크립트를 입력하면 규칙 위반 사항을 표시해줄 수 있다.</p>
<pre><code>if which swiftlint &gt;/dev/null; then
  swiftlint
else
  echo &quot;warning: SwiftLint not installed. Install using Homebrew: brew install swiftlint&quot;
fi</code></pre><p>난 그냥 터미널로 siwftlint를 실행한 뒤 새로 나온 gpt의 기능을 이용해 로그를 요약해서 전달받고 처리했다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/d00d705f-6025-443b-964b-b49773fc5f03/image.png" alt=""></p>
<p>마지막으로 린팅을 한지 좀 지났더니 더 생겼다.
여기에 다음과 같이 자동 수정처리를 하면 자동처리한 것과 수동처리가 필요한 부분을 알려준다.</p>
<pre><code>swiftlint --fix</code></pre><p><img src="https://velog.velcdn.com/images/bom_daddy/post/a5ea6d19-5d60-4152-b3c5-10ddc463b040/image.png" alt=""></p>
<p>만약 특정 파일만 하고 싶다면</p>
<pre><code>swiftlint fix --path 파일경로/파일 이름.swift</code></pre><p>위 코드로 처리할 수 있다.
파일이 아니라 경로로 끝낸다면 해당 경로에 존재하는 파일만 수정할 것이다.</p>
<p>또 바로 수정을 원하는 것이 아니라 원본은 유지하되 별도의 형식으로 수정된 파일을 출력받고 싶다면 </p>
<pre><code>swiftlint fix --format</code></pre><p>위 코드를 사용하면 된다.</p>
<h3 id="경고-규칙">경고 규칙</h3>
<p>기본적인 경고 규칙을 요약하면 다음과 같다.</p>
<ul>
<li>Trailing Whitespace Violation: 줄 끝에 불필요한 공백이 있으면 경고</li>
<li>Vertical Whitespace Violation: 연속된 빈 줄이 1줄을 초과하면 경고</li>
<li>Line Length Violation: 한 줄의 길이가 120자를 초과하면 경고</li>
<li>Function Body Length Violation: 함수 본문이 50줄을 초과하면 경고</li>
<li>Type Body Length Violation: 클래스나 구조체 본문이 350줄을 초과하면 경고</li>
<li>Colon Spacing Violation: 콜론 주변에 적절한 공백이 없으면 경고</li>
<li>Self in Property Initialization Violation: 속성 초기화 시 self 사용이 부적절하면 경고</li>
<li>Force Cast Violation: 강제 캐스팅 as! 을 사용하면 경고</li>
<li>File Length Violation: 파일 길이가 400줄을 초과하면 경고</li>
<li>Comment Spacing Violation: 주석 // 이후 공백이 없으면 경고</li>
</ul>
<p>이중 자동 수정 가능한 규칙은 </p>
<ul>
<li>Trailing Whitespace: 줄 끝의 불필 요한 공백 삭제</li>
<li>Vertical Whitespace: 연속된 빈 줄 삭제</li>
<li>Line Length: 한 줄의 길이가 길 때 가능하다면 처리</li>
<li>Colon Spacing: 콜론 주변에 공백 없을 경우 추가</li>
<li>Opening Brace Spacing: 중괄호 시작({) 앞뒤에 공백이 적절하지 않다면 처리</li>
<li>Comment Spacing: 주석처리(//) 이후에 공백이 없다면 공백 추가</li>
</ul>
<p>즉 웬만하면 코드에 직접적인 영향을 끼치는 자동수정은 없다고 보면 된다.</p>
<p>다음 프로젝트에서는 팀 코드 컨벤션을 적극적으로 추진하고 swiftlint의 사용도 권장해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LuckVII_1 (feat.Concurrency)]]></title>
            <link>https://velog.io/@bom_daddy/LuckVII1-feat.Concurrency</link>
            <guid>https://velog.io/@bom_daddy/LuckVII1-feat.Concurrency</guid>
            <pubDate>Sun, 22 Dec 2024 20:22:54 GMT</pubDate>
            <description><![CDATA[<h2 id="luckvii">LuckVII</h2>
<p>Sparta에서 진행하는 영화 예매 프로젝트 LuckVII에서 <a href="https://www.themoviedb.org">https://www.themoviedb.org</a> 라는 영화 정보 사이트에서 api로 정보를 요청하여 받아오는 부분을 맡았다.</p>
<pre><code>📂 NetworkService  
│  
├── 📄 ImageManager  
│  
├── 📄 MovieData  
│  
├── 📄 MovieDataManager  
│  
├── 📄 MovieData+Extension  
│  
├── 📄 NetworkError  
│  
├── 📄 TypeAlias  
│  
└── 📄 UIImage+Extension  </code></pre><h2 id="tmdb">TMDB</h2>
<p>일단 TMDB에서 api에 GET 요청 시 url 베이스는 다음과 같다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/2e8dd5c4-fc18-48c4-b049-9373c953cf2e/image.png" alt="">
저기 중간의 /now_playing이라는 엔드포인트를 upcoming과 popular로 바꾸어가며 받아올 예정이며 파라미터로 개인 api 요청 키와 언어, 지역 등을 설정한다.</p>
<p>이번엔 코드를 전부 짠 뒤에서야 await/async의 존재를 알게 되어서 작성한 메서드를 기존 방식과 호환 가능한 메서드, 새로운 방식에 맞춘 메서드를 모두 작성하느라 시간이 꽤 걸렸다.</p>
<p>일단 가장 먼저 한 일은 해당 url로 요청 시 받아오는 정보 중 필요한 것들이 뭔지 정하고 Decodable을 준수하는 타입을 정의하는 것이었다.</p>
<h2 id="response">Response</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/7f79f31a-1b6e-47ac-9819-207ab3fef4ae/image.png" alt="">
사이트에 친절히 어떤 데이터를 돌려주는지 알려주고 있으며 뷰를 만들고 있는 팀원들과 상의하여 우선 다음의 데이터만을 가져와보기로 결정했다.</p>
<ul>
<li>adult</li>
<li>originalLanguage</li>
<li>overview</li>
<li>popularity</li>
<li>posterPath</li>
</ul>
<p>이 5가지 정보를 가진 Movie라는 구조체를 만들고
Movie 배열을 가진 MovieData를 만들어 받아온 데이터를 저장했다.
이후 ui구성을 위해 추가적인 데이터를 가져오게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/fd9e68cc-f860-4e73-84f9-edee3549b3ef/image.png" alt=""></p>
<h2 id="moviedata">MovieData</h2>
<p>지난번 포켓몬 연락처에서 작성한 것과 달라진 점이 2가지있다.
그 첫번째가 Decodable이다.
기존에는 큰 생각 없이 Codable을 준수하는 struct를 작성했다.
그러나 GET 요청만 수행하는 내 코드에서 Encodable을 준수할 필요는 없다는 걸 튜터님의 피드백으로 알게 되었다.</p>
<h3 id="codable">Codable</h3>
<p> <img src="https://velog.velcdn.com/images/bom_daddy/post/a2d5bca0-543e-4f0c-8869-a3e9f73e7c65/image.png" alt="">
Codable, Encodable, Decodable을 하나씩 알아보자.
Codable은 위 사진처럼 Incodable과 Decodable을 단순히 typealias로 지정해둔 것이다.</p>
<p>Encodable은 데이터를 직렬화 하는데 사용하는 것이다.
Serialization(직렬화)이란 객체나 데이터를 특정 형식(바이트 스트림)으로 변환하여 저장하거나 전송할 수 있도록 만드는 과정이라고 한다.
직렬화를 거친 데이터는 파일, 네트워크, 데이터 베이스 등으로 전달될 수 있고 역직렬화를 통해 다시 원래의 객체로 복원할 수 있다.</p>
<p>스위프트에서만 사용하는 형식을 외부 시스템이 이해할 수 있는 JSON, XML, plist 등으로 변환하거나 외부 시스템이 사용하는 형식의 데이터를 스위프트에서 사용하기 위한 과정들이다.</p>
<p>피드백 받은 Codable과 Decodable의 차이는 결국 Encodable을 준수하는지와 그로 인해 직렬화가 가능한지에서 차이가 난다.</p>
<p>인코딩을 하지 않을 것임에도 인코딩을 할 수 있도록 하는 것은 불필요한 책임을 지게 하는 것이며 코드의 의도가 불명확해질 수 있다.
때문에 명시적으로 인코딩 가능성을 배제하고 읽기 전용 데이터 모델임을 확실히 하는 것이 더 좋은 코드라고 할 수 있겠다.</p>
<h3 id="codingkey---keydecodingstrategy">Codingkey -&gt; .keyDecodingStrategy</h3>
<p>두 번째는 api 응답으로 돌려 받는 데이터의 이름과 스위프트에서 사용하는 데이터의 네이밍 컨벤션을 일치시키는 작업의 변화다.
기존엔 CodinKey를 사용한 열거형을 정의하여 수동으로 매핑해주었다.
예를 들어 now_playing이 있다면 직접 해당 케이스에 nowPlaying이라는 값을 매핑시킨 것이다.</p>
<p>이러한 방식은 세밀한 매핑이 가능하고 원하는 특정 키만 변환하는 등의 장점이 있다. 대신 이름이 다른 프로퍼티가 많을수록 일일이 작성해야 하는 번거로움이 동반될 수 있다.</p>
<p>대신 이번에 채택한 것은 Decoder의 속성 중 하나인 .keyDecodingStrategy를 설정해주는 것이다.</p>
<p>keyDecodingStrategy는 JSON의 key와 Swift의 프로퍼티 이름 간 차이를 자동으로 처리하여 작업을 줄이고 코드의 양을 감소시켜준다.</p>
<p>이번에 사용한 것은 정확히 keyDecodingStrategy의 설정을 .convertFromSnakeCase로 바꾸어 준 것이다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/849d8a42-5c0f-4ad9-80a4-27ecb7992a2f/image.png" alt=""></p>
<p>JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase는 위 설명대로 3가지의 규칙에 따라 이름을 자동으로 변환시켜준다.</p>
<ol>
<li>밑줄 뒤에 오는 첫 글자를 대문자로 변경</li>
<li>문자열의 시작이나 끝에 있는 밑줄 제거</li>
<li>밑줄을 모두 제거하고 하나의 문자열로 결합</li>
</ol>
<p>ex)fee_fi_fo_fum &gt;&gt; feeFiFoFum</p>
<p>간단하게 일반화된 변환처리를 할 수 있어 편리하지만 약어와 이니셜리즘의 대소문자를 정확히 처리하지 못할 수 있다.</p>
<p>예를 들어 base_url을 baseURL로 변환되기를 희망하더라도 baseUrl로만 자동변환될 것이다.</p>
<p>이러한 경우엔 codingKey를 직접 정의해주어 명시적으로 지정해주어야 한다.
(즉 어느 하나만을 사용할 수 있는 것이 아닌 동시 사용이 가능하다는 말)
 <img src="https://velog.velcdn.com/images/bom_daddy/post/c07a3e91-4dce-44c4-8e8c-5ef8c540fbd8/image.png" alt="">
key 디코딩 전략 외에도 위와 같은 날짜, 데이터, 비호환 부등소수점 값 등에 대한 전략도 설정할 수 있다.
아마 프로젝트 진행 과정에서 날짜 정보가 추가적으로 필요할 것이라 생각되기에 그때 다시 dateDecodingStrategy에 대해 알아보겠다.</p>
<h2 id="moviedatamanager">MovieDataManager</h2>
<p>이제 데이터를 요청하고 MovieData를 반환하여 주는 객체를 살펴보겠다.
이번 fetch메서드를 작성하는데 가장 오래 걸린 부분이 기존의 클로저 콜백을 이용한 메서드에서 await/async를 사용한 메서드로 변경하는 작업이었다.</p>
<h3 id="completion-사용">completion 사용</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/dcc6bf80-b79d-4c96-8002-7d56cf301b3b/image.png" alt="">
기존에는 함수가 종료된 뒤에 네트워크 통신이 끝나는 상황(URLSession.shared.dataTask)을 고려하여 @escaping을 붙인 클로저를 돌려주는 방식을 사용했다. 
이러한 방식은 여러 비동기 작업을 처리할 때 중첩이 발생할 가능성이 존재했다. 이러한 중첩은 코드의 가독성을 떨어트렸다.
또 반드시 @escaping을 붙여주어 함수 밖에서도 클로저가 실행될 수 있음을 명시해줘야 했다.</p>
<h3 id="completion-handler-vs-asyncawait">completion handler VS async/await</h3>
<p>그러나 이번에 사용한 async/await 방식은 비동기 작업이 마치 동기적인 방식처럼 순차적으로 실행하듯 보인다. 덕분에 코드를 더 읽기 쉽고 직관적으로 보이게 한다. 
비동기 작업이 많아질 때 콜백이 많아지는 콜백 지옥에서도 탈출하고 try/catch를 사용하여 비동기 함수의 에러를 동기코드처럼 처리할 수도 있다.
(클로저 콜백을 이용할 땐 try/catch를 이용한 에러처리가 제한된다. 대신 위와 같이 반환 타입에 Result 타입을 사용했다.)</p>
<p><a href="https://forums.developer.apple.com/forums/thread/712303">https://forums.developer.apple.com/forums/thread/712303</a>
위 개발자 포럼의 토의를 읽어보면 async/await을 사용해 얻는 이점이나 코드의 간결성 차이를 좀 더 알 수 있다.</p>
<p>이 외에도 애플의 공식 영상을 참고할 수 있다.
<a href="https://developer.apple.com/videos/play/wwdc2021/10132?time=110">https://developer.apple.com/videos/play/wwdc2021/10132?time=110</a> 
<a href="https://developer.apple.com/videos/play/wwdc2021/10095">https://developer.apple.com/videos/play/wwdc2021/10095</a> </p>
<p>위 영상에서 예시로 준 코드를 한 번 살펴보자.
<img src="https://velog.velcdn.com/images/bom_daddy/post/cb626292-38a3-4b02-8fd0-b46868661466/image.png" alt="">
먼저 일반적인 completion handler를 이용한 방식이다.
여기에 Result를 추가하면 다음과 같다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/429a76c5-2d7f-4837-830a-6289f02fc5c9/image.png" alt=""></p>
<p>옵셔널 처리를 하고 불필요하게 nil을 넣어주던 부분이 사라졌으며 정상 동작과 에러에 대한 처리가 좀 더 명확해졌다.</p>
<p>위 코드를 async/awiat를 사용하여 리팩터링 하면 다음과 같아진다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/f1a218d6-567e-4a37-b3a1-abf9af7dc0fc/image.png" alt=""></p>
<p>글을 보는 누구라도 차이를 느낄 수 있을 것이다.
여기서는 URLSession에 적용된 async/await까지 겹쳐져 더 극적으로 보인다고 생각한다. 기존엔 URLSession도 completion handler 기반의 메서드를 사용했다.
그러나 새로운 코드에선 data와 response의 결과를 기다리고 바로 해당 결과를 사용하여 처리하는 간결한 코드가 완성되었다.</p>
<p>그렇다면 대체 async/await이 뭘까?</p>
<p>async는 해당 함수가 비동기 작업임을 나타낸다. 해당 키워드가 붙은 함수는 호출 될 때 스레드를 차단하지 않는다.
대신 실행 중단점을 만들어 호출이 완료될 때까지 기다린다.</p>
<p>aync/await은 Swift Concurrency Model을 사용해 작업을 스케줄링하며 비동기 작업은 GCD(Grand Central Dispatch) 또는 Executor가 관리한다.
(GCD란 간단히 멀티스레드와 동시성 처리를 위한 라이브러리이다. 작업 단위를 큐에 추가하여 병렬 실행해주는데 우리가 자주 쓰는등DispatchQueue, main과 global 큐 등)</p>
<p>Swift Concurrency Model은 </p>
<p>또한 기본적으로 메인 스레드와 다른 작업 스레드에서 실행되기에 메인 스레드에서는 안전하게 UI 업데이트를 수행할 수 있다.</p>
<p>await은 async 함수 내에서 다른 비동기 함수가 완료될 때까지 기다리며 비동기 함수를 호출할 때 사용된다. 비동기 함수가 완료될 때까지 실행을 일시 중단하지만 현재 스레드가 차단 되지는 않는다</p>
<p>다만 async를 붙인 함수라고 해서 무조건 await을 붙여 호출해야 하진 않다.
awiat을 붙인다는 건 비동기 작업의 결과를 사용할 때, 그 작업이 끝날 때 까지 기다리겠다는 의도를 나타내는 키워드이다.</p>
<h3 id="task">Task</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/f681d5bf-7779-4710-b70e-9459d5115ffa/image.png" alt=""></p>
<p>이제 async를 사용해 작성한 비동기 함수를 사용해보자.
Task는 앞서 말한 Swift Concurrency Model의 구성 요소 중 하나로 비동기 작업의 실행 컨텍스트를 나타낸다.
async 함수를 호출하거나 비동기 작업 실행을 위해 사용되며 내부에서 async/await을 사용할 수 있다.</p>
<p>추가적로 가진 Task의 특징에는 작업 취소 가능, 작업 상태 추적, 비동기적 작업 실행 등이 있다.</p>
<p>그러나 가장 내 눈에 띈 특징은 Task에는 약한 참조를 사용할 필요가 없단 것이다.
Task가 완료되면 Task가 가진 참조들이 모두 해제되기 때문에 Task 내의 클로저에서 약한 참조로 캡처할 필요가 없다는 것. 이는 클로저를 무한히 유지하지 않는다는 특성과 이어진다.</p>
<h4 id="frozen">@frozen</h4>
<p>잠시 딴 길로 새자면 Task의 정의에 처음 보는 어트리뷰트가 존재해서 알아봤다.
@fozen attribute는 struct나 enum에 사용되며 해당 타입이 확장 불가능하다는 것을 명시적으로 지정하는 역할을 한다.</p>
<p>특히 열거형에 적용할 때 해당 열거형의 크기나 메모리 구조가 고정되어서 시스템에서 성능 최적화가 가능해진다고 한다.</p>
<p>처음엔 그저 상속을 막는 final처럼 확장을 막는 frozen 정도로 비슷하다 생각했다. 둘 다 유연성을 포기하고 성능과 안정성을 올린다는 점이 특히 비슷했다.</p>
<p>final은 class, method, property, subscript 등에 사용되는데 서브클래싱이나 오버라이드를 하지 못하게 한다. 주로 객체 지향 프로그래밍에서 사용되며 다형성과 상속을 제어하는 방식으로 예측가능한 상태로 유지시킨다.</p>
<p>frozen은 struct나 enum에 사용하여 타입을 고정시켜 케이스나 프로퍼티를 추가할 수 없게 한다. 확장할 수 없다는 점을 통해 타입 안정상을 높이고 예기치 않은 확장을 방지할 수 있다.</p>
<p>정리하고 봐도 비슷해보이긴 한다.</p>
<h3 id="actor">Actor</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/594f3192-5fd2-414d-b356-5a34af2d091e/image.png" alt=""></p>
<p>앞서 언급한 Swift Concurrency Model에는 Task, async/await 말고도 많은 것들이 있는데 이미 너무 길어져서 Actor만 간단히 더 알아보겠다.
(위의 프로토콜이 Actor 그 자체에 대한 정의는 아니고 actor라면 준수하는 프로토콜이다.)</p>
<p>간단히 말하자면 Actor는 경쟁조건(race conditions)이나 데이터 손상을 방지해줄 수 있는 역할을 한다.</p>
<p>class처럼 정의되지만 동시성 안전을 제공하는 특수한 타입이다. 
정확히 어떤 차이가 있을까?</p>
<p>클래스는 다중 스레드에서 공유될 수 있고 race condition이 발생할 수 있기 때문에 동기화를 직접 관리해야 한다.
구조체는 값 타입으로 복사되기에 비교적 안전하지만 그래도 여러 스레드에서 변경할 수 있는 상태를 다룬다면 여전히 문제가 발생할 수 있다.</p>
<p>Actor는 참조타입이지만 내부 데이터에 한 번에 하나의 스레드만 접근할 수 있도록 보장한다. 이를 통해 동시성을 안전하게 처리할 수 있게 된다.
대신 actor 내부 상태에 접근하기 위해선 비동기적으로 접근해야 하고 내부 데이터나 메서드는 기본적으로 비동기로 호출해야 하며 awiat 키워드를 사용해야 한다.</p>
<p>예를 들어 다음과 같은 코드에서 2가 나오는 것을 보장해줄 수 있다.</p>
<pre><code class="language-swift">actor Foo {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -&gt; Int {
        return value
    }

let foo = Foo()

Task {
    await foo.increment()
}

Taks {
    awiat foo.increment()
}

foo.getValue() // 2가 보장됨 </code></pre>
<p>만약 비동기 작업으로 수행했다면 동시에 value에 접근하면서 2가 아닌 1이 반환될 가능성이 존재했다.
그러나 actor에는 한 번에 한 스레드만 접근가능하므로 첫 번째 Task가 접근하는 동안 비동기적으로 두 번째 Task가 접근하려 해도 기다려야 한다.</p>
<p>이러한 안정성을 얻는 대신 값 복사가 불가능하고 다른 곳으로 actor를 전달하려면 비동기 접근이 필수, 상태를 변경할 땐 await이 사용되어야 한다는 제약이 있다.
추가로 직렬화 된 접근 방식이 가져오는 성능 저하 가능성이 존재한다.</p>
<p>actor와 비슷한 키워드를 본 적이 있는데 바로 @MainActor이다.
@MainActor는 UI 작업을 메인 스레드에서 실행하도록 보장하기 위해 있는 어트리뷰트(attribute)(annotation)으로 주로 UI업데이트와 다른 스레드와의 경쟁을 피하기 위해 사용된다.</p>
<h3 id="await-urlsession">await URLSession</h3>
<p>돌고 돌아 다시 URLSession으로 돌아왔다.
URLSession에는 async/awiat 지원을 위해 다음과 같은 메서드가 추가되었다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/7c35904d-83e6-4f2d-acf5-e1cec95ff355/image.png" alt=""></p>
<ul>
<li>data(from:): 데이터를 다운로드하는 비동기 메서드</li>
<li>download(from:): 파일을 다운로드하는 비동기 메서드</li>
<li>upload(from:): 파일을 업로드하는 비동기 메서드</li>
</ul>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/cbd07753-73be-4946-b7a6-1cc3b6a2c1f9/image.png" alt=""></p>
<p>내가 사용한 건 data로 (data, response)라는 튜플로 받아 사용했다.
비동기 작업이지만 동기적으로 보여서 코드가 읽기 쉽고 직관적이다.
앞서 언급햇 듯 try를 통해 에러처리도 할 수 있고 중첩된 클로저 사용 없이 await을 이용해 순차적인 실행이 가능하다.
또한 실패했을 때 에러를 반환받을 수 있게 try를 사용하는데 이때 반환되는 error는 기존의 completion handler를 사용하며 받던 에러와 동일하다.</p>
<p>이제 완성된 메서드를 사용할 때는? 
앞서 말했듯 Task 컨텍스트 내부에서 사용해주면 된다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/21982828-29ed-4d60-a25c-d8442f322200/image.png" alt=""></p>
<p>위 코드는 팀원이 작성한 코드의 일부로 코드가 Task로 묶인 것을 볼 수있다.
또한 내가 async throw로 반환했기에 try/catch도 사용하고 있다.
그리고 내부에서 fetchData를 사용할 때 await을 사용하여 비동기로 처리할 것을 명시해주는 중이다.</p>
<h3 id="추가적인-async-활용_checkedcontinuation">추가적인 async 활용_CheckedContinuation</h3>
<p>async/await을 알게된 뒤 추후에 겪은 트러블에서 이를 활용해 해결했다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b01f0589-779b-42d7-9dcf-3b699794eb71/image.png" alt=""></p>
<p>겪었던 문제는 결제 완료 페이지에서 사용자에게 알럿을 띄울 때 알럿은 비동기적으로 실행되어 확인 버튼이 눌러지길 대기 중이었지만 알럿창 뒤로 뷰가 변경되는 동작이 실행되었다.
즉 알럿의 확인 버튼을 누르지 않더라도 뒤에 있던 rootViewController로 이동하는 코드가 실행되어버렸다.
이는 알럿에 관한 코드가 기본적으로 비동기적인 처리가 되기 때문이었다.</p>
<p>문제를 해결하기 위해선 알럿창의 확인이 실행되기 전까지 뒤쪽 코드의 지연이 필요했다.
기존엔 일반적인 알럿 present코드였으나 async와 await 그리고 witCheckedContinuation을 활용하여 해결하였다.</p>
<p>withCheckedContinuatoin은 또 무엇인가 하면..</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/000a80a3-56e1-4c79-b8dc-dbaba221f7e5/image.png" alt=""></p>
<p>Continuation은 opaque representation of program state라고 한다.</p>
<p>Opaque는 강의 시간 때 Boxed Protocol과 함께 배우면서 본 단어인데 대략적으로 불투명한, 보이지 않는 정도의 뜻이다.
스위프트 문서에서는 내부 구현 세부사항을 감춘다는 의미로 사용되는 듯.
외부에서 봤을 때 불투명해서 내부가 안 보인다 뭐 그런 의미..?</p>
<p>그리고 representation of program state를 이어서 번역하면 불투명하게 프로그램 상태를 표현한다는 말이 된다.</p>
<p>여러 해석을 살펴보았을 때 프로그램이 중단된 시점의 프로그램 상태를 캡쳐하고 표현하지만 내부 상태를 외부에서 알 수는 없다는 말인 거 같다.(캡슐화 한다는 말같음)</p>
<p>다시 공식문서의 개요를 이어서 전달하자면 다음과 같은 코드로 continuation을 생성할 수 있다고 한다.</p>
<ul>
<li>withUnsafeContinuation(function:_:)</li>
<li>withUnsafeThrowingContinuation(function:_:)</li>
</ul>
<p>그리고 비동기 작업을 다시 시작할 때는 </p>
<ul>
<li>resume(returning:)</li>
<li>resume(throwing:)</li>
<li>resume(with:)</li>
<li>resumt()</li>
</ul>
<p>등을 호출하면 된다.</p>
<p>반드시 주의할 점은 프로그램의 실행 경로에서 반드시 한 번만 resume메서드를 호출해야 한다는 것이다.</p>
<p>Continuation이 여러번 resume 메서드를 호출하는 것은 정의되지 않은 동작(Undefined Behavior)을 초래한다고 한다.
그렇다고 resume을 한 번도 호출하지 않는다면 continuation은 무한 대기 상태에 머물고 이와 관련된 resource에 leak이 발생할 것이다.</p>
<p>이와 관련하여 앞서 나온 Continuation에서 차이가 발생하는데 
CheckedContinuation은 누락되거나 여러번 호출 된 resume을 런타임에 검사한다.</p>
<p>그러나 UnsafeContinuation은 런타임에서 검사를 수행하지 않는다.
대신 낮은 오버헤드로 작업을 이벤트루프, 델리게이트, 콜백 또는 다른 비동기 예약 매커니즘과 연결하기 위한 매커니즘에 활용된다.
안전하지 않은만큼 테스트가 중요해질 것이다.</p>
<p>그럼 당연히 나는 withCheckedContinuation 메서드를 사용해야겠지.</p>
<p>withCheckedContinuation은 비동기 코드를 정의하면서 직접 제어할 수 있는 Continuation객체를 클로저를 통해 제공해준다.</p>
<p>알럿처리를 위해 사용한 이유는 Continuation이 주로 콜백 기반의 API를 async/await으로 변환할 때 유용하기 때문이다.
또한 콜백을 사용하여 코드의 흐름이 분리되는 것보다 훨씬 가독성이 좋기 때문에 적극적으로 async/await을 활용하려 했다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/4b866447-ce4a-4dee-bfab-1002da025ecc/image.png" alt=""></p>
<p>근데 진짜 갈수록 쉽지 않다.
쓸 때야 시간이 없으니 사용방법만 살펴보고 적용하는데
이후 다시 공부하면서 살펴볼 때면 
끝없이 나오는 새로운 키워드들이 자꾸 밤을 새게 만든다...
지금도 새벽 4시인데,,</p>
<p>일단 내가 처음보고 이해안되는 부분만 간략히 정리하겠다.
(물론 간략히 정리한다고 이해도 간단히 되진 않는다.)</p>
<h4 id="isolated-any-actor--isolation">isolated (any Actor)? = #isolation</h4>
<p>먼저 isolated라는 키워드는 특정 Actor의 격리상태를 나타낸다.
여기서 격리 상태란 앞서 말한 Actor의 특징인 단일 실행 컨텍스트에서만 접근 가능하다는 점을 보장해준다는 의미이다.</p>
<p>아니 앞에서 애초에 actor는 한 번에 한 스레드에서만 접근가능하게 설계되었다고 했는데
굳이굳이 다시 저렇게 보증해주는 이유가 뭘까?</p>
<p>그건 매개변수로 전달 된 Actor의 격리를 명확히 나타내기 위함과
이미 안전한 격리 컨텍스트에서 실행되고 있음을 보장함으로써 추가적인 await 키워드 없이 사용할 수 있게 해주기 위함이다.</p>
<p>그렇다면 이미 격리된 상태에서 actor가 실행된다는 것은 뭘까?
격리된 actor는 actor 내부의 상태에 접근하거나 수정하려면 반드시 해당 actor의 실행 컨텍스트 내에서 이루어져야 한다는 뜻이다.
즉 다른 컨텍스트(스레드나 actor 등)에서는 격리된 actore의 상태에 직접적으로 접근할 수 없다.</p>
<p>코드로 살펴보는 게 제일 와닿으니 코드를 보자</p>
<pre><code class="language-swift">actor Foo {
    var value: Int = 0

    func increment() {
        value += 1
    }
}

func bar(foo: Foo) {
    foo.increment // 불가능. awiat 필수
}

func baz(foo: isolated Foo) {
    foo.increment // 격리된 actor임이 보장되어 가능함
    // await 없이 동기적으로 접근
}

//그러나 외부에서 호출 시에는 반드시 await 필요함</code></pre>
<p>어느정도 이해가 됐다면 이제 #isolation은 뭔지 살펴보자.
이후의 #function과 함께 컴파일 타임 메타데이터를 의미하는 키워드로
컴파일러가 실행 시점의 특정 정보를 자동으로 제공해준다.</p>
<p>#이 붙으면 컴파일 타임 키워드라는 것으로 #isolation, #function외에도 #file, #line, #column 등이 있다.</p>
<p>#isolation은 Swift Concurrency Model에서 현재 실행 중인 Actor의 격리 상태를 캡처하는 키워드이다.
즉 실행 중인 actor의 격리 컨텍스트를 자동으로 가져와준다는 것.</p>
<h4 id="string--function">String = #function</h4>
<p>#function은 디버깅용 컴파일타임 메타 데이터 키워드로 함수 이름을 문자열로 반환해준다.(그래서 타입이 String)
함수가 호출될 때 해당 함수 이름을 자동으로 문자열로 제공해줘서 디버깅이나 로깅 때 사용된다고 한다.</p>
<p>솔직히 앞의 #isolated는 잘 쓸지 모르겠지만 #function은 종종 쓸 거 같다.
매개변수가 아니더라도 다음처럼 쓸 수 있다.</p>
<pre><code class="language-swift">func printFunctionName() {
    print(&quot;name: \(#function)&quot;)
}

printFunctionName() // name: printFunctionName</code></pre>
<h4 id="sending-t">sending T</h4>
<p>sending이란 키워드는 Concurrency Model에서 타입 T가 안전하게 호출자에게 전달(sending)됨을 명시해준다.</p>
<p>다시말해 동시성 컨텍스트(Task, Actor 등) 간에 데이터가 안전하게 전달됨을 말한다.
컴파일러가 타입의 동시성 안정성 확인하고 필요시 복사하는데
만약 값이 전달될 대 값타입(복사 가능)이거나 동시성 안전한 참조타입인지 검증하고 안전성이 보장되지 않을 때 컴파일 에러를 발생시킨다.</p>
<p>이번에도 예시코드로 살펴보자</p>
<pre><code class="language-swift">actor Foo {
    func getString() async -&gt; sending String {
        return &quot;String&quot; // 값 타입으로 동시성 안전함.
    }

class Bar {
    var value: Int = 0
}

actor Baz {
    func getValue() async -&gt; sending Bar {// error 발생
        reuturn Bar()// 일반 참조타입은 안정성 보장이 안 됨
    }</code></pre>
<p>만일 참조타입이 안전성을 보장받고 싶다면 Sendable 프로토콜을 준수하면 된다.
이외에는 모든 상태가 불변(Immutability)이거나 명시적으로 동기화 처리를 해주어도 된다. 
참고로 @Sendable로 클로저도 동시성 안전성 보장 가능</p>
<p>이제 다시 본래의 알럿 처리로 돌아가자..
withCheckedContinuation을 쓸 때 기본값은 전부 지정되어 있으므로 바로 클로저를 열어 사용했다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/524cd106-433b-443d-876a-929373e1befe/image.png" alt=""></p>
<p>continuation은 앞서 얘기한 resume을 통해 작업을 완료하고 값이나 에러를 반환하고 중단된 작업을 재개하여준다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/9ca16829-3a75-4575-b0d9-c21da62786c6/image.png" alt="">
알럿 액션에 conitnuation.resume()을 넣어
중단된 작업을 확인버튼을 눌렀을 때 다시 재개할 수 있게 하였다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/bc236bc5-790d-42bf-86aa-1598c3d4aafc/image.png" alt="">
이후 알럿에 대한 처리는 맨 처음에 본 코드처럼 간결하고 흐름을 잃지 않으면서도 알럿 처리가 된 후에 이후 동작이 실행될 것을 확인할 수 있다.</p>
<h3 id="generic">Generic</h3>
<p>이번 fetchData는 처음엔 nowPlaying이나 upcoming 등 엔드포인트만 다를 뿐 동일한 반환 형식을 가진 데이터를 받아왔다.
그러나 이후 조금 더 상세한 정보가 필요하게 되었고 기존의 MovieData 뿐만 아니라 DetailData, VideoData 등도 가져와야 하는 상황이 생겼다.</p>
<p>처음엔 ViedoData의 경우 URL을 반환받는 것이 목적이어서 거의 동일한 코드에 URL을 만드는 코드, 영상 URL을 만들어 반환하는 코드를 가진 fetchViedo()가 생겼다.</p>
<p>그러나 곧바로 중복되는 코드가 너무 많고 URL로 변환하여 반환하는 것만 뺀다면 기존의 fetchMovie()를 재활용할 수 있음을 깨달았다.</p>
<p>그래서 반환 타입은 제너릭으로 어떤 타입이든 Decodable만 준수하면 되도록 하였고 ViedoData를 가지고 URL을 만들어낼 수 있는 코드는 ViedoData 구조체 내부로 분리했다.</p>
<p>Generic 덕분에 중복되는 코드 없이 이후에 필요해진 DetailData 등도 간단하게 fetchData()를 사용할 수 있게 되었다.</p>
<h3 id="urlcomponents-urlqueryitem">URLComponents, URLQueryItem</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/2442ed5a-5dae-47e2-bd5b-cf2112043867/image.png" alt=""></p>
<p>fetchData()를 여러 URL에 맞게 사용하기 위해 URL의 구성 요소 중 엔드포인트와 파라미터를 다양하게 받고 조합해서 URL을 만들어낼 수 있어야 했다.
단순히 String으로 이어붙일 수도 있지만 이번엔 buildURL이라는 헬퍼메서드를 만들어 활용했다.(URLParameters는 typealias)</p>
<p>URLComponents는 초기화가 실패할 경우를 대비하여 옵셔널로 선언한다.
URL을 구성하는 스킴, 호스트, 경로, 쿼리 등을 관리하며 URL을 반환받을 수 있다.</p>
<p>URLQueryItem은 파라미터를 추가하기 위한 것들로 키와 밸류를 넣어주어 파라미터를 만들어준다.</p>
<p>필요한 데이터를 주는 엔드포인트와 파라미터를 전달받아
URLComponents에 넣어주는 직관적인 방식으로 URL을 만들 수 있어 편리했다.</p>
<hr>
<p>여담이지만 
<a href="https://developer.apple.com/documentation/swift/updating_an_app_to_use_swift_concurrency">https://developer.apple.com/documentation/swift/updating_an_app_to_use_swift_concurrency</a>
위 링크를 보면 코드 리팩터링에 대한 가이드도 제공해주는 걸로 보아 애플에서도 async/awiat을 좀 더 선호하는 듯.</p>
<p>이 외에도 Swiftlint와 코어데이터의 Relationship, UserDefaults 등에 대해서도 정리하고 싶은데 시간이 따라줄지 모르겠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Core Data]]></title>
            <link>https://velog.io/@bom_daddy/Core-Data</link>
            <guid>https://velog.io/@bom_daddy/Core-Data</guid>
            <pubDate>Wed, 11 Dec 2024 18:33:06 GMT</pubDate>
            <description><![CDATA[<h2 id="coredata">CoreData</h2>
<p>코어 데이터는 모델 편집기를 통해 <code>데이터의 유형과 관계</code>를 정의하고 <code>해당 클래스의 정의</code>를 자동으로 생성할 수 있다.
또한 다음과 같은 기능을 수행해준다.</p>
<hr>
<h3 id="persistence">Persistence</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/e285f6fe-36cf-4c1e-8afa-a0c0d8d15e14/image.png" alt="">
코어 데이터는 객체를 저장소에 매핑하는 세부사항을 추상화하여 데이터 베이스를 직접 관리하지 않아도 쉽게 저장 가능하도록 만들어준다.</p>
<p>여기서 추상화해준 세부사항은 <code>데이터 저장소 타입 관리</code>, <code>객체 관계 매핑</code>, <code>데이터 일관성 및 무결성</code>, <code>Managed Object Context를 통한 변경사항 관리</code> 등이 있다.
개발자가 데이터를 생성하거나 수정할 경우 먼저 MOC에 저장된다. 이후 MOC에서 PSC(Persistent Store Coordinator)를 통하여 PS(Persistent Store)와 상호작용한다.
PS는 실제로 데이터를 저장하는 저장소로 SQLite가 가장 일반적이며 이는 주로 기기의 디렉토리(ex.Libarary/ApplicationSupport)에 저장된다.</p>
<hr>
<h3 id="undo-and-redo">Undo and Redo</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b778572e-92f0-426a-9bf5-281ef92b6fdf/image.png" alt="">
변경 사항을 추적하여 개별적으로 또는 그룹 단위로 또는 전부 다 되돌릴 수 있다.
이 덕분에 간단하게 실행 취소와 되돌리기 기능을 만들어낼 수 있다.</p>
<hr>
<h3 id="background-data-task">Background data task</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/2690a656-721a-4fa4-af68-9747e4058365/image.png" alt="">
JSON 객체를 파싱하는 것처럼 UI를 중단시킬 수 있는 작업을 백그라운드에서 처리할 수 있게 해준다. 또 결과를 캐싱하거나 저장하여 서버 왕복 요청을 줄일 수 있으며 이번에 사용하게 될 포켓몬 api의 사용 설명에도 불필요한 요청을 줄일 수 있게 캐싱을 하라고 적혀있었다.</p>
<hr>
<h3 id="view-synchronization">View synchronization</h3>
<p>테이블 뷰 및 컬렉션 뷰 등에 데이터 소스를 제공하여 데이터와 뷰의 동기화 상태를 유지하는데 도움을 줄 수 있다.</p>
<hr>
<h2 id="setting-up-core-data">Setting up Core Data</h2>
<p>코어 데이터는 프로젝트 생성 시 저장소 관련 옵션에서 선택하여 관련 파일과 코드를 생성한 채로 시작할 수 있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/be5f0b7e-6646-43d0-b37a-addd9d75a724/image.png" alt="">
만약 코어데이터가 없는 기존 파일에 추가하고 싶다면 새 파일 만들기에서 DataModel 템플릿의 파일을 하나 생성하고 이후 작업을 처리하면 된다.</p>
<hr>
<h2 id="core-data-stack">Core data Stack</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/ff2812cc-5872-43b8-a338-bd5c9970a31f/image.png" alt="">
<code>Core Data Stack</code>이란 앱의 모델 계층을 공동으로 지원하는 클래스로 다음과 같은 요소들로 이루어져있다.</p>
<h3 id="nsmanagedobjectmodel">NSManagedObjectModel</h3>
<p>NSManagedObjectModel은 애플리케이션의 데이터 모델을 정의하는 객체로 엔티티, 속성, 관계 등의 정보를 포함한다. 이는 데이터 베이스의 <code>스키마</code>와 같은 역할을 한다는 뜻.
관리 객체 모델은 <code>.xcdatamodeld</code>파일에서 설정되며 런타임 시 로드되어 NSManagedObjectModel 객체로 변환된다.</p>
<h3 id="nsmanagedobjectcontext">NSManagedObjectContext</h3>
<p>NSManagedObjectContext는 CoreDataStack의 작업 공간으로, 변경 사항을 추적하고 필요할 경우 <code>save()</code>를 호출하여 영구 저장소에 저장한다. 
여러 컨텍스트를 동시에 처리할 수 있어 병렬 처리와 작업 분리 등에 유리하다고 한다.</p>
<h3 id="nspersistentstorecoordinator">NSPersistentStoreCoordinator</h3>
<p>NSPersistentStoreCoordinator는 데이터 저장소를 추가 및 관리하며 관리 객체 컨텍스트의 요청을 실제 저장소와 전달하고 상호작용한다. 이 덕에 SQLite 같은 저수준 데이터 베이스에 대한 직접적인 조작 없이도 데이터를 처리할 수 있다.
또한 SQLite와 inMemory를 동시에 사용하는 등 여러 저장소를 동시에 사용할 수도 있다.</p>
<h3 id="nspersistentcontainer">NSPersistentContainer</h3>
<p>NSPersistentContainer는 CoreDataStack의 설정을 단순화하고 관리하기 위한 고수준 API로 앞서 말한 요소를 모두 포함하고 초기화 할 수 있도록 돕는다.
CoreDataStack의 생성과 접근, 관리 객체 컨텍스트에 대한 접근, 비동기 초기화 등이 가능하다.<img src="https://velog.velcdn.com/images/bom_daddy/post/b4156b62-80e2-44a8-81a3-815b301f795d/image.png" alt=""></p>
<p>애플 공식 문서처럼 CoreDataStack을 직접 선언하는 경우엔 싱글톤 패턴으로 만드는 경우가 일반적이라고 한다.
일반적으론 프로젝트를 만들 때 CoreData를 선택하면 자동으로 생성해주는 NSPersistentContainer를 사용해도 되지만</p>
<ul>
<li>더 세부적인 제어가 필요한 경우(편의 메서드나 확장 기능을 추가할 때)</li>
<li>이전 버전 운영체제를 지원하기 위한 경우</li>
<li>커스터 마이징이 필요한 경우</li>
<li>SRP를 준수하고 코드 가독성을 높이고 싶은 경우</li>
</ul>
<p>위와 같은 경우엔 직접 정의하여 사용하는 것이 더 좋다.</p>
<hr>
<p>CoreData 스택을 설정할 때는 데이터 모델의 버전관리와 데이터 일관성 유지 등을 고려애햐 하고 CoreDataStack의 구성 요소들은 앱의 생명주기와 밀접하게 관련있기 때문에 앱의 시작과 종료 시점에서 적절한 초기화와 정리 작업이 필요하다.
그래야 데이터의 무결성을 유지하고 안정적인 동작을 보장할 수 있기 때문.</p>
<hr>
<h2 id="how-to-use">How to use</h2>
<p>CoreDataStack을 사용하기 위해 파일을 새로 만들고 싱글톤으로 만든 뒤 여러 편의 메서드를 정의해보겠다.
CoreDataStack에서 NSPersistentContainer를 따로 정의해줄 것이기 때문에 자동으로 생성되었던 NSPersistentContainer는 삭제해준다.</p>
<hr>
<h3 id="nspersistentcontainer-1">NSPersistentContainer</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/385ff684-937c-44b7-8e23-302dfe2b3397/image.png" alt="">
먼저 NSPersistentContainer에 .xcdatamodeld파일의 이름을 넣어 기반이 데이터 모델을 연결해준다.</p>
<p>NSPersistentContainer 객체를 만드는 과정에서 메서드 <code>loadPersistentStores</code>를 사용한다.
이 메서드는 영구저장소를 초기화 하고 데이터 모델과 실제 저장소 파일을 연결해준다.
또한 용량 부족이나 권한 문제 등으로 저장소 파일이 없거나 접근 불가할 때 에러를 반환해준다.
(간단히 영구저장소를 준비하는 과정으로 실제 배포 앱에선 에러가 발생한 경우 사용자에게 알리거나 복구를 시도하는 등 적절하게 에러처리를 해주어야 한다고 기본 코드에 적혀있다.)</p>
<p>추가로 이 과정에서 기본 <code>viewContext</code>가 생성된다.</p>
<hr>
<h3 id="viewcontext">viewContext</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/4055053b-dc59-4afa-8650-0009d73423eb/image.png" alt="">
다음으론 context를 설정해준다. 앞서 나왔던 MOC로 PersistentContainer가 가진 viewContext를 할당해줄 건데 viewContext는 다음과 같은 특징이 있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/f893802f-1929-4ae0-a84f-3cdab4f4231e/image.png" alt=""></p>
<p>앞서 말했듯 persistent contianer를 초기화 하는 과정에서 자동으로 생성된다.
그리고 NSPersistentStroeCoordinator와 연결되어있으며 앱의 메인 큐와 연관되어 있다. 즉 메인 스레드에서 처리된다는 의미이고 UI와 관련된 작업에 적합하다는 뜻이 된다.
또한 PSC와 연결되어 있기 때문에 이를 통한 데이터 작업이 가능하다.</p>
<hr>
<h3 id="contextsave">context.save()</h3>
<p>이제 context가 가진 <code>save()</code>와 다른 속성들을 이용해 변경 사항을 저장하는 메서드를 만들어야 한다.
<code>save()</code>의 선언부를 들어가보면 다음과 같이 throws가 적혀있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/b0c14892-b101-4ab6-9892-ffbb5be92764/image.png" alt="">
이는 데이터 저장 작업이 여러 외부 요인에 의존하기 때문에 예상치 못하게 실패할 가능성이 많기 때문이다.
실패 요인은 필수 속성 값이 nil이거나 영구 저장소에 접근할 수 없거나 동일한 데이터를 동시에 변경하려고 시도했거나 데이터 모델이 저장소의 스키마와 동일하지 않는 등의 이유가 있다.
다양한 요인을 알고 있어야 각 상황에 맞는 적절한 에러처리가 가능하기 때문에 실패 요인을 숙지하는 것도 앱의 완성도를 높이는데 중요한 부분 중 하나인 거 같다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/4dd54667-3eaf-454b-9048-075fec3dc3ba/image.png" alt="">
지금은 동작을 구현해보는 게 우선이라 프린트문만 적어서 처리해보았다.</p>
<hr>
<h3 id="add">add</h3>
<p>이제 context를 통해 데이터 작업을 할 수 있게 되었으니 기본적인 데이터 조작에 관한 메서드를 만들어서 사용하면 된다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/2baeff81-2cee-4d01-a14f-8ea059d04954/image.png" alt=""></p>
<p>이번에 본 강의에서는 위와 같은 형식으로 Entity와 context를 바탕으로 새로운 객체를 생성하고 key를 통해 해당 값을 지정해주는 방식으로 배웠다.
그러나 String을 하드코딩으로 넣어주는 방식을 개선하기 위한 방법을 알아보다 다음과 같은 선언을 보게 되었다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/3ed55b46-2402-4dc9-9f36-4523cb748f1e/image.png" alt=""></p>
<p>이는 NSManagedObject의 서브클래스를 사용하는 방식으로 .xcdatamodeld파일에서 자동으로 만들어주는 파일을 보면 다음과 같이 NSManagedObject를 상속받고 있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/c3f68c3b-3ac7-4d2d-ab23-9db27aa9e18d/image.png" alt=""></p>
<p>이렇게 생성한 객체의 속성은 @NSManaged를 통해 제공되기에 문자열 하드코딩을 피할 수 있게 된다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/16219192-984f-4f96-b7bc-b3d81ad6f61a/image.png" alt="">
오류 발생 시점이 런타임에서 컴파일타임으로 변경되었고 유지보수가 쉬우며 간결해진 코드로 가독성 또한 증가한다.</p>
<p>또한 초기화 시 context: context로 NSManagedObjectContext를 주입해주기 때문에 추가설정 없이 해당 컨텍스트를 바로 사용할 수 있다.</p>
<p>다만 완벽히 대체되는 것은 아니다. 
엔티티 이름이 런타임에서 동적으로 변경되어야 하거나 
하위 호환성을 위해서 또는 직관적으로 객체 생성의 동작 원리가 어떻게 되는지 보다 쉽게 이해하기 위해서 등의 이유로 기존 방식을 차용해야 할 수 있다.
그러나 대체적으로는 좀 더 현대적인 스위프트 스타일인 서브클래스 활용으로 코딩한다고 한다.</p>
<hr>
<h3 id="fetch">fetch</h3>
<p>데이터를 가져오는 메서드도 작성해보겠다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/6ef8b4d8-7b86-4ff3-847a-b9b0a27fa66d/image.png" alt="">
강의에서도 컨텍스트에 존재하는 fetch()에다 자동 생성 된 fetchRequest()를 활용했다.
자동 생성 된 부분을 살펴보면 다음과 같이 정의되어 있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/2aca2ee8-81ca-41fe-a4fe-fc28a66d3104/image.png" alt="">
데이터와 연결 되어야 하는 엔티티 네임을 넣어주어 생성한 NSFetchRequest&lt;ContactData&gt;를 반환해준다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/be0258a9-d682-4a1c-a959-96fc5d0ad7ac/image.png" alt="">
반환되는 타입에 대한 퀵 헬프를 보면 NSPersistentStoreRequest를 상속하고 ResultType는 NSFetchRequestResult를 준수해야 한다고 되어있다.
NSPersistentStoreRequest를 준수하였기에 영구저장소와 통신할 수 있다.</p>
<p>추가적으로 준수해야 하는 NSFetchRequestResult는 NSManagedObject의 정의를 찾아갔을 때 준수하고 있다는 것을 확인할 수 없었다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/51b8e596-f6e4-4985-bec7-5683dc045488/image.png" alt="">
대신 이 메서드와 NSFetchRequest의 제네릭 타입 조건을 이용해 NSManagedObject가 NSFetchRequestResult를 준수하고 있다는 걸 간접적으로 알 수 있다.</p>
<p>처음엔 NSManagedObject에 정의 된 fetchRequest()만 보고 NSManagedObject와 fetchRequest()의 반환 타입이 NSFetchRequestResult를 준수해야 하는 것이 무슨 관련이 있다는 건지 이해를 못했으나 가장 위에서 fetchRequest가 구현된 부분을 보고 왜 간접적으로 준수하고 있다는 게 증명되는지 알 수 있었다.
만약 NSManagedObject가 NSFetchRequestResult를 준수하고 있지 않았다면 return NSFetchRequest&lt;ContactData&gt;(entityName: &quot;ContactData&quot;)에서 컴파일 에러가 발생했을 것이다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/6843bad2-27a4-4632-b5c6-163c62b32d41/image.png" alt="">
<img src="https://velog.velcdn.com/images/bom_daddy/post/c127c68a-0968-482b-926c-5ce09370d6c0/image.png" alt=""></p>
<p>이제 fetchRequest를 활용하여 context가 가진 fetch()를 사용한다.
fetch는 context와 연결 된 PSC와 연관 된 영구 저장소에서 객체를 가져오며 context에 등록한다.
따로 조건이 없다면 지정된 엔티티의 모든 인스턴스를 반환해준다.</p>
<p>추가로 주의할 점이 몇 가지 있다.</p>
<ul>
<li>각 객체의 메모리 상태(in memory state)를 평가하는데 때문에 변경이 저장되지 않은 객체도 조건에 따라 포함되거나 포함되지 않을 수 있다.</li>
<li>컨텍스트에서 삭제 된 객체는 저장소에 삭제 여부가 반영되지 않았더라도 검색 결과에서 제외된다.</li>
<li>메모리에 이미 로드 된 객체나 변경 중인 객체에 대해선 새로운 인스턴스를 생성하지 않는다. 이미 fetch를 실행하고 수정한 후 다시 fetch를 수정하더라도 새로운 객체가 아닌 메모리에 올라간 객체를 반환받는다는 말.</li>
</ul>
<hr>
<h3 id="update">update</h3>
<p>다음은 기존 값을 새로운 값으로 수정하는 메서드를 작성해볼 간것이다.
그 전에 새로운 데이터를 저장할 때나 업데이트 할 때 만약 수정해야 할 속성이 많아진다면 그 많은 속성을 전부 매개변수로 받는 게 맞는가 하는 의문이 들었다.
클린 코드에서는 매개변수를 늘리지 않는 것이 좋다고 적혀있기도 했고 실제로 내가 사용하며 느끼기에도 3~4개가 되면 많다고 생각됐다.
처음엔 바로 ContactData 객체를 만들어 전달하면 안 되나 생각했지만 ContactData는 NSManagedObject를 준수하고 있어서 컨텍스트에 대한 의존성이 존재하고 잘못된 컨텍스트로 연결하면 오류가 발생할 수도 있으며 유효성 검증 로직도 따로 작성해주어야 한다고 한다.
대안으로 딕셔너리 전달, 요청에 대한 속성을 캡슐화 한 구조체, 빌더 패턴 등이 존재했다.</p>
<p>구조체를 만드는 건 ContactData를 바로 생성하는 대신 필요한 속성만을 가진 구조체를 새로 정의하여 사용하는 방식이다.
모든 속성을 캡슐화하는 과정이 조금 더 시간이 소요된다.</p>
<p>빌더 패턴은 객체 생성과 수정을 처리하는 클래스를 새로 정의하는 것으로 좀 더 규모가 큰 프로젝트에서 자주 사용된다고 한다.</p>
<p>내 프로젝트는 너무 작고 작지만 그래도 새로운 패턴을 겪어보는 것이 좋으니 빌더 패턴에 대해서 중점적으로 알아보고 사용했다.</p>
<h4 id="builder">builder</h4>
<p>빌더 클래스를 코어데이터와 연계하여 사용하기 위해 빌더의 이니셜라이저를 통해서 컨텍스트를 주입 받아 컨텍스트와의 의존성을 낮추고 경우에 따라 새로운 NSManagedObject 서브 클래스 객체를 만들거나 기존 객체전달 받고 내용을 업데이트하여 다시 반환할 것이다.</p>
<p>빌더는 객체의 속성을 정의하는 메서드를 점표기법을 이용한 체이닝 방식으로 사용할 수 있게 제공하며 최종적으로 객체를 반환하는 build() 메서드를 포함한다.</p>
<p>이를 코드로 작성하면 다음과 같다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/7cb9925f-a4c0-4789-b9fc-0d9a3e7a0a74/image.png" alt=""></p>
<p>속성을 바꾸는 메서드가 다시 자기 자신을 반환하여 체이닝 방식으로 속성을 간결하게 넣어줄 수 있다.
각 단계가 명확히 분리 되어 가독성이 좋고 속성을 선택적으로 설정할 수 있기 때문에 유연하다.
또한 하나의 인터페이스로 추가와 수정이 가능해진다.</p>
<p>지금처럼 작은 프로젝트에서는 과한 설계일 수 있으나 더욱 생성과 설정이 복잡해지거나 캡슐화를 반드시 해야 할 때, 재사용성과 유지보수성이 중요해지는 중대형 프로젝트 등에서는 보다 효과적인 패턴이라고 한다.</p>
<p>아 그리고 코어데이터와 연계하는 경우엔 반드시 build()를 사용하지 않아도 된다.
만약 코어 데이터와 독립된 객체가 필요하거나 생성한 객체를 반환할 필요가 있는 경우엔 필요하지만 컨텍스트가 정해진 객체를 빌더 내부에서 바로 생성하고 수정한 뒤 저장까지 이어지는 사용에서는 필요하지 않다.</p>
<p>아무튼 빌더는 위와 같은 구조로 만들어나가면 되는데 주의할 곳은 클라이언트 코드이다.
빌더를 이용해 앞서 작성했던 add 메서드를 수정하면 다음과 같아진다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/73c48e26-ad53-463b-911f-7f0403598326/image.png" alt=""></p>
<p>여기서만 봤을 땐 속성을 어떻게 정의해야 할지 감이 안 온다.
클로저를 사용하는 코드는 아직도 직관적으로 머리에 들어오지 않는다.ㅠ</p>
<p>코드를 살펴보면 매개변수로 빌더를 매개변수로 받는 클로저를 받는다.</p>
<p>내부에선 NSManagedObjectContext에 앞서 설정한 NSPersistentContainer의 viewContext를 넣어주어 확실히 동일한 컨텍스트에 접근되도록 한다.
이후 매개변수로 전달받은 클로저에 빌더를 넣고 실행하는데 
호출하는 곳에서 클로저를 통해 속성을 지정해주면 해당 메서드들이 실행된 뒤 다시 함수로 돌아와서 saveContext()가 실행된다.</p>
<p>이제 해당 메서드를 다음과 같이 사용하면 된다.</p>
<pre><code class="language-swift">CoreDataStack.shared.addContactByBuilder { builder in
    builder.setName(to: &quot;Bom&quot;)
           .setPhoneNumber(to: &quot;111-1234&quot;)
}</code></pre>
<p>매개변수로 전달받은 클로저에 원하는 컨텍스트 또는 객체를 넣은 뒤 클로저를 실행하여 속성을 정의한다. 계속해서 빌더 자신이 반환되기에 체이닝으로 연결한다는 장점이 잘 나타나는 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/3100e14d-26c6-4773-8507-101b5e54167d/image.png" alt=""></p>
<p>빌더를 활용해 update 메서드도 간단히 구현했는데 살펴보면 add 메서드와 동일한 코드로 이루어져있다. 
즉 중복되는 코드이기 때문에 add와 create를 하나로 합칠 수 있으나 분리하는 것이 중복되는 것보다 좀 더 맞는 선택이라 생각하여 분리했다.</p>
<hr>
<h3 id="delete">delete</h3>
<p>마지막으로 저장되어 있는 데이터를 삭제하기 위한 메서드이다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/9509001b-3f64-4bbe-b292-2c8625b3c88b/image.png" alt="">
데이터를 전달받고 컨텍스트에서 해당하는 데이터를 삭제해주는 delete()를 사용하면 간단하게 처리된다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/02c8c7fb-da83-4b2f-af0c-3f82fd3831f9/image.png" alt="">
delete()는 위의 설명대로 컨텍스트에서 먼저 제거하고 save()를 호출하여 변경 사항이 영구 저장소에 커밋될 때 삭제가 완료된다.
혹시 relationship이 설정되어있다면 관련 객체를 삭제하는 cascade delete rule을 설정해주어 조정할 수 있다.</p>
<p>드디어 코어데이터를 이용한 CRUD가 끝이 났다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[URL Session ]]></title>
            <link>https://velog.io/@bom_daddy/URL-Session</link>
            <guid>https://velog.io/@bom_daddy/URL-Session</guid>
            <pubDate>Mon, 09 Dec 2024 02:42:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bom_daddy/post/a8bacae4-a523-4593-b02c-ad572302763d/image.png" alt="">
api에서 전달해주는 데이터가 위와 같을 때,
먼저 해당 api와 동일한 구조로 데이터 구조를 만들어야 한다.
그리고 해당 구조를 decode(type, from)의 type에 넣어주어야 에러 없이 api가 주는 데이터를 받아올 수 있다.</p>
<p>단, 모든 필드를 만들어야 하는 것은 아니고 만일 id만 필요하다면</p>
<pre><code class="language-swift">struct UserId: Codable{
    let id: Int
}</code></pre>
<p>위와 같은 구조체를 정의하여 id만 받아올 수 있다.</p>
<p>받아올 데이터에 맞는 구조체를 정의했다면 메서드를 작성하여 데이트럴 받아오면 된다.
아 혹시라도 줄지 안 줄지 모르겠다면 세부 타입을 옵셔널로 처리해줄 수 있다.</p>
<hr>
<h4 id="urlsession">URLSession</h4>
<pre><code class="language-swift">let urlSession = URLSession(configuration: .default)</code></pre>
<p>URLSession은 ios에서 HTTP 요청을 관리하고 데이터 송수신을 처리하는 객체.</p>
<p>매개변수 configuration의 주요 옵션 세 가지는 다음과 같다.</p>
<ul>
<li>.default: 기본 캐시와 쿠키 저장소를 사용하는 일반적인 세션.</li>
<li>.ephemeral: 캐시나 쿠키를 저장하지 않는 임시 세션.</li>
<li>.background: 앱이 백그라운드 상태에서도 작업을 계속 수행할 수 있는 세션.</li>
</ul>
<p>URLSession은 공유 세션이 존재하여 간단한 요청은 처리도 간단하게 할 수 있도록 도와준다.
그 외의 일반적인 사용은 configuraion을 .default로 설정하여 기본 세션을 사용한다.
이 외에 타임 아웃을 설정하거나 백그라운드 다운로드 등을 설정하기 위해서는 직접 커스텀해야 하는데 다음 코드처럼 사용할 수 있다.</p>
<pre><code class="language-swift">let configuration = URLSessionConfiguration.default//default 세션
configuration.timeoutIntervalForRequest = 30 //타임 아웃 설정
let urlSession = URLSession(configruation: configuration)
//타임 아웃을 설정한 configuration 설정</code></pre>
<p>URLSession은 비동기로 동작하기 때문에 요청이 완료된 후엔 클로저 콜백을 통해 작업을 처리해야 한다.</p>
<p>사용 흐름을 정리하자면 
URL을 생성하고 dataTask 등의 작업도 생성한다. 
.resume()을 호출하여 작업을 실행하고 클로저를 통해 데이터, 응답, 에러에 관한 처리를 수행한다.</p>
<p>dataTask를 통해 받는 데이터 중 error에 관한 부분은 주로 다음과 같이 접근하여 사용한다.</p>
<pre><code class="language-swift">error.localizedDescription</code></pre>
<hr>
<h4 id="urlsessiontask">URLSessionTask</h4>
<p>세션이 수행할 구체적인 네트워크 작업들로
<code>DataTask</code>, <code>DownloadTask</code>, <code>UploadTask</code>, <code>StreamTask</code> 등이 있다.
이번에 사용할 것은 <code>DataTask</code>로 데이터를 요청하고 응답 데이터를 메모리에 저장하며 주로 API호출에 사용된다.</p>
<p>주요 특징으로는
작업은 비동기로 백그라운드에서 수행되며 요청 완료 후에 Completion Handler라는 클로저를 통해 응답을 전달한다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/4c124b6b-d169-48af-8a6e-8865e7127f45/image.png" alt=""></p>
<p><code>with</code> 파라미터에는 간단한 GET요청을 위해 URL을 바로 사용할 수 있고
요청을 세부적으로 설정할 때는 URLRequest를 설정하여 전달해줄 수도 있다.
이번에 들은 강의에서는 다음과 같이 URLRequest를 설정했다.</p>
<pre><code class="language-swift">var request: URLRequest = URLRequest(url: url)
request.httpMethod = &quot;GET&quot;
request.addValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)</code></pre>
<hr>
<h4 id="urlrequest">URLRequest</h4>
<p>URLRequest로 잠시 빠져보겠다.
기본적으론 URL을 기반으로 생성한다.</p>
<pre><code class="language-swift">var request = URLRequest(url:)</code></pre>
<p>그리고 메서드나 헤더 등을 설정할 수 있다.
HTTP메서드는 기본값을 <code>GET</code>으로 가지고 있으며 CRUD에 맞는 <code>POST</code>, <code>GET</code>, <code>PUT</code>, <code>DELETE</code>등을 String으로 넣어준다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/3fac6de6-84fd-462a-9c29-f117d8bdd102/image.png" alt="">
addValue메서드를 통해 헤더도 설정해줄 수 있는데 
HTTP헤더란 서버와 클라이언트 간의 request와 response 메시지에 포함된 추가적인 메타데이터이다.
(addValue로 헤더 이름에 대한 값을 추가하는 대신 setValue를 통해 값을 대체해버릴 수도 있다.)
api와 통신하기 위한 헤더와 값은 서버가 요구사항에 따라 정의되고 정해진 규칙에 따라 요청해야 올바르게 처리될 수 있다.
해당 규칙은 url만으론 알 수 없고 API문서나 서버의 요구사항을 따로 확인해야 한다. </p>
<hr>
<h4 id="urlsessiontask-1">URLSessionTask</h4>
<p>다시 Task로 돌아와서 completionHandler를 살펴보자면
해당 클로저로 전달 되는 정보는 <code>data</code>, <code>response</code>, <code>error</code>가 있다.</p>
<p><code>data</code>는 서버로부터 반환된 데이터다.
요청 실패를 가정하여 옵셔널로 받고 주로 JSON이나 HTML 형식으로 돌아온다.
즉 다시 디코딩이 필요하다.</p>
<p><code>response</code>는 HTTP 응답 정보이며 주로 HTTPURLResponse로 캐스팅하여 상태코드와 헤더 정보를 확인한다.
여기서 상태코드란 요청의 결과를 나타내는 3자리의 숫자로 범위에 맞는 의미가 정해져있다.
(그 유명한 404 not found도 상태코드)</p>
<h5 id="status-code">Status code</h5>
<ul>
<li><strong>1xx</strong>: 정보 응답<ul>
<li>요청을 수신했으며 추가 작업 진행 중<ul>
<li>ex) 100 Continue: 클라이언트가 요청을 계속 진행해도 됨</li>
</ul>
</li>
</ul>
</li>
<li><strong>2xx</strong>: 성공<ul>
<li>요청이 성공적으로 처리됨<ul>
<li>ex) 200 OK: 요청이 성공적으로 처리 됨</li>
<li>ex) 201 Created: 리소스가 성공적으로 생성됨</li>
</ul>
</li>
</ul>
</li>
<li><strong>3xx</strong>: 리다이렉션<ul>
<li>요청한 리소스가 이동되었으며, 클라이언트는 다른 URL로 요청해야 함<ul>
<li>ex) 301 Moved Permanently: 리소스가 영구적으로 이동 ㅗ딤</li>
<li>ex) 302 Found: 리소스가 임시로 다른 URL에 있음</li>
</ul>
</li>
</ul>
</li>
<li><strong>4xx</strong>: 클라이언트 오류<ul>
<li>요청이 잘못되었거나, 클라이언트의 권한 부족<ul>
<li>ex) 400 Bad Request: 요청 구문이 잘못되거나 파라미터가 틀림</li>
<li>ex) 401 Unauthorized: 인증이 필요하거나 인증 실패</li>
<li>ex) 403 Forbidden: 요청이 허용되지 않음</li>
<li>ex) 404 Not Found: 요청한 리소스가 존재하지 않음</li>
</ul>
</li>
</ul>
</li>
<li><strong>5xx</strong>: 서버 오류<ul>
<li>서버에서 요청을 처리하지 못함.<ul>
<li>ex) 500 Internal Server Error: 서버에서 일반적인 오류 발생.</li>
<li>ex) 503 Service Unavailable: 서버가 현재 요청을 처리할 수 없음.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><code>error</code>는 요청 실패 시 에러 정보를 담고 성공 하면 nil이 담긴다.</p>
<p>이렇게 총 3개의 정보를 가지고 디코딩이나 에러를 처리하는 클로저를 작성한다. </p>
<pre><code class="language-swift">urlSession.dataTask(with: request) { data, response, error in
    let successRange: Range = (200..&lt;300) //status code가 요청 성공에 해당하는 범위
    //data가 존재하는지, error가 존재하는지 체크
    guard let data, error == nil else {
        print(&quot;error: \(error?.localizedDescription ?? &quot;Unknown error&quot;)&quot;)//error설명이 있다면 띄우고 없을 때의 예외처리까디
          return
    }
    if let response = response as? HTTPURLResponse {//타입 캐스팅
        //status code 출력
        print(&quot;statusCode: \(response.statusCode)&quot;)
        //status 코드가 범위 내에 들어가는지 체크
        if successRange.contains(response.statusCode) {
            do {//받아온 data를 원하는 타입으로 디코딩 시도
                //JSONDecoder()는 객체를 생성하여 날짜 처리나 키 변환 전략 등을 직접 조정하여 사용하기도 함
                let data = try JSONDecoder().decode(ResponseData.self, from: data)
                print(&quot;data: \(data)&quot;)
            } catch {
                print(&quot;error: \(error))
            }
        } else {
            print(&quot;failed with status code: \(response.statusCode)&quot;)
        }
    }
}</code></pre>
<h4 id="resume">.resume()</h4>
<p>이후 <code>resume()</code>을 호출하여 작업을 실행하게 되는데,
dataTask를 생성하는 즉시 실행되는 것이 아닌 이유는 작업의 명확한 제어와 유연성을 제공하기 위함이다.</p>
<p>이 메서드를 호출 하면 작업이 백그라운드 큐에 에약된다. URLSession은 GCD를 사용해 비동기 작업을 처리하고 완료된 결과는 클로저를 통해 전달받는다.</p>
<p>비동기로 처리하는 덕에 작업을 실행 중에도 UI가 반응성을 유지할 수 있게 해준다.
그 대신 네트워크 요청 완료 후 UI업데이트를 따로 메인스레드에서 수행하도록 보장해주어야 한다.
메인스레드에서 UI를 업데이트 하기 위해선 <code>DispatchQueue.main.async</code>를 사용한다.
해당 큐는 메인 스레드와 연결된 큐로 UI작업이 실행되어야 하는 큐다.
async를 통해 비동기적으로 작업을 예약하여 큐에 추가된 순서대로 작업을 실행할 수 있다.
(네트워크 외에도 무거운 작업 등에서 <code>DispatchQueue.main.async</code>와 <code>DispatchQueue.global(qos:)</code>를 적절히 활용하여 메인 스레드와 백그라운드 스레드를 구분해 작업을 처리)
(* sync는 작업이 완료될 때까지 해당 스레드를 차단하기 때문에 메인 스레드에서 사용할 경우 앱이 멈출 수 있다.)</p>
<p>다시 한 번 정리하자면 네트워크 처리는 백그라운드에서 처리되며 이를 통해 생긴 데이터를 UI업데이트에 반영하려면 반드시 메인 스레드에서 작업하도록 DispatchQueue에 async를 통한 비동기 작업을 예약한다.
다만 불필요하게 자주 호출하면 성능이 저하될 수 있다.</p>
<p>이렇게 네트워크 요청 - 비동기 처리 - 메인 스레드에서 UI업데이트까지의 흐름을 파악해보았다.</p>
<p>추가로</p>
<ul>
<li><p>응답 데이터는 파일로 저장하지 않고 메모리에서 직접 처리하는데 만약 다운로드가 필요하다면 DataTask가 아닌 DownloadTask를 사용해야 한다.</p>
</li>
<li><p>터미널에서 jq 플러그인을 설치하면</p>
<pre><code class="language-terminal">curl url |jq</code></pre>
<p>를 통해 가장 상단의 사진처럼 개행이 적절히 처리된 데이터를 볼 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[UIKit Code base]]></title>
            <link>https://velog.io/@bom_daddy/UIKit-Code-base</link>
            <guid>https://velog.io/@bom_daddy/UIKit-Code-base</guid>
            <pubDate>Fri, 06 Dec 2024 09:57:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bom_daddy/post/44601d46-30b0-44b0-a11b-a125e7d53a69/image.png" alt=""></p>
<h2 id="code-base">Code base</h2>
<h4 id="1-main-storyboard-삭제">1. Main storyboard 삭제</h4>
<h4 id="2-project---build-setting-에서-storyboard-file-base-name---main-삭제">2. project - build setting 에서 Storyboard File Base Name - main 삭제</h4>
<h4 id="3-infoplist-에서-storyboard-name---main-삭제">3. Info.plist 에서 Storyboard Name - Main 삭제</h4>
<h4 id="4-scene-delegate에서-진입-뷰-설정">4. Scene Delegate에서 진입 뷰 설정</h4>
<p>App Delegate 대신 가능하지만 권장x -&gt; multi-window 지원을 위해 도입된 Scene Delegate를 사용하는 것이 더 권장 됨</p>
<h5 id="why">why?</h5>
<ul>
<li>AppDelegate<ul>
<li>앱의 전체 생명 주기와 관련된 작업을 담당함(푸시 알림, 백그라운드 작업 등)</li>
</ul>
</li>
<li>SceneDelegate<ul>
<li>ios13 이후 지원하기 시작했으며 개별 scene의 생명주기 등을 담당함</li>
<li>scene과 밀접한 rootViewController나 UI상태 복원 등을 맡는 것이 자연스러움</li>
<li>multi scene이 개별적인 초기화를 갖기에도 sceneDelegate가 자연스러움</li>
</ul>
</li>
</ul>
<p>만약 appDelegate가 scene들의 초기화를 맡게 된다면 모든 scene들이 동일한 초기화를 갖게 됨
반면 sceneDelegate가 맡으면  각 scene마다 초기화를 갖고 더욱 조직화, 모듈화 된 코드가 되고 가독성이 높아짐</p>
<h5 id="scene-method-설정">scene method 설정</h5>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/a2e7e0e5-1f99-4bb1-be00-0d4746e09d22/image.png" alt="">
상기 했듯 ios13 이후 지원 시작하며 유연성을 위해 optional로 정의되어있지만 직접 초기화 하기 위해서는 필수적인 메서드.</p>
<p><code>scene: UIScene</code>: 현재 연결된 또는 생성된 scene
<code>session: UISceneSession</code>: 세션 정보, 설정, 상태 등(multi scene은 scene마다 고유한 session이 존재함)
<code>connectionOptions: UIScene.ConnectionOptions</code>: scene이 연결될 때의 추가 정보를 담음(푸시 알림이나 url 등으로 열렸다는 정보 등)</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/458f6c6c-8a60-4c12-8bb2-56ff2b8290a9/image.png" alt=""></p>
<pre><code class="language-swift">guard let windowScene = (scene as? UIWindowScene) else { return }</code></pre>
<p>scene이 UIWindowScene인지 체크 하여 다운캐스팅하게 됨.
&gt;&gt; ios에서 앱의 window를 관리하는 객체로 만들기 위함. 
    UIWindow는 UIWindowScene과 연결되어야 작동가능하기 때문</p>
<p>이후 window를 설정한 후 sceneDelegate의 window를 원하는 scene으로 설정하게 됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[UIViewController]]></title>
            <link>https://velog.io/@bom_daddy/UIViewController</link>
            <guid>https://velog.io/@bom_daddy/UIViewController</guid>
            <pubDate>Tue, 03 Dec 2024 17:54:53 GMT</pubDate>
            <description><![CDATA[<h2 id="uiviewcontroller">UIViewController</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/a9b70238-b8ec-4e12-8bd3-c47ca458ba65/image.png" alt=""></p>
<p>뷰 컨트롤러는 뷰의 계층도를 관리해주는 오브젝트로
다음과 같은 주로 다음과 같은 책임을 지는 객체입니다.</p>
<ul>
<li>사용자와 상호작용하여 반응함.</li>
<li>뷰의 콘텐츠를 업데이트하고 아래에 깔린 데이터를 업데이트함.</li>
<li>앱의 다른 오브젝트와 상호작용</li>
<li>뷰의 크기를 조정하거나 전체 인터페이스의 레이아웃을 조정함</li>
</ul>
<p>해당 객체를 바로 선언하여 사용하는 일은 드물고 주로 <code>UIViewController</code>를 상속하고 필요한 메서드를 재정의하거나 메서드와 프로퍼티 등을 추가하여 사용합니다.</p>
<p>또한 뷰 컨트롤러는 자신만의 뷰 세트를 갖게 됩니다.
여기서 중요한 점은 뷰 컨트롤러는 뷰와 서브뷰의 유일한 소유자여야 한다는 것입니다.
따라서 생성과 소멸주기도 해당 뷰 컨트롤러에게 관리할 책임이 있습니다.</p>
<p>스토리보드 등으로 뷰를 요청한다면 <code>UIKit</code>이 자동으로 해당 뷰 컨트롤러를 위한 뷰 객체를 생성하지만 코드로 직접 뷰를 생성하는 경우는 사용자가 주의를 기울여야 합니다.</p>
<p>만일 여러 뷰 컨트롤러가 동일한 뷰를 공유하게 된다면 뷰와 관련된 작업의 책임을 어디서 가져가야 할지 불분명해지고 상태 관리가 복잡해질 수 있습니다.
또 뷰 컨트롤러가 해제되었으나 공유된 뷰가 남아있으면 의도치 않은 동작이 발생하거나 메모리 누수가 발생할 확률도 높아지게 됩니다.</p>
<hr>
<h2 id="life-cycle">Life Cycle</h2>
<p>뷰 컨트롤러에는 앱과 같이 생명주기가 존재합니다.
그리고 각 생명주기에 맞게 호출되는 메서드들이 존재합니다. 
<img src="https://velog.velcdn.com/images/bom_daddy/post/d330b3aa-2aee-4920-a17a-2d19d2eab9f7/image.png" alt=""></p>
<p>순서를 다시 정리해보자면 다음과 같습니다.</p>
<ul>
<li><code>init</code></li>
<li><code>load view</code></li>
<li><code>view did load</code></li>
<li><code>view will appear</code></li>
<li><code>view is appearing</code></li>
<li><code>view did appear</code></li>
<li><code>view will disappear</code></li>
<li><code>view did disappear</code></li>
<li><code>deinit</code></li>
</ul>
<p>처음 초기화 후 메모리에 로드됩니다.
이후 사용자에게 나타나는 과정이 3단계로 나누어지고 다른 뷰 컨트롤러로 이동하는 등의 이유로 사용자가 볼 수 없게 될 때 사라지는 과정이 2단계로 이루어져있습니다.
이후 완전히 필요없어진다면 객체는 메모리에서 내려가게 됩니다.</p>
<p>다음 이미지는 사용자가 뷰컨트롤러를 볼 수 있는 경우와 상태 전환을 나타내고 있습니다.
 <img src="https://velog.velcdn.com/images/bom_daddy/post/356b0e5d-3894-4efb-9e5f-26a83e186ce7/image.png" alt=""></p>
<p>여기서 <code>will</code> 메서드는 주로 <code>did</code> 메서드와 쌍을 이루게 됩니다.
그렇다고 반드시 저 둘만이 짝을 이루는 것은 아니고 반대편의 <code>will</code> 메서드와도 쌍을 이루기도 합니다.</p>
<p><code>did</code>와 정확히 짝지어지지 않는 경우는 화면 전환 중 인터럽트가 발생하거나 생명주기 단계를 건너뛰는 경우 <code>did</code> 메서드가 호출되지 않을 수 있습니다.
또는 애니메이션의 경우 뷰가 완전히 나타나기 전까지 진행되다가 나타난 후 종료하는 것이 자연스러우나 네트워크 처리같은 경우엔 뷰 컨트롤러가 보여지는 동안 사용하다가 다른 화면으로 넘어가기 시작할 때 즉, 반대편 <code>will</code> 메서드가 실행될 때 종료해야 자연스럽습니다.</p>
<p>중요한 건 <code>will</code> 메서드에서 시작한 메서드는 반드시 쌍을 이루는 <code>did</code> 메서드에서든 반대편 <code>will</code> 메서드에서든 종료되어야 한다는 것입니다. 그리고 작업의 특성에 따라 적절한 종료 시점은 다른 생명 주기에 위치할 수 있다는 점입니다.</p>
<hr>
<h2 id="is-appearing">Is Appearing?</h2>
<p><code>Appear</code>의 단계는 <code>Disappear</code>와 달리 중간에 한 단계가 더 존재합니다. 
왜 그럴까요?? 왜 <code>Disappear</code>는 차별받은 걸까요?</p>
<p>먼저 <code>appear</code>과 관련하여 기존의 <code>will</code>과 <code>did</code>만으로 이루어진 상태에서는 정밀한 애니메이션이나 UI처리가 어려웠습니다.
<code>will</code>이 호출되는 시점에선 뷰의 위치, 크기, 트레이트 등이 지정되지 않은 경우가 존재하고,
<code>did</code>가 호출되는 시점은 이미 사용자에게 모든 화면이 표시되었으므로 UI 처리가 늦을 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/24353887-d4db-46df-89a9-2b4152adff51/image.png" alt=""></p>
<blockquote>
<p> In contrast to viewWillAppear(_:), the system calls this method after it adds the view controller’s view to the view hierarchy, and the superview lays out the view controller’s view. By the time the system calls this method, both the view controller and its view have received updated trait collections and the view has accurate geometry.</p>
</blockquote>
<p>이러한 문제를 해결하기 위한 <code>is appearing</code> 라이프 구간은 생각보다 최근인 <code>WWDC2023</code>에서 추가되었습니다.
공식문서에 따르면 <code>viewWillAppear</code>와 달리 <code>viewIsAppearing</code>은 뷰 컨트롤러의 뷰가 뷰 계층에 추가되고 상위뷰가 뷰 컨트롤러의 뷰를 모두 레이아웃 한 이후 호출됩니다.</p>
<p>즉 보다 적절한 타이밍에 UI 업데이트를 제공할 수 있도록 돕기 위해 추가된 것으로 뷰가 사라지는 과정에는 이러한 세부적인 조정이 필요 없기에 isDisappearing 메서드가 없는 듯 합니다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/97794d91-0eaa-4a70-929f-f669ae1ab8cd/image.png" width="50%" height="30%"></p>
<p>사진에서 보듯 <code>willAppear</code>와 <code>isAppearing</code>은 콜백되는 순서의 차이가 존재하나 모두 동일한 <code>CATransaction</code> 내에서 발생합니다.
따라서 사용자는 동시에 두 메서드의 처리 결과를 보게 됩니다.
대신 앞서 말했듯 <code>isAppearing</code>은 모든 UI의 트레이트나 지오메트리가 최신 상태로 업데이트 된 상태에서 호출되므로 애플은 뷰를 업데이트 할 때 <code>willAppear</code> 대신 <code>isAppearing</code>을 사용할 것을 권장하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/babaa680-e4fc-461a-89aa-39312e31c644/image.png" alt="">
그리고 다음과 같은 경우에만 <code>willAppear</code>를 사용하여 처리할 것을 권합니다.</p>
<ul>
<li>뷰 전환이 시작되기 전에 콜백이 필요한 경우<ul>
<li><code>transitionCoordinator</code>에 접근하여 함께 실행 될 <code>alongside</code> 애니메이션을 추가해야 하는 경우.
(여기서 <code>alongside animation</code> 이란 뷰 컨트롤러 전환 애니메이션과 동시에 프레임워크에서 실행하도록 지시하는 애니메이션을 의미)</li>
</ul>
</li>
<li>뷰 컨트롤러 또는 뷰의 트레이트, 계층 구조나 지오메트리에 의존하지 않는 작업을 수행해야 하는 경우 <ul>
<li><code>willAppear</code>에서 데이터베이스 알림을 등록하고, <code>didDisappear</code>에서 이를 등록 해제하는 경우와 같이 균형 잡힌 콜백이 필요한 작업.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/68ea172f-5dd4-404f-93c2-e084ccb1ab6c/image.png" alt="">
추가로 위 표를 참고하면 좀 더 메서드를 호출할 시점을 정하는 데 도움이 될 것입니다.</p>
<hr>
<h2 id="layout-method">Layout Method</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/79fac8ff-c9bb-41b2-a013-cdb92bc8c5df/image.png" alt="">
마지막으로 살펴볼 것은 앞서 봤던 <code>view</code>로 시작되는 메서드 목록 중 라이프 사이클에 포함되지 않는 메서드 2개입니다.</p>
<p>먼저 알아볼 것은 <code>layoutSubviews</code>로 뷰의 하위뷰(<code>subview</code>)를 재배치하거나 조정할 때 사용되는 메서드입니다.
이 메서드는 하위 뷰가 추가, 삭제 되거나 위치가 바뀌는 등의 상황에서 자동으로 호출됩니다.</p>
<p>애플은 서브뷰의 레이아웃과 관련하여 원하는 동작이 자동으로 지원되지 않을 때만 재정의할 것을 권장합니다.
추가로 이 메서드를 직접 호출하는 대신 다음 UI 업데이트 이전에  <code>setNeedsLayout</code>를 호출 할 것을 권장하고 있습니다.
만약 즉시 레이아웃을 업데이트 하려면  <code>layoutIfNeeded</code>를 호출하라고 합니다.</p>
<p>이렇게 보통은 자동으로 처리되는 <code>layoutSubviews</code> 전후로 호출되는 <code>viewWillLayoutSubviews</code>와 <code>viewDidLayoutSubviews</code>가 마지막 목차의 주인공입니다.</p>
<p>이 두 메서드는 <code>layoutSubviews</code>가 실행될 때마다.
즉 전환이 일어나는 상황이라면 뷰가 표시되는 상황동안 몇 번이든 호출될 수 있습니다.
이 부분이 단 한 번만 실행되는 <code>viewIsAppearing</code>과 가장 큰 차이점입니다.</p>
<p>다른 차이로는 <code>layoutSubviews</code>과 관련 메서드들은 레이아웃이 조정될 때 호출된다면 <code>viewIsAppearing</code>은 레이아웃이 조정되지 않을 때도 호출됩니다.</p>
<p>따라서 반복적인 호출이 필요한 UI업데이트 메서드는 <code>layoutSubviews</code> 관련 메서드에, 단 한 번만 필요한 초기화나 뷰 업데이트 등은 <code>viewIsAppearing</code>에 알맞게 넣어야 합니다.</p>
<p>호출 조건과 목적이 다르다는 점을 염두에 두고 상황에 잘 맞게 선택해야 합니다.</p>
<hr>
<h3 id="참고-문서">참고 문서</h3>
<p><a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/4195485-viewisappearing">Apple 공식 문서: viewIsAppearing</a><br><a href="https://developer.apple.com/documentation/uikit/view_controllers/displaying_and_managing_views_with_a_view_controller">Apple 공식 문서: Displaying and Managing Views</a><br><a href="https://developer.apple.com/documentation/uikit/uiviewcontroller?utm_source=chatgpt.com">Apple 공식 문서: UIViewController 전체</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kiosk_retrospective]]></title>
            <link>https://velog.io/@bom_daddy/Kioskretrospective</link>
            <guid>https://velog.io/@bom_daddy/Kioskretrospective</guid>
            <pubDate>Sun, 01 Dec 2024 16:04:00 GMT</pubDate>
            <description><![CDATA[<h2 id="introduction">Introduction</h2>
<h4 id="제목">제목</h4>
<p>윈디정 (윈터 디저트의 정석)
추운 바람 사이에 있는 따뜻한 정이 있는 음식을 주문 할 수 있다!</p>
<h4 id="목적">목적</h4>
<p>사라지는 겨울 간식을 독점하자. 내가 다 팔겟다. 틈새시장 공략.</p>
<h4 id="프로젝트-기간">프로젝트 기간</h4>
<p>2024.11.25 ~ 2024.11.29</p>
<h2 id="planning-and-goals">Planning and Goals</h2>
<h4 id="필수-구현-사항">필수 구현 사항</h4>
<ul>
<li><p>메인 페이지</p>
</li>
<li><p>상단 메뉴 카테고리 바</p>
</li>
<li><p>메뉴 화면</p>
</li>
<li><p>주문 내역 화면</p>
</li>
<li><p>취소하기 / 결제하기 버튼 화면</p>
<h4 id="도전-구현-사항">도전 구현 사항</h4>
</li>
<li><p>Localizable 을 사용한 한영 전환 기능</p>
</li>
<li><p>Color Assets 을 활용한 커스텀 컬러 설정, 다크 모드 대응</p>
</li>
<li><p>페이징 기능</p>
</li>
<li><p>결제하기 → 알럿창띄우기</p>
<h4 id="와이어-프레임">와이어 프레임</h4>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/c5adb717-e1d9-42d0-8b2f-4b31f7aa9cf8/image.png" alt=""></p>
<h2 id="assigned-features">Assigned Features</h2>
<h4 id="맡은-역할">맡은 역할</h4>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/061f8d69-79c0-49c7-a3b5-3c95d04384c4/image.png" alt=""></p>
</li>
<li><p>상품을 담은 장바구니</p>
</li>
<li><p>각 버튼의 액션과 하단 UI 업데이트 구현 및 연결</p>
<h2 id="tools-and-technologies-used">Tools and Technologies Used</h2>
<h4 id="도구-및-기술">도구 및 기술</h4>
</li>
<li><p>Xcode</p>
</li>
<li><p>Git, GitHub</p>
</li>
<li><p>Figma</p>
</li>
<li><p>Swift UIKit</p>
</li>
<li><p>SnapKit</p>
</li>
<li><p>Closure callback</p>
</li>
</ul>
<h4 id="파일-분리">파일 분리</h4>
<pre><code>BottomView
├── Button
│   ├── ButtonStack
│   ├── EmptyCartButton
│   └── PurchaseButton
├── Cart
│   ├── CartRowStackView
│   ├── CartTableView
│   └── CartTableViewCell
├── Label
│   ├── TotalPriceLabel
│   ├── TotalQuantityLabel
└── BottomView</code></pre><p>BottomView라는 큰 틀 안에서 다시 하단 버튼, 장바구니, 레이블로 나누었다.
버튼은 추후 직원 호출과 같은 추가적인 버튼이 배치될 수 있어 미리 스택뷰에 넣었다.</p>
<p>최종적으로 뷰컨트롤러에서는 바텀뷰만 선언하여 사용하면 되었고,
MVC 패턴을 염두에 두었기에 사용자 인터랙션에 반응하여 데이터를 변경하는 로직은 컨트롤러에서, UI업데이트를 위한 로직은 뷰 내부에서 구현하기로 했다.</p>
<p>이를 위해 클로저 콜백을 활용하여 하단 버튼, 테이블뷰 셀 내부의 버튼 등의 액션을 컨트롤러에서 담당할 수 있게 하였고 UI업데이트 관련 사항은 점표기법으로 접근하여 사용하였다.</p>
<h2 id="challenges-and-solutions">Challenges and Solutions</h2>
<h4 id="1-팀원과의-소통">1. 팀원과의 소통</h4>
<p>이번 프로젝트의 조건이 여럿 있었지만 가장 큰 문제는 하나의 뷰컨트롤러만을 사용하여야 한다는 것이었다.
때문에 역할을 나누기도 애매하고 서로의 작업을 합칠 때 발생할 컨플릭트도 문제였다.</p>
<p>나는 이전에 해왔던 것처럼 각자의 뷰를 만들고 추후 컨트롤러에서 합치자는 방식을 제안하고 그에 맞춰서 역할을 분리하자고 했었다.
MVC에 맞추어 상단뷰, 하단뷰, 데이터, 버튼 액션 등으로 나누고 뷰컨트롤러에서 합치는 작업만 따로 하면 될 거 같았다.</p>
<p>그러나 실제 역할 분배 회의에서 내가 참석을 못하면서 데이터와 버튼 액션에 대한 역할 분배가 되지 않았다.
또, 뷰를 만들고 하나로 합치자고 한 말은 각 뷰에 대해 서브클래스를 생성하자는 말이었으나 팀원들은 전부 뷰 컨트롤러에 각자의 뷰를 만들어왔다.</p>
<p>결과적으로 뷰 컨트롤러가 앱의 기능에 비해 엄청나게 비대해졌고 데이터와 액션은 마지막날 새벽이 되어서야 완성되었다.
비대한 컨트롤러 안에서 원하는 위치를 찾기 위해 탐색하는 시간이 길어졌고 이로 인한 시간 부족은 다시 또 안 좋은 코드를 만들어냈다. </p>
<p>이건 누구 하나의 잘못이라기보다 톱니바퀴가 맞물리듯 작은 불협화음이 하나씩 쌓여 스파게티를 만들어냈다. 사실상 제대로 된 해결은 이루어지지 않았고 프로젝트를 완성해냈다는 것에 의의가 있었다.</p>
<p>언젠가 리팩터링 해야지..하는 생각은 있지만 클린 코드에 나왔던 르블랑의 법칙처럼 아마 그 언젠가는 오지 않을 거란 생각도 든다.</p>
<p>다음부터는 좀 더 확실하게 의견을 교류하고 규칙을 맞춰야 한다는 생각만이라도 잘 가져가야지.</p>
<h4 id="2-테이블-뷰에-대한-이해-부족">2. 테이블 뷰에 대한 이해 부족</h4>
<p>UIKit의 UITableView를 Swift UI의 List처럼 쓸 수 있을 거란 큰 착각을 했었다...
선언형 프로그래밍 언어가 얼마나 사용하기 쉬웠는지 역체감하는 과제였다. 현대적이고 간결한 언어 최고..</p>
<p>테이블 뷰의 셀을 재사용한다는 부분이 제일 이해가 안 되었다. 재사용 식별자는 단 하나뿐인데 이걸 이용해서 셀 자체를 재사용한다는 것을 셀 하나하나 모두 구별하여 그것들을 재사용한다는 것과 혼동했고 이해하는데 시간이 걸렸다.</p>
<p>또 커스텀 셀 이니셜라이저를 사용할 때 정해진 파라미터만 호출하는 이니셜라이저만이 행 추가 시 호출되었는데(원래 이런 건지 내가 잘못 사용한 건지는 아직 파악하지 못했다.) 새로운 행 생성 시 파라미터로 바로 데이터를 넣어주려고 했는데 계속해서 에러가 발생했었다.
해결을 위해서 셀을 추가할 때 새로운 데이터를 바로 넣어주지 않고 셀 내부의 값을 업데이트해주는 메서드를 따로 행의 데이터를 구성하는 메서드 내부에 넣어줘서 해결했다.</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<h4 id="클로저-콜백의-활용">클로저 콜백의 활용</h4>
<p>기존의 야구 게임과 계산기에서는 클로저 콜백을 통해 많은 뷰 계층을 이동하지 않았다. 이번 바텀뷰를 만들면서는 뷰 컨트롤러에서 바텀뷰로, 바텀뷰에서 테이블뷰로, 테이블 뷰에서 셀에 있는 스택뷰의 버튼으로 내려갔다. 그 구조를 맞춰가는 과정에서 파리미터를 활용해 원하는 액션으로 분기하거나 버튼을 구분하는 법을 알게 되었다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/9d025ba7-1a7a-4e94-8a90-1beb078db809/image.png" alt=""></p>
<p>또 테이블 뷰의 셀이 가진 버튼을 어느 위치에서 상위로 연결시키는지도 알게 되었다.</p>
<h4 id="깃-포크를-이용한-보다-안전한-협업">깃 포크를 이용한 보다 안전한 협업</h4>
<p>이번 깃 전략은 각자의 레포지토리로 포크해간 뒤 커밋과 푸시를 하고 원본 레포지토리의 본인의 브랜치로 풀리퀘스트를 한 번 넣었다.
이때 서로의 코드를 리뷰한 뒤 다시 원본 레포지토리의 dev 브랜치로 풀리퀘스를 하고 마지막으로 main에 머지를 했다.
매번 내 레포지토리를 원본 레포지토리와 동기화 하는 작업이 추가되긴 했으나 많은 풀 리퀘스트는 그만큼 더 안정적이고 코드에 대한 리뷰를 받을 수 있는 기회를 늘려주는 만큼 좋은 방법이라 생각되었다.
대신 트레이드오프로 약간의 다른 부분도 모두 컨플릭트로 처리되어 하나의 파일을 수정하게 된다면 반드시 컨플릭트 해결 과정이 필요했다는 점이 아쉬웠다.</p>
<p>또 포크의 문제인지는 모르지만 많은 부분이 한 번에 변경되었을 경우 반드시 깃데스크탑이나 웹 기반이든 프로그램이든 커맨드라인 툴을 열어서 컨플릭트를 해결해야 하는 경우도 있었는데 팀원 모두 처음 겪는 일이라 당황했었다.</p>
<h2 id="retrospective">Retrospective</h2>
<h4 id="결과물">결과물</h4>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/4f152dcf-4a8e-4818-9d66-aad207c45e46/image.png" alt="">
만들어나가는 과정에서 여러 예외처리나 추가적인 기능 요구사항을 해결하는 과정이 꽤 재미있었다.
다음에는 레이블을 하나 쓰더라도 그 안에 들어갈 문자열의 길이를 신경 쓰게 될 것이고, 버튼 하나의 UI도 사용자의 경험이 어떻게 될지를 고려해볼 수 있는 시야를 갖게 해준 의미있는 프로젝트였다.
버튼 하나의 동작엔 하나의 ui업데이트가 거의 필수적이라는 점도 잊지 않을 것이다.</p>
<p>앞서 작성한 클로저 콜백과 깃 포크 외에도 많은 부분을 처음 접하고 새롭게 배웠지만 다 정리를 하지 못하는 것이 아쉽다.
역시 클로저 콜백과 테이블 뷰에 대해서만이라도 다시 공부하며 따로 정리해서 올려야곘다.
정말 가능하다면 꼭 리팩터링을 진행해보고 싶은데 바로 다음주부터 더 심화 기술이 필요한 프로젝트를 시작하게 되어 시간이 생길지 모르겠다.</p>
<h2 id="future-improvements">Future Improvements</h2>
<h4 id="기술">기술</h4>
<p>이번엔 한 번 배우고 사용해봤던 클로저 콜백을 좀 더 확장해서 써보았으니 다음부터는 다른 방법인 델리게이트 패턴과 노티피케이션 센터를 활용하여 비동기 작업을 해봐야겠다.</p>
<h4 id="구조">구조</h4>
<p>이번에 비록 mvc 패턴을 잘 적용하진 못했지만 다른 팀원들의 과제 발표를 보면서 지난 계산기 앱 구조와 이번에 원했던 이상적인 구조 등을 비교하니 어느정도 감이 생길 거 같았다.
이 외에도 많이 사용되는 MVVM 패턴에 대해서 공부하고
레이어드, 클린, 헥사고날 아키텍처에 대해서도 공부해야겠다.
구조를 정하고 어디에 어떤 코드를 넣을지 고민하는 과정이 머리 아프지만 그래도 재미있어서 적성에 맞는 듯하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Calculator_6]]></title>
            <link>https://velog.io/@bom_daddy/Calculator6</link>
            <guid>https://velog.io/@bom_daddy/Calculator6</guid>
            <pubDate>Mon, 25 Nov 2024 00:35:44 GMT</pubDate>
            <description><![CDATA[<h2 id="refactoring">Refactoring</h2>
<p>공부하기 전에 책상을 꼭 치워야 하는 사람이 있다.
새로운 기능을 넣기 전에 코드 정리를 꼭 하고 싶은 사람도 여기 있다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/d979af52-e11a-4fd7-bd04-8b21e5104fb5/image.png" alt="">
길다,,
<img src="https://velog.velcdn.com/images/bom_daddy/post/a2ea360c-bcbd-4200-8af0-2cfcb6eab656/image.png" alt="">
이것도..
<img src="https://velog.velcdn.com/images/bom_daddy/post/a272b6dc-9b8e-4a9b-b2d6-472d0573e2c3/image.png" alt="">
여기도 길다..
<img src="https://velog.velcdn.com/images/bom_daddy/post/8a0ca6d8-27c6-4d4a-b7fc-0f91a5c77858/image.png" alt="">
하나 더,,
<img src="https://velog.velcdn.com/images/bom_daddy/post/0c48ae0c-91d0-4b86-857b-2a2f73698328/image.png" alt="">
...</p>
<h3 id="case">case</h3>
<pre><code class="language-swift">enum Numbers: Equatable, Hashable {
        case one, two, three, four, five, six, seven, eight, nine, zero, dot
    }

    enum Symbols: Equatable, Hashable {
        case plus, minus, multiply, divide, equal, percent, negate, clear, allClear
    }</code></pre>
<p>위에서부터 하나씩 블립시켜보겠다.
여기는 사실 원시값을 넣게 되면 어쩔 수 없이 늘어나겠지만 일단 줄여두었다.</p>
<h3 id="">==</h3>
<p>다음 코드는 Equatable을 준수할 때 비교 연산인 <code>==</code>를 정의해준 부분이다.
지난 숫자야구 게임을 만들 때 가장 상위 열거형에 Equatable을 준수했음에도 Hashable과 더불어 관련 메서드가 자동으로 구현되지 않았다.
이때 자동 구현이 안 될때의 해결방법으로 수동 구현을 배워서 내부 연관값으로 열거형을 추가하면 무조건 수동 구현을 해줘야 한다고 잘못알고 있었다.
자동 구현이 되지 않았던 이유는 내부연관값에 해당 프로토콜을 준수시키지 않아서였다.
따라서 이 코드는 바로 삭제했다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/402c3b23-90a6-4bd2-b879-3a5947e9de99/image.png" alt="">
내부 연관값에도 프로토콜을 잘 준수시키자.</p>
<h3 id="getbuttoncase">getButtonCase()</h3>
<p>이번 메서드는 문자열에 따라 열거형 케이스를 반환시켜준다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/9250b775-8742-4112-952a-31a1c276b509/image.png" alt=""></p>
<p>우선 1:1 매핑이 필요하니 스위치문보다 딕셔너리가 더 빠르게 접근할 수 있어서 좋다고 판단된다.
코드 또한 더 간소화됐다.</p>
<h3 id="buttontitle">buttonTitle()</h3>
<p>케이스에 따른 문자열 반환 메서드는 개선 방법에 2가지가 존재한다.</p>
<ol>
<li>케이스의 원시값을 추가하여 원시값 반환받기</li>
<li>getButtonCase()에 사용하기 위해 선언한 딕셔너리를 활용하기</li>
</ol>
<p>원시값을 설정한다면 컴파일러가 타입을 체크해준다. 성능상으로도 좀 더 낫다고 한다. 대신 매핑이 두 곳으로 분산되고 원시값을 지정해주는 추가적인 작업이 필요하다.
딕셔너리를 활용한다면 하나의 자료구조에서 매핑이완료되므로 간결하다. 대신 런타임까지 매핑 에러를 발견하기 어렵고 해시테이블 조회로 인해 약간의 성능 오버헤드가 발생한다.</p>
<p>이미 길게 선언해둔 딕셔너리를 재사용할 수 있으면 좋겠지만 그래도 성능적으론 원시값을 가진 것이 더 빠르니 원시값으로 처리해보겠다.
(사실 애초에 연관값은 원시값을 아예 못가진다고 애매하게 알아서 만들었다. 원시 좋아.)</p>
<pre><code class="language-swift">  private var buttonTitle: String {
        switch self {
        case .number(let number):
            return number.rawValue
        case .symbol(let symbol):
            return symbol.rawValue
        }
    }</code></pre>
<h2 id="getcalculatedictionary">getCalculateDictionary()</h2>
<p>드디어 마지막 메서드다.
0...9가 모두 동일하게 numberTapped(num:)을 추가하기 때문에 반복문으로 추가해주면 좋을 것이다.
반복문에서 사용하기 위해 Buttons.Numbers에만 추가적으로 CaseIterable을 준수해주었다.</p>
<pre><code class="language-swift"> Buttons.Numbers.allCases.forEach { number in
    dictionary[.number(number)] = {[weak self] in
        guard let self = self else { return }
        self.numberTapped(num: text)
    }
}</code></pre>
<p>고차함수 forEach를 사용해주면 이렇게 중복 코드를 모두 지울 수 있게 된다!</p>
<h2 id="result">Result</h2>
<table>
<thead>
<tr>
<th>before</th>
<th>after</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/bom_daddy/post/1675dfe3-2378-42b5-974e-c9a0b4bfa50b/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/bom_daddy/post/ae712b7c-96c7-40dd-82ac-a1d9dede8fd7/image.png" alt=""></td>
</tr>
</tbody></table>
<p>이건 마지막 클로저 딕셔너리의 전후사진이다.
열거형의 코드도 이전 미니맵을 찍어둘걸 아쉽다.
리팩터링을 해보니 사실 처음부터 이렇게 짜나갈 수 있을 정도의 코드였는데 경험이 부족해서인지 맘이 조급해서인지 바로바로 생각이 안 났다.
다시 한 번 많이 경험해봐야겠다고 생각이 드는 리팩터링 시간이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Calculator_5]]></title>
            <link>https://velog.io/@bom_daddy/Calculator4-uz5365s7</link>
            <guid>https://velog.io/@bom_daddy/Calculator4-uz5365s7</guid>
            <pubDate>Sun, 24 Nov 2024 19:01:19 GMT</pubDate>
            <description><![CDATA[<h2 id="refactoring">Refactoring</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/a12e7fb2-42e9-47a9-b929-429cce72b71f/image.png" alt="">
처음 만들 때 버튼 생성 반복문에 들어갈 배열을 하드코딩해두었다.
내가 느끼기엔 계산기에 버튼이 어떻게 들어갈지 직관적인 모습이긴 했다.
그러나 나누기를 <code>/</code>에서 <code>÷</code>로 변경한다거나 <code>*</code>에서 <code>×</code>로 바꾸는 등의 수정사항이 생기거나 추가적인 버튼 배열이 필요할 때 버튼을 정의해둔 enum에서 수정이 끝나지 않고 다시 하드코딩된 이 부분을 찾아와야 하기 때문에 수정을 하는 게 더 맞다고 판단했다.</p>
<p>저번 숫자야구 게임 프로젝트에서의 연관값을 가진 열거형을 복습할 겸 이번에도 숫자와 연산자를 나누어 연관값으로 처리했보았다.
때문에 케이스별 rawValue를 가지지 못하니 rawValue를 대신해줄 변수를 만들었다.</p>
<pre><code class="language-swift">var buttonTitle: String {
        switch self {
        case .number(let number):
            switch number {
            case .one:
                return &quot;1&quot;
            case .two:
                return &quot;2&quot;
            case .three:
                return &quot;3&quot;
            case .four:
        (...)
    }</code></pre>
<p>해당 변수는 점표기법을 통해 접근할 수 있으며 해당 변수를 호출한 케이스에 따른 String 값을 얻을 수 있다.</p>
<p>이제 별개의 구조체를 선언하여 구조화할지 또는 열거형에서 타입메서드로 값을 반환할지 고민해보았다.
별개의 구조체로 분리한다면 필요한 데이터에 대한 구조를 좀 더 명확히 처리해둘 수 있을 것이다.
열거형에서 반대로 코드의 응집도를 높이니 버튼의 추가가 필요한 상황에서도 바로 해당 열거형 안에서 수정을 마칠 수 있다.
둘 다 static으로 선언한다면 초기화 비용이 거의 없거나 초기화가 한 번만 실행되고 메모리에 한 번만 올라간 뒤 공유하여 사용할 수 있다.</p>
<p>대신 열거형에 추가로 해당 메서드를 선언한다면 코드가 너무 비대해질 수 있고 구조체를 추가로 선언하는 것도 불필요한 확장이 될 수 있다.</p>
<p>이번 계산기 버튼 타이틀에 필요한 데이터는 값이 작고 추가적인 데이터 구조가 확장될 가능성이 적다. (공학용 계산기 모드 등을 지원하지 않을 것이라..)
따라서 더 처리가 간단한 열거형 타입 메서드로 처리해도 괜찮다고 생각했다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/0d859f1f-c733-4882-9b2e-9f5107c53562/image.png" alt=""></p>
<p>상기한 이유를 바탕으로 열거형 내부에 rows 배열을 만들었다.
이제 rows와 buttonTitle을 이용해 최종적인 타이틀 배열을 만들어내면 된다.
rows 안에 있는 row들의 buttonTitle을 요소로 하는 배열을 반환받고 그 배열을 요소로 하는 배열이 필요하다.
그냥 이중배열이 필요하다는 말이고 따라서 .map을 두 번 쓰면 된다.</p>
<pre><code class="language-swift">static func getButtonTitles() -&gt; [[String]]{
        let rows: [[Buttons]] = [
            [.symbol(.clear), .symbol(.negate), .symbol(.percent), .symbol(.divide)],
            [.number(.one), .number(.two), .number(.three), .symbol(.plus)],
            [.number(.four), .number(.five), .number(.six), .symbol(.minus)],
            [.number(.seven), .number(.eight), .number(.nine), .symbol(.multiply)],
            [.number(.zero), .number(.dot), .symbol(.equal)]
        ]
        return rows.map { row in
            row.map { $0.buttonTitle }
        }
    }</code></pre>
<p>(getButtonArray에서 조금 더 명확한 전달을 위해 getButtonTitles로 이름을 변경했다. 프로퍼티 이름도 buttonsArray에서 buttonTitle로 변경.)</p>
<h2 id="result">Result</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/721580ba-e108-4b39-92a3-d5f4efb86567/image.png" alt=""></p>
<p>하드코딩 한 줄이 사라졌다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Calculator_4]]></title>
            <link>https://velog.io/@bom_daddy/Calculator4</link>
            <guid>https://velog.io/@bom_daddy/Calculator4</guid>
            <pubDate>Sat, 23 Nov 2024 18:30:04 GMT</pubDate>
            <description><![CDATA[<h2 id="refactoring">Refactoring</h2>
<p>오늘은 급하게 만드느라 따로 클래스를 분리해서 만들었던 RowStack에 대한 리팩터링을 진행했다.
기존 코드는 계산기의 마지막 행이 다른 행과 달리 버튼이 3개만 존재해야 하면서 문제가 생겼다.</p>
<h3 id="problem">Problem</h3>
<p>처음엔 3개의 버튼을 처리하는 Stack을 따로 만든다는 것뿐이었다.
그러나 StakcView를 생성하는 클래스가 2개가 되자 하나의 반복문으로 처리할 수 없게 되었다.
3개의 버튼 타이틀을 담은 변수도 따로 선언해야 했고, 각 스택에 존재하는 버튼 배열에 대한 다형성을 해서 프로토콜도 만들어야 했다.</p>
<p>처음 클래스를 나누어 만들 땐 이렇게 코드를 복잡하게 만들 일이 될 거란 생각을 못했다.
이번 코드 덕분에 무조건 기능별로 클래스를 나누는 것이 옳은 게 아님을 깨닫게 되었다.
미래에도 거의 없을 재사용성을 위하기보다 4버튼과 3버튼을 하나의 클래스가 책임지는 것이 훨씬 나은 코드라 생각한다.</p>
<pre><code class="language-swift">class MainStack: UIStackView {
    /*
    4버튼, 3버튼의 변수가 따로 정의되어있다.
    리팩터링 후 합쳐지긴 했으나 나중엔 버튼 타이틀을 관리하는 열거형에서 
    메서드로 배열을 반환받도록 수정해야 한다.*/
    private let buttonsArr: [[String]] = [
        [&quot;C&quot;, &quot;±&quot;, &quot;%&quot;, &quot;÷&quot;],
        [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;+&quot;],
        [&quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;-&quot;],
        [&quot;7&quot;, &quot;8&quot;, &quot;9&quot;, &quot;×&quot;],
    ]
    private let zeroRowButtons: [String] = [&quot;0&quot;, &quot;.&quot;, &quot;=&quot;]
    //스택 생성 클래스가 나뉘어졌기 때문에 하나로 묶어줄 프로토콜이 필요했다.
    private(set) var rows: [ButtonStackProtocol] = []
(...)
    private func setButtonRow(buttonsArr: [[String]]){
        for buttons in buttonsArr {
            let buttonRow = RowStack(buttonTitle: buttons)
            rows.append(buttonRow)
            addArrangedSubview(buttonRow)
        }
        //반복문에서 끝나지 않고 추가로 중복되는 코드가 필요했다.
        let zeroRow = ZeroRowStack(titles: zeroRowButtons)
        rows.append(zeroRow)
        addArrangedSubview(zeroRow)
    }
}</code></pre>
<p>선언되었던 2개의 스택뷰 클래스는 코드의 80%가 중복되었고 단순히 3개인 경우 레이아웃을 추가로 설정해주는 부분만이 달랐다.
때문에 두 클래스를 하나로 병합하는 과정은 정말 빠르게 끝났다.</p>
<pre><code class="language-swift">    private func setButtons(titles: [String]) {
        for title in titles {
            let button = CircleButton(title: title, alpha: buttonAlpha)
            buttons.append(button)
            addArrangedSubview(button)
        }

        if titles.count == 3 {
            self.setThreeButtonsLayout(buttons: self.buttons)
        }
    }

    private func setThreeButtonsLayout(buttons: [UIButton]) {
        NSLayoutConstraint.activate(
            buttons.enumerated().map { index, button in
                button.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: index == 0 ? 0.5 : 0.25, constant: (-1 * spacingSize * (index == 0 ? 0.5 : 0.75))
                )
            }
        )
    }</code></pre>
<p>위 코드에서 보듯 그냥 3버튼인 경우의 레이아웃 설정만 따로 해주면 되었다.
(3,4개의 버튼이 아닌 경우의 에러처리도 추가해야 하지만,,)</p>
<p>그래도 이번에 고민없이 나눠버린 클래스가 문제를 발생시켰기에
다음 프로젝트부터는 클래스 분리에 대해서도 더 고민해볼 수 있게 되었다.</p>
<h3 id="result">Result</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/3feb116f-4d6b-457e-a260-875e20578f76/image.png" alt="">
<img src="https://velog.velcdn.com/images/bom_daddy/post/1e9f211c-8daf-4feb-8aa1-9040d02c5c2e/image.png" alt="">
파일 3개가 1개로 줄어들었다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Calculator_3]]></title>
            <link>https://velog.io/@bom_daddy/Calculator3</link>
            <guid>https://velog.io/@bom_daddy/Calculator3</guid>
            <pubDate>Fri, 22 Nov 2024 02:59:21 GMT</pubDate>
            <description><![CDATA[<h2 id="trouble">Trouble</h2>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/3059f1fa-1563-457c-968a-6250ca30d2e4/image.png" alt=""></p>
<p>콤마를 넣는 작업 진행 중 처음 콤마가 삽입된 뒤로는 콤마가 더 추가가 안되는 문제가 발생했다.
이 문제를 생각하다 보니 포맷을 지정하는 메서드의 위치도 문제가 있었다.
포맷은 어쨌든 연산이 아닌 ui의 일부니까 연산이 모두 끝난 후 ui를 업데이트 하는 과정에서 처리하는 게 맞았다.</p>
<p>그래서 원래 위치였던 Model - CalculatorData에서
View - DisplayLabel로 옮기면서 계산 시 ,를 다시 없애고 추가하는 과정이 필요없어졌다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/5d09cb6c-0302-4b8e-bfc2-7cd5e8405143/image.png" alt="">
위치를 바꾸면서 문제가 되는 부분을 찾았다.
컴마를 넣기 위해 넣은 count변수를 0으로 초기화 하지 않고 계속 3인지 체크했기 때문에 2번째부터 컴마가 없기 시작했다.
이제 다 해결된 줄 알고 이것저것 넣어봤는데
이번엔 -를 붙이면 안 되는 문제가 발생했다.
거기에 소수점을 제외하면서 처리해야 하는 부분도 남아있었다.</p>
<p>-를 빼기 위해 -가 있는지 검사하고
알고리즘을 공부하면서 알게된 split을 사용해 소수점 이전과 이후로 나누어 컴마를 붙이는 작업을 했다.</p>
<pre><code class="language-swift">  private func formatNum(num: String) -&gt; String {
        let splitNum = num.split(separator: &quot;.&quot;)
        let isNegative: Bool = num.contains(&quot;-&quot;)//음수 처리를 위한 체크
        var intPart: Substring.SubSequence = splitNum[0]
        let decimalPart: Substring.SubSequence? = (splitNum.count &gt; 1 ? splitNum[1] : nil)//정수와 소수부분 체크
        var commaFormatString: String = &quot;&quot;
        var commaCount: Int = 0

        if isNegative {//음수라면 - 제거
            intPart.removeFirst()
        }

        for num in intPart.reversed() {
            if commaCount == 3 {
                commaFormatString.append(&quot;,&quot;)
                commaCount = 0
            }
            commaFormatString.append(num)
            commaCount += 1
        }

        if let decimalPart = decimalPart {
            commaFormatString = String(commaFormatString.reversed() + &quot;.&quot; + String(decimalPart))
        } else {
            commaFormatString = String(commaFormatString.reversed())
        }

        if isNegative {
            commaFormatString = &quot;-&quot; + commaFormatString
        }

        return commaFormatString
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/211adb06-5111-4689-8129-c836a8fb8e1b/image.png" alt=""></p>
<p>간단한 사칙연산 계산기를 만드는데 생각보다 정말 많은 부분을 신경쓰고 다양한 예외 상황이 발생했다.</p>
<p>이번 과제에 주어진 시간 중 절반 이상은 이론 공부와 알고리즘 공부 그리고 설계를 생각했는데 결과물을 보니 잘못된 선택이었나 싶었다.</p>
<p>머리로는 이렇게 저렇게 하면 되겠다고 막연하게 생각하던 부분이 실제로는 훨씬 복잡한 코드나 구조를 필요로 했다.
내 생각보다 UIKit에서 뷰의 계층을 만들고 예외적인 Stack을 처리하고 기본 프레임워크로는 불만족스러운 상황에 대한 대처가 많은 시간을 필요로 했다.(그래서 대부분 제대로 처리하지 못했다.)
이런 안일한 생각은 결국 시간 관리 실패로 이어졌다.</p>
<p>책으로, 이론 공부로는 이렇게 코드를 짜고 방치하면 안 된다는 걸 배웠지만 일단 만들어야 하다보니 하드코딩된 부분이나 불필요하게 복잡한 코드가 발생한 부분도 있었다.
이러면 안 된다는 걸 알지만 그냥 두고 넘겨야 하니 뭔가 신경쓰이고 괴로웠다.
이번엔 저번 야구 게임에서 처리해두었던 상태변수나 예외처리에 대해서는 아직 한 줄도 적지 못했다.</p>
<p>시간이 있었다면 내가 다 처리했을까 얼마나 시간이 필요했을까 하는 생각을 바탕으로 다음 프로젝트는 좀 더 보수적으로 시간을 계획하고 프로젝트를 시작해야겠다.</p>
<p>그래도 저번 야구 게임을 통해 배운 연관값을 가진 열거형과 클로저 딕셔너리를 이번에 활용해서 생각보다 편하게 계산 로직을 만들었다.
이번 실패를 경험 삼아서 다음 프로젝트는 좀 더 나은 퍼포먼스를 보일 수 있게 잘 갈무리해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git Merge Trio]]></title>
            <link>https://velog.io/@bom_daddy/Git-Merge-Trio</link>
            <guid>https://velog.io/@bom_daddy/Git-Merge-Trio</guid>
            <pubDate>Tue, 19 Nov 2024 08:10:02 GMT</pubDate>
            <description><![CDATA[<h1 id="merge-in-git">Merge in Git</h1>
<p>깃에서 브랜치를 병합할 때 사용되는 머지 삼총사입니다. </p>
<h2 id="merge-commit">Merge Commit</h2>
<p>merge commit은 가장 기본적인 방식입니다.<br>두 브랜치를 병합 시 merge commit을 생성합니다.<br><img src="https://velog.velcdn.com/images/bom_daddy/post/a843cb22-cbd1-4b01-beb4-05544bc75e4b/image.png" alt=""><br>위에 선택 된 점이 브랜치를 병합하면서 생긴 <code>merge commit</code>입니다.<br>merge commit이 생성되는 방식은 <code>Three-Way Merge</code>를 따릅니다.<br>이는 병합하려는 두 브랜치가 서로 다른 커밋으로 나뉘었을 때, 공통 조상을 기준으로 병합 커밋을 생성하는 방식입니다.<br>공통 조상이란 병합하려는 두 브랜치가 공통으로 가진 마지막 커밋입니다. 즉 히스토리를 비교해서 병합합니다.<br>현재 브랜치(HEAD)와 병합 대상 브랜치를 병합할 때 충돌이 없는 경우 자동으로 병합합니다. 충돌이 발생한다면 사용자가 직접 수정합니다.  </p>
<p>이러한 방식은 병합 시점의 두 브랜치 기록을 유지하고 히스토리에도 병합 흔적을 남깁니다.<br>따라서 브랜치의 독립적인 작업 흐름을 파악할 수 있습니다.<br>충돌 발생 시 병합 커밋에서 해결사항을 기록하고 다른 팀원도 히스토리를 쉽게 이해할 수 있습니다.<br>다만 많은 브랜치를 병합할 경우 히스토리가 복잡해집니다.<br>또 브랜치에서 생성된 커밋이 남아 보기 어렵기도 합니다.  </p>
<h2 id="rebase">Rebase</h2>
<p>rebase는 브랜치를 병합할 때 커밋 히스토리를 선형으로 정리하는 방식입니다.<br>merge commit을 생성하지 않고, 병합하려는 브랜치의 커밋을 현재 브랜치의 최신 커밋 뒤로 재배치합니다.  </p>
<p>rebase는 충돌이 발생할 경우 사용자가 직접 충돌을 해결한 뒤 다시 rebase를 이어서 실행해야 합니다.<br>이 방식은 히스토리를 선형으로 정리하므로 보기 쉬운 기록을 제공합니다.<br>다만 기존의 커밋 기록이 변경되므로 협업 중에는 사용에 주의가 필요합니다.<br>브랜치의 작업 흐름을 간결하게 만들어주지만 히스토리의 정확한 흐름을 알기 어려울 수 있습니다.<br>특히, 이미 원격 저장소에 push된 커밋을 rebase할 경우 충돌이나 데이터 손실이 발생할 가능성이 있으니 주의해야 합니다.  </p>
<h2 id="squash-and-merge">Squash and Merge</h2>
<p><code>squash and merge</code>는 브랜치를 병합할 때 여러 커밋을 하나로 압축(squash)하여 병합하는 방식입니다.<br>이 방식은 merge commit을 생성하지 않고 브랜치에 남아있는 모든 커밋을 단일 커밋으로 합칩니다.<br><code>squash and merge</code>는 주로 작은 변경 사항을 하나의 작업 단위로 병합할 때 유용합니다.<br>이 방식은 히스토리를 간결하게 만들어주는 장점이 있지만 브랜치의 세부적인 작업 기록이 사라지기 때문에 작업 내용을 상세히 추적하기 어렵습니다.  </p>
<h2 id="comparison-of-merge-methods">Comparison of Merge Methods</h2>
<h3 id="병합-방식-비교-comparison-table">병합 방식 비교 (Comparison Table)</h3>
<table>
<thead>
<tr>
<th>Merge Method</th>
<th>히스토리 유지</th>
<th>커밋 단순화</th>
<th>협업 충돌 위험</th>
<th>사용 난이도</th>
</tr>
</thead>
<tbody><tr>
<td>Merge Commit</td>
<td>O</td>
<td>X</td>
<td>낮음</td>
<td>쉬움</td>
</tr>
<tr>
<td>Rebase</td>
<td>X</td>
<td>O</td>
<td>높음</td>
<td>중간</td>
</tr>
<tr>
<td>Squash and Merge</td>
<td>X</td>
<td>O</td>
<td>낮음</td>
<td>쉬움</td>
</tr>
</tbody></table>
<h2 id="choosing-the-right-merge-method">Choosing the Right Merge Method</h2>
<h3 id="merge-commit-사용-시기">Merge Commit 사용 시기</h3>
<ul>
<li>브랜치의 작업 흐름을 명확하게 유지하고 싶은 경우  </li>
<li>병합 작업이 빈번하고 협업이 많은 프로젝트에서 충돌 기록을 남기고 싶은 경우  </li>
<li>충돌 해결 과정과 병합 히스토리를 보존해야 할 때  </li>
</ul>
<h3 id="rebase-사용-시기">Rebase 사용 시기</h3>
<ul>
<li>히스토리를 선형으로 정리해 간결한 기록을 유지하고 싶은 경우  </li>
<li>브랜치에서 독립적인 작업을 수행한 후, 최종 병합 시 병합 기록을 제거하고 싶을 때  </li>
<li>팀 협업보다는 개인 프로젝트에서 유용  </li>
</ul>
<h3 id="squash-and-merge-사용-시기">Squash and Merge 사용 시기</h3>
<ul>
<li>브랜치에서 여러 커밋을 단일 작업 단위로 병합하고 싶을 때  </li>
<li>커밋 히스토리를 간결하게 유지하고 싶은 경우  </li>
<li>브랜치에서 수행된 작업이 하나의 기능 또는 단일 변경 사항으로 요약될 때  </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Higher-Order Functions ]]></title>
            <link>https://velog.io/@bom_daddy/Higher-Order-Functions</link>
            <guid>https://velog.io/@bom_daddy/Higher-Order-Functions</guid>
            <pubDate>Fri, 15 Nov 2024 07:13:49 GMT</pubDate>
            <description><![CDATA[<h2 id="higher-order-functions-고차-함수">Higher-Order Functions (고차 함수)</h2>
<p>고차함수란 다음 두 가지 중 하나를 만족하는 함수입니다.</p>
<ol>
<li>함수를 인자로 받는다.</li>
<li>함수를 반환한다.</li>
</ol>
<p>이는 함수를 데이터처럼 다룬다는 아이디어를 기반으로 합니다.
함수가 일급 시민으로 간주되기 때문에 가능합니다.
일급 시민으로 간주된다는 것은 함수도 class나 struct처럼 간주될 수 있다는 것으로 외부 상태를 캡쳐하느냐에 따라 힙 또는 스택에 저장됩니다.
특정 로직을 일반화하고 추상화할 수 있으며 클로저와 자주 결합하여 사용합니다.</p>
<h3 id="types-of-higher-order-functions-고차-함수의-두-가지-유형">Types of Higher-Order Functions (고차 함수의 두 가지 유형)</h3>
<h4 id="functions-taking-functions-as-parameters-함수를-매개변수로-받는-함수">Functions Taking Functions as Parameters (함수를 매개변수로 받는 함수)</h4>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b45bd313-5395-46ed-94a4-d62604399a42/image.png" alt="">
간단한 사용법은 위와 같으며 좀 더 실사용적인 예시는 다음과 같습니다.</p>
<pre><code class="language-swift">func animateView(_ view: UIView, completion: @escaping () -&gt; Void) {
    UIView.animate(withDuration: 1.0, animations: {
        view.alpha = 0
    }, completion: { _ in
        completion()
    })
}

animateView(someView) {
    print(&quot;Animation finished. Performing next steps...&quot;)
}</code></pre>
<p>위 코드는 애니메이션이 진행된 후의 동작을 정의해서 사용하고 있습니다.
상황에 따라 저런 프린트가 아닌 데이터를 업데이트하거나 화면전환을 시도하는 등 유연한 사용이 가능해집니다.
또 애니메이션의 경우 비동기로 작동하므로 완료 시점을 알아야 하는 상황에서도 유용합니다.</p>
<h4 id="functions-returning-functions-함수를-반환하는-함수">Functions Returning Functions (함수를 반환하는 함수)</h4>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/f6548ebc-80ee-4a81-b253-2515369731d9/image.png" alt="">
위 코드처럼 함수를 반환하는 함수는 상황에 맞게 필요한 함수를 동적으로 만들 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/cead0111-469a-495e-a296-7e27e1f05d8d/image.png" alt="">
또는 이처럼 클로저 캡처를 활용하여 호출 횟수를 추적하는 등의 동작도 가능합니다.</p>
<h3 id="in-swift">In Swift</h3>
<p>스위프트에서는 정말 다양한 고차함수를 표준라이브러리로 제공하고 있습니다.</p>
<ul>
<li>map</li>
<li>compactMap</li>
<li>flatMap</li>
<li>reduce</li>
<li>enumrated</li>
<li>filter</li>
</ul>
<p>이외에도 sorted(by:)나 forEach 등 다양한 것들이 있습니다.
컬렉션 타입과 결합하여 사용하는 경우가 많습니다.
for이나 if 문 대신 map, filter, reduce 등을 사용하여 코드의 간결성과 의도를 더 정확히 알 수 있는 가독성을 높일 수 있고
재사용성도 높습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pure Function]]></title>
            <link>https://velog.io/@bom_daddy/Pure-Function-bp4djnnq</link>
            <guid>https://velog.io/@bom_daddy/Pure-Function-bp4djnnq</guid>
            <pubDate>Fri, 15 Nov 2024 05:34:06 GMT</pubDate>
            <description><![CDATA[<h2 id="pure-function-순수-함수">Pure Function (순수 함수)</h2>
<p>함수형 프로그래밍이라는 패러다임이 있습니다.
스위프트에서 지원하는 패러다임 중 하나로 순수 함수, 고차함수, 불변성, 함수 합성이라는 특징을 갖고 있습니다.</p>
<h3 id="features-of-a-pure-function-순수-함수의-특징">Features of a Pure Function (순수 함수의 특징)</h3>
<h4 id="deterministic-결정적">Deterministic (결정적)</h4>
<p>첫 번째 특징인 순수 함수는 동일한 입력에 동일한 출력을 보장합니다.
즉 입력에만 의존하고 외부의 어떤 상태나 조건에 따라 바뀌지 않습니다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/cd519cca-d5db-458e-81b3-187deca93c1c/image.png" alt="">
<code>addOne</code>은 동일한 입력에 동일한 출력을 보장합니다.
그러나 <code>addRandomNum</code>은 입력이 동일하더라도 외부의 타입 메서드를 활용했고, 동일한 출력을 보장할 수 없습니다.</p>
<h4 id="no-side-effects-부작용-없음">No Side Effects (부작용 없음)</h4>
<p>사이드 이펙트란 외부의 상태를 변경하는 등 외부로 영향을 끼치는 것을 말합니다.
순수 함수는 이러한 사이드 이펙트가 없습니다.
<img src="https://velog.velcdn.com/images/bom_daddy/post/77a43791-635a-4f94-ab91-3931621f5701/image.png" alt="">
<code>changeStr</code>는 함수 외부에 존재하는 <code>hello</code>를 변화시키며 사이드 이펙트를 발생시켰습니다.
<code>getHi</code>는 <code>&quot;hi&quot;</code>를 반환시켜 다시 hello에 넣는 방식으로 불변성을 유지하고 사이드이펙트를 없앴습니다.</p>
<h3 id="advantages-of-pure-functions순수-함수의-이점">Advantages of Pure Functions(순수 함수의 이점)</h3>
<p>순수 함수의 특징으로 인해 코드의 예측 가능성이 높아집니다.
또 외부에 의존하지 않으므로 테스트와 디버깅이 용이합니다.
외부 상태를 변경하지 않고 공유 상태 없이 독립적인 실행이 가능하니 병렬처리에 적합합니다.
의존성이 없으니 재사용이 쉽고 다른 함수와 쉽게 합성할 수 있습니다.
즉 안정성, 유지보수성, 재사용성이 높게 유지됩니다.</p>
<h3 id="side-effect사이드-이펙트">Side Effect(사이드 이펙트)</h3>
<p>잠깐 언급된 사이드 이펙트는 반드시 나쁜 것이 아닙니다.
실제로 프로그래밍을 하게 된다면 반드시 발생하게 됩니다.
예를 들어 파일에서 Data를 읽거나 저장할 때, 네트워크를 요청하거나 이벤트를 처리하고 UI를 업데이트 하는 등 다양한 상황에서 필수적으로 발생합니다.
사이드 이펙트는 어떻게 막을지가 아니라 어떻게 관리할지를 고민해야 합니다.
관리에 대한 방법으론 순수함수로 로직을 관리하고 사이드 이펙트는 꼭 필요한 부분만 분리시켜 작성하기, 불변성 - Immutable 상태와 단방향 데이터 흐름을 유지하기 등을 신경쓰는 것이 좋습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Chess Board]]></title>
            <link>https://velog.io/@bom_daddy/Chess-Board</link>
            <guid>https://velog.io/@bom_daddy/Chess-Board</guid>
            <pubDate>Fri, 15 Nov 2024 02:19:09 GMT</pubDate>
            <description><![CDATA[<h3 id="invariants-in-algorithms">Invariants in Algorithms</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/d02f3664-b945-4feb-b747-30f365f2826a/image.jpg" alt=""></p>
<p>8*8사이즈의 체스판에서 대각선으로 마주보는 두 꼭짓점 칸을 제거한 망가진 체스판이 있습니다. 이 체스판을 도미노로 덮을 예정입니다. 도미노는 정확히 체스판의 2칸을 덮습니다.
도미노끼리 겹치거나 망가진 체스판 밖으로 나오지 않도록 하면서 체스판을 완전히 덮을 수 있을까요?</p>
<h3 id="variables">Variables</h3>
<p>변수 설정을 먼저 해보겠습니다. 도미노 하나를 덮을 때마다 변화하는 것은 체스판의 칸입니다. 하나를 덮을 때마다 2칸이 사라지고 정확히는 흰 칸과 검은 칸 하나씩 사라지게 됩니다.
여기서 흰 칸을 <code>w</code>, 검은 칸을 <code>b</code>라고 두겠습니다.
도미노를 덮어서 칸을 없애는 것은 <code>w,b := w-1, b-1</code>로 둘 수 있습니다.</p>
<h3 id="invariants">Invariants</h3>
<p>이 체스판에서 도미노를 덮는 행위를 반복하더라도 절대 변하지 않는 것은 총 체스판의 칸 수가 있습니다. 또 흰 칸과 검은 칸의 차이입니다. <code>w - b = 2</code>이고 이것이 문제의 불변량이 됩니다.</p>
<h3 id="conclusion">Conclusion</h3>
<p>이제 <code>(w - b)[w,b := w-1, b-1] = w - b = 2</code>가 성립하는지 살펴보는 것으로 문제는 해결됩니다.
식을 정리하면 <code>0 = 2</code>가 나오므로 도미노로 체스판을 덮는 것은 불가능함을 알 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MVC Pattern]]></title>
            <link>https://velog.io/@bom_daddy/MVC-Pattern</link>
            <guid>https://velog.io/@bom_daddy/MVC-Pattern</guid>
            <pubDate>Thu, 14 Nov 2024 07:22:41 GMT</pubDate>
            <description><![CDATA[<h1 id="mvc-pattern-mvc-패턴">MVC Pattern (MVC 패턴)</h1>
<p>mvc패턴은 UIKit을 사용해 개발하는 ios 앱에 주로 적용되는 소프트웨어 설계 패턴입니다.
Model, View, Controller의 앞글자를 딴 이름으로 각 컴포넌트의 특징은 다음과 같습니다.</p>
<h2 id="m-model">M: Model</h2>
<p>모델은 데이터와 비즈니스 로직을 담당합니다.
데이터를 직접 제공하고 사용자의 인터랙션을 컨트롤러가 알려주면 그에 맞게 데이터를 업데이트하기도 합니다.</p>
<h2 id="v-view">V: View</h2>
<p>사용자에게 보여지는 화면을 담당하는 컴포넌트입니다.
Swift에서는 <code>UIView</code>나 <code>UILabel</code>등을 사용해 구현합니다.</p>
<h2 id="c-controller">C: Controller</h2>
<p>컨트롤러는 <code>Model</code>과 <code>View</code>의 중간에서 서로의 중개자 역할을 합니다.
때때로 비즈니스 로직을 컨트롤러에서 처리하기도 하는데 권장되지 않습니다. Model과 View는 서로 의존하지 않고 Controller를 통해 상호작용합니다.</p>
<h2 id="mvc-workflow">MVC Workflow</h2>
<p>사용자가 View를 통해 인터랙션을 발생시킵니다.
Controller는 해당 이벤트에 맞게 Model에게 필요한 데이터를 받거나 View를 업데이트합니다.
Controller는 Model을 조작하고 Model은 View에 반영됩니다. 다시 View를 통해 Controller가 사용자 입력을 받게 되는 순환 구조이지만 직접적인 양방향 상호작용이 없습니다.
이런 단방향 구조는 각 컴포넌트의 결합과 의존성을 줄여 유지보수와 확장성을 더욱 높이게 됩니다.</p>
<h2 id="pros-and-cons">Pros and Cons</h2>
<p>앞서 언급했듯 각 컴포넌트는 분리되어있고 독립적이기 때문에 한 요소의 수정이 다른 요소에 끼치는 영향이 작습니다. 
동일한 모델을 여러 View에서 사용하는 등 재사용성도 좋습니다.</p>
<p>다만 View와 Controller의 결합이 강해지는 경우가 생길 수 있습니다.
예를 들어 UI업데이트를 위해 세부적인 로직을 작성하며 의존성이 강해질 수 있습니다. 또는 스토리보드의 IBOutlet연결이 많아져 ViewController가 View에 종속되기 쉬워지는 경우도 있습니다.
이를 해결하기 위해 <code>Delegate</code>, <code>Coordinator</code> 등의 패턴을 사용하거나 데이터 바인딩을 하는 등 책임 분리와 이벤트 처리를 외부로 분리하는 등 Controller가 정말 UI와 Model의 중간 매개체 역할에 집중하도록 하는 것이 좋습니다.
단 데이터 바인딩을 사용한다면 순수한 MVC 패턴과는 약간의 차이가 있습니다. MVVM과 MVC의 중간쯤 될 거 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Go ]]></title>
            <link>https://velog.io/@bom_daddy/Go</link>
            <guid>https://velog.io/@bom_daddy/Go</guid>
            <pubDate>Thu, 14 Nov 2024 04:49:01 GMT</pubDate>
            <description><![CDATA[<h3 id="invariants-in-algorithms">Invariants in Algorithms</h3>
<p><img src="https://velog.velcdn.com/images/bom_daddy/post/b718554a-b743-4aca-bf7b-465d8f94a8d9/image.png" alt="">
바둑알 항아리에 흰 돌과 검은 돌이 담겨있고 항아리 밖에도 바둑알이 널부러져있습니다.
항아리 안에 하나의 돌만 남을 때까지 다음 과정을 반복합니다.
&#39; 항아리에서 돌 두 개를 꺼낸다. 돌이 같은 색이라면 검은 돌을, 다른 색이라면 흰 돌을 항아리에 넣는다. &#39;
과정을 반복할 수록 항아리의 바둑알은 하나씩 줄게 됩니다.
초기에 항아리에 들어 있는 흰 돌과 검음 돌의 개수와 관련하여 항아리에 남아있는 마지막 돌의 색에 대해 논의합니다.</p>
<h3 id="variables">Variables</h3>
<p>수치가 변하는 건 검정돌과 흰돌이므로 검정돌을 <code>b</code>, 흰돌을 <code>w</code>로 두겠습니다.
이제 돌을 꺼내는 상황 3가지를 모델링하겠습니다</p>
<ul>
<li>검정돌 2개를 꺼낼 때: <code>b,w := b+1, w-2</code></li>
<li>흰돌 2개를 꺼낼 때: <code>b,w := b-1, w</code></li>
<li>각 1개씩 꺼낼 때: <code>b,w := b-1, w</code></li>
</ul>
<h3 id="invariants">Invariants</h3>
<p>위 표현식을 살펴보면 흰돌은 그대로거나 2개가 감소하게 됩니다.
즉 흰돌의 홀짝성이 불변하는 것을 확인할 수 있습니다.</p>
<h3 id="conclusion">Conclusion</h3>
<p>흰돌의 홀짝성에 따라 홀수라면 마지막엔 흰돌이 짝수라면 검정돌이 남게 됩니다.</p>
]]></description>
        </item>
    </channel>
</rss>