<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>zoe.log</title>
        <link>https://velog.io/</link>
        <description>개발하면서 마주친 문제들을 정리하는 공간입니다. </description>
        <lastBuildDate>Wed, 06 Dec 2023 16:34:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>zoe.log</title>
            <url>https://images.velog.io/images/na-young-kwon/profile/c3e2adbf-39a9-4437-91b8-c4be3dc9244a/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. zoe.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/na-young-kwon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[setNeedsLayout 과 LayoutIfNeeded]]></title>
            <link>https://velog.io/@na-young-kwon/LayoutIfNeeded</link>
            <guid>https://velog.io/@na-young-kwon/LayoutIfNeeded</guid>
            <pubDate>Wed, 06 Dec 2023 16:34:32 GMT</pubDate>
            <description><![CDATA[<h3 id="layoutsubviews">layoutSubViews()</h3>
<ul>
<li>view의 값을 호출한 즉시 변경시켜주는 메서드</li>
<li>호출되면 해당 View의 모든 SubView들의 layoutSubviews()이 연달아 호출된다</li>
<li><code>비용이 많이 드는 메서드이고, 때문에 직접 호출하는 것은 지양된다</code></li>
</ul>
<h4 id="아래의-경우에서는-자동으로-layoutsubviews-메서드-호출이-예약된다">아래의 경우에서는 자동으로 layoutSubviews() 메서드 호출이 예약된다</h4>
<ul>
<li>view의 크기를 조절할 때</li>
<li>subView를 추가할 때</li>
<li>사용자가 UIScrollView를 스크롤 할 때</li>
<li>디바이스를 회전시켰을 때(Portrait, Landscape)</li>
<li>View의 AutoLayout constraint값을 변경시켰을 때</li>
</ul>
<h4 id="하지만-수동으로-layoutsubviews를-예약하고-싶다면">하지만 수동으로 layoutSubviews()를 예약하고 싶다면?</h4>
<p>layoutSubviews를 유도할 수 있는 여러 방법이 존재한다 -&gt; layoutIfNeeded / setNeedsLayout
이는 일종의 update cycle에서 layoutSubviews의 호출을 예약하는 행위라고 할 수 있다</p>
<h3 id="layoutifneeded">layoutIfNeeded()</h3>
<blockquote>
<p>Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.</p>
</blockquote>
<p>이 방법은 뷰가 레이아웃을 즉시 업데이트하도록 합니다. 오토레이아웃을 사용하는 경우 레이아웃 엔진은 constraints의 변경 사항을 충족하기 위해 뷰 위치를 업데이트합니다. 이 방법은 메시지를 수신하는 뷰를 루트뷰로 사용하여 뷰 하위 트리를 루트부터 배치합니다. 보류 중인 레이아웃 업데이트가 없는 경우 이 메서드는 레이아웃을 수정하거나 레이아웃 관련 콜백을 호출하지 않고 종료됩니다.</p>
<h3 id="setneedslayout">setNeedsLayout()</h3>
<blockquote>
<p>Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.</p>
</blockquote>
<p>뷰의 서브뷰 레이아웃을 조정하려면 프로그램의 기본 스레드에서 이 메소드를 호출하십시오. 이 방법은 즉시 업데이트를 수행하지 않고 대신 다음 업데이트 주기를 기다리기 때문에 뷰가 업데이트되기 전에 여러 뷰의 레이아웃을 무효화할 수 있습니다. 이 동작을 통해 모든 레이아웃 업데이트를 한 번의 업데이트 주기로 통합할 수 있으므로 일반적으로 성능이 더 좋습니다.</p>
<h3 id="둘의-공통점">둘의 공통점</h3>
<p>수동으로 <code>layoutSubviews()</code>를 예약하는 행위이이다</p>
<h3 id="둘의-차이점">둘의 차이점</h3>
<p>layoutIfNeeded는 </p>
<ul>
<li>해당 예약을 바로 실행시키는 <code>동기적</code>으로작동하는 메서드이다</li>
<li>update cycle이 올 때까지 기다리는것이 아니라 그 즉시 layoutSubviews()를 발동시킨다</li>
</ul>
<p>setNeedsLayout은</p>
<ul>
<li><code>비동기적</code>으로 작동한다</li>
<li>layoutSubviews()를 예약하는 행위 중 가장 비용이 적게 드는 방법이다</li>
<li>이 메소드를 호출한 View는 재계산되어야 하는 View라고 수동으로 체크가 되며 update cycle에서 layoutSubviews()가 호출되게 된다</li>
<li>View의 보여지는 모습은 update cycle에 들어갔을 때 바뀌게 된다</li>
</ul>
<blockquote>
<p>참고
<a href="https://baked-corn.tistory.com/105">https://baked-corn.tistory.com/105</a>
RunLoop가 무엇일까? <a href="https://babbab2.tistory.com/68">https://babbab2.tistory.com/68</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] 네트워크 통신 여러번 하는 문제 share로 해결하기]]></title>
            <link>https://velog.io/@na-young-kwon/RxSwift-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EC%97%AC%EB%9F%AC%EB%B2%88-%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-share%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@na-young-kwon/RxSwift-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%86%B5%EC%8B%A0-%EC%97%AC%EB%9F%AC%EB%B2%88-%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C-share%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 01 Dec 2023 14:47:36 GMT</pubDate>
            <description><![CDATA[<h3 id="문제점">문제점</h3>
<p>API를 활용해 검색기능을 구현하던 중 통신이 2번 되는 현상이 있었다. 셀을 누르면 네트워크 통신을 하도록 구현해놓았는데, 왜인지 통신이 2번 된다. 왜그럴까?
<img src="https://velog.velcdn.com/images/na-young-kwon/post/7d051a4f-9362-429d-9f09-0f64564aa476/image.png" alt=""></p>
<br>

<h3 id="원인">원인</h3>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/eadb879b-3df1-42e3-ad95-676d3f68fac2/image.png" alt=""></p>
<p>위 사진에서 post는 input.itemSelected에 이벤트가 들어오면 fetchSearchResult로 유즈케이스에서 검색 결과를 가져오는 평범한 코드이다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/9b5457f4-3113-42fb-9c45-fcc974d4d17d/image.png" alt="">
근데 이 옵저블로 emptyPost라는 옵저버블을 하나 더 만들었고, 문제는 여기서 발생한다. 
이렇게 옵저버블을 공유하면 input.itemSelected에 대한 구독이 여러개 생기게 되고, subscribe가 여러번 일어나서 통신도 여러번 되는것이다. 
그니까 하나의 옵저버블을 두 곳에서 사용했고, 구독이 두 번 생성됐고, itemSelected가 방출하는 이벤트가 두 구독에 각각 전달되면서 통신도 두번씩 하게되었다. </p>
<p>결론은 하나의 옵저버블을 두 군데에서 사용하는게 문제였다.</p>
<br>

<h3 id="해결">해결</h3>
<p>이 문제를 해결하기 위해서 옵저버블을 한 번만 구독하는 방법에 대해 검색해보았고 <code>share()</code> 라는 키워드를 알게되었다. </p>
<p>share()는 하나의 옵저버블을 여러 구독자에게 공유하게 해준다. 즉, 구독을 한 번만 생성하고 이후에 동일한 구독을 여러 구독자에게 재사용하게 해준다.</p>
<p>코드로 예를 들자면:</p>
<pre><code class="language-swift">let post = input.itemSelected
    .flatMap {
        self.useCase.fetchSearchResult(with: $0)
     }.share()

let emptyPost = post
    .map { $0.isEmpty ? &quot;EmptySearch&quot; : nil }
    .asDriver(onErrorJustReturn: nil)</code></pre>
<p>post를 share()로 선언하면 emptyPost에서 post를 사용할 때 새롭게 구독을 만드는게 아니라, 동일한 구독을 재사용하게 된다. 이렇게 하면 리소스도 효율적으로 사용할 수 있게 된다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/2e3eefbf-3e0a-4282-a75f-63c26f5f408e/image.png" alt=""></p>
<p>이렇게 공유하고자 하는 옵저버블에 share 오퍼레이터를 추가해주었다. 그결과 통신이 한 번만 되는것을 확인할 수 있었다. 불필요한 통신을 제거하고 리소스를 절약하게 되었다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/ef4f04e5-13e2-4e41-b8f2-922f22a2ec8a/image.png" alt="">
이렇게 행복하게 마무리되는듯 싶었으나, 그럴리 없지.</p>
<br>

<h3 id="또-다른-문제">또 다른 문제</h3>
<p>이번엔 다른 문제다. 두 번째 입력부터 셀이 업데이트 된다...
<img src="https://velog.velcdn.com/images/na-young-kwon/post/0b8d142c-9ae0-4103-b7e4-e01b214116cb/image.gif" alt="">
textField로 들어오는 값을 받아서 테이블뷰 셀을 띄우는 기능인데, 원래라면 첫번째 <code>ㅇ</code>을 입력했을 때 셀이 업데이트 되어야 한다. 
문제는 텍스트필드가 분명 이벤트는 방출하는데, 첫번째 글자를 입력할때는 업데이트가 안되고, 두번째 글자 이벤트를 수신하고 셀이 업데이트 되는 것이다.</p>
<br>

<h3 id="원인-1">원인</h3>
<p>처음에는 textField에서 첫번째 이벤트를 안보내는줄 알았다. 근데 .debug()로 확인해보니 이벤트는 정상적으로 보낸다. 그러면 뭐가문제지 생각하다가.. 구독을 늦게하나? 이런 생각이 들었다. 
구독 타이밍이 첫번째 글자 이후에 되기 때문인가 하는 의심이 들기 시작했고, 그게 맞았다. 구독하는 시점이 첫 번째 값 방출 이후다 보니까, 첫번째 이벤트를 받을 수 없던 것이었다.</p>
<p>이 문제는 share의 replay를 1로 설정하면서 해결할 수 있었다.</p>
<br>

<h3 id="sharereplay-1">Share(replay: 1)</h3>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/d4e0b86d-4365-4adb-b98f-f899ce9e8ed2/image.png" alt=""></p>
<p>share()는 하나의 구독을 여러 구독자와 공유하게 해주는 역할을 한다. 
다만, share() 연산자가 반환하는 Observable을 구독하는 시점이 중요하다. </p>
<p>share는 hotObservable을 반환하기 때문에 구독자가 있던 없던 걍 이벤트를 방출해 버린다. 따라서 <strong>share()를 한 후, 해당 Observable을 구독하기 전에 이미 이벤트가 방출되었다면 그 이벤트를 놓칠 수 있게 되는것이다.</strong></p>
<p>그래서 share()를 사용할때는 <strong>Observable을 구독하는 시점을 잘 고려해야 한다!</strong></p>
<br>

<h3 id="sharereplay-1-의-정확한-의미는-뭘까">share(replay: 1) 의 정확한 의미는 뭘까?</h3>
<p>share(replay: 1)는 <strong>Observable이 방출하는 가장 최근의 1개의 이벤트를 저장해 둔다는 뜻이다</strong>. 만약 이후에 새로운 구독자가 생겼을 때, 이미 방출된 가장 최근의 이벤트를 즉시 받을 수 있도록 해준다.</p>
<blockquote>
<p>예를 들어, searchTerm이 &quot;검색어1&quot;, &quot;검색어2&quot;, &quot;검색어3&quot; 세 개의 이벤트를 순서대로 방출했다고 가정해 보자. share()만 사용했을 때는, searchTerm을 구독하는 시점에 따라 이벤트를 놓칠 수 있다.
하지만 share(replay: 1)를 사용하면, searchTerm을 구독하는 시점에 상관없이 가장 마지막에 방출된 &quot;검색어3&quot; 이벤트를 받을 수 있다. 이후에 searchTerm이 새로운 이벤트를 방출하면, 그 때부터는 새로운 이벤트를 계속 받게 된다.</p>
</blockquote>
<br>

<h3 id="해결-1">해결</h3>
<p>share(replay: 1) 코드를 추가해줬더니 searchTerm Observable이 이벤트를 방출한 후에 구독해도 이벤트를 받을 수 있게 되었고,  가장 최근 이벤트를 놓치지 않고 셀의 띄울 수 있었다.
이제 첫번째 입력부터 셀이 업데이트 된다!
<img src="https://velog.velcdn.com/images/na-young-kwon/post/587b1011-3ca4-4157-a3ad-0c7457cac790/image.gif" alt=""></p>
<h3 id="결론">결론</h3>
<p>하나의 네트워크 요청을 여러 곳에서 동시에 사용하고 싶을 때 share() 연산자를 사용할 수 있다. 이 경우, 실제 네트워크 요청은 한 번만 이루어지지만 그 결과를 여러 곳에서 동시에 사용할 수 있게 된다.
그리고 새로운 구독자가 생겼을 때, 이미 방출된 가장 최근의 이벤트를 즉시 받을 수 있도록 하려면 share(replay: n)을 사용하고, 이는 가장 최근의 n개의 이벤트를 저장해 둔다는 의미이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어떻게 하면 이미지를 효율적으로 처리를 할 수 있을까? (DownSampling)]]></title>
            <link>https://velog.io/@na-young-kwon/%EB%8B%A4%EC%9A%B4%EC%83%98%ED%94%8C%EB%A7%81%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@na-young-kwon/%EB%8B%A4%EC%9A%B4%EC%83%98%ED%94%8C%EB%A7%81%EC%9D%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Wed, 22 Nov 2023 07:44:43 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/51704045-5e70-42db-8ff3-dc82754da56f/image.png" alt=""></p>
<p>다운샘플링을 통해 이미지를 효율적으로 처리하는 방법 알아보자</p>
<h3 id="서론">서론</h3>
<p>쇼핑몰 컬렉션뷰를 구현하는 과정에서 크기가 큰 이미지가 들어올 때 아래같은 현상이 발생했다. </p>
<ol>
<li>이미지 로딩 속도가 느리고, 메모리 사용량이 급격하게 늘어나는 문제</li>
<li>스크롤시 이미지가 깜빡거리면서 바뀌기도 하는 현상</li>
</ol>
<p>이 문제를 다운샘플링으로 해결할 수 있었는데, 오늘 그것에 대해 알아보고자 한다.</p>
<blockquote>
<p>들어가기 전에 알아둘 것!
메모리 많이 사용 → CPU 활용률 증가 → 많은 CPU 사용 → 배터리 수명 / 앱의 반응성에 부정적인 영향을 미침</p>
</blockquote>
<br>

<h3 id="image-and-graphics-best-practices"><strong>Image and Graphics Best Practices</strong></h3>
<p>UIImage는 이미지를 로드한다.</p>
<ul>
<li>데이터 버퍼를 → 이미지 버퍼로 디코딩하는 역할</li>
</ul>
<p>UIImageView는 렌더링을 담당한다.</p>
<ul>
<li>이미지 버퍼를 렌더링 해서 → 프레임 버퍼에 표시하는 역할</li>
</ul>
<br>

<h3 id="다운샘플링이란-뭘까">다운샘플링이란 뭘까?</h3>
<p>디코딩 작업이 비싸다고 하니까 디코딩 하기 전에 사진의 사이즈를 줄이고 → 디코딩하자고 하는것.
그리고 그 디코딩된 이미지 버퍼를 저장하자! 라는 아이디어가 다운샘플링이다. 
이렇게 했을 때 앱의 메모리 사용량을 줄일 수 있다. 
<img src="https://velog.velcdn.com/images/na-young-kwon/post/5dae1451-c2ea-4ff3-8500-c2dbdeacccfc/image.png" alt="">
사진에서 <code>Thumbnail()</code> 부분이 다운샘플링을 진행하는 곳이다. 순서를 봤을 때 디코딩 작업 전에 위치하는것을 확인할 수 있다. </p>
<br>

<h3 id="디코딩이란">디코딩이란?</h3>
<p>네트워크를 통해 다운받았거나 디스크에서 읽어온 이미지 파일이 data buffer에 담겨있다.</p>
<p>우리는 각 픽셀에 대한 데이터를 frame buffer에 제공해줘야 한다. 하지만 data buffer에는 각 픽셀에 대한 정보가 담겨져 있지 않다. 여기서 decoding 개념이 나오게 되는 것이다. </p>
<ul>
<li>데이터 버퍼 → 이미지 버퍼로 바꾸는 작업</li>
<li>JPEG, PNG로 된 데이터를 각 픽셀 이미지 정보로 바꾸어주는 작업</li>
</ul>
<p>이때, UIImage는 data buffer에 담겨져 있는 이미지의 크기와 같은 image buffer를 할당해준다. 이게 문제임.</p>
<br>

<h3 id="디코딩은-비싸다">디코딩은 비싸다</h3>
<p>UIImage가 하는 <strong>decoding</strong> 작업은 굉장히 비싸다. 이유는
<img src="https://velog.velcdn.com/images/na-young-kwon/post/af92f1fb-7d33-4458-84bd-c92c04d61e47/image.png" alt=""></p>
<p><strong>decoding</strong> 단계는 특히나 사이즈가 큰 이미지에서 <strong>CPU-intensive process</strong>이다.</p>
<p>디코딩은 이미지가 클 경우 CPU를 많이 소모할 가능성이 있기 때문에 UIImageView가 랜더링할 때마다 디코딩을 수행하지 않는다. 대신, 디코딩된 이미지를 이미지 버퍼에 보관된다. </p>
<p>때문에 디코딩되는 모든 이미지에 대해 영구적이고 큰 메모리 할당이 필요해질 수 있다. (한번 디코딩 해놓으면 렌더링 하기 전에는 그 이미지를 다시 디코딩 하지 않기 때문에 → 영구적이라고 하는것 같다. 특히 사진이 큰 경우에 메모리 할당이 더 많아지는 측면에서 그 단점이 더 부각되는 듯..)</p>
<p><strong>정리하자면</strong> </p>
<p>UIKit이 계속 image view에게 rendering하도록 요청하는 것이 아니라 UIImage가 해당 image buffer를 계속 가지고 있어 한 번만 해당 작업이 일어나도록 한다. 또, decoding된 이미지 데이터가 image buffer에 보관되기 때문에 decode되는 모든 이미지에 대해 영구적이고 큰 메모리 할당이 필요해질 수 있다.</p>
<p><strong>따라서 크기가 작은 데이터 버퍼를 디코딩 하는것이 유리하다.</strong> 그 이유는
→ 디코딩 이라는 작업이 이미지 크기가 큰 경우 CPU를 많이 소모한다
→ 따라서 이미지뷰가 렌더링 할때마다 디코딩을 하지 않는다 (비용이 많이 들기 때문에)
→ 디코딩 된 이미지는 이미지 버퍼에 보관된다 
→ 따라서 디코딩 되는 모든 이미지에 대해 영구적이로 큰 메모리 할당이 필요해질 수 있다</p>
<br>

<h3 id="프로젝트로-실습하기">프로젝트로 실습하기</h3>
<p>실습을 통해서 <strong>실제로 메모리 사용량을 줄어드는지 알아보았다.</strong>
간단한 예시 프로젝트를 만들어서 화면에 <code>이미지 뷰</code>와  <code>일반버튼</code>, 그리고 <code>다운샘플버튼</code>을 추가해주었다. </p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/e30f2582-0d75-491e-b834-99aad2a1cae5/image.png" alt=""></p>
<p>두 버튼 모두 이미지 뷰에 이미지를 로드하는 액션을 연결해 주었다. 
단, 일반버튼은 원본 이미지를 그대로 띄우는 방식이고, 다운샘플버튼은 다운샘플링한 이미지를 로드하는 방식이다.
(이미지는 6048 x 4024 사이즈를 사용했다. 일부러 해상도가 엄청 높은 사진을 가져와봤다.)</p>
<br>

<h3 id="코드">코드</h3>
<p><code>일반 방식</code> 
파라미터로 url을 받아서 이미지 뷰에 넣어주는 코드이다</p>
<pre><code class="language-swift">func setImage(url: URL) {        
    guard let data = try? Data(contentsOf: url),
          let image = UIImage(data: data) else {
        return
    }

    DispatchQueue.main.async {
        self.mainImageView.image = image
    }
}</code></pre>
<p><code>다운샘플</code>
다운샘플링을 구현한 코드 이다</p>
<pre><code class="language-swift">func downsample(url: URL) {        
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else {
        return
    }

    let maxDimensionInPixels = 200 * 8
    let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
                             kCGImageSourceShouldCacheImmediately: true,
                             kCGImageSourceCreateThumbnailWithTransform: true,
                             kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return
    }
    let image = UIImage(cgImage: downsampledImage)
    DispatchQueue.main.async {
       self.mainImageView.image = image
    }
}</code></pre>
<br>

<h3 id="메모리-사용량-확인하기">메모리 사용량 확인하기</h3>
<p>두 버튼을 각각 눌러봤을 때 메모리 사용량에 어떤 변화가 있는지 <code>Instrument</code>의 <code>Allocation</code>을 통해 메모리 사용량을 확인해보았다. 
결과는 이렇게 나왔는데, 더 자세히 알아보면</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/452e9684-b112-4db6-857f-2bc0a7f0bc3e/image.png" alt=""></p>
<p>URL을 직접 이미지를 할당했을때는 메모리 사용량이 <strong>304MB</strong> 인것을 확인할 수 있다</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/82372ffc-360a-4b1d-9da5-c89a60a62cf7/image.png" alt=""></p>
<p>반면 다운샘플링을 사용했을땐 메모리 사용량이 <strong>199MB 이다. (</strong>메모리 105MB 덜 사용)</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/6535965c-ec18-4ea6-af47-e0fe116faaef/image.png" alt="">
해석해보자면 보통은 150MB정도를 유지하고 있다가, 일반 버튼을 누으면 304MB로 메모리가 치솟고, 다운샘플 버튼을 누으면 199MB의 메모리를 사용하게 된다.
확실히 다운샘플링을 구현한 쪽이 메모리 사용량이 적은 것을 확인할 수 있었다. </p>
<br>

<h3 id="이미지-퀄리티-차이는">이미지 퀄리티 차이는?</h3>
<p>왼쪽이 일반적이 방법, 오른쪽이 다운샘플링 방법일 때를 나타낸다. 확인해보면 생각보다 차이가 안나는것을 알 수 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/na-young-kwon/post/7866ebfd-9488-4ecf-80f6-2c9928dbeed0/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/na-young-kwon/post/c6e9cae4-a936-4d7c-b0c3-672fa5e4437e/image.png" alt=""></th>
</tr>
</thead>
</table>
<br>

<h3 id="마무리">마무리</h3>
<p>이렇게 몇 번의 처리만으로 메모리를 상당히 아낄 수 있다는 것을 깨달았다. 나의 경우는 사진이 많이 크진 않아서 앱이 깜빡거리는 정도였지만, 썸네일을 많이 사용하는 쇼핑몰같은 경우에는 앱이 종료되는 일이 발생할 수도 있으므로 다운샘플링을 통한 이미지 최적화가 필수일 것 같다. </p>
<p>킹피셔 라는 라이브러리에서도 다운샘플링 기능통해서 이미지 최적화를 한다고 한다. 아직 사용해보진 않았지만 추후에 라이브러리를 사용하게 될 일이 있다면 꽤 유용할 것으로 예상된다.</p>
<br>

<h3 id="참고">참고</h3>
<p><a href="https://ahyeonlog.tistory.com/87">https://ahyeonlog.tistory.com/87</a>
<a href="https://developer.apple.com/videos/play/wwdc2018/416/">https://developer.apple.com/videos/play/wwdc2018/416/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[COW에 관한 고찰]]></title>
            <link>https://velog.io/@na-young-kwon/COW%EC%97%90-%EA%B4%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@na-young-kwon/COW%EC%97%90-%EA%B4%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Thu, 09 Nov 2023 09:15:13 GMT</pubDate>
            <description><![CDATA[<p>스위프트에서는 값타입을 최적화하기위해 COW를 사용한다
값타입이 복사될때 실제 복사가 일어난다면 너무 많은 메모리를 사용하게 되고, 성능이 저하될 수 있다.
이것을 최적화 하기 위해 복사될때는 *<em>원본 리소스를 공유하고 있다가, 값이 수정될 때 실제 복사가 일어나도록 하는 방법이다. *</em></p>
<p>그리고 공식문서에 &quot;Array, Set, Dictionary에는 COW가 구현되어 있습니다.&quot; 고 나와있다. </p>
<p>그런데 실험을 하는 중에 <strong>왜 Set과 Dictionary에는 COW가 적용되지 않을까?</strong> 라는 의문이 들어서 이 글을 적게 되었다.</p>
<br>

<h2 id="메모리-주소-확인">메모리 주소 확인</h2>
<p>본격적으로 실험을 하기 전에 같은 메모리주소를 가졌는지 확인해보기한 메서드를 정의했다. 
아래 두 메서드는 객체의 메모리 주소를 출력해주는 메서드이다.</p>
<pre><code class="language-swift">// 구조체용 메모리 주소 확인 메서드
func address(o1: UnsafeRawPointer) {
    let address = String(format: &quot;%p&quot;, Int(bitPattern: o1))
    print(address)
}


// 클래스용 메모리주소 확인 메서드
func address&lt;T: AnyObject&gt;(o2: UnsafePointer&lt;T&gt;) {
    let address = String(format: &quot;%p&quot;, Int(bitPattern: o2))
    print(o2.pointee) // 변수가 가르키는 타입
    print(address) // 주소값
}</code></pre>
<br>

<h2 id="실험시작">실험시작</h2>
<p>실험에 앞서 구조체를 하나 선언해주었다.</p>
<pre><code class="language-swift">struct MyStruct {
    var name = &quot;zoe&quot;
}</code></pre>
<p>A, B 구조체를 선언하고 각각의 메모리 주소를 확인해봤을때 </p>
<pre><code class="language-swift">var structA = MyStruct()
var structB = structA

address(o1: &amp;structA) // 0x10aa38950
address(o1: &amp;structB) // 0x10aa38960</code></pre>
<p>메모리 주소가 다른것을 확인할 수 있었다. 즉, 할당하는 순간 복사가 일어났다고 할 수 있다. 
왜지 분명 수정할때 복사가 일어난다고 했는데..?
COW가 작동하고 있지 않네! 라고 생각할수도 있다</p>
<p>근데 이 결과가 옳은 결과이다. 왜냐하면 커스텀 타입에는 COW가 구현되어있지 않기 때문이다. 즉 직접 COW를 구현해줘야 한다는 의미이다.</p>
<br>

<h2 id="custom-type-에서의-값타입-복사">Custom type 에서의 값타입 복사</h2>
<p>기본 데이터 타입들(예: String, Array 등)은 Swift 표준 라이브러리에서 제공하며, Copy-on-Write 최적화가 구현되어 있다. 따라서 이들 타입의 인스턴스는 값이 변경될 때만 실제로 복사가 일어나게 된다.</p>
<p>그러나 사용자가 정의한 구조체에 대해서는, 이런 최적화를 수동으로 구현해야 한다. 만약 수동으로 CoW를 구현하지 않는다면, <strong>구조체 인스턴스는 메모리에 할당될 때마다 복사된다.</strong> </p>
<br>

<h2 id="array의-복사">Array의 복사</h2>
<p>Swift에서 제공하는 기본 데이터 타입들은 COW가 구현되어 있다고 했다.
따라서 컬렉션 타입 중 하나인 Array에서 복사가 최적화 되는지 먼저 살펴보자!</p>
<p><strong>가설1. 값타입을 복사하기 전에는 메모리 주소가 같아야 한다</strong></p>
<pre><code class="language-swift">var numberA = [1, 2, 3, 4]
var numberB = numberA

address(o1: &amp;numberA) // 0x6000024d9ea0
address(o1: &amp;numberB) // 0x6000024d9ea0</code></pre>
<p>주소를 확인해보니 정말 같은 메모리 주소를 공유하고 있는것을 확인할 수 있었다. </p>
<br>

<p><strong>가설2. 복사후 값을 수정하면 메모리 주소가 달라야 한다</strong></p>
<pre><code class="language-swift">var numberA = [1, 2, 3, 4]
var numberB = numberA

numberB[0] = 3

address(o1: &amp;numberA) // 0x6000009044a0
address(o1: &amp;numberB) // 0x6000009046e0</code></pre>
<p>0번째 값을 수정해주었더니, 그제서야 복사가 일어났고 
-&gt; 메모리 주소가 달라졌음을 확인할 수 있었다.</p>
<p>가설1,2가 모두 맞는것으로 보아 Array에서는 COW가 잘 작동한다고 할수있다. 
즉, 배열을 복사하면 원본 리소스를 공유하고 있다가, 값의 수정이 일어나면 실제 복사가 이루어진다는 것을 알게되었다. </p>
<br>

<h2 id="set와-dictionary의-복사">Set와 Dictionary의 복사</h2>
<p>먼저 Set으로 실험해보면</p>
<p><strong>가설1. 값타입을 복사하기 전에는 메모리 주소가 같아야 한다</strong></p>
<pre><code class="language-swift">var numberA: Set = [1, 2, 3, 4]
var numberB = numberA

address(o1: &amp;numberA) // 0x106b70970
address(o1: &amp;numberB) // 0x106b70978</code></pre>
<p>결과: <code>0x106b70970</code> vs <code>0x106b70978</code> 
주소값이 다르다. 왜지? 분명 값을 수정하지 않았는데 메모리 주소가 다르게 나온다. COW가 되고있지 않다고 할 수 있다.</p>
<br>

<p><strong>그렇다면 딕셔너리도?</strong></p>
<pre><code class="language-swift">var dictionaryA = [&quot;0&quot;: 0]
var dictionaryB = dictionaryA

address(o1: &amp;dictionaryA) // 0x108574990
address(o1: &amp;dictionaryB) // 0x108574998</code></pre>
<p>결과: <code>0x108574990</code> vs <code>0x108574998</code> 
딕셔너리 역시 값을 수정하기 전부터 두 인스턴스의 메모리 주소가 다르게 출력되었다.</p>
<br>

<h2 id="스택오버플로우">스택오버플로우</h2>
<p>왜그런걸까? COW는 Array에만 적용 되는 건가? 
고민중에 스택오버플로우에서 글을 발견했다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/882cf81f-392b-4a19-a283-2efc2c371f6e/image.png" alt="">
위 글을 해석해보면 </p>
<blockquote>
<p>컬렉션 타입은 결국 값 타입 이기 때문에 값이 전달되는 그 순간 즉시 새로운 메모리 주소가 할당될 가능성이 있다. 그들의 backing storage만 공유될 뿐이다.</p>
</blockquote>
<p>라고 적혀있는데, 사실 이분들도 정확히 모르는 것 같다. 
답변이 전부 &quot;가능성이 있다&quot; 로 적혀있기도 하고, 뭐가 정답인지 모르겠다.
결국 명쾌하게 답을 못내린채 실험을 종료했다..</p>
<h2 id="느낀-점">느낀 점</h2>
<p>아무리 공식문서에서 Set에 COW가 구현되어 있기 때문에 최적화된다고 하더라도 실제와 다르다는 점이 놀라웠다. COW로 인해서 메모리 주소를 공유할 수 있지만, 무조건 그렇게 동작하는건 아니라는 것만 알고 넘어가려고 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RxSwift] Hashable에 관하여]]></title>
            <link>https://velog.io/@na-young-kwon/Hashable</link>
            <guid>https://velog.io/@na-young-kwon/Hashable</guid>
            <pubDate>Tue, 07 Nov 2023 12:38:00 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기">시작하기</h3>
<p>DiffableDataSource를 공부하면서 identifier의 타입이 Hashable해야 한다는 말을 많이 들어봤을 것이다.
그래서 Hashable이 뭘까?</p>
<h2 id="diffabledatasource">DiffableDataSource</h2>
<p>기존의 DataSource는 section과 item의 indexPath를 들고 있어서 만약 데이터가 수정, 삭제된다면 indexPath도 변경될 수 있기 때문에 불안정한 정보를 가진다. 
특정 indexPath에 반드시 해당 값이 존재한다고 확실할수가 없기 때문에 reloadData()를 할 때, 어떤 데이터가 변했는지 datasource에서 파악할 수가 없어서 모든 데이터를 다시 로딩하여 컬렉션뷰를 업데이트 한다.</p>
<p>이런 한계를 극복하기 위해 DiffableDataSource가 나왔는데, DataSource와 비교해서 DiffableDataSource가 안정적인 이유는 뭘까? </p>
<p>결론부터 말하면 identifier덕분이다.
구체적으로 변경된 item이 무엇인지 파악하는 방법은 
현재 상태를 나타내는 스냅샷과 이전 상태를 나타내는 스냅샷을 비교해서 달라진 item을 파악하는것. 
이때 비교는 identifier로 하게 된다. 
변하지 않는 identifier덕분인데, 그러면 어떻게 변하지 않는 identifier를 만들 수 있을까?</p>
<h2 id="hashable">Hashable</h2>
<p>타입이 Hashable 하다는것 = 값이 해시함수에 들어가서 정수 해시값으로 변경될 수 있다는 것을 의미한다.
변환된 해시 값은 해시 테이블의 key로 사용될 수 있다.</p>
<p>해시테이블은 딕셔너리처럼 key값을 index로 사용해서 value를 저장한다.
key만 가지고 있으면, 쉽게 해시 테이블에서 저장된 value를 꺼내올 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/d30c0be1-cba0-4f00-9757-5b6bf1738b29/image.png" alt=""></p>
<p>해시테이블은 딕셔너리나 Set처럼 순서가 없지만, 내부적으로는 배열로 구현되어 있어서 index가 있다.</p>
<p>순서가 없다면서 무슨 index가 나오지? 할 수 있지만
해시함수가 있어서 가능한 일이다.</p>
<p>그림의 예시를 보면 James라는 키가 해시함수에 들어가면 04라는 해시값(index)로 변환되는 모습을 볼 수 있다.
해시함수가 키를 해시 주소값(index)로 변환해주어서 해시 테이블 내부적으로 인덱스가 존재할 수 있었던 것이다. </p>
<p>다시 정리하자면, 해시 함수를 이용해 key를 해시 주소값(해시 테이블의 index)으로 바꾸고,
해시 주소값(해시 테이블의 index)를 통해 해시 테이블에 접근하여 값을 가져오거나 저장하는 형태이다.</p>
<h2 id="해시의-장점">해시의 장점</h2>
<p>해시테이블은 key-value가 1:1로 매핑되어 있기 때문에 삽입, 삭제, 검색의 과정에서 모두 평균적으로O(1)의 시간복잡도를 가지고 있다.</p>
<h2 id="해시의-단점">해시의 단점</h2>
<p>해시 충돌이 발생(개방 주소법, 체이닝 과 같은 기법으로 해결해 줘야 한다.)</p>
<p><strong>그러면 여기서 드는 의문은 Hashable은 왜 Equatable을 상속받을까?</strong></p>
<h2 id="equatable을-채택해야-하는-이유">Equatable을 채택해야 하는 이유</h2>
<p>Equatable 프로토콜을 채택하면 두 객체가 서로 같은 값을 가졌는지 비교할 수 있기 때문이다.
Hashable은 Equatable을 상속받고 있는데, 그 이유는 hashvalue가 항상 unique하지 않기 때문이다.
hashValue가 같아서 해시 충돌이 날 수 있기 때문에, Equatable이 필요한 것이다.</p>
<ul>
<li>hashValue로는 사용자가 찾는 객체에 접근하고</li>
<li>Equatable의 구현사항인 == 함수로 hashValue가 고유값인지 식별한다</li>
</ul>
<h2 id="자동으로-hashable한-타입은-뭐가-있지">자동으로 Hashable한 타입은 뭐가 있지</h2>
<p>스위프트 스탠다드 라이브러리 중 거의 다 이미 Hashable을 채택하고 있음</p>
<p>따라서,, 따로 우리가 채택해주지 않아도 값을 비교할 때 ==를 사용할 수 있다.</p>
<p>String, Integer, Float, Bool, Double</p>
<h2 id="커스텀-타입을-만약-hashable을-채택하도록-하려면">커스텀 타입을 만약 Hashable을 채택하도록 하려면</h2>
<ol>
<li><p>모든 저장 프로퍼티가 Hashable한 Int타입인 경우 Hashable만 채택하면 hash(into:) 구현하지 않아도 자동으로 생김</p>
</li>
<li><p>그 외의 경우에는 두가지를 구현해줘야 하는데</p>
</li>
<li><p><code>== 함수</code> ← Equatable 프로토콜을 따라야해서</p>
</li>
<li><p><code>hash(into:)</code> 함수 ← Hashable 프로토콜을 따라야해서</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Alamofire 적용하고 가독성 높이기]]></title>
            <link>https://velog.io/@na-young-kwon/Alamofire-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B3%A0-%EA%B0%80%EB%8F%85%EC%84%B1-%EB%86%92%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@na-young-kwon/Alamofire-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B3%A0-%EA%B0%80%EB%8F%85%EC%84%B1-%EB%86%92%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Mon, 18 Sep 2023 15:17:58 GMT</pubDate>
            <description><![CDATA[<h2 id="alamofire로-네트워크-레이어-개선하기">Alamofire로 네트워크 레이어 개선하기</h2>
<p>오늘은 Alamofire에 대해서 알아보려고 한다. 사실 야곰아카데미에서는 오픈소스, 라이브러리를 사용하기보단 원리를 이해하고 기초를 알길 권장하기 때문에 한번도 사용해본적이 없었다. 근데 Alamofire가 채용공고에 자주 등장하기도 하고 어떤라이브러리인지 궁금해서 이번기회에 Alamofire에 대해 알아보고, HighwayInfo 프로젝트에 적용해보고자 한다.</p>
<h3 id="개요">개요</h3>
<p>Alamofire는 애플의 서버 통신 API인 URLSession을 기반으로 더 편리한 네트워킹을 도와주는 라이브러리이다. </p>
<ul>
<li>복잡한 네트워킹 작업을 은닉하고 주요 로직에 집중할 수 있도록 해주어서 많은 기업에서 사용하고 있는것같다.</li>
<li>또, 코드가 더 간단해지고 여러 기능을 직접 구축하지 않아도 된다는 것도 장점이다.</li>
</ul>
<h3 id="구현-목표">구현 목표</h3>
<ul>
<li>Request Routing을 활용해 Moya를 사용하지 않고 구현해보기</li>
<li>중복되는 코드 최소화</li>
</ul>
<table>
<thead>
<tr>
<th>메서드 1</th>
<th>메서드 2</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/na-young-kwon/post/819b0a04-0d96-4a39-8ba9-25a863e81417/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/na-young-kwon/post/6c1b5415-1dee-4d53-8ee3-1b5d7cc8a81c/image.png" alt=""></td>
</tr>
</tbody></table>
<p>이렇게 파라미터만 다른 중복되는 두 메서드를 리팩토링 해보고자 합니다.</p>
<h3 id="alamofire-코드에-적용-하기">Alamofire 코드에 적용 하기</h3>
<p>우선 가장 먼저 만들어볼것은 서버 통신 규약을 정의하는 Router 프로토콜과 해당 프로토콜을 따르면서 실제 서버 통신을 담당하는 RouterManager를 만들어보겠습니다. </p>
<h3 id="1-router">1. Router</h3>
<p>Router는 네트워크 통신에 필수요소인 URL과 path, HTTP 통신 방법, 헤더, 그리고 데이터 요청 업무인 task 통 5가지를 가지도록 선언해주었습니다.</p>
<pre><code class="language-swift">import Alamofire

protocol Router {    
    var baseURL: URL { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var headers: [String: String]? { get }
    var task: Task { get }
}

enum Task {
    case requestPlain
    case requestJSONEncodable(Encodable)
    case requestParameters(parameters: [String: Any])
}
</code></pre>
<p>그 다음으로 해당 프로토콜을 채택해 실제 통신을 구현하는 RouterManager를 만들어보겠습니다.</p>
<h3 id="2-router-manager">2. Router Manager</h3>
<p>RouterManager는 Session과 Interceptor를 가집니다.
<code>request</code> 메서드를 호출하면 통신이 이루어지고
메서드의 실구현은 extension으로 빼서 가독성을 높여보았습니다.</p>
<p>에러에 대해서도 커스텀한 에러 프로토콜을 만들고(<code>RouterManagerError</code>) 채택해 서비스에 맞는 에러 형태를 띠도록 구현해보았습니다.</p>
<pre><code class="language-swift">struct RouterManager&lt;T: Router&gt; {
    private let alamofireSession: Session
    private let interceptor: Interceptor?

    init(alamofireSession: Session = .default, interceptor: Interceptor? = nil) {
        self.alamofireSession = alamofireSession
        self.interceptor = interceptor
    }

    func request(router: T) -&gt; Single&lt;Data&gt; {
        return sendRequest(router: router)
    }
}
</code></pre>
<p>sendRequest 메서드를 보면 </p>
<pre><code class="language-swift">extension RouterManager {
    func sendRequest(router: T) -&gt; Single&lt;Data&gt; {
        return Single.create(subscribe: { single in
            let dataRequest: DataRequest = makeDataRequest(router: router)

            dataRequest

             // response.result로 응답의 결과를 알 수 있음
                .responseData { response in

                   // switch를 사용해서 결과에 따른 클로져를 작성
                    switch response.result {
                    case .success:
                        guard let statusCode = response.response?.statusCode else { return }
                        let isSuccessful = (200..&lt;300).contains(statusCode)

                        if isSuccessful {
                            guard let data = response.data else { return }
                            single(.success(data))
                        } else {
                            let error = RouterManagerError(code: .isNotSuccessful, response: response)
                            single(.failure(error))
                        }

                    // 요청이 실패하는 경우 .failure과 error를 전달
                    case .failure(let underlyingError):
                        let error = RouterManagerError(
                            code: .failedRequest,
                            underlying: underlyingError,
                            response: response
                        )
                        single(.failure(error))
                    }
                }
            return Disposables.create()
        })
    }
}</code></pre>
<h3 id="3-api-통신을-하기-위한-request-구성하기">3. API 통신을 하기 위한 Request 구성하기</h3>
<pre><code class="language-swift">import Alamofire

enum AccidentAPI {
    // api 목록
    case getAccidents
}

extension AccidentAPI: Router {
    // base url
    var baseURL: URL {
        return URL(string: &quot;http://openapigits.gg.go.kr/api/rest/&quot;)!
    }

    // base url 뒤에 붙는 path
    var path: String {
        switch self {
        case .getAccidents:
            return &quot;getIncidentInfo?&quot;
        }
    }

    // API 요청방식
    var method: HTTPMethod {
        switch self {
        case .getAccidents:
            return .get
        }
    }

    // API 요청 헤더
    var headers: [String: String]? {
        return nil
    }

    // 인코딩 방식
    // 파라미터로 보낼야할 것이 있으면 .requestParameters
    // 바디에 담아서 보내야할 것이 있으면 .requestJSONEncodable
    var task: Task {
        switch self {
        case .getAccidents:
            return .requestParameters(
                parameters: [&quot;serviceKey&quot;: Bundle.main.accidentApiKey]
            )
        }
    }
}</code></pre>
<h3 id="4-api-통신-service-객체">4. API 통신 Service 객체</h3>
<p>실제 API통신을 수행하는 Service 객체를 만들어보자</p>
<pre><code class="language-swift">import Foundation
import RxSwift

struct AccidentService {
    let fetchAccidents: () -&gt; Observable&lt;[AccidentDTO]&gt;

    init(fetchAccidents: @escaping () -&gt; Observable&lt;[AccidentDTO]&gt;) {
        self.fetchAccidents = fetchAccidents
    }
}

extension AccidentService {
    static let live = Self(
        fetchAccidents: {
            return RouterManager&lt;AccidentAPI&gt;
                .init()
                .request(router: .getAccidents)
                .map({ data in
                    let parser = AccidentParser(data: data)
                    let decoded = parser.parseXML()
                    return decoded
                })
                .asObservable()
        }
    )
}
</code></pre>
<ul>
<li>여기에서 하는 일은 API를 연결시키고 - 디코딩한 후 - map으로 원하는 데이터로 변환 하게된다.</li>
<li>해당 서비스를 통해 연결시켜줄 API 종류를 프로퍼티로 가진다.</li>
</ul>
<h3 id="5-실제-코드에서-호출">5. 실제 코드에서 호출</h3>
<ul>
<li>Service에서 API 통신 후 받은 데이터들 중 뷰에 필요한 데이터를 Repository에서 호출해주면 된다.</li>
<li>그 후 UseCase에서 Repository에 원하는 데이터를 요청하는 방식으로 구현해보았다.</li>
</ul>
<pre><code class="language-swift">final class DefaultAccidentRepository: AccidentRepository {
    private let service: AccidentService

    init(service: AccidentService) {
        self.service = service
    }

    func fetchAllAccidents() -&gt; Observable&lt;[AccidentDTO]&gt; {
        service.fetchAccidents()
    }
}
</code></pre>
<h3 id="결론">결론</h3>
<p>Alamofire를 적용하면서 이렇게 중복되는 메서드를 리팩토링해보았습니다. RouterManager를 보면 기존에 두번 반복되던 코드가 사라진걸 확인해볼 수 있어요.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/2e3fdc17-1388-4b39-8b22-22c4e5752321/image.png" alt=""></p>
<h3 id="소감">소감</h3>
<ul>
<li>파라미터만 다른 두 메서드를 리팩토링하여 하나의 메서드로 만들 수 있었습니다.</li>
<li>복잡한 네트워킹 작업이 은닉화되고 코드 가독성이 향상된것같습니다.</li>
<li>네트워크 작업을 처리할 때 생산성이 향상되었습니다.</li>
</ul>
<p>앱에서 수많은 API 요청이 발생하는데, Request Router를 통해 모든 요청을 생성할 수 있고, 이를 통해 하나의 파일에서 일관적으로 관리할 수 있었어요.
기존에 여기저기 흩어져 있는 통신관련 파일들을 하나로 볼 수 있어서 가독성이 높아졌고, 유지보수하기도 수월해진것같아요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Coordinator 패턴 적용기]]></title>
            <link>https://velog.io/@na-young-kwon/Coordinator-pattern</link>
            <guid>https://velog.io/@na-young-kwon/Coordinator-pattern</guid>
            <pubDate>Tue, 20 Jun 2023 13:59:46 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기">시작하기</h3>
<p>프로젝트를 구현하면서 코디네이터 패턴에 대해 알게되었다. 
MVVM구조로 역할분리를 하는데 성공했다고 생각했는데, 확장성과 프로젝트 규모의 증가 등의 측면에서 이점이 있을것같아 하이웨이 인포에 적용해보았다. </p>
<h3 id="현재-구현의-문제">현재 구현의 문제</h3>
<p>사실 코디네이터 패턴 없이도 앱은 잘 작동한다. 
다만 몇가지 아쉬운점이 있다</p>
<ol>
<li>재사용성 문제</li>
<li>뷰컨트롤러간의 강한 결합의 문제</li>
<li>Massive View Controller
등등...</li>
</ol>
<p>MVC 패턴에서는 뷰 객체를 생성하고, 띄워주는 순서로 화면전환을 구현했다. 
이게 간단한 프로젝트에서는 괜찮은데, 프로젝트 규모가 커질수록 VC의 코드가 길어지고 또 다양한 방식의 화면전환 플로우가 요구될 가능성도 있다.</p>
<p>코디네이터는 위의 문제점을 해결하기 위해 <strong>화면전환을 관리하는 독립적인 클래스</strong>를 이용하자는 아이디어에서 나왔다.
그럼 본격적으로 코디네이터 패턴에 대해 알아보자</p>
<h3 id="코디네이터-패턴">코디네이터 패턴</h3>
<p>코디네이터는 UIViewController를 보여주고 숨기는 역할을 한다. </p>
<ul>
<li>뷰 컨트롤러보다 한 층 위의 레이어에서 컨트롤러들을 관리하기 때문에 뷰 컨트롤러간의 강한 결합문제를 해결할 수 있다.</li>
<li>또, 화면전환에 특화된 클래스 라는 점에서 화면전환 기능의 유지보수가 편리해진다는 장점이 있다.</li>
<li>나의 경우 클린아키텍처와 함께 사용했기 때문에 의존성 주입도 관리해줄 수 있었다.</li>
</ul>
<br>

<h3 id="프로젝트-구조-살펴보기">프로젝트 구조 살펴보기</h3>
<p>프로젝트에 어떻게 적용했는지 살펴보기전 구조파악을 위해 앱 플로우를 가져와보았다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/dc3ef88a-3181-49b8-8ab2-94a438a01b83/image.jpg" alt=""></p>
<ol>
<li>첫번째 화면에서 검색탭을 누르면 </li>
<li>장소검색뷰가 push되고, 검색어를 탭하면</li>
<li>맨 마지막 결과화면이 push되는 구조이다.</li>
</ol>
<br>

<h3 id="프로젝트에-적용하기">프로젝트에 적용하기</h3>
<p>일단 코디네이터의 가장 기본적인 구조를 형성해주었다.
바로 코디네이터 프로토콜 정의하기 인데, 사람마다 프로토콜의 구성은 다양한것같다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/6ad1aaf3-44f1-4146-906c-df3cd5125095/image.png" alt=""></p>
<p>나의 경우 기본적으로 <code>start()</code> 메서드와 자식코디네이터 프로퍼티를 정의했고, 
뷰가 사라졌을때 메모리관리를 해주기 위해 <code>removeChildCoordinator()</code>도 추가 구현해주었다.</p>
<br>


<h3 id="appcoordinator">AppCoordinator</h3>
<p>이제 앱코디네이터를 만들 시간이다. 앱 코디네이터는 SceneDelegate에서 사용해줄것인데, window를 전달받아 앱의 윈도우에 보여지는 presentation을 세팅하게된다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/4b86b9c9-68fe-463f-9ef6-4073393732e1/image.png" alt=""></p>
<p>내 프로젝트는 화면이 탭바로 나누어져 있기때문에 rootViewController에 탭바컨트롤러를 지정해주었다.
<code>start()</code>는 모든 것들이 시작되는 곳이다. 
특히 윈도우가 루트뷰에 나타나는 곳이므로, 눈에 보이기위해 필요한 작업들을 이 메서드 안에서 해주었다.</p>
<p>이렇게 정의하고 씬델리게이트에서 <code>appCoordinator.start()</code> 를 호출 해주면 된다.
아래는 씬델리데이트의 코드이다.</p>
<pre><code class="language-swift"> func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    self.window = UIWindow(windowScene: windowScene)
    guard let window = window else { return }

    appCoordinator = AppCoordinator(window: window)
    appCoordinator?.start()
 }</code></pre>
<br>

<h3 id="화면전환하기">화면전환하기</h3>
<p>RoadViewControlelr에서 서치바를 탭하면 SearchViewController로 화면전환을 해줘야 한다.</p>
<p>원래라면 
인스턴스 생성, 뷰컨에 필요한 자원전달, 화면푸시 등으로 기본 4~5줄의 코드를 작성해야했지만, </p>
<p>코디네이터 패턴 적용 후 화면전환 코드는 모두 1줄이면 끝난다.
이렇게 뷰모델에게 다음뷰를 보여달라는 메서드를 호출하기만 하면 된다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/59470768-56fa-479c-a527-fe7db2241a42/image.png" alt=""></p>
<br>


<p>뷰모델의 구현코드도 심플하다. 
코디네이터에게 필요한 위치정보를 전달하고 <code>showSearchView()</code>메서드를 호출하는게 끝이다.</p>
<pre><code class="language-swift">final class RoadViewModel: ViewModelType {
    private let coordinator: RoadCoordinator?

 // ... 생략

    func showSearchView() {
        guard let currentLocation = currentLocation else { return }
        coordinator?.showSearchView(with: currentLocation)
    }
}</code></pre>
<br>

<h3 id="코디네이터의-실제-구현-모습">코디네이터의 실제 구현 모습</h3>
<p>이제 RoadCoordinator가 어떻게 구현되어 있는지 살펴보자
<img src="https://velog.velcdn.com/images/na-young-kwon/post/9a0cc2c7-2235-44d4-9c79-a71ff3811e20/image.png" alt=""></p>
<ul>
<li>우선 RoadCoordinator 프로토콜을 채택했는데, RoadCoordinator는 Coordinator프로토콜을 채택한 프로토콜이다.</li>
</ul>
<h4 id="1-start-메서드">1. start() 메서드</h4>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/84dd73b3-a0c2-4394-962b-400f0c0e6bc7/image.png" alt=""></p>
<ul>
<li>start() 메서드의 코드를 보면 스토리보드에서 인스턴스를 이니셜라이징 해주는것을 알수있다.</li>
<li>그후 MVVM패턴을 사용해주었기 때문에 ViewModel을 만들어서 뷰컨에 할당해주었다.</li>
<li>그렇게 다 만들어진 뷰컨을 네비게이션 컨트롤러에 push 해주면 된다.</li>
</ul>
<br>

<h4 id="2-showservice메서드">2. showService()메서드</h4>
<p>다음 화면으로 전환하기 위해 <code>showService()</code>라는 메서드를 정의해주었는데, 이것 역시 <code>start()</code> 메서드와 동일하다
<img src="https://velog.velcdn.com/images/na-young-kwon/post/33a1c593-b6e9-4202-b547-760d4036fa4a/image.png" alt=""></p>
<p>다음 뷰의 코디네이터 인스턴스를 만들어 화면시작 메서드를 호출해주면 된다.
조금 다른 부분이 있다면 방금 만든 코디네이터를 <em>childCoordinator에 append</em> 해주고 있는것을 볼 수 있다._ 이건 왜 필요한걸까?_</p>
<br>


<h3 id="자식코디네이터는-왜-필요하지">자식코디네이터는 왜 필요하지?</h3>
<p>결론부터 말하면 메모리관리를 위해 필요하다.</p>
<p>이번 프로젝트를 하면서 제일 골치아팠던 문제이기도 한데 
테이블뷰 셀을 누르면 결과화면이 여러번 푸시되는 문제가 있었다.</p>
<p>원인을 여러 방면으로 찾다가 <a href="https://limjs-dev.tistory.com/135">이글</a>을 읽고 해결할 수 있었다. </p>
<hr>
<p>뷰를 푸시하고 pop해서 이전화면으로 돌아갔을 때
VC2가 deinit되고 메모리에서 내려가야하는데 
화면전환을 여러번 하면 아래 사진 처럼 쓸모없는 코디네이터가 세개나 살아있는게 원인이었다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/ca65674e-3114-494c-94d5-c0a13330f08f/image.png" alt=""></p>
<p>이런 메모리 누수를 막기 위하여 우리는 하위뷰들이 pop되어 화면에서 사라질 때 하위뷰의 인스턴스를 제거해줘야 한다고 한다.</p>
<br>

<h3 id="자식코디네이터-메모리에서-제거하기">자식코디네이터 메모리에서 제거하기</h3>
<p>코디네이터 프로토콜 기본구현에 정의해주었던 메서드를 사용해서</p>
<pre><code class="language-swift">extension Coordinator {
    func removeChildCoordinator(_ child: Coordinator) {
        for (index, coordinator) in childCoordinators.enumerated() {
            if coordinator === child {
                childCoordinators.remove(at: index)
                break
            }
        }
    }
}
</code></pre>
<p>뷰컨트롤러가 할당해제 되는 시점에 호출해주는 방법으로 해결할 수 있었다.</p>
<pre><code class="language-swift">final class SearchViewController: UIViewController {
    // 생략...
    deinit {
        viewModel.removeCoordinator()
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/ee058555-a47b-4e58-af72-917484e9f7db/image.png" alt=""></p>
<p>짜잔 
이제는 쓸모없이 메모리에 남아있는 코디네이터가 없어진 모습을 볼수있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WWDC] DiffableDataSource]]></title>
            <link>https://velog.io/@na-young-kwon/WWDC</link>
            <guid>https://velog.io/@na-young-kwon/WWDC</guid>
            <pubDate>Wed, 07 Jun 2023 13:35:40 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/na-young-kwon/post/562a8415-b958-44f8-a83c-0a8a6f27b1ce/image.png">


<h2 id="wwdc2019-advances-in-ui-data-sources">WWDC2019 [Advances in UI Data Sources]</h2>
<p>iOS13에서 Diffable DataSource와 Compositional Layout 이라는 새로운 컴포넌트가 등장했다.
iOS14부터 새로운 기능을 도입할 수 있는데, 먼저 Diffable DataSource를 살펴보면</p>
<ul>
<li>snapshot 데이터 타입을 통해서 UI상태관리를 간소화 했다.</li>
<li>스냅샷은 고유 섹션과 identifier를 통해 현재 UI 상태를 캡슐화하고</li>
<li>컬렉션뷰를 업데이트할 때 새 스냅샷 생성 후 데이터를 apply하는 방식으로 작동한다.</li>
</ul>
<p>게다가 추가 작업 없이 그 차이를 계산하고 애니메니션을 자동으로 생성해주기도 한다고 한다.</p>
<br>

<h3 id="diffable-datasource란-무엇인가">Diffable DataSource란 무엇인가?</h3>
<ul>
<li>컬렉션뷰를 그리기 위한 데이터를 관리하고, 바뀐 부분에 해당하는 UI를 자연스럽게 업데이트 해주는 객체를 말한다.</li>
<li>기본적으로 DataSource와 역할은 같지만, 이전 뷰와 달라진 부분을 자동으로 알아차리고, 새로운 부분만 다시 그린다는 점이 차이점이다.</li>
</ul>
<blockquote>
<p>Diffable은 무엇을 뜻하는걸까?
뷰의 항목을 업데이트할 때마다 컬렉션뷰가 업데이트된 컬렉션과 이번에 표시된 컬렉션 간의 차이를 자동으로 계산한다는 것을 의미한다.</p>
</blockquote>
<br>

<h3 id="diffable-datasource의-장점은">Diffable DataSource의 장점은?</h3>
<ol>
<li><p>애니메이션
데이터를 추가, 업데이트, 삭제할 때마다 자동으로 데이터 변경 애니메이션이 적용된다.</p>
</li>
<li><p>자동 데이터 동기화
UI 데이터의 동기화 부분 대신 앱의 동적인 데이터와 내용에 집중할 수 있다.
Centralized Truth를 사용하기 때문에 UI와 데이터소스간의 Truth가 맞지 않아 크래시가 발생하는 일이 없음</p>
</li>
<li><p>코드감소
전반적으로 더 적은 코드를 작성할 수 있다.</p>
</li>
</ol>
<br>

<h3 id="diffabledatasource를-왜-사용해야-하지">DiffableDataSource를 왜 사용해야 하지?</h3>
<p>Diffable DataSource가 나오기 전에도 테이블뷰를 문제없이 그렸는데, 왜 디퍼블 데이터소스를 사용해야 하는걸까?</p>
<p>아마 iOS앱을 개발한다면 테이블뷰를 그리다 아래와 같은 에러를 만난 경험이 있을것이다. 섹션 수가 잘못되어 앱이 강제종료 되는 경우이다.</p>
<img src="https://velog.velcdn.com/images/na-young-kwon/post/041025d4-de00-4c58-882e-a1756f92db54/image.png">



<p>어쨌든 해당 에러는 데이터의 변경 사항을 수동으로 관리하고 동기화 할때의 문제점을 나타낸다고 할 수 있는데,
이 문제는 tableView.reloadData()를 이용하여 해결할 수 있다. 
그런데도 왜 DiffableDataSource가 필요한걸까?</p>
<h3 id="결론부터-말하자면-사용자-경험-때문이다"><strong>결론부터 말하자면 사용자 경험 때문이다.</strong></h3>
<p><strong>reloadData()</strong>의 경우 모든 셀을 다시 그리면서 테이블뷰를 한번에 업데이트 하므로  UI가 애니메이션 없이 부자연스럽게 변경된다. 
→  사용자 경험 저하로 이어질 수 있음</p>
<p>하지만 Diffable DataSource와 함께하면 아래와같이 빠르고 매끄럽고 자연스럽게 테이블뷰를 그릴 수 있다는 것이다.</p>
<img src="https://velog.velcdn.com/images/na-young-kwon/post/83717360-624a-428a-bf70-d65b67f3b4bb/image.gif">

<br>

<h2 id="compositional-layout">Compositional Layout</h2>
<p>compositional layout은 빠르고 유연하고, composable하게 컬렉션뷰를 구현할 수 있는 컬렉션뷰 레이아웃의 한 종류이다. 
컴포지셔널 레이아웃은 </p>
<ul>
<li>빠르고</li>
<li>하나의 컬렉션뷰로 다양한 레이아웃을 구성할 수 있으며</li>
<li>정교한 UI를 구축하는데 도움이 되는 섹션별 레이아웃 기능도 포함되어있다.</li>
</ul>
<p>Compositional Layout 역시 iOS13에서 도입되었고 item, group, section으로 구성되어있다.
하나의 셀을 item, 아이템의 집합을 group, 그룹의 집합을 section이라고 한다.</p>
<p>섹션 스냅샷을 사용하여 뷰를 업데이트 하고 레이아웃을 구성하는 방법까지 공부하고 내 프로젝트에 적용해보았다.</p>
<br>

<h2 id="highwayinfo-휴게소-화면-구조-분석">HighwayInfo 휴게소 화면 구조 분석</h2>
<p>HighwayInfo를 만들때 휴게소 화면을 어떻게 구현했는지 예시와 함께 설명해보면</p>
<p>휴게소 섹션은 한개의 그룹, 한개의 섹션으로 이루어져 있고</p>
<p>그룹의 가로는 화면의 넓이의 절반, 높이의 1/4정도 된다
<img src="https://velog.velcdn.com/images/na-young-kwon/post/7d7571a2-f198-47db-81e1-1627fa68d7ed/image.png" alt=""></p>
<p>이 화면을 그리기 위해</p>
<pre><code class="language-swift">// item
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)

// group
let groupSize = NSCollectionLayoutSize(widthDimension: widthDimension, heightDimension: heightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

// section
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary</code></pre>
<p>group의 가로/세로는 각각 0.5, 0.25로 설정하고 horizontal 레이아웃을 적용해주었다. 
아이템은 그룹을 꽉 채우도록 fractionalWidth, fractionalHeight를 각각 1.0으로 설정했다.</p>
<p>섹션의 orthogonalScrollingBehavior를 주어 좌우로 스크롤 되는 레이아웃으로 구현해보았다.</p>
<br>

<h2 id="header-view-구현">Header View 구현</h2>
<p>헤더뷰도 Compositional Layout으로 구성 가능한데, 
아래 뷰를 구성하기 위해</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/12a02c43-9b64-4245-9cdd-ccc9dd5c6e80/image.jpg" alt=""></p>
<pre><code class="language-swift">// header
let titleSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: titleSize,
    elementKind: &quot;title-element-kind&quot;,
    alignment: .top)
section.boundarySupplementaryItems = [titleSupplementary]</code></pre>
<p>이렇게 헤더를 만들고 섹션의 boundarySupplementaryItems 에 지정해주면 된다.</p>
<br>

<h2 id="datasource-구현">DataSource 구현</h2>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/49a337f2-5156-4890-b544-7909c8082c37/image.jpg" alt="">
위 화면을 그리기 위해 데이터소스를 어떻게 구현했는지 알아보자.</p>
<ol>
<li>셀 구성
먼저, 각각의 셀이 사용할 데이터 타입과 셀을 등록하고, 클로저에 셀의 내용을 구성해주었다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/fced8052-4501-4e0f-a8d5-5c40d600590a/image.png" alt=""></li>
</ol>
<ol start="2">
<li>데이터소스 구성
다음으로 데이터 소스를 구성하고 디큐해주면 된다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/8a7812e8-e382-4cfa-8350-2f0d14f56121/image.png" alt=""></li>
</ol>
<p>여기에서 어려웠던 점은 내앱의 경우 하나의 컬렉션뷰가 세개의 셀을 나타내야 했기 때문에 food, convenience, brandList 모두 등록해주었는데
저장된 데이터를 꺼내 뷰에 나타낼때 item의 타입을 한개로만 정의할 수 없었다는 점이다.</p>
<blockquote>
<p>💡 고민하던 중 FoodMenu, ConvenienceList, Brand 모두 Hashable을 채택했다는 점을 깨닫고 item의 타입을 AnyHashable로 적어주어서 해결했다.</p>
</blockquote>
<ol start="3">
<li>스냅샷 적용
스냅샷을 적용할때는 새로운 스냅샷을 만들어서 섹션과 아이템을 스냅샷에 append 해주기만 하면 된다. dequeue할때와 마찬가지로 [AnyHasable] 타입의 아이템을 받아서 main 섹션을 업데이트 해주었다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/33fcc638-b421-406d-9aa3-426bffe6b9bf/image.png" alt=""></li>
</ol>
<ol start="4">
<li>뷰에 바인딩
<img src="https://velog.velcdn.com/images/na-young-kwon/post/d3af2142-b7ce-4a78-b383-8f6ae64ebd55/image.png" alt="">
현재 내 프로젝트에서는 RxSwift를 사용하고 있기 때문에 데이터가 들어올때마다 applySnapShot을 호출해주는 식으로 바인딩 처리를 해줘서 마무리했다.</li>
</ol>
<br>

<h3 id="뷰-완성">뷰 완성</h3>
<p>그렇게 해서 완성된 뷰가 휴게소 상세보기 화면 이다
<img src="https://velog.velcdn.com/images/na-young-kwon/post/ee7d3caa-7a4a-4f29-9d8e-e0e0e139ecc0/image.png" alt=""></p>
<br>

<h2 id="왜-collectionview-만-업데이트하지">왜 CollectionView 만 업데이트하지?</h2>
<p>WWDC2020 컬렉션뷰 레퍼런스를 보면서 왜 테이블뷰도 아니고 컬렉션뷰이지? 라는 궁금증이 생겼는데</p>
<h4 id="애플이-컬렉셔뷰의-api만-업데이트하는-이유는-컬렉션뷰가-uitableview같은-인터페이스를-대체하기-때문이라고-생각한다">애플이 컬렉셔뷰의 API만 업데이트하는 이유는 컬렉션뷰가 UITableView같은 인터페이스를 대체하기 때문이라고 생각한다.</h4>
<p>iOS14의 경우 Composition Layout을 기반으로 Lists라고 하는 새로운 기능이 추가된 것을 보면 알수있다.</p>
<p>실제로 2020 WWDC를 보면 UICollectionLayoutListConfiguration에 대한 이야기가 나오는데, iOS14부터 사용 가능한 ListConfiguration을 소개하는것을 알 수 있다.</p>
<p>UICollectionLayoutListConfiguration을 사용하면
이렇게 간단한 코드로 
<img src="https://velog.velcdn.com/images/na-young-kwon/post/38a7ba2f-3cf4-4df2-84fc-e5c26978b07d/image.png" alt=""></p>
<img src="https://velog.velcdn.com/images/na-young-kwon/post/f9716b76-b657-4a0a-9c26-953ab05915c7/image.png">


<p>이런 <del>테이블 뷰</del> 리트스 뷰를 만들 수 있다고 하니
언젠가는 컬렉션뷰가 테이블뷰를 대체하고, UITableView관련 API들이 deprecated 될 수도 있겠다는 생각이 들었다.</p>
<p>ListConfiguration은 Compositional Layout 위에서 작동하며 UICollectionViewListCell, header, footer, sidebar를 모두 제공한다고 하는데 테이블뷰가 남아있을 이유가 있을까</p>
<br>

<h3 id="소감">소감</h3>
<p>Diffable DataSource와 Compositional Layout을 처음 사용해보았는데, 너무 만족스럽다.</p>
<p>새롭게 접하는 개념들이라 낯설고 적용하기 어려울것같다는 우려와는 다르게 사용법도 생각보다 쉬웠다.</p>
<p>제일 좋은점은 기존에 extension으로 컬렉션뷰 UICollectionViewDelegate를 채택하고, 관련 데이터소스와 델리게이트 매서드를 정의해준 부분이 없어졌다는 것이다. 코드가 길고 복잡해서 한눈에 알아보기 어려웠는데 DiffableDataSource는 컬렉션뷰 관련 코드를 한곳에서 관리할 수 있어서 가독성이 높아진거같다.</p>
<p>또 달라지는 데이터만 알아서 다시 그려주니 간편하고 UI업데이트가 자연스러운 점도 장점이다.</p>
<br>

<p>참고</p>
<ul>
<li><a href="https://developer.apple.com/videos/play/wwdc2019/220">https://developer.apple.com/videos/play/wwdc2019/220</a></li>
<li><a href="https://developer.apple.com/videos/play/wwdc2020/10026">https://developer.apple.com/videos/play/wwdc2020/10026</a></li>
<li><a href="https://developer.apple.com/videos/play/wwdc2020/10026/">https://developer.apple.com/videos/play/wwdc2020/10026/</a></li>
<li><a href="https://www.kodeco.com/8241072-ios-tutorial-collection-view-and-diffable-data-source">https://www.kodeco.com/8241072-ios-tutorial-collection-view-and-diffable-data-source</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] HighwayInfo: RxTest로 유닛테스트하기]]></title>
            <link>https://velog.io/@na-young-kwon/iOS-HighwayInfo-RxBlocking%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9C%A0%EB%8B%9B%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@na-young-kwon/iOS-HighwayInfo-RxBlocking%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9C%A0%EB%8B%9B%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Thu, 01 Jun 2023 05:18:38 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 이번 포스트는 RxSwift 코드 테스트에 대한 이야기 입니다. 
앱을 MVVM으로 구성했더니 테스트에 용이한 구조가되었습니다. MVVM 아키텍처의 또 다른 이점은 코드의 테스트 용이성 증가라고 할 수 있는데요, 오늘은 특히 뷰 모델에 대한 단위 테스트를 만든 경험을 공유하고자 합니다.</p>
<h3 id="rxtest-목적">RxTest 목적</h3>
<ul>
<li>모듈이 정해진 기능을 정확히 수행하는지 확인하기 위해</li>
<li>프로그램의 각 부분을 고립시켜 각각의 부분이 의도된대로 정확히 동작하는지 확인하기 위해</li>
</ul>
<p><strong>RxTest</strong> 라이브러리를 이용하여 RxSwfit 연산자 및 코드에 대한 테스트를 작성해 보았습니다.</p>
<h3 id="테스트-대상-결정">테스트 대상 결정</h3>
<p>테스트를 하기 전 실제로 테스트하고 싶은 것이 무엇인지 생각해보았습니다. 뷰 처럼 테스트를 할 수 없는 부분을 제외한 비즈니스 로직을 테스트 하기로 결정했습니다.
특히 <strong>뷰 모델의 Input에 대해서 원하는 Output이 잘 나오는지 확인</strong>하는것을 목표로 했습니다.</p>
<h3 id="테스트를-위한-준비">테스트를 위한 준비</h3>
<p>테스트를 위해 필요한 프로퍼티를 정의해줍니다.</p>
<pre><code class="language-swift">private var viewModel: SearchViewModel!
private var disposeBag: DisposeBag!
private var scheduler: TestScheduler!
private var input: SearchViewModel.Input!
private var output: SearchViewModel.Output! </code></pre>
<p>저는 뷰 모델을 테스트 할 것이기 때문에 뷰 모델과 인풋, 아웃풋, 스케쥴러 등을 선언했습니다.</p>
<pre><code class="language-swift">override func setUpWithError() throws {
    viewModel = SearchViewModel(useCase: MockSearchUseCase(), coordinator: nil)
    scheduler = TestScheduler(initialClock: 0)
    disposeBag = DisposeBag()
}</code></pre>
<p><strong>setUp()</strong> 에서 각 테스트가 시작되기 전에 호출되는 프로퍼티들을 초기화 해줍니다.</p>
<ul>
<li>scheduler는 테스트 스케쥴러가 각 테스트에서 사용할 인스턴스 입니다.</li>
<li>RxTest에 의해 내부적으로 계산된 가상 시간 단위를 사용합니다.</li>
</ul>
<pre><code class="language-swift">override func tearDownWithError() throws {
    viewModel = nil
    disposeBag = nil
}</code></pre>
<p><strong>tearDown()</strong> 에서 뷰모델과 디즈포즈백의 메모리를 해제해줍니다.</p>
<h3 id="테스트-코드-작성">테스트 코드 작성</h3>
<ol>
<li>옵저버를 만듭니다</li>
<li>테스트 값을 추가합니다.</li>
<li>실제 결과가 예상 결과와 일치하는지 확인합니다.</li>
</ol>
<pre><code class="language-swift">func test_fetch_search_result() {
    let searchResultObserver = scheduler.createObserver([LocationInfo].self)
    let viewWillAppearTestableObservable = scheduler.createHotObservable([.next(10, ())])
    let keywordObservable = scheduler.createHotObservable([.next(20, &quot;seoul&quot;)])

    input = SearchViewModel.Input(viewWillAppear: viewWillAppearTestableObservable.asObservable(),
                                  searchKeyword: keywordObservable.asObservable(),
                                  itemSelected: Observable.just(nil))
    output = viewModel.transform(input: input)
    output.searchResult.subscribe(searchResultObserver).disposed(by: disposeBag)
    scheduler.start()

    XCTAssertEqual(searchResultObserver.events, [
        .next(20, [LocationInfo(
            uuid: &quot;test_id&quot;,
            name: &quot;newLocation&quot;,
            businessName: &quot;eroom&quot;,
            distance: &quot;10&quot;,
            coordx: &quot;10&quot;,
            coordy: &quot;10&quot;,
            address: nil
        )])
    ])
}</code></pre>
<p><code>scheduler.start()</code> 를 통해 스케줄러를 시작하고 옵저버가 이벤트를 수신합니다.
테스트를 빌드하고 실행한 후 성공여부를 확인했고, 예상한 결과와 일치하는 것 을 알 수 있었습니다.</p>
<h3 id="사용하지-않는-코드-발견">사용하지 않는 코드 발견</h3>
<p>테스트코드 작성의 이점은 코드를 다시 한번 되돌아볼 수 있다는 점입니다.
실제로 아래 사진에서 SearchViewModel에 대한 테스트 진행 중 더 이상 쓰이지 않는 코드를 발견해서 리팩토링 할 수 있었습니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/57d1fdf5-1b73-49f9-9979-0c9d7a2651a9/image.png" alt=""></p>
<h3 id="코드-커버리지">코드 커버리지</h3>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/dc62c437-5ffa-47f0-a0b2-b1b2d0efcb5b/image.png" alt="">
엑스코드 테스트에서 제공하는 코드 커버리지 확인 기능을 통해 
커버가 안 된 부분을 확인하고</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/77582441-34e6-4bd2-9c82-05d364132fe3/image.png" alt=""></p>
<p>새로운 테스트를 추가 작성했습니다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/5a98bc06-c009-4fcf-8e6c-f471c00f1be6/image.png" alt="">
화면 전환 처럼 테스트 코드로 확인할 수 없는 부분을 제외한 모든 코드에 대해 테스트를 마쳤습니다.</p>
<h3 id="마무리">마무리</h3>
<p>이렇게 유닛 테스트를 통해 개발한것이 제대로 작동하고 있는지 확인해보았습니다. 
<strong>비즈니스 로직에서 뷰 라이프 사이클을 분리</strong>하였더니 뷰 모델을 매우 간단하게 테스트 할 수 있었습니다. 
RxTest도 처음이고 단위테스트를 하는것도 쉽지 않았지만, 유닛테스트 작성을 통해 사용자가 버그에 최소한으로 노출될 수 있도록 한 점이 뿌듯하네요:)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] HighwayInfo: 앱 출시기]]></title>
            <link>https://velog.io/@na-young-kwon/iOS-HighwayInfo-%EC%95%B1-%EC%B6%9C%EC%8B%9C%EA%B8%B0</link>
            <guid>https://velog.io/@na-young-kwon/iOS-HighwayInfo-%EC%95%B1-%EC%B6%9C%EC%8B%9C%EA%B8%B0</guid>
            <pubDate>Fri, 26 May 2023 14:10:58 GMT</pubDate>
            <description><![CDATA[<h3 id="프로젝트-소개">프로젝트 소개</h3>
<img src="https://velog.velcdn.com/images/na-young-kwon/post/afdb6733-30cb-4169-bc9c-721f2da073b9/image.png" width="200" height="200">

<p>안녕하세요, 최근 3개월동안 개발했던 앱을 출시했습니다. 
제가 진행한 프로젝트는 <code>HighwayInfo</code>라는 앱으로, 고속도로 교통정보 및 휴게시설에 대한 정보를 제공합니다.</p>
<p>기획부터 디자인, iOS개발을 하면서 느꼈던 점들을 공유해보고자 합니다. 
이번 포스트는 <strong>MVVM과 클린아키텍처, RxSwift</strong>를 도입한 경험에 대한 이야기입니다.</p>
<h3 id="앱-전체-디자인">앱 전체 디자인</h3>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/21861562-04fa-42b9-8816-5c5cba443b13/image.png" alt=""></p>
<h3 id="mvvm--clean-architecture--rxswift-도입">MVVM + Clean Architecture + RxSwift 도입</h3>
<h3 id="mvvm">MVVM</h3>
<p>새 애플리케이션을 구축할 때 마다 항상 어떤 아키텍처 패턴을 선택해야 하는지 고민이 된다. 
나도 어떤 패턴이 좋을지 고민하다가 이전 프로젝트들에서 MVC 패턴의 단점이 크게 와닿아 MVVM 패턴을 적용해보기로 했다.
특히 화면을 그리는 코드와 데이터를 처리하는 코드를 분리 구성하여 앱을 구성했다.</p>
<img src="https://velog.velcdn.com/images/na-young-kwon/post/fa853073-2ed1-4df1-af14-3f7013534cc4/image.jpg" width="500" height="200">

<p>각자의 역할을 정리해보면</p>
<ol>
<li>뷰: 화면을 그리는 역할을 담당합니다.</li>
<li>뷰 모델: 사용자의 입력을 받아 그에 맞는 이벤트를 처리하고, 모델의 read, update, delete을 담당합니다.</li>
<li>모델: 데이터 구조를 정의합니다.</li>
</ol>
<h3 id="highwayinfo에서는">HighwayInfo에서는</h3>
<p>실시간 돌발정보를 가져와 화면을 그리고 새로고침 버튼으로 유저의 입력을 받습니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/71036ea4-44dd-4f19-aaf1-180eaa5c44be/image.png" width="100" height="100"></p>
<p>위 화면을 MVVM으로 나누어보면
<img src="https://velog.velcdn.com/images/na-young-kwon/post/2d5cd2af-d983-4569-a238-3cb96583f00a/image.jpg" alt=""></p>
<p><strong>뷰모델</strong>은 실시간 돌발정보를 가져오고 새로고침 버튼이 눌렸을 때 최신 돌발정보를 가져옵니다. 
뷰가 무엇인지 또는 뷰가 어떤일을 하는지 알지 못합니다. </p>
<p><strong>뷰</strong>는 단순히 유저 인터페이스를 표시하기위한 로직만을 담당하고, 그 외에는 메서드 호출정도의 코드만 존재합니다.</p>
<p>이렇게 했을때의 장점은 뷰와 모델이 서로를 전혀 알지 못하기때문에 <strong>독립성을 유지</strong>할 수 있다는 것입니다. 
독립성을 유지하기 때문에 테스트하기에 용이하다는 장점도 있습니다.</p>
<h3 id="massive-viewmodel">Massive ViewModel</h3>
<p>MVVM 패턴을 사용하면서 뷰와 비즈니스 로직을 분리하는것에는 성공했지만, 
앱이 커지면서 <strong>뷰컨트롤러에 속했던 모든 책임이 이제 뷰모델에 속하는 문제</strong>가 발생했습니다. 
뷰컨트롤러가 방대해졌던 것 처럼 뷰모델도 Massive ViewModel이 될 여지가 남아있겠죠?</p>
<p>저는 이 문제를 해결하기 위해 <strong>CleanArchitecture</strong>와 <strong>Coordinator</strong> 패턴을 적용해보았습니다.</p>
<h3 id="clean-architecture">Clean Architecture</h3>
<p>클린아키텍쳐 구성
<img src="https://velog.velcdn.com/images/na-young-kwon/post/80303f9c-5a3f-4762-900c-c4670983def1/image.jpg" width="300" height="200"></p>
<p><code>프레젠테이션 레이어</code>는 ViewModel과 View로 구성됩니다. 
<strong>뷰모델</strong>이 이 계층에 속하는 이유는 기존엔 비즈니스 로직이 뷰모델이 포함되지만, 이제부터는 비즈니스로직을 제외한 화면구현에 필요한 데이터를 포함하기 때문입니다.
<strong>뷰</strong>는 화면을 그리는 역할, <strong>뷰모델</strong>은 화면에 그릴 데이터를 준비하는 역할을 수행하게 되는것이지요.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/c4cfab91-3556-4b13-a7fc-2ac28c9d09cd/image.jpg" width="300" height="200"></p>
<p>뷰모델에서 분리된 <strong>비즈니스 로직</strong>은 UseCase라는 이름으로 <code>도메인</code>에 위치하게 됩니다. 
유즈케이스는 데이터를 표현하는 모델을 갱신하고, 비즈니스 로직에 의해 영향을 받게되는 모델이 도메인 계층에 속합니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/6d7d2569-4a5d-4b3e-a459-ac5f8529093a/image.jpg" width="300" height="200"></p>
<p><code>데이터 레이어</code>는 Repository Inplementation로 구성됩니다. 
Data에서 외부의 데이터를 가져오는 객체를 <strong>레포지토리</strong> 라고 합니다.</p>
<h3 id="repository-interface는-왜필요할까">Repository Interface는 왜필요할까?</h3>
<p>클린아키텍처를 공부하면서 들었던 의문은 <strong>Repository Protocol이 왜 필요한가?</strong> 였습니다.
굳이 왜 프로토콜을 만들어 Domain 레이어에 두고, 실구현한 레파지토리 객체는 Data레이어에 속하는지 궁금했는데요</p>
<p>그 이유는 유닛테스트를 하면서 찾을 수 있었습니다. 
결론부터 말하자면 <strong>유즈케이스가 레파지토리를 직접 소유하면 안되기 때문</strong>에 도메인 레이어에 레파지토리에 대한 인터페이스를 둔것이었습니다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/a3e89db4-7eb7-4760-853f-add0a0ad2351/image.jpg" alt=""></p>
<p>이렇게 하면 </p>
<ul>
<li>의존성 역전 원칙을 준수하게 되고, </li>
<li>의존성의 방향을 내부로 향하도록 유지하면서도 </li>
<li>레파지토리에 요청을 보낼 수 있게 됩니다.</li>
</ul>
<p>즉, <strong>종속성 반전</strong>에 필요한 인터페이스가 필요했던것입니다.</p>
<h3 id="클린아키텍처의-핵심-규칙">클린아키텍처의 핵심 규칙</h3>
<p>클린아키텍쳐의 핵심 규칙은 내부레이어에서 외부레이어의  종속성을 가지지 않는다는 것입니다. 
화살표는 바깥에서 안쪽으로만 가리킬 수 있습니다.
사진을 보면 화살표가 모두 안쪽으로 향하는 것을 알수있습니다.</p>
<h3 id="reactive-programming">Reactive Programming</h3>
<p>사용자와 상호작용하며 그에 따라 화면이 실시간으로 변하기 위해 <strong>RxSwift를</strong> 사용했습니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/a0dbd1cb-6f04-43b2-9056-e883a9ff1191/image.png" width="100" height="100"></p>
<p>예를들어, 위 화면은 검색창에 단어를 하나씩 입력할 때마다 관련 검색어들이 자동완성으로 바로바로 제시되는 화면입니다.</p>
<p>새로운 검색어에 대한 검색결과는 아래와 같은 흐름으로 데이터를 받아오게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/0a74c38f-9e7d-4222-9ed4-833053c2173e/image.jpg" alt=""></p>
<p>이를 구현하기 위해 textFiled의 text에 대해 지속적으로 관찰하고, 해당 값이 변할때마다 바로 <strong>재연산이 수행</strong>되도록 했습니다.
RoadSerivce부터 레파지토리, 유즈케이스, 뷰모델로 이어지는 데이터의 <strong>연결통로가 끊어지지 않도록</strong> 데이터 바인딩을 구현했습니다.</p>
<p>이러한 비동기 처리를 단순 콜백 함수를 사용할 수도 있지만, 비동기 작업이 연속될 경우 구조를 복잡하게 만들기도 합니다. 
따라서 콜백보다는 Observation이 비동기 작업 처리에 효율적이고 더 명확하게 데이터를 처리할 수 있었다고 생각합니다.</p>
<h3 id="inputoutput-modeling">Input/Output Modeling</h3>
<p>데이터 바인딩을 통해 모델과, 뷰, Input과 Output이 서로서로 데이터를 실시간으로 공유받고 업데이트하도록 구현했습니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/3fb707c2-c9c8-458e-a9f0-a7082e1e1243/image.png" width="400" height="200"></p>
<p>화면에서 일어나는 이벤트를 <strong>Input</strong>에 정의하고, 뷰로 넘겨줄 데이터들을 <strong>Output</strong>에 정의해주었습니다.
transform 메서드로 이벤트나 데이터를 처리해 Output으로 반환하도록 구현했습니다.
<img src="https://velog.velcdn.com/images/na-young-kwon/post/acb69ab5-7f03-4ebd-9de3-75c725af02c2/image.png" width="400" height="200"></p>
<p>뷰 컨트롤러는 Output에 담겨있는 Observable들을 구독해두고
새로운 값이 스트림에 들어올 때 마다 <strong>UI를 업데이트</strong> 하게됩니다.</p>
<p>이렇게 비동기 처리방식을 하나로 통합하여 코드에 일관성을 주었고 가독성을 높였습니다.</p>
<h3 id="data-flow">Data Flow</h3>
<p><img src="https://velog.velcdn.com/images/na-young-kwon/post/a7aac8f5-1af0-488b-916e-c6f7ee45cc47/image.jpg" alt=""></p>
<ol>
<li>View가 ViewModel의 메서드르 호출합니다.</li>
<li>ViewModel이 UseCase를 실행시킵니다.</li>
<li>UseCase가 데이터를 취합해 비즈니스로직을 수행합니다.</li>
<li>Repository는 각 서비스를 통해 네트워크 통신을 합니다.</li>
<li>반환된 데이터가 아이템들을 화면에 출력할 뷰에 전달됩니다.</li>
</ol>
<p>클린아키텍처를 통해 각 모듈들의 역할을 분리하고, RxSwift를 통해 데이터를 뷰에 바인딩하여 화면을 업데이트 할 수 있었습니다.</p>
<h3 id="마무리">마무리</h3>
<p>이번 프로젝트를 하면서 기획부터 디자인, iOS개발 모두 혼자 해야해서 힘들었지만, 그만큼 배운점도 많았다.</p>
<p>RxSwift, CleanArchitecture, Coordinator 모두 처음 접하는 개념들이라 목표했던 출시일보다 2주정도 늦어진게 좀 아쉽긴 하다. 특히 RxSwift의 단점이 러닝커브 높음 인 만큼 익숙해지는데 시간이 꽤 걸렸던 것 같다. </p>
<p>코드를 잘 짜는것 외에도 저작권이나 문구들, 디자인, 실행속도 등 신경쓸게 꽤 많았다. 야곰아카데미에서는 요구사항을 제공해주기 때문에 그에 맞게 개발하면 되는데, 이번에는 요구사항을 구체적으로 명시해놓지 않아서 자꾸 방향을 잃기도 했다.</p>
<p>앞으로 피드백 받으면서 디자인, 사용성 측면에서 더 보완할 부분을 찾아서 리팩토링 해야겠다.</p>
<h3 id="프로젝트-보러가기">프로젝트 보러가기</h3>
<p><a href="https://github.com/na-young-kwon/HighwayInfo">https://github.com/na-young-kwon/HighwayInfo</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모장 프로젝트 문제해결]]></title>
            <link>https://velog.io/@na-young-kwon/%EB%A9%94%EB%AA%A8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@na-young-kwon/%EB%A9%94%EB%AA%A8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%AC%B8%EC%A0%9C%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 10 Feb 2022 12:17:54 GMT</pubDate>
            <description><![CDATA[<h3 id="textview-스크롤시-키보드-높이만큼-올리기">TextView 스크롤시 키보드 높이만큼 올리기</h3>
<p>contentInset의 bottom을 키보드 높이만큼 올리면 됨</p>
<p>키보드 없어질 때는 bottom을 0으로 설정</p>
<pre><code class="language-swift">NotificationCenter.default.addObserver(
      self,
      selector: #selector(keyboardWillShow),
      name: UIResponder.keyboardWillShowNotification,
      object: nil
    )
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(keyboardWillHide),
      name: UIResponder.keyboardWillHideNotification,
      object: nil
    )


  @objc private func keyboardWillShow(_ notification: Notification) {
    guard
      let userInfo = notification.userInfo,
      let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
        return
      }
    textView.contentInset.bottom = keyboardFrame.height
    textView.verticalScrollIndicatorInsets.bottom = keyboardFrame.height
  }

  @objc private func keyboardWillHide(_ notification: Notification) {
    textView.contentInset.bottom = 0
    textView.verticalScrollIndicatorInsets.bottom = 0
  }</code></pre>
<h3 id="드래그-시-키보드-없애기">드래그 시 키보드 없애기</h3>
<pre><code class="language-swift">private let textView: UITextView = {
    let textView = UITextView()
    textView.font = UIFont.preferredFont(forTextStyle: .body)
    textView.keyboardDismissMode = .interactive
    return textView
  }()</code></pre>
<h3 id="키보드-내리기">키보드 내리기</h3>
<pre><code class="language-swift">view.endEditing(true)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[특정 화면 세로고정 하기]]></title>
            <link>https://velog.io/@na-young-kwon/%ED%8A%B9%EC%A0%95-%ED%99%94%EB%A9%B4-%EC%84%B8%EB%A1%9C%EA%B3%A0%EC%A0%95-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@na-young-kwon/%ED%8A%B9%EC%A0%95-%ED%99%94%EB%A9%B4-%EC%84%B8%EB%A1%9C%EA%B3%A0%EC%A0%95-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 13 Dec 2021 11:09:13 GMT</pubDate>
            <description><![CDATA[<h2 id="앱-전체-화면-회전을-설정하는-방법">앱 전체 화면 회전을 설정하는 방법</h2>
<p>Project Target에서 설정해주면 Info.plist에 자동으로 반영된다
<img src="https://i.imgur.com/kmhIxNb.png" alt="">
<img src="https://i.imgur.com/YW5pJoH.png" alt=""></p>
<h2 id="특정-뷰-컨트롤러를-세로고정-하는-방법">특정 뷰 컨트롤러를 세로고정 하는 방법</h2>
<h3 id="방법1-appdelegate-사용하기">방법1. AppDelegate 사용하기</h3>
<p><a href="https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623107-application"><code>application(_:supportedInterfaceOrientationsFor:)</code></a> 로 설정하기</p>
<ol>
<li><p>AppDelegate에 아래 코드를 추가한다</p>
<pre><code class="language-swift">var supportOnlyPortrait = true

 func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -&gt; UIInterfaceOrientationMask {
     if (supportOnlyPortrait == false) {
         return UIInterfaceOrientationMask.allButUpsideDown
     }
     return UIInterfaceOrientationMask.portrait
 }</code></pre>
</li>
<li><p>세로로만 구현할 뷰컨에 아래 코드를 추가한다</p>
<pre><code class="language-swift">let appDelegate = UIApplication.shared.delegate as! AppDelegate

 override func viewWillAppear(_ animated: Bool) {
     super.viewWillAppear(animated)
     appDelegate.supportOnlyPortrait = true
 }

 override func viewWillDisappear(_ animated: Bool) {
     super.viewWillDisappear(animated)
     appDelegate.supportOnlyPortrait = false
 }</code></pre>
</li>
</ol>
<h3 id="적용한-모습">적용한 모습</h3>
<p>가로로 눕혀도 화면 보기가 세로로 고정된다
<img src="https://i.imgur.com/l3thfd9.jpg" alt=""></p>
<h3 id="단점">단점</h3>
<p>전역변수를 선언해야 해서 부담이 된다</p>
<h3 id="방법2-viewcontroller-내부에서-설정하기">방법2. viewController 내부에서 설정하기</h3>
<p><a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations"><code>supportedInterfaceOrientations</code></a> 를 이용하기 </p>
<ul>
<li>이 프로퍼티는 뷰컨트롤러가 지원하는 방향을 의미한다</li>
<li>원하는 값을 리턴해주면 된다<ul>
<li>세로고정 하고싶으면 <code>.portrait</code> 을 반환한다</li>
</ul>
</li>
</ul>
<p>둘 다 가능하게 하려면 이렇게 배열에 담아서 리턴하면 된다 </p>
<pre><code class="language-swift">override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return [.portrait, .landscapeLeft]
    }</code></pre>
<h3 id="주의해야-하는-경우">주의해야 하는 경우</h3>
<p>이렇게 고정하고 싶은 뷰컨트롤러가 <strong>네비게이션 컨트롤러</strong> 같은 <code>상위뷰컨트롤러</code>를 가지는 경우
<img src="https://i.imgur.com/QwFnxQW.png" alt="">
아무리 <code>supportedInterfaceOrientations</code> 프로퍼티를 재정의 해줘도 세로고정이 안된다.
왜냐하면 상위뷰컨의 세팅을 따르기 때문이다
*(참고로<code>supportedInterfaceOrientations</code> 의 default가 <code>.all</code> 이다)</p>
<h4 id="해결방법">해결방법</h4>
<ol>
<li>navigationController 에서 supportedInterfaceOrientations를 아래처럼 재정의 해준다</li>
</ol>
<pre><code class="language-swift">class NavigationController: UINavigationController {

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        topViewController?.supportedInterfaceOrientations ?? .all
    }
}</code></pre>
<ol start="2">
<li>그리고 원하는 뷰컨에서 <code>.portrait</code> 을 반환해주면 해당 뷰컨에서만 세로고정이 된다</li>
</ol>
<pre><code class="language-swift">override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return .portrait
    }</code></pre>
<p>이런식으로 view controller마다 프로퍼티를 재정의해서 지원하는 방향을 설정할 수 있다</p>
<h3 id="회전여부를-결정하는-방법">회전여부를 결정하는 방법</h3>
<p>회전 여부를 결정하기 위해 시스템은 <code>뷰컨트롤러가 지원되는 방향</code>과 <code>앱이 지원하는 방향</code>을 비교한다. 
앱이 지원하는 방향은 <code>Info.plist</code> 파일 또는 AppDelegate의 메서드인<code>application(_:supportedInterfaceOrientationsFor:)</code> 에 의해 결정된다</p>
<p>그 순서는 일단 Info.plist 에 있는 값을 기본으로 사용하고,<code>application(_:supportedInterfaceOrientationsFor:)</code> 메서드가 정의 되었다면 해당 메서드를 따른다.</p>
<h3 id="관련-프로퍼티">관련 프로퍼티</h3>
<ul>
<li><a href="https://developer.apple.com/documentation/uikit/uiviewcontroller/1621419-shouldautorotate"><code>shouldAutorotate</code></a>
이 프로퍼티가 True 일때만 메서드들을 호출한다고 한다. 우리가 핸드폰에서 설정하는 <code>자동회전</code> 설정을 의미하는 것 같다</li>
</ul>
<blockquote>
<p>참고
 오동나무 블로그 <a href="https://odong-tree.github.io/ios/2020/12/27/supportedInterfaceOrientations/">https://odong-tree.github.io/ios/2020/12/27/supportedInterfaceOrientations/</a>
 라자냐 블로그 <a href="https://velog.io/@wonhee010/%ED%8A%B9%EC%A0%95-ViewController%EC%97%90%EC%84%9C-%ED%99%94%EB%A9%B4-%ED%9A%8C%EC%A0%84-%EC%B2%98%EB%A6%AC">https://velog.io/@wonhee010/특정-ViewController에서-화면-회전-처리</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSON이란?]]></title>
            <link>https://velog.io/@na-young-kwon/JSON</link>
            <guid>https://velog.io/@na-young-kwon/JSON</guid>
            <pubDate>Sun, 05 Dec 2021 06:46:59 GMT</pubDate>
            <description><![CDATA[<h2 id="json이란">json이란?</h2>
<p>메모리에 있는 객체를 사람도 읽을 수 있는 문자열로 표현하는 방법론
약속된 형태의 0과1로 바꿔서 -&gt; 다른 컴퓨터에 전달하고 다시 0과1로 변환해내자
사람이 읽기도 편하고, 컴퓨터 끼리 똑같은 객체를 주고 받기도 편하다
디스크에 저장될 때는 사람이 읽을 수 있는 문자로 저장되는것이 아니라, 0과1로 저장된다</p>
<ul>
<li><code>{ }</code>: 객체(딕셔너리)</li>
<li><code>[ ]</code>: 배열</li>
<li><code>&quot; &quot;</code>: 문자열</li>
<li>문자열 외: 숫자</li>
</ul>
<h2 id="read-data-from-arrays">Read Data From Arrays</h2>
<p>When the JSON you use contains a homogeneous array of elements, you add a conformance to the Codable protocol on the individual element&#39;s type. </p>
<p>To decode or encode the entire array, you use the syntax [Element].self.
배열 전체를 인코딩/디코딩 하려면 <code>[Element].self</code> 이렇게 작성</p>
<pre><code class="language-swift">let decoder = JSONDecoder()
let products: [GroceryProduct] = try decoder.decode([GroceryProduct].self, from: jsonData)</code></pre>
<p>JSON 배열에 GreatProduct 인스턴스가 아닌 요소가 하나라도 포함되어 있으면 디코딩이 실패합니다. 
이렇게 하면 JSON 배열이 제공자가 제공한 보증에 대한 오탈자나 오해 때문에 데이터가 자동으로 손실되는일이 없습니다</p>
<h2 id="change-key-names">Change Key Names</h2>
<p>Names you use in your Swift code don&#39;t always match the names in JSON that refer to the same values. When working with the JSONEncoder and JSONDecoder classes in Swift, you can easily adopt conventional Swift names in your data types even when using JSON that requires the use of other names.
Swift 코드에서 사용하는 이름이 항상 동일한 값을 참조하는 JSON의 이름과 일치하지 않습니다. Swift에서 JSONNcoder 및 JSONDecoder 클래스로 작업할 때 다른 이름을 사용해야 하는 JSON을 사용하는 경우에도 데이터 유형에 기존 Swift 이름을 쉽게 채택할 수 있습니다.</p>
<p>To create a mapping between Swift names and JSON names, you use a nested enumeration named CodingKeys within the same type that adds conformance to Codable, Encodable, or Decodable.
Swift 이름과 JSON 이름 간의 매핑을 만들려면 Codable, Encodable 또는 Decodable에 준수를 추가하는 동일한 유형 내에 CodingKeys라는 중첩 열거형을 사용합니다.</p>
<p>In the example below, see how the Swift property name points is mapped to and from the name &quot;product_name&quot; when the property is encoded and decoded.
아래 예에서는 속성을 인코딩 및 디코딩할 때 Swift 속성 이름 포인트가 &quot;product_name&quot; 이름으로 매핑되는 방법을 확인하십시오.</p>
<blockquote>
<p>원래 josn 데이터와 똑같은 이름을 쓰더라도 GroadyProduct 구조에 필요한 값이기 때문에 CodingKeys 열거에 포함시킬 수 있습니다.</p>
</blockquote>
<h2 id="access-nested-data-intermidiate-type">Access Nested Data (Intermidiate type)</h2>
<p>외부 소스 또는 기존 로컬 형식에서 JSON을 사용하는 앱을 작성할 수 있습니다. 두 경우 모두 앱에서 모델링 중인 개념의 구조와 JSON의 제작자가 모델링한 개념 사이의 불일치를 발견할 수 있습니다. </p>
<p>Swift 프로그램에 대한 데이터의 논리적 번들이 사용자가 사용하는 JSON의 여러 중첩된 개체 또는 배열에 분산되어 있는 경우가 있습니다. 당신이 읽고 있는 JSON의 구조와 일치하는 해독 가능한 타입을 작성하여 구조적 갭을 메우세요. </p>
<p>디코딩 가능한 유형은 디코딩하기에 안전한 중간 유형의 역할을 합니다. 나머지 앱에서 사용할 유형의 이니셜라이저에서 데이터 원본 역할을 합니다.</p>
<p>중간 유형을 사용하면 다양한 모양의 외부 JSON과의 호환성을 유지하면서 자체 코드에서 가장 자연스러운 유형을 사용할 수 있습니다.</p>
<h2 id="merge-data-from-different-depths">Merge Data from Different Depths</h2>
<p>JSON 파일이나 API에서 사용하는 데이터 모델이 앱에서 사용 중인 모델과 일치하지 않는 경우가 있습니다. 이 경우 인코딩 및 디코딩 시 개체를 JSON에서 병합하거나 분리해야 할 수 있습니다. </p>
<p>결과적으로, 단일 인스턴스의 인코딩 또는 디코딩은 JSON 객체의 계층 구조에서 위나 아래로 도달하는 것을 포함한다.</p>
<h2 id="optional타입을-디코딩-해주는-메서드">Optional타입을 디코딩 해주는 메서드</h2>
<p><img src="https://images.velog.io/images/na-young-kwon/post/9e23bdde-2728-430a-82fe-d426263c3eaf/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-05%20%EC%98%A4%ED%9B%84%205.26.43.png" alt=""></p>
<h2 id="docode-관련-메서드">docode 관련 메서드</h2>
<p><img src="https://images.velog.io/images/na-young-kwon/post/d6ff3397-175f-4b88-b8cd-1f5e222fd4f9/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-12-05%20%EC%98%A4%ED%9B%84%205.36.51.png" alt="">
지정된 키 타입으로 입력된 컨테이너에 표현된 지정된 키에 대해 저장된 데이터를 반환합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ARC]]></title>
            <link>https://velog.io/@na-young-kwon/ARC</link>
            <guid>https://velog.io/@na-young-kwon/ARC</guid>
            <pubDate>Mon, 22 Nov 2021 17:12:57 GMT</pubDate>
            <description><![CDATA[<h3 id="✔-arc">✔ ARC</h3>
<ul>
<li>자동으로 메모리를 관리해주는 친구</li>
<li>객체에 대한 참조 카운트를 관리하고 0이 되면 자동으로 메모리를 해제한다</li>
<li>compile time에 실헹한다(run time에 계속 실행되는게 아님)</li>
<li>retain cycle에 유의해야 함</li>
</ul>
<h3 id="✔-retain-release">✔ retain, release</h3>
<p><code>retain</code> reference count 증가를 통해 현재 scope에서 객체가 유지되는 것을 보장한다
<code>release</code> reference count를 감소시킨다. retain 후에 필요없을 때 reslease를 한다.</p>
<h3 id="✔-compile-time에-실행되는데-어떻게-동적으로-실행되는-것들의-reference-count를-세고-메모리-관리를-할-수-있는가">✔ compile time에 실행되는데 어떻게 동적으로 실행되는 것들의 reference count를 세고 메모리 관리를 할 수 있는가?</h3>
<p><img src="https://images.velog.io/images/na-young-kwon/post/d7cc8733-ef5d-44a4-8082-b67958870a1f/0*uPhaQKYGc7AAdTvw.jpg" alt="">
complie time에 자동으로 retain, release를 적절한 위치에 삽입하는 방식으로 메모리를 관리한다.
이렇게 자동으로 retain, release를 삽입해서 관리를 하고 0이될 때 deinit을 호출해서 메모리를 해제시킨다.</p>
<h3 id="✔-arc를-이해해야-하는-이유">✔ ARC를 이해해야 하는 이유?</h3>
<ul>
<li><p>메모리 관리는 Data, Heap, Stack, Code 이렇게 4가지 가상 메모리 영역중 Heap영역과 관련되어 있다</p>
</li>
<li><p>Heap은 Class, closure 등 참조형 자료들이 머무는 공간이자, 개발자가 동적으로 할당하는 메모리 공간이기 때문에 관리가 필요하다</p>
</li>
<li><p>관리를 하기 위해서 Heap영역에 참조형 자료들이 얼마나 참조되고 있는지 카운팅하고 이에따라 메모리를 항당 및 제거하면 된다</p>
</li>
<li><p>그리고 이것을 자동으로 해주는 것이 바로 ARC이다</p>
</li>
<li><p>ARC가 해주는건 retain과 releae를 자동으로 삽입해주는것 밖에 없다. 자동으로 삽입하는 방법을 우리가 조금 조절하고 싶을 때</p>
</li>
<li><p>우리가 원하는 방향으로 메모리 관리가 이루어지려면 ARC 에 명확한 힌트를 주어야 하기 때문이다</p>
</li>
</ul>
<h4 id="우리가-원하는-방향이-아닌-경우는-언제일까">우리가 원하는 방향이 아닌 경우는 언제일까?</h4>
<ul>
<li>클래스의 인스턴스에서 강한 참조 싸이클 문제가 발생할 때</li>
<li>클로저에서 강한 참조 싸이클 문제가 발생할 때</li>
</ul>
<h3 id="✔-참조횟수가-되는-경우">✔ 참조횟수가 (+)되는 경우</h3>
<ol>
<li>인스턴스를 새로 생성할 때</li>
<li>기존 인스턴스를 다른 변수에 대입할 때</li>
</ol>
<h3 id="✔-참조횟수가--되는-경우">✔ 참조횟수가 (-)되는 경우</h3>
<ol>
<li>인스턴스를 가리키던 변수가 메모리에서 해제됐을 때<pre><code class="language-swift">func makeClone(_ origin: Human) {
 let clone = origin                          // ② RC : 2
}
</code></pre>
</li>
</ol>
<p>let sodeul = Human(name: &quot;Sodeul&quot;, age: 26)     // ① RC : 1
makeClone(sodeul) // ③ RC : 1 함수가 종료되어 지역변수 clone이 스택에서 해제되는 순간 RC -1이 된다</p>
<pre><code>2. nil이 지정되었을 때
3. 변수에 다른 값을 대입한 경우
```swift
var zoe = Human(name: &quot;zoe&quot;, age: 24) // zoe의 RC : 1
var allie = Human(name: &quot;allie&quot;, age: 22) // allie의 RC : 1

zoe = allie // zoe의 RC : 0, allie의 RC : 2
zoe에 allie의 값을 대입하게 되면 zoe의 RC는 -1, allie의 RC는 +1
zoe 변수에 저장된 주소값이 바뀌었으니 당연히 참조 카운트도 변하게 된다
zoe가 가리키던 인스턴스의 RC가 0이 되었으므로 메모리에서 해제된다</code></pre><ol start="4">
<li>프로퍼티의 경우, 속해있는 클래스의 인스턴스가 메모리에서 해제될 때</li>
</ol>
<h3 id="✔-구조체와-열거형은-왜-메모리-관리를-안해주는가">✔ 구조체와 열거형은 왜 메모리 관리를 안해주는가?</h3>
<ul>
<li>값타입이기 때문이다</li>
</ul>
<h3 id="✔-순환참조란">✔ 순환참조란?</h3>
<ul>
<li>순환참조 발생시 영구적으로 메모리가 해제되지 않을 수 있다. </li>
<li>strong으로 선언된 변수들이 순환참조 됐을 시 서로가 서로를 참조하고 있어서 RC가 0이 되지 못한다 </li>
<li>심지어 해당 인스턴스를 가리키던 변수도 nil이 되었기 때문에 인스턴스에 접근할 수 있는 방법도 없어 메모리 해제도 못하게 된다</li>
<li>즉 어플이 죽기 전까진 memory leak이 계속 발생하는 것이다
이렇게 strong을 사용해서 순환참조에 문제가 생긴 경우를 <code>강한 순환참조</code> 라고 한다</li>
</ul>
<h3 id="✔-weak">✔ weak</h3>
<ul>
<li>인스턴스를 참조할 때 RC를 증가시키지 않는다</li>
<li>참조하던 인스턴스가 메모리에서 해제된 경우 , 자동으로 nil이 할당되어 메모리가 해제된다</li>
<li>프로퍼티를 선언한 이루 나중에 nil이 할당된다는 관점으로 보아 weak는 <code>옵셔널 타입의 변수</code>여야 한다</li>
</ul>
<h3 id="✔-weak는-어느쪽에-붙이면-좋을까">✔ weak는 어느쪽에 붙이면 좋을까?</h3>
<ul>
<li>수명이 동일하다면 둘 중 한 쪽을 weak로 선언해 주지만</li>
<li>수명이 다를경우 수명이 더 짧은 인스턴스를 가리키는 애를 약한 참조로 선언해야 한다
<code>영희가 먼저 죽는다 -&gt; 철수의 girlFriend가 nil이 될 수 있다 -&gt; 철수의 girlFriend를 weak으로 선언한다</code></li>
</ul>
<h3 id="✔-언제-strong을-써야-하는가">✔ 언제 strong을 써야 하는가?</h3>
<ul>
<li>Strong은 소유대상의 reference count를 1 증가시킴으로써 dealloc 되지 않도록 하고, Weak 는 reference count는 증가시키지 않고 소유함으로써 상호참조 발생을 막아 메모리 누수를 막기위해 사용한다</li>
<li>복잡한 뷰 Hierarchy일때 weak대신 strong을 써야하는 경우도 존재한다</li>
</ul>
<h3 id="✔-lldb에서-rc를-알려주는-명령어">✔ lldb에서 RC를 알려주는 명령어</h3>
<p><code>po CFGetRetainCount(self)</code></p>
<h3 id="method-dispatch가-무엇인가-읽어보기">Method dispatch가 무엇인가? 읽어보기</h3>
<p>final class 가 class 보다 성능이 좋다
일반 클래스는 Method Dispatch가 Dynamic 인데, 파이널 클래스는 Static이기 때문이다
<a href="https://sihyungyou.github.io/iOS-method-dispatch/">https://sihyungyou.github.io/iOS-method-dispatch/</a></p>
<h3 id="writing-high-performance-swift-code">Writing High-Performance Swift Code</h3>
<p><a href="https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#advice-use-final-when-you-know-the-declaration-does-not-need-to-be-overridden">https://github.com/apple/swift/blob/main/docs/OptimizationTips.rst#advice-use-final-when-you-know-the-declaration-does-not-need-to-be-overridden</a></p>
<blockquote>
<p>참고한 블로그 
<a href="https://sujinnaljin.medium.com/ios-arc-%EB%BF%8C%EC%8B%9C%EA%B8%B0-9b3e5dc23814">https://sujinnaljin.medium.com/ios-arc-뿌시기-9b3e5dc23814</a>
<a href="https://babbab2.tistory.com/26?category=831129">https://babbab2.tistory.com/26?category=831129</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[lldb / 디버깅]]></title>
            <link>https://velog.io/@na-young-kwon/lldb-%EB%94%94%EB%B2%84%EA%B9%85</link>
            <guid>https://velog.io/@na-young-kwon/lldb-%EB%94%94%EB%B2%84%EA%B9%85</guid>
            <pubDate>Mon, 15 Nov 2021 11:33:13 GMT</pubDate>
            <description><![CDATA[<h3 id="디버깅이란">디버깅이란?</h3>
<p>‘디버깅’이라는 용어는 여러 다양한 것을 의미할 수 있지만 대부분의 경우 코드에서 버그를 제거하는 것을 의미합니다</p>
<h3 id="디버깅하는-방법">디버깅하는 방법</h3>
<ul>
<li>print문 찍어보기</li>
<li>break point 걸어보기</li>
</ul>
<hr>
<h3 id="컴파일-vs-빌드">컴파일 vs 빌드</h3>
<ul>
<li>컴파일: 코드변환 / 컴퓨터 언어로 변환하는 과정 / low-level 언어로 변환</li>
<li>빌드: 실행파일 생성 / 소스코드 파일을 실행가능한 소프트웨어 산출물로 만드는 일련의 과정</li>
</ul>
<h3 id="과정">과정</h3>
<p>전처리 -&gt; 컴파일 -&gt; 링크 -&gt; 빌드</p>
<h3 id="컴파일">컴파일</h3>
<p>언어 -&gt; 언어
원시코드(소스코드) -&gt; 목적코드</p>
<hr>
<h3 id="컴파일언어">컴파일언어</h3>
<ul>
<li>빌드시점에 모두 컴파일되어서 나오는 언어 </li>
<li>타겟별로 모두 따로 컴파일 &amp; 빌드 되어있어야 한다</li>
<li>C, C++, Swift</li>
</ul>
<p>장점) 실행속도가 빠르다
단점) 버그가 터졌을 때 빨리 대응하기 어렵다</p>
<h3 id="인터프리터언어">인터프리터언어</h3>
<ul>
<li>코드를 실행할때마다 번역이 이루어지는 언어</li>
<li>번역해주는 친구만있으면(브라우저) 어디에서든 사용할 수 있다</li>
<li>Python, Ruby, JavaScript</li>
</ul>
<p>장점) 개발자가 실수해도 3초만에 고칠 수 있다
단점) 실시간 번역을 해야하기때문에 속도가 느리다</p>
<h3 id="바이트코드-언어">바이트코드 언어</h3>
<ul>
<li>속도도 빨리 내고싶고, 어디에서든지 쓰이고 싶다</li>
<li>컴파일을 거쳐서 중간언어로 바꾸고 중간언어를 배포</li>
<li>중간언어를 해석할 수 있는 친구만 있으면 어디에서든지 사용가능</li>
<li>Java, C#</li>
</ul>
<h3 id="swift는-왜-컴파일-언어를-사용하는가">swift는 왜 컴파일 언어를 사용하는가?</h3>
<p>가벼운 언어는 인터프리터 언어로 만들어도 됨</p>
<hr>
<h3 id="gcc">GCC</h3>
<p>GNU Compiler Collection
다양한 오픈소스들에 컴파일러를 제공해주는
&quot;컴파일러 갖다쓸거면 너네코드도 공개해!&quot;</p>
<h3 id="llvm">llvm</h3>
<p>중간번역기
LLVB 형태로 바꿔주면 컴파일 가능하도록 만든 시스템</p>
<h3 id="lldb">lldb</h3>
<p>LLVM front-end debugger
우리가 작성한 코드를 LLVM 프로토콜에 맞게 번역하는중 발생하는 오류를 잡기위한 도구?</p>
<hr>
<h2 id="학습활동">학습활동</h2>
<h3 id="축약-표현">축약 표현</h3>
<p><img src="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152f8d7ccd9ef11a51aee5d/6191c77f143f8b5487805f91.png" alt="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152f8d7ccd9ef11a51aee5d/6191c77f143f8b5487805f91.png"></p>
<h3 id="파일의-라인에-브레이크포인트-걸기">파일의 라인에 브레이크포인트 걸기</h3>
<p>AA.swift 파일의 23번째 줄에 브레이크 포인트를 설정
breakpoint set --file AA.swift --line 23
(축약형 👉🏻 br s -l 23)
<img src="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152efbaccd9ef11a51aee4f/6191c2e4143f8b5487805f7a.png" alt="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152efbaccd9ef11a51aee4f/6191c2e4143f8b5487805f7a.png"></p>
<h3 id="examplemethod라는-심볼에-브레이크-포인트를-설정">exampleMethod라는 심볼에 브레이크 포인트를 설정</h3>
<p>br -M exampleMethod</p>
<h3 id="뷰의-오토레이아웃-제약을-확인하는-방법">뷰의 오토레이아웃 제약을 확인하는 방법</h3>
<p>po view.constraints</p>
<h3 id="뷰의-색상-변경하기">뷰의 색상 변경하기</h3>
<p>po view.backgroundColor = .systemPink</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[열거형을 테스트하는 방법]]></title>
            <link>https://velog.io/@na-young-kwon/%EC%97%B4%EA%B1%B0%ED%98%95%EC%9D%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@na-young-kwon/%EC%97%B4%EA%B1%B0%ED%98%95%EC%9D%84-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sat, 13 Nov 2021 12:33:43 GMT</pubDate>
            <description><![CDATA[<h2 id="이-상태에서-어떻게-테스트를-하면-좋을까">이 상태에서 어떻게 테스트를 하면 좋을까?</h2>
<pre><code class="language-swift">   func test_add케이스일때_15와_5를입력하면_20이나오는지() {
        if sut == .add {
            let result = sut.calculate(lhs: 15, rhs: 5)
            XCTAssertEqual(result, 7)
        }
    }

    func test_subtract케이스일때_15와_5를입력하면_10이나오는지() {
        if sut == .subtract {
            let result = sut.calculate(lhs: 15, rhs: 5)
            XCTAssertEqual(result, 103)
        }
    }

    func test_divide케이스일때_15와_5를입력하면_3이나오는지() {
        if sut == .divide {
            let result = sut.calculate(lhs: 15, rhs: 5)
            XCTAssertEqual(result, 33)
        }
    }

    func test_multiply케이스일때_15와_5를입력하면_75가나오는지() {
        if sut == .multiply {
            let result = sut.calculate(lhs: 15, rhs: 5)
            XCTAssertEqual(result, 7)
        }
    }</code></pre>
<p>  처음에는 setUpWithError에서 Operator.allCases에 접근하는방식으로 해결해보려고 했다.
  하지만 모든 테스트들은 순서에 상관없이 테스트가 가능해야 하므로 적절하지 못하다고 생각했다.</p>
<p>  그래서 생각해보니 func test() { }안에서 sut = .add이런식으로 </p>
<pre><code class="language-swift">  class OperatorTests: XCTestCase {
    var sut: Operator!

    override func setUpWithError() throws {
        try super.setUpWithError()
        sut = Operator(rawValue: &quot;+&quot;)
    }

    override func tearDownWithError() throws {
        try super.tearDownWithError()
        sut = nil
    }

    func test_add케이스일때_15와_5를입력하면_20이나오는지() {
        sut = .add
        let result = sut.calculate(lhs: 15, rhs: 5)
        XCTAssertEqual(result, 20)

    }

    func test_subtract케이스일때_15와_5를입력하면_10이나오는지() {
        sut = .subtract
        let result = sut.calculate(lhs: 15, rhs: 5)
        XCTAssertEqual(result, 10)
    }

    func test_divide케이스일때_15와_5를입력하면_3이나오는지() {
        sut = .divide
        let result = sut.calculate(lhs: 15, rhs: 5)
        XCTAssertEqual(result, 3)
    }

    func test_multiply케이스일때_15와_5를입력하면_75가나오는지() {
        sut = .multiply
        let result = sut.calculate(lhs: 15, rhs: 5)
        XCTAssertEqual(result, 75)
    }
}</code></pre>
<p>  이렇게 해결~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로져]]></title>
            <link>https://velog.io/@na-young-kwon/%ED%81%B4%EB%A1%9C%EC%A0%B8</link>
            <guid>https://velog.io/@na-young-kwon/%ED%81%B4%EB%A1%9C%EC%A0%B8</guid>
            <pubDate>Tue, 09 Nov 2021 16:40:53 GMT</pubDate>
            <description><![CDATA[<h1 id="closure">Closure</h1>
<ul>
<li>클로저는 변수나 상수가 선언된 위치에서 참조를 획득하고 저장할 수 있다</li>
<li>이를 산수나 변수의 클로징 이라고 한다. 여기에서 착안된 이름이다</li>
</ul>
<p>클로저의 3가지 형태</p>
<ol>
<li>이름이 있고 어떤 값도 획득하지 않는 전역함수의 형태</li>
<li>이름이 있고 다른 함수 내부의 값을 획득할 수 있는 중첩된 함수의 형태</li>
<li>이름이 없고 주변 문맥에 따라 값을 획득할 수 있는 축약 문법으로 작성된 형태</li>
</ol>
<p>특징</p>
<ol>
<li>파라미터와 리턴타입을 생략할 수 있다(문백을 통해 타입을 유추할 수 있기 때문)</li>
<li>클로저에 단 한줄의 표현만 들어있다면 암시적으로 이를 반환 값으로 취급한다</li>
<li>축약된 argument label 이름을 사용할 수 있다</li>
<li>trailing closure 문법을 사용할 수 있다</li>
</ol>
<h3 id="클로저의-통상적인-형식">클로저의 통상적인 형식</h3>
<pre><code class="language-swift">{(매개변수들) -&gt; 리턴타입 in
    실행코드
}</code></pre>
<h3 id="in-키워드"><code>in</code> 키워드</h3>
<p>매개변수 &amp; 반환타입과  실행코드를 구분하기 위해 사용하는 키워드</p>
<h3 id="후행-클로저">후행 클로저</h3>
<p>함수의 마지막 차라미터로 위치하는 클로저는 함수의 소괄호를 닫은 루 작성해도 된다
클로저가 조금 길어지거나 가독성이 조금 떨어진다 싶으면 트레일링 클로저로 작성하면 좋다
단, 후행클로저는 맨 마지막 파라미터로 전달되는 클로저에만 해당되므로 전달인자로 클로저를 여러개 전달할때는 -&gt; 맨 마지막 클로저만 후행 클로저로 사용할 수 있다</p>
<p>단 하나의 클로저만 파라미터로 전달하는 경우 -&gt; 소괄호를 생략해도 된다</p>
<pre><code class="language-swift">before
reversed = names.sorted() { (first: String, second: String) -&gt; Bool in
    return  first &gt; second
}

after
reversed = names.sorted { (first: String, second: String) -&gt; Bool in
    return  first &gt; second
}</code></pre>
<pre><code class="language-swift">// sorted(by areIncreasingIrder: (Element, Element) -&gt; Bool ) -&gt; [Element]

let names: [String] = [&quot;A&quot;, &quot;B&quot;, &quot;f&quot;, &quot;w&quot;, &quot;t&quot;, &quot;y&quot;]

func backwards(first: String, second: String) -&gt; Bool {
    return first &gt; second
}


// 오리지널
var reversed = names.sorted(by: {(first: String, second: String) -&gt; Bool in
                return  first &gt; second
})

// 소괄호 생략
reversed = names.sorted { (first: String, second: String) -&gt; Bool in
    return  first &gt; second
}

// 파라미터 타입 생략 (타입추론을 사용 / 적합한 타입을 준수하고 있다고 유추할 수 있기 때문에)
reversed = names.sorted { (first, second) -&gt; Bool in
    return  first &gt; second
}

// 리턴타입 생략
reversed = names.sorted { (first, second) in
    return  first &gt; second
}

// 단축인자 이름 사용 &amp; in 생략 ( in = 파라미터, 리턴타입 | 싫행코드를 구분하기 위해 사용했던 키워드 )
reversed = names.sorted {
    return  $0 &gt; $1
}

// 리턴 키워드 생략 (클로저가 반환값을 가지는 클로저 이고, 내부의 실행문이 단 한줄이라면 암시적으로 해당 실행문을 반환값으로 사용할 수 있다)
reversed = names.sorted { $0 &gt; $1 }</code></pre>
<h2 id="단축인자-이름">단축인자 이름</h2>
<p>파라미터이름을 간결에서 표현할 수 있도록 
첫번째 전달인자부터 $0, $1, $2 순서도 표현한다.
단축인자 표현을 사용하게 되면 매개변수 및 반환타입과 실행코드를 구분하기 위해 썼던 <code>in</code>키워드를 사용할 필요가 없다.</p>
<h2 id="값-획득">값 획득</h2>
<ul>
<li>클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 획득할 수 있다</li>
<li>값 획득을 통해 클로저는 상수나 변수가 더 이상 존재하지 않더라도 행당 값을 자신 내부에서 참조하거나 수정할 수 있씁니다</li>
<li>이 이야기를 하는 이유는 = 클로져가 비동기 작업에 많이 사용되기 때문이다</li>
</ul>
<h2 id="클로저는-참조타입">클로저는 참조타입</h2>
<p>함수나 클로저를 변수에 할당한다는 것은 클로저의 내용물, 즉 값을 할당하는 것이 아니라 해당 클로저의 <code>참조</code>를 할당하는 것이다. 결국 클로저의 참조를 다른 상수에 할당해준다면 이는 두 상수가 모두 같은 클로저를 가리킨다는 뜻이다.</p>
<blockquote>
<p>클로저에 참조가 있다? 클로저가 주소를 가진다?</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[@objc란?]]></title>
            <link>https://velog.io/@na-young-kwon/objc%EB%9E%80</link>
            <guid>https://velog.io/@na-young-kwon/objc%EB%9E%80</guid>
            <pubDate>Fri, 05 Nov 2021 04:51:00 GMT</pubDate>
            <description><![CDATA[<p>*@objc를 붙이는 이유?
Swift4부터는 Selector 타입으로 전달할 메소드를 작성할 때 반드시 @objc 어트리뷰트를 붙여주어야 한다. 이는 Objective-C와의 호환성을 위한 것으로, Swift에서 정의한 메소드를 Objective-C에서도 인식할 수 있게 해준다.</p>
<h3 id="objc-키워드가-붙은-메서드도-일반-메서드-처럼-사용할-수-있다">@objc 키워드가 붙은 메서드도 일반 메서드 처럼 사용할 수 있다</h3>
<p>아샌이 알려주셨다
Notification을 사용하면서 노티피케이션을 받았을 때 실행할 메서드를 selector에 적어줘야한다.
이때 #selector에 들어갈 메서드는 @objc 키워드를 붙여줘야 한다. 나는 이 메서드가 #selector 전용인 줄 알았다. 따라서 일반 메서드처럼 사용할 수 없다고 생각했는데, 아니었다.</p>
<p><img src="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152efbaccd9ef11a51aee4f/6184b544a8de9c225ae0aee6.png" alt="https://s3.ap-northeast-2.amazonaws.com/media.yagom-academy.kr/resources/6152efbaccd9ef11a51aee4f/6184b544a8de9c225ae0aee6.png"></p>
<p>위 사진을 보면 @objc키워드가 붙은 <code>refreshStockLabele()</code> 메서드를 </p>
<ol>
<li>viewDidLoad에서</li>
<li>notification의 selector로 </li>
</ol>
<p>사용하고 있다.
이렇게 작성함으로서 중복되는 코드를 없애고, 재사용성을 높일 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[class 와 struct]]></title>
            <link>https://velog.io/@na-young-kwon/class-%EC%99%80-struct</link>
            <guid>https://velog.io/@na-young-kwon/class-%EC%99%80-struct</guid>
            <pubDate>Fri, 05 Nov 2021 04:11:31 GMT</pubDate>
            <description><![CDATA[<h2 id="fruitstore-타입은-왜-클래스로-구현하라고-요구했을까요">FruitStore 타입은 왜 클래스로 구현하라고 요구했을까요?</h2>
<ul>
<li>과일 재고를 앱 내에서 계속 수정해나가야 하므로 참조타입인 클래스로 구현하는 것이 용이하다.<ul>
<li>값타입에서도 할 수는 있지만 mutating을 쓰나 class를 쓰나 메서드가 모두 mutating 키워드가 붙은걸로 가정한다면 동일하므로 class를 선택</li>
</ul>
</li>
<li>앱 내에서 과일 재고를 공통적으로 관리해주고 싶을 때, 싱글톤 패턴 구현을 필요로 할때 클래스를 사용할 수도 있겠다.</li>
<li>과일 별 재고를 통합적으로 관리해줘야하기 때문에 재고에 변동이 생기면 바로 heap에 저장된 실체값이 변해야 한다</li>
<li>따라서 인스턴스를 생설할 때 값을 복사하기 보단 참조하여 재고를 변경할 수 있는 클래스가 더 적합하다</li>
<li>여러곳에서 인스턴스를 가지고 있을때 한군데에서 재고값이 수정되면 원본값도 바뀌어야 한다. </li>
</ul>
<h2 id="juicemaker-타입은-왜-구조체로-구현하라고-요구했을까요">JuiceMaker 타입은 왜 구조체로 구현하라고 요구했을까요?</h2>
<ul>
<li>참조 타입에 비해 시스템 리소스가 적게 들어가는 이유로, 즉 성능적인 측면으로 구조체를 사용</li>
<li>JuiceMaker는 주스를 만드는 함수만 가지고 있다. 즉 간단한 메서드들을 캡슐화 하는 것만이 목적이기 때문에 구조체를 사용했다</li>
<li>클래스는 참조를 전달하게되는데, 원본 값을 변경하는건지 정확히 파악하기 어렵다. 반면에 구조체는 값만 전달하기 때문에 변경사항에 대해서 개발자가 직관적으로 파악하기 쉽다.</li>
<li>클래스가 지원하는 추가 기능들은 복잡성을 증가시킨다 (상속, 힙할당 등 때문에)</li>
<li>힙 할당을 피했다</li>
<li>공식문서에 디폴트로 구조체를 사용하라고 나와있다</li>
<li>메모리에 할당된 값 타입의 데이터를 다른 변수에 복사해도 각각의 인스턴스는 데이터의 유일한 복사값을 가진다.</li>
</ul>
<h2 id="구조체와-클래스를-선택할때-핵심">구조체와 클래스를 선택할때 핵심</h2>
<p>값 타입을 써서 원본을 지키고 복사본을 수정할 것 인가? 
혹은 클래스 타입을 써서 원본까지 수정할 것인가?</p>
<hr>
<h3 id="스택은-왜-빠를까">스택은 왜 빠를까?</h3>
<ul>
<li>스택은 한 방향으로만 데이터를 넣고 빼는 단순한 구조이기 때문에 스택 포인터를 사용하여 빠르게 접근할 수 있습니다. 그래서 스택 할당은 많은 시간을 필요로 하지 않습니다. </li>
</ul>
<h3 id="힙은-왜-느릴까">힙은 왜 느릴까?</h3>
<ul>
<li>stack에는 reference인 주소값을 할당하고, 실질적인 데이터는 heap에 할당합니다.</li>
<li>하지만 힙은 할당할 때마다 적절한 메모리 공간이 있는지 확인한 후에 할당을 처리하는 동적인 구조입니다. </li>
<li>이러한 과정은 스택보다 복잡하기 때문에 더 많은 오버헤드(Overhead)가 발생하게 됩니다. 그렇기 때문에 일반적으로 더 좋은 성능의 코드를 작성하기 위해서는 값 타입을 사용하는 것이 좋습니다.</li>
</ul>
<hr>
<h3 id="copy-on-write">Copy-on-Write</h3>
<p>COW의 기본원리: 인스턴스를 복사할 때 먼저 참조를 통해 불필요한 복사를 줄이고, 수정이 발생하는 경우에만 실제로 복사를 하는 방식
스위프트에서는 기본적으로 Collection 타입에 COW가 구현되어있고, 타입에 직접 COW를 구현할 수도 있다
구조체가 복사를 한다고 하지만 실제로는 참조타입처럼 작동한다고 한다. 참조타입처럼 주소값을 가지고 있다가 값에 수정이 발생하는 경우 실제 복사가 일어나는 것이다.</p>
<hr>
<h3 id="swfitui에-struct타입이-많은-이유">SwfitUI에 struct타입이 많은 이유</h3>
<blockquote>
<p>UIKit의 view나 control의 데이터타입은 거의 class인데 SwiftUI의 데이터타입은 거의 struct 이네요.
프레임웍이 SwiftUI로 넘어오면서 struct로 전격 교체된 이유가 궁금합니다.</p>
</blockquote>
<p>아직 SwiftUI는 접해보지 않아서 잘 모르겠지만 SwiftUI의 데이터타입은 거의 struct 라고 한다. 왜그럴까?
<a href="https://namocom.tistory.com/905">https://namocom.tistory.com/905</a> (SwiftUI에 struct 타입이 많은 이유)
여기에 적혀있긴 한데 왜인지 잘 모르겠다</p>
<hr>
<p>(11월 22일 추가)</p>
<h3 id="언제-구조체를-선택하고-언제-클래스를-선택해야할까">언제 구조체를 선택하고 언제 클래스를 선택해야할까?</h3>
<h3 id="choosing-between-structures-and-classes">Choosing Between Structures and Classes</h3>
<ul>
<li><p>기본적으로는 구조체를 사용한다
Use structures by default.</p>
</li>
<li><p>Objective-C interoperability가 필요하면 클래스를 사용한다
Use classes when you need Objective-C interoperability.</p>
</li>
<li><p>데이터의 identity를 컨트롤해야 한다면 클래스를 사용한다. (주소와 관련이 있는 듯)
Use classes when you need to control the identity of the data you’re modeling.</p>
</li>
<li><p>구현을 공유하기 위해서는 구조체와 프로토콜을 함께 사용한다
Use structures along with protocols to adopt behavior by sharing implementations.</p>
</li>
</ul>
<hr>
<p>참고한 블로그</p>
<p>cory - 구조체와 클래스의 차이
<a href="https://corykim0829.github.io//swift/Understanding-Swift-Performance/#">https://corykim0829.github.io//swift/Understanding-Swift-Performance/#</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[두번째 내비게이션 컨트롤러는 왜 필요한가?]]></title>
            <link>https://velog.io/@na-young-kwon/%EB%91%90%EB%B2%88%EC%A7%B8-%EB%B7%B0%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</link>
            <guid>https://velog.io/@na-young-kwon/%EB%91%90%EB%B2%88%EC%A7%B8-%EB%B7%B0%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</guid>
            <pubDate>Mon, 01 Nov 2021 03:30:05 GMT</pubDate>
            <description><![CDATA[<h2 id="궁금한점">궁금한점</h2>
<ul>
<li>야곰은 왜 재고수정 뷰컨을 Navigation Controller에 임베드 했을까</li>
<li>왜 재고수정 뷰컨은 Navigation Controller를 가지는가</li>
<li>Navigation Controller 에 Navigation Controller에 임베드된 뷰를 띄웠을때의 장점<ul>
<li>언제 저렇게 사용해야 할까</li>
</ul>
</li>
</ul>
<h2 id="궁금한-이유">궁금한 이유</h2>
<p>나는 재고수정화면이 모달로 띄워져야 한다고 생각한다. 
왜냐하면 재고수정화면은 잠깐 머무는 화면이고 정보의 흐름과 관계가 없기 때문에 depth가 깊어질 필요가 없다고 느꼈다. 화면전환을 모달방식으로 할거라면 내비게이션 컨트롤러를 가질 필요가 없다. 
여기서 Navigation Controller에 왜 임베드했는지 1차로 이해가 안됐다</p>
<h3 id="하지만">하지만</h3>
<p>사람마다 생각이 다를 수 있다. 누군가는 재고수정 뷰컨을 navigation 방식으로 띄우는게 적절하다고 생각 할 수 있기때문에 navigation 방식으로 화면을 보여주는것에 대해서도 생각을 해봤다
그래도 왜 navigation controller에 임베드 했는지는 이해가 안된다</p>
<p>왜냐하면 첫번째 뷰컨이 navigationController를 가지기 때문에 navigation controller에 임베드 했을 때 누릴 수 있는 것들은 다 누릴 수 있다고 생각했다 (예를들면 네비게이션 바)</p>
<h3 id="재고수정-뷰컨이-navigationcontroller에-임베드-됨으로서-좋은점은-무엇일까">재고수정 뷰컨이 navigationController에 임베드 됨으로서 좋은점은 무엇일까?</h3>
<ul>
<li>프로젝트의 스텝3에 있는 완성본 사진처럼 재고수정뷰컨에 뒤로가기 버튼을 없앨 수 있다 (재고수정 뷰컨이 내비게이션 컨트롤러의 루트뷰가 되기 때문이다)</li>
<li>핵심경험을 준수한다...?</li>
<li>프로젝트가 커지고, 기능이 추가되면서 재고수정 뷰컨에사 depth가 깊어질 여지가 있다면 그에 대비할 수 있다</li>
</ul>
<h3 id="아리의-생각">아리의 생각</h3>
<p>스텝2 핵심경험중 하나인 <code>내비게이션 바 및 바 버튼 아이템의 활용</code> 도 준수하고 + 기획서에 나타난 완성본 사진 그대로 구현하려면 내비게이션 컨트롤러가 필요합니다</p>
<p>(재고수정 뷰컨이 내비게이션 컨트롤러의 루트뷰가 되면서 -&gt; 뒤로가기 버튼이 없어지고 -&gt; 내비게이션 바 아이템을 사용해서 오른쪽 위에 <code>닫기</code> 버튼도 넣어줄 수 있기 때문에)</p>
<p>생각이 바뀌셨는지 모르겠지만 저번주에 이야기 해봤을 때 아리는 재고수정 뷰컨을 모달방식으로 띄워져야 할것같다고 말해주셨다. 이유는 핵심경험 준수 + 기획서에 이전으로 돌아가는 버튼이 없기 때문이었던걸로 기억한다. </p>
<p>근데 위의 이유로 모달로 띄울 뷰에 내비게이션 컨트롤러가 필요하다고 하는건 좀 모순적이라고 생각한다.
왜냐면 모달로 띄울 뷰에 내비게이션 컨트롤러가 있을 필요가 없다. 아니 오히려 있으면 안된다. 
모달은 depth가 깊어질 수 없고 / 깊어지면 안되기 때문에....</p>
<h3 id="허황이-말해주신-것">허황이 말해주신 것</h3>
<p>내가 내비게이션 컨트롤러의 존재를 너무 불편해했더니 허황이 이렇게 말해주셨다.</p>
<blockquote>
<p>object library에 내비게이션바가 있어요. 내비게이션 컨트롤러 없이도 내비게이션바를 따로 추가해줄 수 있습니다. 내비게이션바 때문에 내비게이션 컨트롤러를 가질 필요는 없습니다.</p>
</blockquote>
<p>몰랐는데 object library에 내비게이션바가 있다 (허황 알려주셔서 감사합니다!)</p>
<blockquote>
<img src="https://images.velog.io/images/na-young-kwon/post/f32adca5-b6be-401b-9ac2-c1eef93ef267/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-10-31%20%EC%98%A4%ED%9B%84%201.57.34.png" width="500" height="400"/>
하이라이트 된 부분을 해석해보면 
</blockquote>
<p>이 말을 듣고 내비게이션 컨트롤러을 없앨 수 있어서 처음에는 너무 좋았다. 이렇게 허황 말대로 하면<br>(저작권 때문에 스토리보드 사진 첨부 불가.. 소중한 저작권)</p>
<ul>
<li>재고수정뷰컨을 모달로 띄우면서 </li>
<li>내가 불편해했던 내비게이션 컨트롤러를 없앨 수 있고 </li>
<li>내비게이션 바 버튼 아이템을 사용하니 핵심경험도 준수할 수 있고 </li>
<li>기획서에 나타난 완성본 사진 그대로 구현할 수 있다</li>
</ul>
<p>근데 이게 대체 머선 의미가 있는지 모르겠다. 닫기버튼과 타이틀을 만들려고 + 핵심경험 준수를 위해 + 기획서에 있는 그대로 구현하려고  모달로 띄울 뷰에 내비게이션바를 추가한다?</p>
<p>(공식문서를 봤는데 이럴때 사용하라고 내비게이션바를 만들어 놓은것 같지 않다. 뭔가 커스텀할때 개별적으로 추가하라는것 같은데... 나중에 알아봐야겠다)</p>
<h3 id="아샌의-생각">아샌의 생각</h3>
<p>야곰은 프로젝트의 확장성 까지 고려했을 수도 있다
나중에 기능이 추가되면서 재고수정 뷰컨을 루트뷰로 해서 depth가 깊어질 수도 있기 때문이다
그래서 navigationController를 넣어준 것 아닐까? (더불어 확장성도 고려해야 한다는 교훈을 주기 위해?)</p>
<p>근데 이렇게 생각해도 모순되는 점이 너무 많다. 왜냐하면 </p>
<ul>
<li>프로젝트 요구사항에 있는 재고수정 뷰컨에는 <code>닫기</code> 버튼이 있다</li>
<li>야곰이 만들어준 스토리보드에서 재고수정 뷰컨은 내비게이션 컨트롤러의 루트뷰 이다</li>
</ul>
<p>= 루트뷰 컨트롤러에 닫기버튼이 있다</p>
<h3 id="우디가-해주신-말">우디가 해주신 말</h3>
<p>모달뷰는 최종화면 입니다. 모달뷰에서 더 깊어질 수 없다. 그러면 안된다 (HIG에 위배된다)</p>
<h3 id="결론">결론</h3>
<p>야곰은 모달방식을 의도했지만 일부러 내비게이션 컨트롤러를 넣었다.
HIG를 잘 읽자
무지성 개발자가 되지 말자</p>
<p>한가지 궁금한건 모달방식을 의도했다고 가정했을때
재고수정 뷰에 왜 타이틀을 넣어주셨을까?
가로화면이라서?</p>
]]></description>
        </item>
    </channel>
</rss>