<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wooyoung-tom.log</title>
        <link>https://velog.io/</link>
        <description>🌱 주니어 안드로이드 개발자 최우영입니다.</description>
        <lastBuildDate>Sun, 18 Sep 2022 15:15:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>wooyoung-tom.log</title>
            <url>https://velog.velcdn.com/images/wooyoung-tom/profile/b75a9542-9bb8-4258-a3af-2405ad5e73ec/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. wooyoung-tom.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wooyoung-tom" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[memo] 투명도(%)를 HEX 값으로 바꾸기]]></title>
            <link>https://velog.io/@wooyoung-tom/memo-transparency-by-hex</link>
            <guid>https://velog.io/@wooyoung-tom/memo-transparency-by-hex</guid>
            <pubDate>Sun, 18 Sep 2022 15:15:24 GMT</pubDate>
            <description><![CDATA[<h1 id="투명도-제발-hex-변환-해줘요">투명도 제발 HEX 변환 해줘요...</h1>
<p>안드로이드를 개발할 때, 디자인에 따라서 투명도를 조절해야 하는 UI 가 생기기 마련입니다.</p>
<p>저는 보통 zeplin 을 통해 디자인을 받는데, View Component 에 대한 Opacity 값이 다음과 같이 % 의 형식으로 넘어오기 때문에 해당 값을 HEX 로 변환하여 xml 에 정의해주어야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/dc8f14c1-77c1-4156-8288-dd51f5ec5f54/image.png" alt="제발 HEX 로 바꿔줘요...."></p>
<p>항상 필요할 때 마다 HEX 값을 계산하는 것 보다 메모해두고 사용하면 용이할 것 같아 남겨두려고 합니다. (5% 단위로 정리하였습니다.)</p>
<p>윈도우 기준 Ctrl + F, 맥북 기준 ⌘ + F 를 사용하셔서 찾으면 더욱 빠릅니다.</p>
<table>
<thead>
<tr>
<th>투명도 (%)</th>
<th>HEX</th>
</tr>
</thead>
<tbody><tr>
<td>100</td>
<td>FF</td>
</tr>
<tr>
<td>95</td>
<td>F2</td>
</tr>
<tr>
<td>90</td>
<td>E6</td>
</tr>
<tr>
<td>85</td>
<td>D9</td>
</tr>
<tr>
<td>80</td>
<td>CC</td>
</tr>
<tr>
<td>75</td>
<td>BF</td>
</tr>
<tr>
<td>70</td>
<td>B3</td>
</tr>
<tr>
<td>65</td>
<td>A6</td>
</tr>
<tr>
<td>60</td>
<td>99</td>
</tr>
<tr>
<td>55</td>
<td>8C</td>
</tr>
<tr>
<td>50</td>
<td>80</td>
</tr>
<tr>
<td>45</td>
<td>73</td>
</tr>
<tr>
<td>40</td>
<td>66</td>
</tr>
<tr>
<td>35</td>
<td>59</td>
</tr>
<tr>
<td>30</td>
<td>4D</td>
</tr>
<tr>
<td>25</td>
<td>40</td>
</tr>
<tr>
<td>20</td>
<td>33</td>
</tr>
<tr>
<td>15</td>
<td>26</td>
</tr>
<tr>
<td>10</td>
<td>1A</td>
</tr>
<tr>
<td>5</td>
<td>0D</td>
</tr>
<tr>
<td>0</td>
<td>00</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android 의 Context 알아보기]]></title>
            <link>https://velog.io/@wooyoung-tom/android-context</link>
            <guid>https://velog.io/@wooyoung-tom/android-context</guid>
            <pubDate>Mon, 05 Sep 2022 14:41:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/3a9cdb80-09e4-4c45-83f7-87974101023b/image.png" alt="context 의 사전적 의미"></p>
<p>Android Context 에 관해서 공부한 것을 정리한 글입니다.</p>
<h1 id="context-란">Context 란?</h1>
<p>사전적 정의로는 ‘문맥&#39;, ‘맥락&#39; 의 뜻을 가지고 있는데, 안드로이드에 입각해서 생각해보면 애플리케이션 현재 상태에 대한 ‘문맥&#39; 이라고 볼 수 있을 것입니다.</p>
<p><a href="https://developer.android.com/reference/android/content/Context">안드로이드 공식 레퍼런스</a> 에서는 Context 는 애플리케이션에 대한 글로벌 정보를 가지는 인터페이스이며, 안드로이드 시스템에서 구현체가 제공되는 추상 클래스라고 소개하고 있습니다.</p>
<h2 id="context-가-할-수-있는-것">Context 가 할 수 있는 것</h2>
<ul>
<li>application 각각의 리소스나 클래스에 대한 접근을 허용합니다.</li>
<li>activity 를 시작하거나, broadcasting, intent 수신 등 애플리케이션 레벨의 작업을 위한 up-call 을 허용합니다.</li>
</ul>
<h1 id="context-의-종류">Context 의 종류</h1>
<h2 id="application-context">Application Context</h2>
<p>Application Context 는 application 의 생명주기와 연결되어 application 이 실행중일 때 항상 같은 Context 를 유지하는 <strong>싱글톤 인스턴스</strong>입니다. </p>
<p>이는 application 내에서 현재 Context 와 분리된 생명주기를 가진 Context 가 필요하거나, activity 의 범위를 넘어 Context 를 전달해야 할 경우에 사용할 수 있다는 의미이기도 합니다.</p>
<h2 id="activity-context">Activity Context</h2>
<p>Activity 내에서만 사용할 수 있는 Context 입니다. 물론 application 과 마찬가지로 activity 와 생명주기가 연결되어 있습니다.</p>
<p>이는 곧 activity 내부에서 사용되거나, 해당 activity 와 같은 생명주기를 공유하는 어떤 다른 객체를 생성할 때 사용될 수 있다는 뜻입니다.</p>
<p>activity 와 생명주기를 공유하기 때문에, 당연히 activity 가 종료되면 context 또한 함께 소멸됩니다.</p>
<h2 id="주의할-점은-없는가">주의할 점은 없는가?</h2>
<blockquote>
<p>💡 둘의 생명주기가 다르다.</p>
</blockquote>
<p>앞서 두 종류의 Context 를 다루면서 공통적으로 이야기했던 부분이 바로 생명주기입니다.</p>
<p>activity 와 application 의 관계에 대해서 한 번 생각해보면 이해하기 편할 것 같습니다. 아래 코드를 보면,  <a href="https://developer.android.com/guide/components/activities/intro-activities?hl=en#da">AndroidManifest 에서 우리는 activity 를 application 의 하위 요소로 작성합니다.</a></p>
<pre><code class="language-xml">&lt;manifest ... &gt;
    &lt;application ... &gt;
        &lt;activity android:name=&quot;.ExampleActivity1&quot; /&gt;
        &lt;activity android:name=&quot;.ExampleActivity2&quot; /&gt;
        ...
    &lt;/application ... &gt;
  ...
&lt;/manifest &gt;</code></pre>
<ul>
<li>application 이 종료되면 해당 application 의 하위 activity 가 모두 종료된다.</li>
<li>특정 activity 가 종료된다고 해서 application 이 종료되는 것은 아니다.</li>
</ul>
<p>만약, activity 내에서 application 전역으로 사용되는 객체 (예를 들어 SharedPreferences) 를 생성할 때 Context 를 넘겨주어야 하는 상황이 생기면 어떤 Context 를 넘겨주어야 하는지에 대해 고민할 수 있습니다.</p>
<p>여기서 수명주기를 고려하지 않고 Activity 의 context 를 넘겨주게 된다면, 해당 Activity 가 종료되어도 application 은 종료되지 않으므로 application 전역으로 사용되는 객체는 소멸되지 않기 때문에 activity context 에 대한 참조가 사라지지 않습니다. </p>
<p>즉, 해당 Activity 는 종료되었으나 이에 대한 참조는 남아있기에 프로세스의 GC는 해당 activity 를 collect 하지 않게 됩니다. 이는 memory leak 이 발생하는 비극적인 결말을 낳게됩니다. 😢</p>
<h3 id="그럼-context-가-필요할-때-항상-application-context-를-사용하면-되겠네요">그럼 context 가 필요할 때 항상 application context 를 사용하면 되겠네요?</h3>
<p>그렇게 생각할 수 있지만, application context 는 만능이 아닙니다. 분명히 수행하지 못하는 작업이 있습니다. </p>
<p>application 은 사용자가 앱과 상호작용할 수 있도록 해주는 UI(View) 를 가지지 않습니다. 다시 말해, GUI 와 연관된 작업들에 필요한 context 들은 application context 가 도와줄 수 없습니다.</p>
<h1 id="context-클래스">Context 클래스</h1>
<blockquote>
<p>😎 “안드로이드 프로그래밍 Next Step” 의 4장 Context 부분을 공부 후 정리하였습니다.</p>
</blockquote>
<p>마지막으로 짧게나마 Context 클래스의 조금 더 상세한 부분을 정리해보았습니다.</p>
<p>Context 는 추상 클래스이므로 이를 상속한 자식 클래스가 구현되어야 합니다.</p>
<p>안드로이드에서 주요한 Context 의 하위 클래스로는 ContextWrapper 클래스가 있고, ContextWrapper 클래스를 한번 더 상속한 우리가 잘 아는 Activity, Service, Application 이 있습니다.</p>
<h2 id="contextwrapper">ContextWrapper</h2>
<p>ContextWrapper 클래스는 이름 그대로 Context 를 한번 더 감싼 형태의 클래스로, 다음과 같은 <code>ContextWrapper(Context base)</code> 생성자를 가지고 있습니다.</p>
<p>ContextWrapper 클래스 내부에서 아래와 같은 메소드를 찾을 수 있습니다.</p>
<pre><code class="language-java">protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException(&quot;Base context already set&quot;);
    }
    mBase = base;
}</code></pre>
<p>우리가 자주 사용하는 Activity, Service, Application 클래스들은 ContextWrapper 의 생성자를 직접 사용하는 것이 아닌, 각 컴포넌트 내부의 <code>attach()</code> 메소드의 <code>attachBaseContext()</code> 메소드를 호출합니다.</p>
<h2 id="contextimpl">ContextImpl</h2>
<p>ContextImpl 클래스는 Context 의 하위 클래스가 아닌, Context 의 여러 메서드를 직접 구현한 인스턴스입니다.</p>
<p>앞서 <code>attachBaseContext(Context base)</code> 메소드에서 <code>base</code> parameter 가 바로 각각의 컴포넌트에서 생성된 ContextImpl 인스턴스입니다.</p>
<h3 id="contextimpl-의-주요-메소드">ContextImpl 의 주요 메소드</h3>
<p>ContextImpl 의 메소드는 기능별로 helper, permission, access system service 이 세 가지 그룹으로 나눌 수 있습니다.</p>
<ul>
<li>앱 패키지 정보를 제공하거나, 내/외부 파일, SharedPreferences, 데이터베이스 등을 사용하기 위한 helper 메소드가 있다.</li>
<li>Activity, BroadcastReceiver, Service 와 같은 컴포넌트를 시작하는 메소드와 앱 내부 권한을 체크하는 메소드가 있다.</li>
<li>시스템 서비스에 접근하기 위한 <code>getSystemService()</code> 메소드가 있다.</li>
</ul>
<h2 id="하위-클래스">하위 클래스</h2>
<p>지금까지 공부해 온 내용을 토대로 다음과 같은 diagram 을 그릴수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/3152a234-9387-41bd-8a64-860297ca70ee/image.png" alt="Context Diagram"></p>
<p>객체 지향 원칙에서 상속보다는 구성을 사용하라고 하는데, 위 다이어그램을 보면 원칙에 맞다는 것을 알 수 있습니다.</p>
<p>Activity, Service, Application 을 보면 ContextImpl 을 직접 상속하는 것이 아니라, ContextImpl 의 메소드를 호출하는 형태임을 알 수 있습니다.</p>
<p>이렇게 되면 ContextImpl 의 변수가 외부로 노출될 일도 없고, ContextWrapper 에서는 ContextImpl 의 퍼블릭 메소드만 호출하게 됩니다. 또한, 각 컴포넌트 별 사용하는 기능을 제어하기도 편해집니다.</p>
<h2 id="그럼-사용할-수-있는-context-가-많네요">그럼 사용할 수 있는 Context 가 많네요?</h2>
<p>Activity, Service, Application 모두 내부에서 Context 를 사용할 수 있습니다.</p>
<p>Activity 를 예로 들면 Context 인스턴스를 3가지 사용할 수 있습니다.</p>
<ul>
<li>Activity 자신이 가지는 Context</li>
<li>ContextWrapper 의 <code>getBaseContext()</code> 메소드를 통해 가져오는 ContextImpl 인스턴스</li>
<li>ContextWrapper 의 <code>getApplicationContext()</code> 를 통해 가져오는 Application 인스턴스<ul>
<li>Activity 내부의 <code>getApplication()</code> 메소드로 가져오는 인스턴스와 같습니다.</li>
<li>당연히 Activity 가 자신이 Context 를 가지는 것처럼 Application 도 가지니깐요.</li>
</ul>
</li>
</ul>
<h3 id="주의">주의!</h3>
<p>당연히 세 가지의 인스턴스는 다릅니다. 자칫 세 가지의 Context 가 모두 동일하다 생각하고 <code>getBaseContext()</code> 로 가져온 ContextImpl 인스턴스를 Activity 로 캐스팅했다가는 ClassCastException 이 발생합니다.</p>
<h3 id="view-클래스에도-context-있어요">View 클래스에도 Context 있어요!</h3>
<p>View 의 생성자에도 Context 가 있는데, 이 Context 가 어디서 왔는지를 알아봅시다.</p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val helloWorld = findViewById&lt;TextView&gt;(R.id.hello_world)
        Log.d(&quot;MainActivity&quot;, &quot;${helloWorld.context == this}&quot;)
        Log.d(&quot;MainActivity&quot;, &quot;${helloWorld.context == baseContext}&quot;)
        Log.d(&quot;MainActivity&quot;, &quot;${helloWorld.context == applicationContext}&quot;)
        Log.d(&quot;MainActivity&quot;, &quot;${helloWorld.context == application}&quot;)
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/39650557-a8d8-4b78-abc2-a3c6fc75561a/image.png" alt="앱 실행 결과"></p>
<p>앱을 실행시키고 로그를 확인해보면 위와 같은 결과를 얻을 수 있습니다.</p>
<p>물론 View 의 생성자에 앞서 알아보았던 세 가지 Context 모두 다 전달이 가능합니다. 하지만, View 는 Activity 와 가장 연관이 있기에 Activity 의 Context 가 전달된 것을 확인할 수 있었습니다.</p>
<h1 id="마무리">마무리</h1>
<p>여기까지 Context 에 대해 공부한 것을 정리해 보았습니다. </p>
<p>안드로이드의 Context 는 application context 와 activity context 두 가지 종류를 가지고 있었습니다. 사실 이 두 가지에 대한 차이점을 알고, 적용하는 데에는 시간이 오래 걸리지 않습니다. 하지만 그만큼 쉽게 잊을 수 있다고 생각합니다.</p>
<p>쉽게 잊었을 때에 대한 리스크는 memory leak 이나 exception 에 의한 app crash 일 가능성이 높겠죠, 아주 치명적입니다. 그래서 우리는 항상 Context 를 숙지해야 합니다. 마치 안드로이드 처럼.</p>
<p>또한 Context 의 세부적인 사항까지도 한 번 훑어봤습니다. 단순히 application context 와 activity context 를 비교하고 적절히 사용한다 까지만 하면 좀 아쉽습니다.</p>
<p>안드로이드에서 Context 클래스가 어떻게 되어있는지, 그 하위 클래스들은 어떤지, 우리가 자주 사용하는 컴포넌트들은 어떻게 Context 를 가지는 지에 대해서 까지 공부하면 더 기억에 오래 남지 않을까 생각해봅니다. 👍</p>
<h1 id="참고">참고</h1>
<p><a href="https://blog.mindorks.com/understanding-context-in-android-application-330913e32514">Understanding Context In Android Application</a></p>
<p><a href="https://shinjekim.github.io/android/2019/11/01/Android-context%EB%9E%80/">[Android] 안드로이드 Context란?</a></p>
<p><a href="https://velog.io/@haero_kim/Android-Context-%EB%84%88-%EB%8C%80%EC%B2%B4-%EB%AD%90%EC%95%BC">[Android] Context, 너 대체 뭐야?</a></p>
<p><a href="http://www.yes24.com/Product/Goods/41085242">안드로이드 프로그래밍 Next Step - YES24</a></p>
<p><a href="https://lakue.tistory.com/82">[Android/안드로이드] Context, 뭐하는 녀석인지 알고 사용하자!</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android 의 Notification Permission 적용하기]]></title>
            <link>https://velog.io/@wooyoung-tom/android-notification-permission</link>
            <guid>https://velog.io/@wooyoung-tom/android-notification-permission</guid>
            <pubDate>Mon, 29 Aug 2022 14:46:05 GMT</pubDate>
            <description><![CDATA[<p>이 글은 안드로이드 공식 medium 블로그에 게시된 <strong><a href="https://medium.com/androiddevelopers/top-tips-for-adopting-androids-notification-permission-bf69afd677b8">Top Tips for Adopting Android’s Notification Permission</a></strong> 원본 글을 나름대로 해석하면서 정리한 글입니다. 😎</p>
<h1 id="개요">개요</h1>
<p>세계적으로 과도한 알림은 digital-wellbeing (디지털-웰빙) 문제로 자리잡고 있습니다. </p>
<p>그래서 이번 <strong>Android 13</strong> 에서 <a href="https://developer.android.com/develop/ui/views/notifications/notification-permission">runtime permission</a> 을 도입해, 다시 사용자가 알림 환경을 컨트롤할 수 있도록 했습니다.</p>
<p> 사용자들은 <code>foreground service</code> 를 포함하여 알림 센터에 알림을 포스트하기 전에 이 권한을 부여해야 합니다.</p>
<h1 id="target-sdk-가-32-이하일-때-in-context-요청">Target SDK 가 32 이하일 때, In-Context 요청</h1>
<p>Android 13 에서는 앱이 첫 <code>notification channel</code> 을 생성할 때 사용자가 볼 수 있는 activity 가 디스플레이 된 경우에 알림 권한 요청화면이 표시됩니다. </p>
<p>즉, 앱을 시작할 때 또는 백그라운드에서 실행하는 동안 앱이 첫 번째 <code>notification channel</code> 을 만드는 경우 사용자는 앱을 실행한 후 바로 권한 요청 화면을 볼 수 있습니다.</p>
<p>하지만 이처럼 갑작스럽게 권한 요청 화면이 등장하면 앱의 사용자 이동에 방해가 될 수 있고, 또한 권한 거부율이 증가될 수도 있습니다. 이는 당연히 앱에 익숙치 않은 신규 사용자들은 개인 정보에 대한 접근을 허용하는 것에 대해 불편하게 여길 수 있기 때문입니다.</p>
<p>이러한 이유들로 인해, <a href="https://developer.android.com/develop/ui/views/notifications/notification-permission#wait-to-show-prompt">in-context</a> 상태일 때 권한을 요청하기를 적극 권장합니다.</p>
<pre><code>🤔 in-context?

- 해당 권한을 사용하는 적절한 기능으로 이동할 때를 의미합니다.</code></pre><p>앱이 Android 13 (target SDK 가 33 이상) 을 대상으로 할 때 <code>in-context</code> 상태에서 권한을 요청하는 것은 쉽지만, 이전 Android 버전에서는 <code>in-context</code> 상태에서 권한 요청하는 방식을 첫 번째 <code>notification channel</code> 을 생성할 때로 변경하면 좀 더 수월할 것입니다.</p>
<p>Android 는 사용자가 <code>in-context</code> 상태일 때 첫 번째 <code>notification channel</code> 을 생성하는 것을 권장합니다.</p>
<p><a href="https://developer.android.com/training/permissions/requesting#explain">또한 알림에 대한 접근 권한이 필요한 이유를 설명해주는 UI 도 작성할 수 있을 것입니다.</a>
 지금까지 특정 권한이 필요한 이유를 설명해주는 앱들이 더 높은 opt-in 비율을 얻는 경향이 있다는 리서치가 있었습니다.</p>
<pre><code>🤔 opt-in?

- opt-in : 당사자가 개인 데이터 수집을 허용하기 전까지 당사자의 데이터 수집을 금지하는 제도
- opt-in 비율 : 사용자가 개인 데이터 수집을 허용한 비율입니다.</code></pre><p>앱이 새로운 사용자에게 온보딩 플로우를 제공한다면, 첫 번째 <code>notification channel</code> 을 만들기 전에 이전에 언급했던 UI 를 표시할 수 있을 것입니다.</p>
<p>새로운 사용자의 관점에서 해당 권한 요청 타이밍이 적절한지 테스트 해봅시다.</p>
<h1 id="권한-요청-테스트하기">권한 요청 테스트하기</h1>
<p>알림 권한 기능을 앱에 통합하는 것에 대한 테스트 프로세스를 간소화하는 데 도움이 되는 몇 가지 새로운 adb 명령어가 있습니다!</p>
<ol>
<li>Android 13 버전을 기기에 설치합니다.</li>
<li>기기에 adb 를 setup 합니다.</li>
<li>아래의 adb 명령어를 테스트하고 싶은 시나리오에 따라 실행시킵니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/0fb75c96-bbbd-4ab8-9a4f-6afdf3234a71/image.png" alt="Figure.1"></p>
<h1 id="알림을-보낼-때에-대한-best-practice">알림을 보낼 때에 대한 Best Practice</h1>
<h2 id="알림을-보내기-전-사용-권한-확인">알림을 보내기 전, 사용 권한 확인</h2>
<p>알림을 보내기 전에 사용자가 알림에 대한 권한을 허용했는지 확인해야 합니다.</p>
<p><a href="https://developer.android.com/reference/android/app/NotificationManager#areNotificationsEnabled()">NotificationManager#areNotificationEnabled()</a> API 를 통해 확인할 수 있습니다.</p>
<p>사용자가 Android 13 으로 기기를 업그레이드 한다면, 해당 API 는 설정 페이지의 알림 전환 외에 알림 권한에 대한 사용자의 응답 또한 반영하게 됩니다.</p>
<p>또한, 권한을 확인하는 것은 권한에 따라 기능이 달라지는 영향 (예를 들면, opt-out 비율) 을 평가하는 데에 도움이 됩니다.</p>
<pre><code>🤔 opt-out? (아까는 opt-in 이었는데? 반대말이라고 생각하시면 됩니다.)
- opt-out : 당사자가 자신의 데이터 수집을 허용하지 않는다고 명시할 때 정보수집이 금지되는 제도
- opt-out 비율 : 사용자가 개인 데이터 수집을 거부한 비율입니다.</code></pre><h2 id="권한을-거부했을-때-처리">권한을 거부했을 때 처리</h2>
<p>만약 앱에 알림이 필수적인데 사용자가 알림 권한을 거부하는 경우에는 위에서 언급했던 교육용 UI 를 보여주면서 사용자가 해당 권한을 거부하는 것에 대한 <a href="https://developer.android.com/training/permissions/requesting#handle-denial">의미를 이해할 수 있도록 적극적으로 도와야 합니다.</a></p>
<h2 id="채널에-대한-지원">채널에 대한 지원</h2>
<p>사용자가 앱에서 수신할 알림 유형을 선택하고 (앱의 알림을 완전히 비활성화 하는 것 말고), 알림에 대한 시각적/청각적 행동을 제어할 수 있도록 알림을 채널로 계속해서 분류해야 합니다.</p>
<p>추가적으로, 사용자가 채널에 포함된 알림 유형을 이해하기 쉽도록 채널의 이름과 설명을 유의미하게 작성해야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Shallow copy 와 Deep copy]]></title>
            <link>https://velog.io/@wooyoung-tom/shallow-copy-and-deep-copy</link>
            <guid>https://velog.io/@wooyoung-tom/shallow-copy-and-deep-copy</guid>
            <pubDate>Tue, 16 Aug 2022 14:48:32 GMT</pubDate>
            <description><![CDATA[<p><a href="https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EB%B3%B5%EC%82%AC">객체 복사 - 위키백과, 우리 모두의 백과사전</a></p>
<p>객체 지향 프로그래밍에서 객체를 복사할 때의 두 가지 방식인 Shallow Copy(얕은 복사)와 Deep Copy(깊은 복사)에 대해서 공부한 것을 코틀린을 기반으로 정리해보았습니다.</p>
<p>이 두 가지 방식에 대한 차이점을 제대로 숙지하고 있지 않는다면, 프로그래밍 할 때 예기치 못한 복사로 인한 오류로 디버깅하기 어려워질 수 있습니다. (물론 제 이야기 입니다 🥲)</p>
<h1 id="shallow-copy---얕은-복사">Shallow Copy - 얕은 복사</h1>
<p>Shallow Copy 는 얕은 복사를 의미합니다. 얕은 복사란, 주소 공간에서 heap 영역에 저장된 데이터 자체를 복사하는 것이 아닌, stack 영역에 있는 해당 데이터를 가리키는 참조 주소를 복사하는 것을 의미합니다.</p>
<p>아래 코드가 대표적인 shallow copy 예제입니다.</p>
<pre><code class="language-kotlin">class Person(var name: String, var age: Int)

fun shallowCopy() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
    val shallowCopiedPerson = person

    println(person)
    println(shallowCopiedPerson)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/207667e6-0ac7-470c-a463-f7f2332af98b/image.png" alt="figure 1"></p>
<p>위 코드의 결과를 보면 <code>person</code>, <code>shallowCopiedPerson</code> 두 객체의 주소가 같은 것을 볼 수 있습니다. 이는 shallow copy 로 인해 참조 주소가 복사되었기 때문입니다.</p>
<p>여튼, 우리는 복사한 객체를 가지고 지지고 볶고 할 수 있을텐데요. 제가 아래와 같은 안일한 생각으로 코딩하다가 실수를 많이 범했습니다….😢</p>
<blockquote>
<p>그럼 <code>shallowCopiedPerson</code> 이라는 객체를 새롭게 복사본으로 생성했으니까, 이걸 아무리 수정해도 origin 객체인 <code>person</code> 내부 프로퍼티들의 값은 변하지 않을거야!</p>
</blockquote>
<pre><code class="language-kotlin">fun editShallowCopiedObject() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
    val shallowCopiedPerson = person

  shallowCopiedPerson.name = &quot;tom&quot;
  shallowCopiedPerson.age = 27

    println(person)
    println(shallowCopiedPerson)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/0a543989-7ea5-4821-98ab-7c994bc7be2c/image.png" alt="figure 2"></p>
<p>당연히 저의 예상과 희망과는 다르게 값이 전부 변했습니다. 그런데 어떻게 생각해보면 당연한 일이죠, 참조하고 있는 주소를 복사하고 그 주소 내부에 있는 프로퍼티의 값을 변경하였으니 origin 객체의 값 또한 변경되는 것이.</p>
<p>그러면 제가 원했던 대로 작업을 진행하기 위해서는 어떻게 해야할까요?</p>
<h1 id="deep-copy---깊은-복사">Deep Copy - 깊은 복사</h1>
<p>남은 한 가지 copy 방식인 Deep Copy(깊은 복사)를 하면 되지 않을까요?</p>
<p>Deep Copy 는 깊은 복사를 의미합니다. 깊은 복사란, 얕은 복사와 달리 stack 에 존재하는 참조 주소를 복사하는 것이 아닌 heap 영역에 존재하는 해당 객체의 데이터 자체를 복사하는 것을 의미합니다.</p>
<p>깊은 복사를 하게 되면, 참조 주소를 복사하는 것이 아니기 때문에, 새로운 객체 주소에 origin 객체가 가진 데이터 값 자체를 복사하여 넣어주는 작업을 하게 됩니다.</p>
<p>그런데, 그 전에 한 가지 의문점이 있습니다. 분명히 위의 shallow copy 코드에서는 단순히 대입 연산자를 가지고 copy 를 수행했는데, 그럼 deep copy 는 당연히 대입 연산자로는 안된다는 뜻일텐데…. 먼저 어떤 방법들이 있는지 알아보도록 하겠습니다.</p>
<h2 id="가장-1차원적이고-확실한-방식">가장 1차원적이고 확실한 방식</h2>
<p>가장 1차원적으로 생각해보면 직접 객체를 생성하여 Deep Copy 하는 방식이 있습니다. 아래 코드를 한 번 보겠습니다.</p>
<pre><code class="language-kotlin">fun deepCopy() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
    val deepCopiedPerson = Person()

  deepCopiedPerson.name = person.name
  deepCopiedPerson.age = person.age

    println(person)
    println(deepCopiedPerson)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/d2beecec-cb95-4e65-bc06-237c09144f2b/image.png" alt="figure 3"></p>
<p>위 코드에서는 새로운 <code>deepCopiedPerson</code> 객체를 기본 생성자를 사용하여 default 값으로 객체를 생성하고, origin 객체의 값을 setter 를 통해 붙여넣었습니다.</p>
<p>물론 객체를 새로 생성하는 과정에 대한 오버헤드가 있을 수 있고, 효율적인 방식인지는 잘 모르겠습니다. 하지만 가장 쉽고, 빠르고, 정확한 방법임은 틀림없어 보입니다.</p>
<h2 id="생성자-또는-복사-팩토리를-사용하는-방식">생성자 또는 복사 팩토리를 사용하는 방식</h2>
<pre><code class="language-kotlin">class Person(var name: String, var age: Int) {
    constructor() : this(name = &quot;&quot;, age = 0)

    constructor(person: Person) : this(name = person.name, age = person.age)
}

fun deepCopy() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
  val deepCopiedPerson = Person(person)

  println(person)
  println(deepCopiedPerson)
}</code></pre>
<pre><code class="language-kotlin">fun newPersonInstance(person: Person): Person {
    return Person(person.name, person.age)
}

fun deepCopy() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
  val deepCopiedPerson = copyPersonFactory(person)

  println(person)
  println(deepCopiedPerson)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/9cde2156-1f22-4115-b841-2dcf24bb2bb9/image.png" alt="figure 4"></p>
<p>위의 코드는 생성자를 통해 origin 객체를 주입하여 새로운 객체를 생성하는 방식과 factory 메소드의 인자로 origin 객체를 넘겨주어 새로운 객체를 return 하도록 하는 방식입니다.</p>
<p>아래 결과에서 볼 수 있듯 완전히 새로운 객체가 생성되어 두 객체의 참조 주소가 다른 것을 확인할 수 있었습니다. (물론 내부 프로퍼티 값도 똑같습니다. 🙂)</p>
<h2 id="코틀린-data-class-기본-제공-메서드를-사용하기">코틀린 data class 기본 제공 메서드를 사용하기</h2>
<p>Person 클래스를 data class 로 작성하게 되면 코틀린 data class 에서 기본적으로 제공하는 copy 메소드를 사용할 수 있습니다.</p>
<p>이 copy 메소드는 어떻게 보면 위에서 보았던 factory 메소드를 이용하는 것과 얼추 비슷해 보이는데요, 아래 코드로 확인해보겠습니다.</p>
<pre><code class="language-kotlin">data class Person(var name: String, var age: Int)

fun deepCopy() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
    val deepCopiedPerson = person.copy()

    println(person)
    println(deepCopiedPerson)
}</code></pre>
<p>여기까지 deep copy 방식에 대해서 간단하게 알아보았습니다. 그렇다면 아까 전 shallow copy 를 진행했을 때 발생했던 저의 문제점에 대해서 deep copy 가 해결해 줄 수 있는지에 대해서 알아보도록 하겠습니다.</p>
<p>아래 코드는 코틀린의 data class 가 제공해주는 copy 메소드를 통해 deep copy 한 이후 복사된 객체의 내부 프로퍼티 값을 수정하고 origin 객체의 값과 비교해보는 코드입니다.</p>
<pre><code class="language-kotlin">fun main() {
    val person = Person(name = &quot;최우영&quot;, age = 26)
    val deepCopiedPerson = person.copy()

    deepCopiedPerson.name = &quot;tom&quot;
    deepCopiedPerson.age = 27

    println(person)
    println(deepCopiedPerson)
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/95c45f7f-c37d-4d87-a215-1a175922108f/image.png" alt="figure 5"></p>
<p>결과를 보면 알 수 있듯이 기존의 객체의 값은 변하지 않고 copy 메소드를 통해 deep copy 한 객체의 프로퍼티의 값만 변경된 것을 알 수 있습니다.</p>
<p>이를 통해 제가 shallow copy 를 수행함으로써 실패했던 작업을 이제는 진행할 수 있게 된 것입니다.</p>
<h1 id="조심해야하는-점">조심해야하는 점!</h1>
<p>Shallow Copy 는 객체의 참조 주소 자체를 복사하기 때문에, 원본 객체가 만약 할당을 해제해버린다면 복사한 객체는 그 사실을 모르고 해당 주소를 호출하게 되는 현상이 발생합니다.</p>
<p>Shallow Copy 는 이처럼 쉽고 빠르게 복사본을 만들 수 있지만, 개발자가 신경써야할 부분이 아무래도 생깁니다.</p>
<p>Deep Copy 는 해당 주소에 있는 데이터를 꺼내와서 새로운 객체를 생성하는 방식이기 때문에 아무래도 효율이 Shallow Copy 에 비해 떨어집니다.</p>
<p>하지만, 원본 객체가 모두 사용되고 할당이 해제되어도 사본 객체는 아예 다른 참조 주소를 가지는 독립적인 존재이므로 개발자가 그 사실을 인지하지 못해도 된다는 점이 있습니다.</p>
<p>물론 Shallow Copy 든, Deep Copy 든 사용이 완료되었다면 할당 해제 해주는 습관을 가지는 것이 좋습니다. 😀</p>
<h1 id="끝으로">끝으로</h1>
<p>이렇게 Shallow Copy 와 Deep Copy 에 대해서 알아보았습니다. 사실 이름과 동작 방식이 무언가 맞지 않은지라 한 번 공부해놓아도 뒤돌아서면 헷갈리고 까먹기 좋은 주제라고 생각됩니다.</p>
<p>이번에는 아주 간단하게 기본적인 부분들에 대해서만 알아보았는데,</p>
<ul>
<li>객체 내부의 프로퍼티</li>
<li>primitive type</li>
<li>Array or List</li>
<li>…</li>
</ul>
<p>위와 같이 세분화 하여 각각의 copy 방식을 비교해보아도 재밌을 것 같다는 생각이 들었습니다.</p>
<p>(언젠가 시간이 허락한다면 비교해보고 싶네요. 😃)</p>
<h1 id="참고">참고</h1>
<p><a href="https://jaehun2841.github.io/2019/01/13/java-object-copy/#Deep-Copy">Shallow Copy와 Deep Copy</a></p>
<p><a href="https://choiblack.tistory.com/41">Java - Shallow copy 와 Deep Copy</a></p>
<p><a href="https://jackjeong.tistory.com/100">Java - Shallow Copy(얕은 복사) vs Deep Copy(깊은 복사)</a></p>
<p><a href="https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;blogId=ahalinux&amp;logNo=220870150121">얕은복사(Shallow copy)문제 깊은복사(Deep)로 해결</a></p>
<p><a href="https://velog.io/@dddooo9/Kotlin-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%ACDeep-Copy-%ED%95%98%EB%8A%94-3%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95">[Kotlin] 깊은 복사(Deep Copy) 하는 3가지 방법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth 2.0 정리하기]]></title>
            <link>https://velog.io/@wooyoung-tom/oauth</link>
            <guid>https://velog.io/@wooyoung-tom/oauth</guid>
            <pubDate>Mon, 01 Aug 2022 15:19:23 GMT</pubDate>
            <description><![CDATA[<h1 id="oauth-20-이란">OAuth 2.0 이란?</h1>
<ul>
<li>인증을 위한 업계 표준 프로토콜이며, 애플리케이션에서의 특정 권한 부여 흐름을 제공하는 동시에 개발자의 단순성에 초점을 맞춥니다.</li>
<li>써드파티 프로그램에게 리소스 소유자를 대신하여 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식을 제공합니다.</li>
</ul>
<h1 id="이전에는-어쨌길래">이전에는 어쨌길래?</h1>
<p>이전에는 A에서 B의 리소스를 가져오기 위해서는 B의 ID, Password 를 직접 입력하여 필요할 때 마다 불러와서 요청하는 방식을 사용했습니다. 이런 방식에서의 문제점은 다음과 같습니다.</p>
<ul>
<li>사용자 입장에서는 A에게 B에 있는 내 개인 정보를 넘기기엔 신뢰도가 부족합니다.</li>
<li>A 입장에서는 B의 ID, Password 를 모두 받았기 때문에 신뢰도가 높아야하는 부담이 생깁니다.</li>
<li>B 입장에서는 A를 그냥 신뢰할 수 없으니 함부로 리소스를 제공하면 안됩니다.</li>
</ul>
<p>이러한 문제점을 해결하고자 나온 것이 OAuth 인증 방식입니다.</p>
<h1 id="주요-용어">주요 용어</h1>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Resource Server</td>
<td>사용자의 보호된 자원을 호스팅하는 서버, 다시 말해 OAuth 서비스를 제공하며 자원을 관리하는 서버입니다.</td>
</tr>
<tr>
<td>Resource Owner</td>
<td>Resource Server 의 계정을 보유하고 있는 사용자입니다. OAuth 프로토콜 흐름에서 클라이언트를 인증하는 역할을 수행합니다. 어떻게 보면 중재자 역할을 한다고 생각해도 될 것 같네요.</td>
</tr>
<tr>
<td>Client</td>
<td>보호된 자원을 사용하려고 접근 요청을 하는 애플리케이션입니다.</td>
</tr>
<tr>
<td>Authorization Server</td>
<td>Client 가 Resource Server 의 서비스를 사용할 수 있게 인증하고 토큰을 발행해주는 서버입니다.</td>
</tr>
<tr>
<td>Access Token</td>
<td>리소스 서버에게서 리소스 소유자의 보호된 자원을 획득하려할 때 사용되는 만료 기간이 있는 Token 입니다. (대체로 만료 기간이 짧습니다.)</td>
</tr>
<tr>
<td>Refresh Token</td>
<td>Access Token 이 만료되었을 때 이를 갱신하기 위해 사용하는 Token 입니다. 일반적으로 Access Token 보다 만료 기간이 깁니다.</td>
</tr>
<tr>
<td>Authentication</td>
<td>인증 → 접근 자격이 있는지에 대해 검증한다는 뜻입니다.</td>
</tr>
<tr>
<td>Authorization</td>
<td>인가 → 리소스에 접근할 수 있는 권한을 부여한다는 뜻입니다. 이 단계가 완료되어야 권한이 담긴 Access Token 을 Client 에게 부여할 수 있습니다.</td>
</tr>
</tbody></table>
<h1 id="request--response">Request &amp; Response</h1>
<p>위에서 주요 용어들에 대해서 다루었다면 이번에는 주요 API parameter 들에 대해서 한 번 알아보고 넘어가도록 하겠습니다.</p>
<table>
<thead>
<tr>
<th>parameter</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>client_id, client_secret</td>
<td>클라이언트의 자격증명입니다. Authorization Server 에서 클라이언트의 검증에 사용합니다.</td>
</tr>
<tr>
<td>redirect_url</td>
<td>Authorization Server 에서 Client 가 요청한 응답을 보낼 때 사용할 url 입니다.</td>
</tr>
<tr>
<td>response_type</td>
<td>권한 부여 방식에 대한 parameter 이며 아래 값 중 하나를 사용합니다. <br> - code: Authorization Code Grant <br> - token: Implicit Grant</td>
</tr>
<tr>
<td>state</td>
<td>CSRF 공격에 대비하기 위해 클라이언트가 권한서버에 요청 시 포함하는 임의의 문자열입니다. 필수 사항은 아니지만 클라이언트가 요청 시에 state 를 포함시켰다면, Authorization Server 는 똑같이 클라이언트에게 보내야 합니다.</td>
</tr>
<tr>
<td>grant_type</td>
<td>Access Token 요청 시 포함되는 값으로, 권한 부여 방식에 대한 설정입니다. 아래 값 중 하나를 사용합니다. <br> - authorization_code: Authorization Code Grant <br> - password: Resource Owner Password Credentials Grant <br> - client_credentials: Client Credentials Grant</td>
</tr>
<tr>
<td>code</td>
<td>Authorization Code Grant 방식에서 Access Token 요청 시에 사용됩니다. Authorization Server 에서 받은 code 와 동일합니다.</td>
</tr>
<tr>
<td>token_type</td>
<td>발행된 Token 의 타입으로, 대표적으로 Baerer, MAC(Message Authentication Code) 가 있습니다.</td>
</tr>
<tr>
<td>expires_in</td>
<td>토큰의 만료 시간입니다.</td>
</tr>
</tbody></table>
<blockquote>
<p>❔ CSRF 공격이란? → 사이트 간 요청 위조
<em>사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격입니다.</em></p>
</blockquote>
<h1 id="여러가지-인증-절차">여러가지 인증 절차</h1>
<p>OAuth 2.0 프로토콜에서는 권한 부여 방식에 따른 4가지 종류로 구분하여 제공하고 있습니다.</p>
<h2 id="1-authorization-code-grant-권한-부여-승인-코드-방식">1. Authorization Code Grant (권한 부여 승인 코드 방식)</h2>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/5fd31259-ef60-4e69-99ae-69628c242e69/image.png" alt="figure-1"></p>
<ul>
<li>권한 부여 승인을 위해 자체 생성한 Authorization Code 를 전달하는 방식으로 많이 쓰입니다.</li>
<li>보통 타사의 클라이언트에게 보호된 자원을 제공하기 위한 인증에 사용됩니다.</li>
<li>Refresh Token 의 사용이 가능합니다.</li>
<li>Access Token 이 Authorization Server 와 Client, Resource Server 를 옮겨다닐 때 공격자가 중간에 가로챌 위험이 있습니다.</li>
</ul>
<h2 id="2-implicit-grant-암묵적-승인-방식">2. Implicit Grant (암묵적 승인 방식)</h2>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/d1a3ce51-d7e1-46e6-aa13-3aa85a7ac7c2/image.png" alt="figure-2"></p>
<ul>
<li>자격증명을 안전하게 저장하기 힘든 클라이언트에게 최적화된 방식입니다.</li>
<li>앞서 소개한 권한 부여 승인 코드 없이 바로 Access Token 이 발급됩니다.</li>
<li>Refresh Token 사용이 불가능합니다.</li>
<li>Access Token 이 URL을 통해 전달되므로 위험합니다.<ul>
<li>그래서 Access Token 의 만료 기간을 되도록 짧게 설정하여 위험을 줄여야 합니다.</li>
</ul>
</li>
</ul>
<h2 id="3-resource-owner-password-credentials-grant-자원-소유자-자격증명-승인-방식">3. Resource Owner Password Credentials Grant (자원 소유자 자격증명 승인 방식)</h2>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/b3e0f58e-a26d-454e-a645-70df221b0fa3/image.png" alt="figure-3"></p>
<ul>
<li>OAuth 제공자가 서비스하는 애플리케이션에서만 사용가능한 방식입니다.</li>
<li>단순히 username 과 password 로 자격을 증명하는 방식입니다.</li>
<li>물론 Refresh Token 은 사용할 수 있습니다.</li>
</ul>
<h2 id="4-client-credentials-grant-클라이언트-자격증명-승인-방식">4. Client Credentials Grant (클라이언트 자격증명 승인 방식)</h2>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/2db0a588-58df-4a60-8686-eca48e7b57e2/image.png" alt="figure-4"></p>
<ul>
<li>가장 간단한 방식으로, 클라이언트 자신이 관리하는 리소스 혹은 권한 서버에 해당 클라이언트를 위한 제한된 리소스 접근 권한이 설정되어 있는 경우 사용됩니다.<ul>
<li>한마디로 그냥 클라이언트 자체가 Resource Owner 인 경우를 뜻한다고 생각하면 좋을 것 같습니다.</li>
</ul>
</li>
<li>Refresh Token 을 사용할 수 없습니다.</li>
</ul>
<h1 id="참고">참고</h1>
<p><a href="https://oauth.net/2/">OAuth 2.0 - OAuth</a></p>
<p><a href="https://itwiki.kr/w/OAuth#%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8_%EC%9E%90%EA%B2%A9_%EC%A6%9D%EB%AA%85">OAuth</a></p>
<p><a href="https://blog.naver.com/mds_datasecurity/222182943542">MDS인텔리전스 블로그 : 네이버 블로그</a></p>
<p><a href="https://velog.io/@kimjaejung96/OAuth2.0-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%9E%91%EB%8F%99%EB%B0%A9%EC%8B%9D">OAuth2.0 개념 및 작동방식</a></p>
<p><a href="https://doqtqu.tistory.com/295">[OAuth] OAuth2.0란?</a></p>
<p><a href="https://yonghyunlee.gitlab.io/temp_post/oauth-authorization-code/">OAuth 2.0 - Authorization code Grant</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2주차 - 재귀와 분할 정복법, 동적 계획법을 통한 설계]]></title>
            <link>https://velog.io/@wooyoung-tom/algorithms-dynamic-programming</link>
            <guid>https://velog.io/@wooyoung-tom/algorithms-dynamic-programming</guid>
            <pubDate>Mon, 18 Jul 2022 13:31:49 GMT</pubDate>
            <description><![CDATA[<h1 id="재귀와-분할-정복법">재귀와 분할 정복법</h1>
<h2 id="재귀란-무엇인가">재귀란 무엇인가?</h2>
<blockquote>
<p>작업 중에 자기 자신을 호출 하는 것을 <strong>재귀 호출</strong>(recursive call)이라 하고, 재귀 호출을 하는 함수를 <strong>재귀 함수</strong>(recursive function)라 부릅니다.</p>
</blockquote>
<p><strong>베이스 케이스(base case)</strong></p>
<ul>
<li>재귀 함수 안에서 재귀 함수를 부르지 않고 return 하는 경우를 뜻합니다.</li>
</ul>
<p><strong>재귀 함수 템플릿</strong></p>
<pre><code class="language-cpp">(반환값형) func(인수) {
    if (베이스 케이스) {
        return 베이스 케이스에 대응하는 값;
    }

    // 재귀 호출
    func(다음 인수);
    return 응답;
}</code></pre>
<h2 id="재귀-사용의-예시들">재귀 사용의 예시들</h2>
<h3 id="유클리드-호제법">유클리드 호제법</h3>
<blockquote>
<p>재귀 함수를 사용해서 명쾌하게 서술 가능한 알고리즘의 대표적인 예시로, 최대 공약수($GCD(m,n)$)를 구하는 알고리즘입니다.</p>
</blockquote>
<pre><code class="language-cpp">int GCD(int m, int n) {
  // 베이스 케이스
    if (n == 0) return m;

  // 재귀 호출
    return GCD(n, m % n);
}</code></pre>
<ul>
<li>유클리드 호제법의 복잡도는 $m ≥ n &gt; 0$ 이면 $O(log n)$입니다.</li>
</ul>
<h3 id="피보나치-수열">피보나치 수열</h3>
<pre><code class="language-cpp">int fibo(int N) {
    // 베이스 케이스
    if (N == 0) return 0;
    else if (N == 1) return 1;

    // 재귀 호출
    return fibo(N - 1) + fibo(N - 2);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/c779db43-218e-4417-ad61-07d8152625a1/image.png" alt="figure 2.1"></p>
<ul>
<li>같은 계산을 몇 번이나 계속 실행하므로 효율이 무척이나 나쁩니다.<ul>
<li>예를 들어, <code>fibo(4)</code>를 위 사진에서 왼쪽에서 계산했는데, 오른쪽에서 또 <code>fibo(4)</code>를 계산해야 합니다.</li>
<li>마찬가지로 <code>fibo(3)</code>, <code>fibo(2)</code> 또한 모두 똑같겠죠?</li>
</ul>
</li>
<li>계산 시간이 N에 대한 지수 시간이 나오게 됩니다.</li>
</ul>
<h3 id="재귀-함수를-사용한-전체-탐색">재귀 함수를 사용한 전체 탐색</h3>
<p><strong>부분합 문제</strong></p>
<p><img src="https://velog.velcdn.com/images/wooyoung-tom/post/771e3101-d9f6-4d99-a0ec-1f70f8dc62b2/image.png" alt="figure 2.2"></p>
<ul>
<li>처음에는 N개의 정수에 관한 문제에서, 재귀적으로 생각하면 그 다음에는 N-1개의 정수에 관한 문제로 바뀝니다.</li>
<li>기본적인 재귀함수를 통한 해결은 최악의 경우 모두 No인 경우 $2^N$가지 선택지를 모두 조사해야 한다는 것입니다. 그렇다면 복잡도는 $O(2^N)$이므로 굉장히 비효율적입니다.</li>
</ul>
<h2 id="메모이제이션-동적-계획법">메모이제이션 동적 계획법</h2>
<blockquote>
<p>위에서의 피보나치 수열이나 부분합 문제에서 처럼 계속 중복해서 계산하는 것이 아닌, 이전에 계산한 값을 메모리에 저장해두고 같은 인수라면 저장해 둔 값을 돌려줍니다.</p>
</blockquote>
<p><strong>캐시</strong>(cache) 방식</p>
<ul>
<li>메모이제이션을 사용하면 복잡도는 $O(N)$이 됩니다.</li>
</ul>
<pre><code class="language-cpp">vector&lt;long long&gt; memo;

long long memoFibo(int N) {
  // 베이스 케이스
    if (N == 0) return 0;
    else if (N == 1) return 1;

  // 메모 확인(이미 계산한 값이면 반환)
    if (memo[N] != -1) return memo[N];

  // 답을 메모하면서 재귀 호출
    return memo[N] = memoFibo(N - 1) + memoFibo(N - 2);
}</code></pre>
<h2 id="분할-정복법">분할 정복법</h2>
<blockquote>
<p>주어진 문제를 몇 가지 부분 문제로 분해한 후 각 부분 문제를 재귀적으로 풀고 그 답을 조합해 원래 문제의 답을 구성합니다.</p>
</blockquote>
<ul>
<li>이미 다항식 시간이 걸리는 알고리즘을 구한 문제에 대해 좀 더 빠른 알고리즘을 설계하기 위해 의식적으로 분할 정복법을 사용합니다.</li>
</ul>
<h1 id="동적-계획법">동적 계획법</h1>
<blockquote>
<p>주어진 문제 전체를 일련의 부분 문제로 잘 분해해 각 부분 문제의 답을 메모이제이션하면서 작은 부분 문제에서 큰 부분 문제로 순서대로 답을 구하는 방법입니다.</p>
</blockquote>
<h2 id="동적-계획법-관련-개념도">동적 계획법 관련 개념도</h2>
<p><strong>완화</strong></p>
<ul>
<li>일단 배열 dp 에서 각 값이 점점 작아지는 값으로 갱신되어 가는 분위기만 파악해봅시다.</li>
<li>아주 큰 값으로 초기화되어 있는 배열을 minimum 값으로 교체하는 작업입니다.<ul>
<li>상황에 따라서는 maximum 값이 될 것입니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-cpp">template&lt;classT&gt;
void chmin(T &amp;a, T b) {
    if (a &gt; b) a = b;
}</code></pre>
<p><strong>끌기 전이 형식과 밀기 전이 형식</strong></p>
<ul>
<li>끌기 전이 형식은 이전 값을 사용하여 현재 값을 갱신하는 방법입니다.</li>
<li>밀기 전이 형식은 현재 값을 사용하여 이후의 값을 갱신하는 방법입니다.</li>
</ul>
<blockquote>
<p>❗️그래프에서, 꼭지점 u에서 꼭지점 v로 전이하는 변에 관한 완화 처리가 성립하려면, dp[u] 값이 확정이어야 합니다.</p>
</blockquote>
<p><strong>전체 탐색 메모이제이션을 이용한 동적 계획법</strong></p>
<blockquote>
<p>재귀함수가 한 번 호출되어 같은 인수의 해답을 알고 있다면, 그 시점에 해답을 메모이제이션 해야합니다.</p>
</blockquote>
<h3 id="knapsack-problem">knapsack problem</h3>
<p>우선 시험 삼아 동적 계획법의 부분 문제를 다음과 같이 정의해보겠습니다.</p>
<blockquote>
<p>dp[$i$] ← 최초 $i$개의 물건 {${ 0, 1, …, i - 1 }$} 중에서 무게가 W를 넘지 않도록 고른 가격 총합의 최대값</p>
</blockquote>
<p>그런데 위 내용뿐이라면, 부분 문제 사이의 전이를 만들 수 없어 풀이가 막히게 됩니다.</p>
<p>그래서 아래와 같이 한 번 더 변경해보겠습니다.</p>
<blockquote>
<p>dp[$i$][$w$] ← 최초 $i$개의 물건 {${ 0, 1, …, i - 1 }$} 중에서 무게가 $w$를 넘지않도록 고른 가격 총합의 최대값</p>
</blockquote>
<p>위와 같이, 일단 만들어 본 테이블 설계로 전이가 제대로 되지 않을 것 같으면 점점 인덱스를 추가해서 전이가 성립하도록 하는 작업을 반복합니다.</p>
<h3 id="편집-거리">편집 거리</h3>
<blockquote>
<p>두 문자열 S, T가 얼마나 닮았는지 그 유사성을 측정하는 방법입니다.
dp[$i$][$j$] ← S의 $i$번째까지의 문자열과 T의 $j$번째까지의 문자열 사이의 편집 거리</p>
</blockquote>
<h3 id="구간-분할-최적화">구간 분할 최적화</h3>
<p>일렬로 나열한 N개의 대상물을 구간으로 나누어 분할하는 방법을 최적화 해봅시다.</p>
<blockquote>
<p>dp[$i$] ← 구간 [0, $i$)에 대해 구간을 분할하는 최소 비용</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[1주차 - 빅오 표기법과, 전체탐색을 통한 설계]]></title>
            <link>https://velog.io/@wooyoung-tom/algorithms-brute-force</link>
            <guid>https://velog.io/@wooyoung-tom/algorithms-brute-force</guid>
            <pubDate>Thu, 14 Jul 2022 14:04:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 <em><strong>&quot;문제 해결력을 높이는 알고리즘과 자료구조&quot;</strong></em> 책을 읽고 간단하게 주관적인 중요한 부분만 정리한 글입니다.</p>
</blockquote>
<h1 id="복잡도와-빅오-표기법">복잡도와 빅오 표기법</h1>
<h2 id="복잡도란">복잡도란?</h2>
<p>실제로 알고리즘을 구현하지 않아도 계산 시간이 얼마나 걸릴지 어림짐작할 수 있는 척도 역할을 하는 것</p>
<h3 id="복잡도는-왜-배우나요">복잡도는 왜 배우나요?</h3>
<ul>
<li>구현하려는 알고리즘을 실제로 프로그래밍해 보지 않아도 컴퓨터에서 실행할 때 걸리는 시간을 미리 대략적으로 파악할 수 있습니다.</li>
</ul>
<h2 id="복잡도와-빅오-표기법-1">복잡도와 빅오 표기법</h2>
<ul>
<li>알고리즘 A의 계산 시간 $T(N)$ 이 대략 $P(N)$ 에 비례하면 $T(N) = O(P(N))$ 이라 표현하고, 알고리즘 A의 복잡도는 $O(P(N))$ 이라 부릅니다.</li>
</ul>
<h3 id="복잡도를-빅오-표기법으로-표시하는-이유">복잡도를 빅오 표기법으로 표시하는 이유</h3>
<ul>
<li>최고차항 이외를 제외해도 되는 이유는 N의 크기가 커질수록 분명해집니다.</li>
<li>계수를 생략하는 이유는, 대략적인 성능을 가름할 때 계수차이가 무시해도 될 정도로 작기 때문입니다.</li>
</ul>
<h2 id="복잡도-사용법">복잡도 사용법</h2>
<p>알고리즘을 설계하기 전에는 다음 내용을 미리 확인해야 합니다.</p>
<ul>
<li>계산 실행 시간 제약은 어느 정도인가?</li>
<li>풀고 싶은 문제 크기는 어느 정도인가?</li>
</ul>
<p>위와 같은 내용을 알고 있으면 복잡도를 어느 정도까지 허용할 수 있는지 역산할 수 있습니다.</p>
<blockquote>
<p>일반적인 1초 간 처리 가능한 계산 횟수는 $<code>10^9 = 1,000,000,000</code>$ 회 정도입니다.</p>
</blockquote>
<h2 id="란다우-빅오-표기법-상세-설명">란다우 빅오 표기법 상세 설명</h2>
<ul>
<li>빅오 표기법은 <code>점근적 상한선(asymptotic upper bound)</code>으로 아무리 나쁜 상황이더라도 비교 함수보다 같거나 좋다는 뜻입니다.</li>
</ul>
<h1 id="설계-기법1-전체-탐색">설계 기법(1): 전체 탐색</h1>
<h2 id="전체-탐색1-선형-탐색법">전체 탐색(1): 선형 탐색법</h2>
<ul>
<li>요소를 하나하나 순서대로 조사하는 탐색법입니다.</li>
<li>N개 값을 순서대로 조사하므로 $O(N)$입니다.</li>
</ul>
<h2 id="선형-탐색법의-응용-flag">선형 탐색법의 응용 (flag)</h2>
<h3 id="조건을-만족하는-위치-파악-가능">조건을 만족하는 위치 파악 가능</h3>
<ul>
<li>수열에 조건을 만족하는 값이 있는지 판정할 뿐만 아니라 위치를 알고 싶을 때도 사용합니다.</li>
</ul>
<h3 id="최소값-구하기">최소값 구하기</h3>
<ul>
<li>for문으로 반복할 때 <code>min_value</code> 변수에 지금까지 확인한 값 중 가장 작은 값을 저장합니다.</li>
</ul>
<h2 id="전체-탐색-2-쌍-전체-탐색">전체 탐색 (2): 쌍 전체 탐색</h2>
<p>앞선 문제보다 조금 더 발전된 다음과 같은 문제를 생각해 봅시다.</p>
<ul>
<li>주어진 데이터 안에서 최적의 쌍을 탐색하는 문제</li>
<li>주어진 두 쌍의 데이터에서 각각의 요소를 추출하는 방법을 최적화하는 문제</li>
</ul>
<p>이런 문제는 이중 for문을 사용하면 풀 수 있습니다.</p>
<p>→ 모든 경우의 수를 조사해 보는 것이죠.</p>
<h2 id="전체-탐색-3-조합-전체-탐색">전체 탐색 (3): 조합 전체 탐색</h2>
<p>부분 합 문제와 같은 것들은 부분 집합 $2^N$개를 모두 조사하면 풀 수 있습니다.</p>
<p>→ 아래와 같이 정수의 이진법 표현인 비트 연산을 사용하는 방법도 있습니다.</p>
<pre><code class="language-C++">//
// Created by 최우영 on 2022/07/12.
//
#include &lt;iostream&gt;
#include &lt;vector&gt;

using namespace std;

void code_3_6() {
    // 입력
    int N, W;
    cin &gt;&gt; N &gt;&gt; W;
    vector&lt;int&gt; a(N);
    for (int i = 0; i &lt; N; ++i) {
        cin &gt;&gt; a[i];
    }

    // bit는 2^N개 존재하는 부분 집합 전체를 대상으로 동작
    bool exist = false;
    for (int bit = 0; bit &lt; (1 &lt;&lt; N); ++bit) {
        int sum = 0;    // 부분 집합에 포함된 요소의 합
        for (int i = 0; i &lt; N; ++i) {
            // i번째 요소 a[i]가 부분 집합에 포함되는지 여부
            if (bit &amp; (1 &lt;&lt; i)) {
                sum += i;
            }
        }

        // sum이 W와 일치하는지 여부
        if (sum == W) exist = true;
    }

    if (exist) cout &lt;&lt; &quot;Yes&quot; &lt;&lt; endl;
    else cout &lt;&lt; &quot;No&quot; &lt;&lt; endl;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존성 주입, Hilt 기초부터 해보실래요?]]></title>
            <link>https://velog.io/@wooyoung-tom/android-hilt-basic</link>
            <guid>https://velog.io/@wooyoung-tom/android-hilt-basic</guid>
            <pubDate>Sun, 20 Mar 2022 05:50:13 GMT</pubDate>
            <description><![CDATA[<p>이번 글은 Android Jetpack 의존성 주입 라이브러리인 Hilt 에 대한 기초를 정리한 글입니다. </p>
<p>의존성 주입의 기초적인 부분을 알고있다는 가정 하에 작성하였습니다. 만약 의존성 주입의 기초에 대해 제가 정리한 글을 읽어보고 싶으시다면 아래 링크를 통해 읽으실 수 있습니다.</p>
<p><a href="https://velog.io/@wooyoung-tom/Dependency-InjectionDI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%97%90-%EB%8C%80%ED%95%B4-%EC%A1%B0%EA%B8%88-%EC%89%BD%EA%B2%8C-%EC%A0%91%EA%B7%BC%ED%95%B4%EB%B3%B4%EA%B8%B0">Dependency Injection(DI), 의존성 주입에 대해 조금 쉽게? 접근해보기</a></p>
<hr>
<h2 id="🌱-의존성-주입-방식">🌱 의존성 주입 방식</h2>
<p>의존성 주입에는 다음과 같은 세가지 방법이 있습니다.</p>
<ol>
<li>생성자 주입</li>
<li>메소드 (Setter) 주입</li>
<li>필드 주입</li>
</ol>
<p>Android MVVM 구조와 Repository Pattern 을 사용하여 개발을 하고 있는 저는 첫번째 방식인 생성자 주입을 <code>Repository</code>구현체와 <code>ViewModel</code>에서 다음과 같이 주로 사용합니다.</p>
<pre><code class="language-kotlin">class InjectTargetRepository(
    private val injectedAPI: InjectedAPI
)

class InjectTargetViewModel(
    private val injectedRepository: InjectedRepository
)</code></pre>
<p>하지만 위에서 생성한 ViewModel 을 View 에서 사용하기 위해서는 View 에서 Repository 객체를 생성하여 ViewModel 생성 시 함께 주입해주어야 합니다. 이렇게 된다면 View 와 Repository 또한 뗄래야 뗄 수 없는 사이가 되어버립니다.</p>
<p>이러한 문제점을 해결하기 위해서 Koin, Dagger, Hilt 와 같은 의존성 주입을 도와주는 라이브러리를 사용합니다. 이 중 우리는 Hilt 에 대해서 알아보겠습니다.</p>
<h2 id="🗡-hilt">🗡 Hilt</h2>
<p>Hilt 라이브러리는 Dagger 기반의 컴파일 타임에 코드를 생성하는 의존성 주입 라이브러리 입니다. 이전의 Dagger 라이브러리는 많은 Annotation 과 보일러 플레이트로 인해서 러닝커브가 높다는 평을 받아 사용하기 쉽지 않았습니다.</p>
<p>하지만 이러한 단점들을 보완하고 Android 프레임워크에 알맞게 수정된 Hilt 라이브러리가 Android 공식블로그에 따르면 5월 4일부로 Stable Release 를 사용할 수 있는 것을 알 수 있습니다.</p>
<p>자세한 사항을 알아보기 전에, 먼저 어떻게 사용하는지 부터 간단하고 빠르게 알아보도록 하겠습니다.</p>
<pre><code class="language-kotlin">@HiltAndroidApp
class HiltExampleApplication: Application() {...}

@AndroidEntryPoint
class HiltActivity: Activity() {...}

@HiltViewModel
class HiltViewModel : ViewModel() {...}</code></pre>
<p>위 코드에서 볼 수 있듯이, 기본적인 Annotation 세가지가 존재합니다.</p>
<ul>
<li>Android의 Application을 지정해주는 <code>@HiltAndroidApp</code></li>
<li>EntryPoint 를 지정해주는 <code>@AndroidEntryPoint</code></li>
<li>ViewModel 을 지정해주는 <code>@HiltViewModel</code></li>
</ul>
<p>그 후에는 아래와 같이 Android class 에 생성자 주입을 진행해주어야 해당 모듈이 정상적으로 주입이 됩니다. 주입 시에는 <code>@Inject</code> Annotation 을 사용합니다.</p>
<pre><code class="language-kotlin">@HiltViewModel
class SimpleViewModel
@Inject
constructor(
      private val simpleRepository: SimpleRepository
) : ViewModel() { ... }</code></pre>
<p>또한 <code>@HiltViewModel</code> 은 Android AAC ViewModel 주입도 지원합니다. 그리고, Navigation Graph 에 따른 SharedViewModel 또한 지원합니다.</p>
<pre><code class="language-kotlin">@AndroidEntryPoint
class SimpleFragment: Fragment() {
      private val simpleViewModel: SimpleViewModel by viewModels()
      private val simpleSharedViewModel: SimpleSharedViewModel by hiltNavGraphViewModels(R.id.simple_graph)
      ...
}</code></pre>
<p>그렇다면 위 코드에서 생성자 주입을 했는데, 이 주입하려는 모듈들은 어떻게 생성될까요?</p>
<pre><code class="language-kotlin">@Module
@InstallIn(SingletonComponent::class)
object SimpleModuleObject {
      @Provides
      fun provideSimpleModule(): SimpleInterface = SimpleInterfaceImpl
}</code></pre>
<pre><code class="language-kotlin">@Module
@InstallIn(SingletonComponent::class)
abstract class SimpleModuleAbstract {
      @Binds
      abstract fun bindSimpleInterface(simpleInterfaceImpl: SimpleInterfaceImpl): SimpleInterface
}</code></pre>
<p>위의 코드에서 볼 수 있듯이, 모듈을 생성할 때는 기본적으로 네 가지 Annotation이 필요합니다.</p>
<ul>
<li><code>@module</code></li>
<li><code>@InstallIn</code></li>
<li><code>@Provides</code></li>
<li><code>@Binds</code></li>
</ul>
<h3 id="module-annotation"><code>@module</code> Annotation</h3>
<p><code>@HiltAndroidApp</code>, <code>@AndroidEntryPoint</code>, <code>@HiltViewModel</code> 등, 해당 Object 나 Class 가 Hilt 에서 의존성 주입에 사용하게 될 모듈임을 알려주는 역할을 합니다.</p>
<h3 id="installin-annotation"><code>@InstallIn</code> Annotation</h3>
<p>해당 모듈을 어떤 곳에 설치할 지를 알려주는 역할을 합니다. 설치할 수 있는 컴포넌트는 다음과 같습니다.
<img src="https://images.velog.io/images/wooyoung-tom/post/2fc19a37-8288-4197-b407-1430813b4d8b/image.png" alt="Intallable Components"></p>
<p>위 컴포넌트들의 lifetime 에 대해서는 <a href="https://developer.android.com/training/dependency-injection/hilt-android#component-lifetimes">다음 링크</a>를 참조하시면 되겠습니다.</p>
<h3 id="binds-annotation"><code>@Binds</code> Annotation</h3>
<p>만약 주입이 필요한 모듈 중, Interface가 있다면 생성자 주입을 할 수 없기에, 해당 Interface의 구현체를 Binding 하는 역할을 합니다. 주로 Android 에서 Repository Pattern 을 사용하는 프로젝트에서, Repository layer 를 예시로 들 수 있겠습니다.</p>
<h3 id="provides-annotation"><code>@Provides</code> Annotation</h3>
<p>외부 라이브러리에서 생성되는 인스턴스이거나 Builder Pattern 으로 생성되는 인스턴스에 대한 주입 객체를 생성할 때 필요합니다. 주로 Android 에서는 Retrofit 객체나, OkHttp Client 객체를 Singleton 방식으로 생성하는 데에 사용합니다.</p>
<h2 id="✅-test">✅ Test</h2>
<p>이제는 Hilt 를 사용하여 Test code 에 어떻게 적용하는 지 알아보겠습니다.</p>
<p>사실 Hilt 는 Unit Test 에서는 효용가치가 별로 없다고 볼 수 있습니다. 생성자 주입을 진행할 때, 실제 모듈을 Inject 하는 방식보다는 Mocking 된 의존성을 주입하면 되기 때문입니다.</p>
<p>그렇다면 UI Test 에서는 어떻게 사용되는지 알아보도록 하겠습니다.</p>
<p>Android UI Test 를 진행할 때, <code>@HiltAndroidTest</code> Annotation 을 사용합니다.</p>
<pre><code class="language-kotlin">@HiltAndroidTest
class SimpleFragmentTest { ... }</code></pre>
<p>물론, 기본적인 모듈을 생성하는 것처럼 Test 에서 사용할 모듈을 테스트 클래스 내에서 선언하여 사용할 수도 있습니다.</p>
<p>지금까지 Hilt 의 기본 Annotation 과 사용법, 그리고 어디에 사용하는 지에 대해서 알아보았습니다.</p>
<p>Hilt 는 컴파일 타임에 주입할 코드를 생성하기 때문에 컴파일 타임에 오류를 잡아낼 수 있는 장점이 있습니다.</p>
<p>또한 다른 DI 라이브러리도 있지만, 구글 공식 문서에서 DI 의 Best Practice 로 발표한 Hilt 가 다른 라이브러리 보다는 좀 더 안드로이드에서 Stable 할 것으로 생각됩니다. 한 번 써보시는 것도 좋을 것 같습니다.</p>
<hr>
<h2 id="references">References</h2>
<p><a href="https://medium.com/androiddevelopers/hilt-is-stable-easier-dependency-injection-on-android-53aca3f38b9c">Hilt is stable! Easier dependency injection on Android</a></p>
<p><a href="https://jaeyeong951.medium.com/android-di-with-hilt-hilt%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-5076d9e9c46b">Android DI with Hilt — Hilt를 이용한 의존성 주입</a></p>
<p><a href="http://labs.brandi.co.kr/2020/11/13/leest.html">안드로이드 Hilt 알아보기</a></p>
<p><a href="https://hyperconnect.github.io/2020/07/28/android-dagger-hilt.html">Dagger Hilt로 안드로이드 의존성 주입 시작하기</a></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[공식문서 번역] Thinking in Compose 읽어보기]]></title>
            <link>https://velog.io/@wooyoung-tom/android-thinking-in-compose</link>
            <guid>https://velog.io/@wooyoung-tom/android-thinking-in-compose</guid>
            <pubDate>Mon, 21 Feb 2022 13:53:50 GMT</pubDate>
            <description><![CDATA[<p>오늘은 Jetpack Compose 의 가장 첫 부분인 Thinking in Compose 공식문서를 번역하며 읽은 것을 공유하려고 합니다. 아래 링크는 아티클의 링크입니다.
<a href="https://developer.android.com/jetpack/compose/mental-model">Thinking in Compose</a></p>
<hr>
<h1 id="thinking-in-compose">Thinking in Compose</h1>
<p>Jetpack Compose는 안드로이드를 위한 현대적인 선언형 UI Toolkit입니다. Compose는 view를 불필요하게 변경하지 않고 UI를 렌더링할 수 있는 <em>선언형 API</em>를 제공하여 UI를 더 쉽게 작성하고 유지할 수 있게 해줍니다.</p>
<h2 id="the-declarative-programming-paradigm">The declarative programming paradigm</h2>
<p>전통적으로 안드로이드의 view 계층구조는 UI 위젯의 tree 구조로 표현되어 왔습니다.</p>
<p>사용자 상호작용과 같은 요인으로 인해 앱의 상태가 변경되면, UI 계층구조는 데이터를 표출하기 위해 업데이트 되어야 할 것입니다.</p>
<p>대부분의 UI 업데이트 방식은 <code>findViewById()</code> 와 같은 함수를 사용하여 tree를 탐색하거나, <code>button.setText(String)</code>, <code>container.addChild(View)</code>, <code>img.setImageBitmap(Bitmap)</code> 과 같은 메소드를 호출하여 node를 변경하는 방법입니다.</p>
<p>이러한 방법들은 위젯의 내부 상태를 변경합니다.</p>
<p>view들을 직접 조작하는 것은 error 비중을 높이게 만듭니다. 어떤 한 데이터가 여러 군데에서 렌더링 된다면, 이들 중 하나를 업데이트 하는 것을 잊기 쉽습니다. 또한 예상치 못한 두 번의 업데이트가 충돌할 때, 비정상적인 상태를 만들기도 합니다.</p>
<p>지난 몇년간, 모든 업계가 UI를 빌드하거나 업데이트하는것에 대한 개발 시간을 굉장히 단순화하는 선언형 UI 모델로 전환하기 시작했습니다.</p>
<p>이 기술은 처음부터 전체적인 화면을 재생성하며, 필요한 변경사항만 적용하도록 되어있습니다.</p>
<p>이러한 접근은 stateful view 계층구조를 직접 업데이트 하는 복잡함을 피할 수 있습니다.</p>
<p>전체적인 화면을 재생성하는 데에 있어서 한가지 문제점은, 시간, 컴퓨팅파워 및 배터리 사용 측면에서 잠재적인 비용이 많이 든다는 것입니다. 이 비용을 줄이기 위해서, Compose는 지능적으로 다시그려야 하는 부분만을 골라냅니다. 이 부분은 UI 컴포넌트를 디자인하는데 여러 시사점이 있는데, Recomposition 부분에서 논해보도록 합시다.</p>
<h2 id="a-simple-composable-function">A simple composable function</h2>
<p>Compose를 사용하면, 데이터를 가져와 UI에 내보내는 역할을 하는 <em>composable</em> 함수를 정의하여 UI를 빌드할 수 있습니다. 아래는 아주 간단한 예시로 String 데이터를 가져와 Text UI에 내보내는 역할을 하는 Greeting 위젯입니다.</p>
<pre><code class="language-kotlin">@Composable
fun Greeting(name: String) {
    Text(&quot;Hello $name&quot;)
}</code></pre>
<p>이 함수에 대해 몇 가지 짚고 넘어가야할 점들이 있습니다.</p>
<ul>
<li><p>함수에 <code>@Composable</code> 어노테이션이 붙어있습니다. 모든 Composable 함수들은 해당 어노테이션을 가져야 합니다. 이 어노테이션은 Compose compiler에 이 함수가 데이터를 UI로 나타내기 위한 함수라는 것을 알려주는 역할을 합니다.</p>
</li>
<li><p>함수는 데이터를 받아와야합니다. Composable 함수들은 UI를 표현하는 앱 로직 수행을 허용하는 parameter를 받아올 수 있습니다. 위 경우에서는 <code>String</code> parameter를 받아온 것을 볼 수 있습니다.</p>
</li>
<li><p>함수가 UI에 텍스트를 표시합니다. 텍스트 UI 요소를 생성하는 <code>Text()</code> composable 함수를 사용했기 때문입니다. Composable 함수들은 다른 composable 함수들을 호출하는 형식으로 UI 계층구조를 내보냅니다.</p>
</li>
<li><p>함수가 아무것도 반환하지 않습니다. UI 위젯을 구성하는 대신 원하는 화면의 상태를 설명하므로 UI를 내보내는 기능을 하는 Compose 함수들은 아무것도 반환하지 않아도 됩니다.</p>
</li>
<li><p>함수가 빠르고, <a href="https://ko.wikipedia.org/wiki/%EB%A9%B1%EB%93%B1%EB%B2%95%EC%B9%99">idempotent(멱등성)</a>을 가지며, <em>side-effect</em> 가 없습니다.</p>
<ul>
<li><p>동일한 argument를 사용하여 여러 번 호출을 해도 동일한 방식으로 동작하며, 전역변수 혹은 <code>random()</code> 메소드 호출과 같이 다른 값을 사용하지 않습니다.</p>
</li>
<li><p>프로퍼티나 전역변수의 변경과 같은 side-effects 없이 UI를 나타냅니다.</p>
<p>일반적으로, 모든 composable 함수들은 이러한 속성들을 지키며 작성되어야 합니다. 이러한 이유에 관해서는 Recomposition 부분에서 논하도록 합니다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="the-declarative-paradigm-shift">The declarative paradigm shift</h2>
<p>많은 명령형(imperative) 객체지향 UI toolkit들에서는 UI를 위젯 트리를 인스턴스화 하여 초기화합니다. 현재는 XML 레이아웃 파일을 가져오는 방식을 사용합니다. 각각의 위젯은 자체적인 내부 상태를 저장하며, getter와 setter 메소드를 앱 로직이 위젯과 상호작용할 수 있도록 드러냅니다.</p>
<p>Compose의 선언형 접근에서는, 위젯은 상태가 없으며, setter와 getter 함수가 없습니다. 사실은 위젯은 객체로 나타내지지 않습니다. UI를 다른 인자를 가지는 같은 composable 함수를 통해 업데이트할 수 있습니다. 따라서 <a href="https://developer.android.com/jetpack/guide">Guide to app architecture</a>에 설명된 대로, <code>[ViewModel](https://developer.android.com/reference/androidx/lifecycle/ViewModel)</code>과 같은 아키텍처 패턴에 상태를 쉽게 제공할 수 있습니다. 그 다음, composable 함수들은 observable 데이터가 업데이트 될 때마다 현재 애플리케이션의 상태를 UI로 변환하는 책임을 지게됩니다.</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/bd104ca3-dc12-4d96-8b3b-fc525a3d723c/figure1.png" alt=""></p>
<p><strong><em>Figure 2.</em></strong>  앱 로직은 최상위(top-level) composable 함수에 데이터를 제공합니다. 해당 함수는 데이터를 사용하여 다른 composable 함수들을 호출하고, 그 composable 함수들에 적절한 데이터를 전달하여 계층구조로 이동하는 방식을 통해 UI를 표현합니다.</p>
<p>사용자가 UI와 상호작용을 할 때, UI는 <code>onClick</code>과 같은 이벤트를 발생시킵니다. 해당 이벤트들은 앱의 상태를 변경할 수 있도록 앱 로직에 알려져야 합니다. 상태가 변경될 때, composable 함수들은 새로운 데이터와 함께 다시 호출됩니다. 이러면 UI 요소가 다시 그려지게 되는데 — 이 과정을 <em>Recomposition</em> 이라고 합니다.</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/482b0a4a-bc96-4297-be01-60d5e05ac066/figure2.png" alt=""></p>
<p><strong><em>Figure 3.</em></strong> 사용자가 UI 요소와 상호작용하여 이벤트가 발생하였습니다. 앱 로직은 이벤트에 대해 반응을 하게되고, 그 이후에 composable 함수들은 필요에 따라 자동으로 새로운 parameter들과 함께 다시 호출됩니다.</p>
<h2 id="dynamic-content">Dynamic content</h2>
<p>composable 함수들은 XML이 아닌 Kotlin으로 작성되었기 때문에, 다른 Kotlin 코드처럼 동적(dynamic)일 수 있습니다. 예를 들어보면, 사용자 목록 UI 빌드해볼 때를 가정하겠습니다:</p>
<pre><code class="language-kotlin">@Composable
fun Greeting(names: List&lt;String&gt;) {
    for (name in names) {
        Text(&quot;Hello $name&quot;)
    }
}</code></pre>
<p>이 함수는 사용자의 이름 목록을 가져와 각 사용자에 대한 인사말을 생성합니다. Composable 함수들은 상당히 정교합니다. 특정한 UI 요소를 보여주고 싶을 때는 <code>if</code> 구문을 사용하여 결정할 수 있습니다. loop도 사용할 수 있습니다. helper 함수들을 호출할 수도 있습니다. 기본적인 언어의 유연성을 충분히 갖추고 있습니다. 이와 같은 힘과 유연성이 Jetpack Compose의 핵심 장점 중 하나입니다.</p>
<h2 id="recomposition">Recomposition</h2>
<p>명령형 UI 모델에서는, 위젯을 바꾸기 위해서는 setter를 호출하여 내부 상태를 바꾸어 주었습니다. Compose 에서는 새로운 데이터를 가지고 composable 함수만 호출하면 됩니다. 이렇게 하면 함수가 <em>recompose</em>되며, 필요한 경우 함수가 내보내는 위젯이 새로운 데이터와 함께 다시 그려집니다. Compose 프레임워크는 지능적으로 바뀐 컴포넌트들에 대해서만 recompose 하도록 되어있습니다.</p>
<p>예를 들어, 버튼을 표현하는 다음의 composable 함수를 보겠습니다:</p>
<pre><code class="language-kotlin">@Composable
fun ClickCounter(clicks: Int, onClick: () -&gt; Unit) {
    Button(onClick = onClick) {
        Text(&quot;I&#39;ve been clicked $clicks times&quot;)
    }
}</code></pre>
<p>버튼이 클릭될 때 마다, caller는 <code>clicks</code>의 값을 업데이트합니다. Compose는 새로운 값을 보여주기 휘애서 lambda를 <code>Text</code> 함수와 함께 다시 호출하게됩니다; 이러한 과정을 <em>recomposition</em>이라고 부릅니다. 다른 함수들은 해당 값과 의존성이 없기 때문에 recompose 되지 않습니다.</p>
<p>앞서 논했듯이, 모든 UI 트리를 recomposing 하는 것은 컴퓨팅 파워와 배터리 수명 측면에서 비용이 많이 들게됩니다. Compose는 해당 문제를 <em>intelligent recomposition</em>으로 해결합니다.</p>
<p>Recomposition은 변경사항을 입력했을 때 composable 함수를 재호출하는 과정입니다. 이 과정은 함수의 입력이 변경되었을 때만 일어납니다. 새로운 입력에 따라 Compose가 recompose 되었을 때, 변경된 부분의 함수나 lambda만 호출하고 나머지는 건너뛰게 됩니다. 변경된 parameter들을 가지지 않은 함수나 lambda를 건너뜀으로서 Compose는 효율적으로 recompose할 수 있습니다.</p>
<p>함수의 recomposition이 건너뛰어질 수 있으므로, composable 함수들의 실행으로부터 나오는 side-effect에 의존하지 않아야 합니다. 이렇게 되면 사용자가 앱에서 이상하고 예측할 수 없는 동작을 경험하게 될 수 있습니다. side-effect는 앱의 나머지 부분에 나타나는 모든 변경사항입니다. 예를 들어보면, 다음과 같은 동작들이 위험한 side-effect들입니다:</p>
<ul>
<li>shared object의 프로퍼티에 writing 하는 것</li>
<li>ViewModel 내부의 observable이 업데이트 되는 것</li>
<li>shared preferences 가 업데이트 되는 것</li>
</ul>
<p>Composable 함수들은 애니메이션이 렌더링되는 경우와 같은 모든 프레임에서 재실행될 수 있습니다. Composable 함수들은 애니메이션 도중에 jank를 피하기 위해 빨라야합니다. shared preferences에서 데이터를 읽어 오는것과 같이 비용이 높은 작업을 수행하려고 한다면, Background Coroutine에서 수행하고 결과값을 composable 함수의 parameter로 전달해야 합니다.</p>
<p>예를 들어, 다음 코드는 <code>SharedPreferences</code>의 값을 업데이트하기 위한 composable 함수를 만듭니다. composable 함수는 shared preferences 직접 read/write를 하게 해서는 안됩니다. 대신에, background coroutine이 있는 ViewModel로 read/write 코드를 옮겨야 합니다. 앱 로직은 업데이트를 트리거하기 위해 현재 값과 callback을 전달합니다.</p>
<pre><code class="language-kotlin">@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -&gt; Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}</code></pre>
<p>이 문서에서는 Compose를 프로그래밍할 때 알아둬야 할 몇 가지 사항들을 다루고 있습니다.</p>
<ul>
<li>Composable 함수들은 임의의 순서로 실행할 수 있습니다.</li>
<li>Composable 함수들은 병렬로 실행할 수 있습니다.</li>
<li>Recomposition은 최대한 많은 composable 함수들과 lambda들을 건너뜁니다.</li>
<li>Recomposition은 optimistic하여 cancel할 수 있습니다.</li>
<li>하나의 composable 함수는 애니메이션의 모든 프레임에서와 같이 자주 실행될 수 있습니다.</li>
</ul>
<p>이어지는 섹션에서는 recomposition을 지원하기 위한 composable 함수를 구성하는 방법에 대해 알아보려고 합니다. 모든 경우에 있어서, composable 함수를 빠르고, idempotent 하며, side-effect가 없이 유지하는게 가장 중요합니다.</p>
<h2 id="composable-functions-can-execute-in-any-order">Composable functions can execute in any order</h2>
<p>composable 함수의 코드를 보면, 보이는 순서대로 코드가 동작한다고 생각할 수도 있습니다. 하지만 꼭 그것이 그렇지만은 않습니다. 만약 composable 함수가 다른 composable 함수들을 내부에서 호출하고 있다면, 그 함수들은 임의의 순서대로 동작할 수도 있습니다. Compose는 일부 UI 요소들이 다른 요소들보다 우선 순위가 높은것을 인지하고 먼저 그리게 하는 옵션을 갖추고 있습니다.</p>
<p>예를 한번 들어보겠습니다. 아래와 같이 Tab layout 내에 세 개의 화면을 그리는 코드를 보겠습니다:</p>
<pre><code class="language-kotlin">@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}</code></pre>
<p><code>StartScreen</code>, <code>MiddleScreen</code>, <code>EndScreen</code> 함수들의 호출은 임의의 순서로 수행될 수 있습니다. 이는 예를 들어 <code>StartScreen()</code>에서 전역 변수(a side-effect)를 설정하고 <code>MiddleScreen()</code>에서 해당 변수의 변경 사항을 활용할 수 없음을 의미합니다.</p>
<h2 id="composable-functions-can-run-in-parallel">Composable functions can run in parallel</h2>
<p>Compose는 composable 함수들을 병렬실행함으로써 recomposition을 최적화 할 수 있습니다. 이로 인해 Compose가 여러개의 코어를 사용하여 장점을 얻게되고, 또한 composable 함수들을 낮은 우선순위로 실행하게 됩니다.</p>
<p>이 최적화는 background thread pool 내에서 composable 함수가 실행될 수 있음을 의미합니다. 만약 composable 함수가 ViewModel 내의 함수를 호출하는 경우, Compose는 동시에 여러 thread에서 호출할 수 있을것입니다.</p>
<p>애플리케이션이 정확하게 동작하는 것을 보장하기 위해서는, 모든 composable 함수들이 side-effect가 없어야 합니다. 대신에, onClick과 같은 콜백에서 UI thread에서 실행되는 side-effect들을 트리거합니다.</p>
<p>composable 함수가 실행되었을 때, 함수를 호출한 thread와 다른 thread에서 실행되어야 합니다. 즉, composable lambda 내의 변수를 수정하는 것은 thread-safe도 아닐 뿐 더러, composable lambda가 허용하지 않는 side-effect이기 때문에 피해야 합니다.</p>
<p>다음은 list와 그 개수를 나타내는 composable을 보여주고 있습니다:</p>
<pre><code class="language-kotlin">@Composable
fun ListComposable(myList: List&lt;String&gt;) {
    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text(&quot;Item: $item&quot;)
            }
        }
        Text(&quot;Count: ${myList.size}&quot;)
    }
}</code></pre>
<p>해당 코드는 side-effect가 없고, 입력된 리스트에 따라 UI를 변경시킵니다. 규모가 작은 리스트를 표현하기에 아주 적절한 코드입니다. 하지만, 함수가 지역 변수에 writing 하는 경우, 이 코드는 thread-safe 하지 않거나 정확하지 않습니다:</p>
<pre><code class="language-kotlin">@Composable
@Deprecated(&quot;Example with bug&quot;)
fun ListWithBug(myList: List&lt;String&gt;) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text(&quot;Item: $item&quot;)
                    items++ // Avoid! Side-effect of the column recomposing.
                }
            }
        Text(&quot;Count: $items&quot;)
    }
}</code></pre>
<p>위의 예시에서는, <code>items</code> 변수가 모든 recomposition마다 수정되었습니다. 애니메이션의 모든 프레임 또는 리스트가 업데이트 될 때마다 발생할 수 있습니다. 어쨋든, UI는 잘못된 개수를 표현할 것입니다. 이러한 이유를 기반으로, Compose는 이렇게 writing 하는것을 지원하지 않습니다; 이러한 write들을 금지함으로써 프레임워크에서 composable lambda를 실행하도록 thread를 변경할 수 있습니다.</p>
<h2 id="recomposition-skips-as-much-as-possible">Recomposition skips as much as possible</h2>
<p>UI의 일부가 유효하지 않았을 때, Compose는 업데이트 되어야 하는 해당 부분만 recompose 하기 위해 최선을 다할 것입니다. 다시 말해 UI tree에서 Button의 위 또는 아래에 있는 composable들중 어떤 것도 실행하지 않고 해당 compsable을 다시 실행하는 것을 건너뛸 수 있습니다.</p>
<p>모든 composable 함수와 lambda는 스스로 recompose 할 수 있습니다. 다음은 리스트를 렌더링할 때 recomposition이 몇몇 요소들을 skip하는 방법을 보여주는 예시입니다:</p>
<pre><code class="language-kotlin">/**
 * Display a list of names the user can click with a header
 */
@Composable
fun NamePicker(
    header: String,
    names: List&lt;String&gt;,
    onNameClicked: (String) -&gt; Unit
) {
    Column {
        // this will recompose when [header] changes, but not when [names] changes
        Text(header, style = MaterialTheme.typography.h5)
        Divider()

        // LazyColumn is the Compose version of a RecyclerView.
        // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
        LazyColumn {
            items(names) { name -&gt;
                // When an item&#39;s [name] updates, the adapter for that item
                // will recompose. This will not recompose when [header] changes
                NamePickerItem(name, onNameClicked)
            }
        }
    }
}

/**
 * Display a single name the user can click
 */
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -&gt; Unit) {
    Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}</code></pre>
<p>이러한 각각의 scope들은 recomposition 중에 실행할 수 있는 유일한 항목일 수 있습니다. Compose는 <code>header</code>가 변경될 때 상위 항목을 실행하지 않고 <code>Column</code> lambda로 skip할 수 있습니다. 또한 Column이 실행될 때, Compose는 <code>names</code>가 변경되지 않았다면 <code>LazyColumnItems</code>를 skip할 수 있습니다.</p>
<p>다시 한 번 말하지만, 모든 composable 함수나 lambda를 실행할 때에는 side-effect가 없어야 합니다. side-effect를 다루어야 한다면, callback을 통해서 트리거 해야합니다.</p>
<h2 id="recomposition-is-optimistic">Recomposition is optimistic</h2>
<p>Recomposition은 composable 함수의 인자가 변경되었다고 Compose가 판단할 때마다 시작합니다. Recomposition은 <em>optimistic</em>(낙관적)입니다. 이 말인 즉슨, Compose는 인자가 또 변경되기 전에 recomposition을 끝낸다는 것입니다. 만약에 recomposition이 끝나기 전에 인자가 <em>변경</em>되었다면, Compose는 진행하고 있던 recomposition을 취소하고 새로운 인자를 가지고 다시 시작해야 할 것입니다.</p>
<p>recomposition이 취소되었을 때, Compose는 recomposition에서 UI tree를 폐기합니다. 표시되는 UI에 따라 side-effect가 있는 경우에는, composition이 취소되더라도 side-effect는 적용됩니다. 이로 인해서 앱의 상태가 일치하지 않을 수 있습니다.</p>
<p>optimistic(낙관적) recomposition을 쉽게 다룰 수 있도록, 모든 composable 함수들과 lambda들을 idempotent(멱등성), side-effect가 없도록 보장해야 합니다.</p>
<h2 id="composable-functions-might-run-quite-frequently">Composable functions might run quite frequently</h2>
<p>몇몇의 경우, composable 함수가 모든 애니메이션 프레임에서 동작할 수도 있습니다. 만약 함수가 기기 저장소를 읽어오는 것과 같이 비용이 높은 동작을 수행한다면, 해당 함수를 UI jank를 일으킬 수 있습니다.</p>
<p>예를 들어, 만약 위젯이 기기의 설정을 읽어오려고 시도한다면, 해당 설정들을 1초에 100번 정도 읽어와야 할 수 있습니다. 이것은 앱의 성능에 치명적인 시도입니다.</p>
<p>만약 composable 함수가 데이터가 필요하다면, 함수의 인자로 정의되어야 합니다. 비용이 많이 드는 작업을 compsition 밖 다른 thread로 옮기고 <code>mutableStateOf</code> 또는 <code>LiveData</code>를 사용하여 Compose에 데이터를 전달하는것이 좋습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dependency Injection(DI), 의존성 주입에 대해 조금 쉽게? 접근해보기]]></title>
            <link>https://velog.io/@wooyoung-tom/di-basic</link>
            <guid>https://velog.io/@wooyoung-tom/di-basic</guid>
            <pubDate>Tue, 15 Feb 2022 11:04:29 GMT</pubDate>
            <description><![CDATA[<p>Dependency Injection 말그대로 &quot;의존성 주입&quot;입니다.</p>
<p>객체지향 프로그래밍을 공부하다보면 한번쯤은 나오고, 들어봤을 용어입니다.</p>
<p>그래서 오늘은 이 <strong>DI(Dependency Injection)</strong>, 의존성 주입이 어떤 것인지 알아보려고 합니다.</p>
<h2 id="di의-장점">DI의 장점</h2>
<p>DI로 인하여 아래와 같은 이점들을 가질 수 있습니다.</p>
<ul>
<li>코드의 재사용성을 높여준다.</li>
<li>테스트의 편의성을 높여준다.</li>
<li>객체간의 의존성을 줄이거나 없앨 수 있다.</li>
<li>객체간의 결합도를 낮출 수 있다.</li>
<li>위와 같은 장점들로 인하여 리팩터링의 편의성도 함께 높아진다.</li>
<li>...</li>
</ul>
<h2 id="의존성-dependency">의존성 (Dependency)</h2>
<p>먼저 의존성에 대해서 알아보도록 하겠습니다.</p>
<p>흔히 객체지향 프로그래밍으로 개발을 진행하면, 하나의 클래스는 다른 클래스의 참조가 필요하게 됩니다.</p>
<p>말로는 잘 모르겠습니다. 예시를 저희한테 꼭 필요한 <code>Computer</code> 객체를 통해 들어보도록 하겠습니다.</p>
<p><code>Computer</code> 는 동작을 시작하거나 지속하기 위해서 <code>Power</code> 를 필요로 합니다. <code>Power</code> 가 없다면 <code>Computer</code> 는 동작할 수 없기 때문이죠. <em>(Developer 도 Computer 가 없다면 동작할 수 없듯이말이죠...)</em></p>
<pre><code class="language-kotlin">class Computer {
    private val power = Power()

    fun start() {
        power.on()
    }
}

class Power {
    fun on() {
        println(&quot;Power ON!&quot;)
    }
}</code></pre>
<p>위의 코드처럼 <code>start()</code> 메소드를 동작시키기 위해 <code>Computer</code> 클래스 내에 변수로 <code>Power</code> 클래스의 인스턴스를 생성함으로써 <code>Computer</code> 클래스는 <code>Power</code> 클래스에 의존하게 되었습니다.</p>
<p>한 마디로 위에서 설명한 예시처럼, <code>Computer</code> 를 <code>start</code> 하기 위해서는 꼭 <code>Power</code> 가 필요하기 때문에 <code>Computer</code> 가 <code>Power</code> 에 의존한다는 것입니다.</p>
<p>의존성을 설명하였으니, 이제 주입 (Injection) 에 대해 알아야 합니다.</p>
<h2 id="주입-injection">주입 (Injection)</h2>
<p>주입이란, 위의 <code>Computer</code> 클래스처럼 클래스 내에서 다른 클래스의 인스턴스를 생성하여 사용하는 것이 아니라 클래스 외부에서 인스턴스를 생성하여 넘겨주는 것을 의미합니다.</p>
<p>또 말로만 해서는 잘 모르겠습니다. 위의 <code>Computer</code> 클래스 코드를 조금 고쳐보겠습니다.</p>
<pre><code class="language-kotlin">class Computer(private val power: Power) {
    fun start() {
        power.on()
    }
}

class Power {
    fun on() {
        println(&quot;Power ON!&quot;)
    }
}</code></pre>
<p>수정된 코드의 <code>Computer</code> 클래스를 보면, 생성자의 매개변수로 <code>Power</code> 인스턴스를 받고 있습니다.</p>
<p>어딘가<em>(외부)</em> 에서 <code>Computer</code> 인스턴스를 생성하기 위해서는 <code>Power</code> 의 인스턴스도 <em>(외부에서)</em> 함께 생성하여 <code>Computer</code> 의 생성자로 해당 인스턴스를 주입해주어야 한다는 의미입니다. </p>
<p>이렇게 주입까지 알아보았습니다.</p>
<h2 id="더하기">더하기</h2>
<p>그렇다면 위에서 다룬 의존성과 주입을 더하면 의존성 주입, 즉 DI가 완성 될 것 같습니다.</p>
<p>다음의 <code>main()</code> 함수를 보도록 하겠습니다.</p>
<pre><code class="language-kotlin">fun main() {
    val power = Power()
    val computer = Computer(power)

    computer.start()
}</code></pre>
<p><code>Computer</code> 클래스의 생성을 위해서 <code>Power</code> 클래스의 인스턴스를 먼저 생성한 후, <code>Computer</code> 클래스의 생성자에 넣어주는 것을 확인할 수 있습니다.</p>
<p>위에서 설명하였듯이, <code>Computer</code> 는 <code>Power</code> 에 의존하는데 <code>Computer</code> 의 생성자에 <code>Power</code> 인스턴스를 주입하고 있습니다.</p>
<p>이렇게 되면 의존성+주입이 완성되었습니다.</p>
<p>그러면 의존성주입이 주는 이점들을 한번 다시 확인해보겠습니다.</p>
<ol>
<li><p>코드의 재사용성을 높여준다.
우리는 이제 <code>Computer</code> 를 바꿀 때, 같은 <code>Power</code> 를 계속 사용할 수 있게 된 것입니다.</p>
</li>
<li><p>테스트의 편의성을 높여준다.
우리는 이제 <code>Computer</code> 가 정상적으로 동작하는 지 확인할 때 여러가지 <code>Power</code> 들로 테스트할 수 있게 되었습니다.</p>
</li>
<li><p>객체간의 의존성을 줄이거나 없앨 수 있다.
주입을 진행하지 않았다면 <code>Computer</code> 클래스의 생성과 함께 <code>Power</code> 클래스가 생성되는 것과 마찬가지인데, 그렇게 되면 <code>Computer</code> 와 <code>Power</code> 는 완전의존성을 가진다고 볼 수 있습니다.
하지만, 주입을 통해 <code>Power</code> 클래스 생성과 <code>Computer</code> 클래스의 생성을 독립적으로 진행함으로써 의존성을 줄였다고 생각할 수 있습니다.</p>
</li>
<li><p>객체간의 결합도를 낮출 수 있다.
위의 3번과 마찬가지입니다. <code>Computer</code> 클래스가 생성되지 않으면 <code>Power</code> 클래스가 생성될 수 없기 때문에 결합도가 아주 높아집니다. 하지만 주입으로 인해 결합도가 낮아졌다고 볼 수 있습니다.</p>
</li>
<li><p>위와 같은 장점들로 인하여 리팩터링의 편의성도 함께 높아진다.
우리는 이제 <code>Power</code> 가 고장나면 <code>Power</code> 만 고치면 됩니다. 물론 <code>Computer</code> 내에 다른 부분이 고장이 났다면 <code>Power</code> 를 고칠 필요도 없다는 뜻입니다.</p>
</li>
</ol>
<p>이렇게 의존성 주입에 대해서 알아보았습니다.</p>
<hr>
<p>참고 링크 (좋은 설명들 감사합니다!):</p>
<p><a href="https://developer.android.com/training/dependency-injection?hl=ko">https://developer.android.com/training/dependency-injection?hl=ko</a></p>
<p><a href="https://velog.io/@wlsdud2194/what-is-di">https://velog.io/@wlsdud2194/what-is-di</a></p>
<p><a href="https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f">https://medium.com/@jang.wangsu/di-dependency-injection-이란-1b12fdefec4f</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android ConstraintLayout 의 Guideline 과 Barrier 알아보기]]></title>
            <link>https://velog.io/@wooyoung-tom/android-guideline-barrier</link>
            <guid>https://velog.io/@wooyoung-tom/android-guideline-barrier</guid>
            <pubDate>Tue, 08 Feb 2022 11:57:48 GMT</pubDate>
            <description><![CDATA[<p>안드로이드 앱 레이아웃을 작성할 때, 필수적으로 사용하는 ConstraintLayout을 조금 더 효율적으로 사용할 수 있게 도와주는 2가지 컴포넌트 Guideline 과 Barrier 에 대해서 간단히 공부한 것을 공유하려고 합니다.</p>
<hr>
<h1 id="1-guideline">1. Guideline</h1>
<p><code>Guideline</code> 은 말그대로 레이아웃 작성시에 가이드라인을 잡아주는 역할을 합니다. 다시말해, 한 화면 내에서 가상의 선을 하나 그어, 해당 선에 view들의 constraint를 줄 수 있게 도와주는 역할입니다.</p>
<p>먼저, 사용법부터 알아보겠습니다.</p>
<pre><code class="language-xml">&lt;androidx.constraintlayout.widget.Guideline
    android:id=&quot;@+id/guideline&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:orientation=&quot;vertical&quot;
    app:layout_constraintGuide_begin=&quot;24dp&quot; /&gt;</code></pre>
<p>아래 사진은 위에서 작성한 코드에 대한 preview입니다 (가이드라인의 사용을 보여드리기 위해 간단한 TextView를 추가하였습니다).</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/f30186a1-cb6f-4069-bcb1-0cf236d4d493/guideline_1.png" alt="Guideline 1"></p>
<p>대충 가이드라인이 어떤 역할을 하는지 느낌이 오셨다면 가이드라인을 지정할 때 어떤 필드들을 사용하는지 위 코드, 사진과 함께 한번 보겠습니다. layout_width와 layout_height는 건너뛰도록 하겠습니다. 😀</p>
<p><code>android:id</code></p>
<p>해당 가이드라인의 id를 뜻합니다. 레이아웃 내에 들어갈 view들의 constraint를 지정할 때 해당 id를 사용합니다. 위 사진은 TextView를 배치할 때 <code>app:layout_constraintStart_toEndOf=&quot;@id/guideline&quot;</code> 를 통해 가이드라인의 id를 통해 start constraint를 지정한 것입니다.</p>
<p><code>android:orientation</code></p>
<p>가이드라인을 수직으로 그을 것인가 수평으로 그을 것인가를 결정합니다. 위 사진에서는 orientation이 vertical임을 알 수 있습니다.</p>
<p><code>app:layout_constraintGuide_xxx</code></p>
<p>가이드라인의 위치를 지정할 때 사용합니다. </p>
<p>이 필드는 orientation에 따라서 위치가 바뀌게 되는데요, 만약 orientation이 horizontal이라면 화면의 왼쪽과 오른쪽이 시작과 끝이 되고, vertical이라면 화면의 위쪽과 아래쪽이 시작과 끝이 됩니다.</p>
<p>위 코드에서는 <code>app:layout_constraintGuide_begin</code>을 사용하여 화면의 시작으로부터 24dp 만큼 떨어져 있었으면 좋겠다고 지정해주고 있습니다. </p>
<p>또한 <code>app:layout_constraintGuide_end</code> 나 <code>app:layout_constraintGuide_percent</code>를 사용할 수도 있습니다. <code>app:layout_constraintGuide_end</code>는 <code>app:layout_constraintGuide_begin</code>의 반대로 화면의 끝에서 얼마만큼 떨어져있는지를 정해줄 수 있고, <code>app:layout_constraintGuide_percent</code>는 화면의 시작을 0%, 화면의 끝을 100%로 생각하고 전체 화면에 대해 몇 퍼센트 위치에 있는지 정해줄 수 있습니다.</p>
<hr>
<h1 id="2-barrier">2. Barrier</h1>
<p><code>Barrier</code>는 Guideline이 정적인 값을 주어 위치를 지정해준 것과는 다르게, 여러개의 view를 묶어서 가상의 벽을 만들어 동적인 위치로 constraint를 줄 수 있게 만드는 것입니다.</p>
<p>아래 사진을 보면서 설명해보겠습니다.</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/3f681fa9-e9ae-4a99-9493-d23470c866de/barrier_1.png" alt="Barrier 1"></p>
<p>왼쪽 두 개의 TextView를 묶어서 Barrier를 만들어 준것을 확인할 수 있습니다. Barrier의 위치가 왼쪽 아래의 텍스트 길이에 따라 형성되는 것을 확인할 수 있습니다.</p>
<p>그렇다면 왼쪽 위의 텍스트가 아래의 텍스트보다 길어지면 어떻게 될까요?</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/238e69c5-197c-4a99-9175-96c13610c672/barrier_2.png" alt="Barrier 2"></p>
<p>위와같이 Barrier가 왼쪽 위의 텍스트 길이에 맞추어 위치가 이동된 것을 볼 수 있습니다.</p>
<p>이처럼, Barrier는 묶어놓은 여러개의 View의 높이나 너비가 바뀜에 따라 동적으로 형성가능하다는 것을 알 수 있습니다.</p>
<p>그러므로 여러가지 언어를 지원하는 앱을 만들 때, 언어마다 텍스트의 길이가 달라지기 때문에 Barrier를 사용한다면 더욱 유연하게 레이아웃을 짤 수 있게 됩니다.</p>
<p>이제, Barrier의 예제 코드를 보면서 알아보겠습니다.</p>
<pre><code class="language-xml">&lt;androidx.constraintlayout.widget.Barrier
    android:id=&quot;@+id/barrier&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    app:barrierDirection=&quot;end&quot;
    app:constraint_referenced_ids=&quot;text1, text2&quot; /&gt;</code></pre>
<p><code>android:id</code> </p>
<p>위의 Guideline과 동일하게, Barrier의 id를 지정하여 해당 Barrier에 constraint를 걸 수 있습니다. 위 사진에서는 오른쪽 텍스트에 <code>app:layout_constraintStart_toEndOf=&quot;@id/barrier&quot;</code> 를 사용하여 Barrier에 constraint를 걸어주었습니다.</p>
<p><code>app:barrierDirection</code> </p>
<p>Barrier의 방향을 지정합니다. 위 사진에서는 end를 사용하여 참조하는 view들의 끝부분에 Barrier가 형성되도록 하였습니다. 다른 방향으로는 top, bottom, start, left, right가 있습니다.</p>
<p>top, bottom 속성은 참조하는 view들의 위, 아래쪽에 수평 barrier가 형성되며, start, end, left, right 속성은 참조하는 view들의 왼, 오른쪽에 수직 barrier가 형성됩니다.</p>
<p><code>app:constraint_referenced_ids</code></p>
<p>묶으려고 하는 view들의 id를 콤마(,)기호로 구분하여 입력하면 해당 id를 가진 view들이 묶이면서 barrier를 형성하게 됩니다. 위에서는 왼쪽의 위아래 텍스트의 id를 <code>text1</code>, <code>text2</code> 로 정하여 입력해주었습니다.</p>
<hr>
<p>여기까지 간단하게 ConstraintLayout을 조금 더 효율적으로 사용할 수 있게 도와주는 Guideline과 Barrier 두가지에 대해서 알아보았습니다.</p>
<p>References</p>
<ul>
<li><a href="https://www.charlezz.com/?p=691">https://www.charlezz.com/?p=691</a></li>
<li><a href="https://jaeyeong951.medium.com/constraintlayout%EC%9D%84-%EB%8D%94-%EC%99%84%EB%B2%BD%ED%95%98%EA%B2%8C-%EB%A7%8C%EB%93%A4%EC%96%B4-%EC%A4%84-2%EA%B0%80%EC%A7%80-%EB%8F%84%EA%B5%AC-15f51418e6d0">https://jaeyeong951.medium.com/constraintlayout을-더-완벽하게-만들어-줄-2가지-도구-15f51418e6d0</a></li>
<li><a href="https://constraintlayout.com/basics/barriers.html">https://constraintlayout.com/basics/barriers.html</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Fragment에서의 BackPress 동작 제어]]></title>
            <link>https://velog.io/@wooyoung-tom/android-backpress-in-fragment</link>
            <guid>https://velog.io/@wooyoung-tom/android-backpress-in-fragment</guid>
            <pubDate>Mon, 07 Feb 2022 13:49:01 GMT</pubDate>
            <description><![CDATA[<h1 id="🌱-발단">🌱 발단</h1>
<p>Android Fragment에서는 Activity에 존재하는 override function 인 <code>onBackPressed()</code> 를 지원하지 않습니다.</p>
<p>따라서 Fragment에서 BackPress에 대한 동작을 제어하기 위해서는 <code>OnBackPressedCallback</code> 오브젝트를 Fragment가 속해있는 Activity에 등록해주어야 합니다.</p>
<p>이제 차례차례 어떻게 제어할 수 있는지에 대해서 알아보겠습니다.</p>
<hr>
<h1 id="🍀-전개">🍀 전개</h1>
<p>먼저, 현재 Fragment가 속해있는 FragmentActivity를 <code>requireActivity()</code> 메소드를 호출하여 찾아야합니다. 해당 메소드는 Fragment클래스의 멤버 메소드이기 때문에, Fragment클래스를 상속받았다면 사용할 수 있습니다.</p>
<p>위의 <code>requireActivity()</code> 를 통해서 return 받은 FragmentActivity는 ComponentActivity를 상속하기 때문에 ComponentActivity의 멤버 메소드인 <code>getOnBackPressedDispatcher()</code>메소드를 사용할 수 있게 됩니다.</p>
<pre><code class="language-java">&lt;ComponentActivity.java 내부의 getOnBackPressedDispatcher 함수&gt;

/**
 * Retrieve the {@link OnBackPressedDispatcher} that will be triggered when
 * {@link #onBackPressed()} is called.
 * @return The {@link OnBackPressedDispatcher} associated with this ComponentActivity.
 */
@NonNull
@Override
public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
    return mOnBackPressedDispatcher;
}</code></pre>
<p>해당 메소드 주석의 return 부분을 읽어보면, 해당 메소드는 현재 ComponentActivity와 연관되어있는 <code>OnBackPressedDispatcher</code> 오브젝트를 반환한다고 되어있습니다.</p>
<p>그렇다면 이렇게 가져온 <code>OnBackPressedDispatcher</code> 메소드를 어떻게 사용하는지 알아야합니다.</p>
<hr>
<h1 id="🌵-위기">🌵 위기</h1>
<p>일단, <code>OnBackPressedDispatcher</code>는 클래스입니다.</p>
<p><code>public final class OnBackPressedDispatcher {...}</code></p>
<p>그리고 Dispatcher, 말 그대로 무언가 관리하는 친구같습니다. 그럼 클래스 내부의 멤버 메소드중에 BackPress 이벤트에 대해서 관리하는 친구가 또 같이 있지 않을까 의구심을 가져볼만 합니다.</p>
<p><img src="https://images.velog.io/images/wooyoung-tom/post/fc9dd322-71ce-4828-ad87-1353f786790a/figure_fragment_1.png" alt="onBackPressedDispatcher class 에서 onBackPressed 메소드 찾기"></p>
<p>위 캡쳐된 사진에서 볼 수 있듯이, <code>addCallback()</code> 메소드가 발견됩니다. 이제 저 <code>addCallback()</code> 메소드를 지지고 볶아서 BackPress 이벤트가 발생했을 때 동작해야 할 코드를 작성하여 <code>OnBackPressedCallback</code>오브젝트에 넣어주고 Callback 등록만하면 완성일 것 같습니다!</p>
<pre><code class="language-kotlin">requireActivity().onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        // 뒤로가기 눌렀을 때 동작할 코드
    }
})</code></pre>
<hr>
<h1 id="🪴-절정">🪴 절정</h1>
<p>그런데, Fragment를 벗어나면 해당 Fragment에서만 동작할 수 있는 코드라고 한다면 등록된 Callback 이 호출될 때 Exception 이 발생할 수 있습니다.</p>
<p>여기서 또 친절한 Android 는 addCallback 함수를 오버로딩하여 등록하려는 Callback 에 owner 를 정해주어 해당 owner 의 lifecycle 을 따라갈 수 있도록 해놓았습니다.</p>
<p>그럼 다음과 같이 현재 Fragment에서만 동작하게 하고 싶다면, 현재 Fragment를 owner parameter 로 넣어주면 되겠습니다.</p>
<pre><code class="language-kotlin">requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)</code></pre>
<hr>
<h1 id="🌳-결말">🌳 결말</h1>
<p>사실 아주 간단한 코드이지만, 어떻게 동작하는지에 대해서 좀 더 알아보고 싶어 ComponentActivity까지만 일단 다녀왔습니다. lifecycle 과 관련하여 조금 더 공부를 한 뒤에 다시 보면 좋을 것 같다는 생각이 들었습니다.</p>
]]></description>
        </item>
    </channel>
</rss>