<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://jja08111.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jja08111.github.io/" rel="alternate" type="text/html" /><updated>2024-04-13T12:16:06+09:00</updated><id>https://jja08111.github.io/feed.xml</id><title type="html">코딩하는 만두</title><subtitle>My First IT Blog.</subtitle><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><entry><title type="html">[Kotlin] Flow 구현체 파헤치기</title><link href="https://jja08111.github.io/kotlin/implementation-of-flow/" rel="alternate" type="text/html" title="[Kotlin] Flow 구현체 파헤치기" /><published>2024-03-01T21:00:00+09:00</published><updated>2024-03-01T21:00:00+09:00</updated><id>https://jja08111.github.io/kotlin/implementation-of-flow</id><content type="html" xml:base="https://jja08111.github.io/kotlin/implementation-of-flow/"><![CDATA[<p>앱 개발에 있어 자주 사용되는 <code class="language-plaintext highlighter-rouge">Flow</code> 구현을 파헤쳐보겠다. 버전은 <code class="language-plaintext highlighter-rouge">kotlinx-coroutines-core-jvm-1.6.4</code> 기준이다.</p>

<h1 id="flow">Flow</h1>

<p><code class="language-plaintext highlighter-rouge">Flow</code>는 Cold Stream이고 단순한 예제는 다음과 같다.
5번 방출을 하는 flow를 선언하였고 이를 collect하여 순차적으로 값들을 출력한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">flow</span> <span class="p">{</span>
    <span class="nf">repeat</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span> <span class="n">n</span> <span class="p">-&gt;</span>
        <span class="nf">emit</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}.</span><span class="nf">collect</span> <span class="p">{</span> <span class="n">n</span> <span class="p">-&gt;</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// 출력 결과</span>
<span class="c1">// 0</span>
<span class="c1">// 1</span>
<span class="c1">// 2</span>
<span class="c1">// 3</span>
<span class="c1">// 4</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">flow</code> 함수 구현은 아래와 같다. <code class="language-plaintext highlighter-rouge">SafeFlow</code> 클래스를 생성하는 모습이다. 이때 <code class="language-plaintext highlighter-rouge">FlowCollector</code> 컨텍스트로 된 block 함수가 인자로 전달된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">flow</span><span class="p">(</span><span class="nd">@BuilderInference</span> <span class="n">block</span><span class="p">:</span> <span class="k">suspend</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">):</span> <span class="nc">Flow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">SafeFlow</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">SafeFlow</code>를 살펴보기 전에 <code class="language-plaintext highlighter-rouge">Flow</code>를 확인해보자. 먼저 <code class="language-plaintext highlighter-rouge">Flow</code>의 선언부이다. <code class="language-plaintext highlighter-rouge">collect</code>라는 함수가 있고 매개변수로 <code class="language-plaintext highlighter-rouge">FlowCollector</code>가 있다.
<code class="language-plaintext highlighter-rouge">FlowCollector</code>는 <code class="language-plaintext highlighter-rouge">emit</code> 함수가 존재한다. <code class="language-plaintext highlighter-rouge">FlowCollector</code>가 <code class="language-plaintext highlighter-rouge">fun interface</code>인 이유는 Kotlin에서 SAM(Single Abstract Method) 특성을 사용하기 위해서이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Flow</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collect</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">interface</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="k">in</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">emit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그렇다면 저 interface들이 어떻게 이용될까? <code class="language-plaintext highlighter-rouge">SafeFlow</code>를 보면 <code class="language-plaintext highlighter-rouge">AbstractFlow</code>를 상속하였다.
<code class="language-plaintext highlighter-rouge">AbstractFlow</code>는 <code class="language-plaintext highlighter-rouge">Flow</code>를 구현하였고 <code class="language-plaintext highlighter-rouge">collector</code>를 전달받아 <code class="language-plaintext highlighter-rouge">SafeCollector</code>로 감싸서 <code class="language-plaintext highlighter-rouge">collectSafely</code>를 호출한다.
<code class="language-plaintext highlighter-rouge">SafeFlow</code>의 <code class="language-plaintext highlighter-rouge">collectSafely</code> 함수는 앞서 <code class="language-plaintext highlighter-rouge">flow</code> 함수로 생성했던 그 <code class="language-plaintext highlighter-rouge">block</code>을 실행한다.
이때 block으로 작성했던 <code class="language-plaintext highlighter-rouge">FlowCollector.emit()</code>이 5번 호출되고, 방출된 값을 출력하게 된것이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SafeFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">block</span><span class="p">:</span> <span class="k">suspend</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">:</span> <span class="nc">AbstractFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collectSafely</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">{</span>
        <span class="n">collector</span><span class="p">.</span><span class="nf">block</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">abstract</span> <span class="kd">class</span> <span class="nc">AbstractFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">Flow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">CancellableFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">public</span> <span class="k">final</span> <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collect</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">safeCollector</span> <span class="p">=</span> <span class="nc">SafeCollector</span><span class="p">(</span><span class="n">collector</span><span class="p">,</span> <span class="n">coroutineContext</span><span class="p">)</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="nf">collectSafely</span><span class="p">(</span><span class="n">safeCollector</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">safeCollector</span><span class="p">.</span><span class="nf">releaseIntercepted</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">abstract</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collectSafely</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>무언가 알듯말듯하다. 이해하기 쉽도록 예제 코드를 수정하여 다시 살펴보자. 아래는 기존 예제 코드를 생략하지 않고 작성한 코드이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">myFlow</span> <span class="p">=</span> <span class="nf">flow</span><span class="p">(</span>
    <span class="n">block</span> <span class="p">=</span> <span class="p">{</span>
        <span class="nf">repeat</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">emit</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">)</span>
<span class="n">myFlow</span><span class="p">.</span><span class="nf">collect</span><span class="p">(</span>
    <span class="n">collector</span> <span class="p">=</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">emit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">println</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Flow</code>를 생성하면 <code class="language-plaintext highlighter-rouge">SafeFlow</code>의 생성자 매개변수로 아래의 블록이 전달되고</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">block</span> <span class="p">=</span> <span class="p">{</span>
    <span class="nf">repeat</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">emit</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Flow</code>의 <code class="language-plaintext highlighter-rouge">collect</code>를 호출하면 익명의 클래스가 인자로 전달되어 해당 블록이 실행된다. 주석 처리된 부분을 보면 이해하기 수월하다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SafeFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">block</span><span class="p">:</span> <span class="k">suspend</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;.()</span> <span class="p">-&gt;</span> <span class="nc">Unit</span><span class="p">)</span> <span class="p">:</span> <span class="nc">AbstractFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collectSafely</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;)</span> <span class="p">{</span>
        <span class="c1">// collector = object : FlowCollector&lt;Int&gt; {</span>
        <span class="c1">//     override suspend fun emit(value: Int) {</span>
        <span class="c1">//         println(value)</span>
        <span class="c1">//     }</span>
        <span class="c1">// }</span>
        <span class="c1">//</span>
        <span class="c1">// block = {</span>
        <span class="c1">//     repeat(5) {</span>
        <span class="c1">//         emit(it)</span>
        <span class="c1">//     }</span>
        <span class="c1">// }</span>
        <span class="n">collector</span><span class="p">.</span><span class="nf">block</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>정리하자면 아래의 흐름이다.</p>

<ol>
  <li>Cold Stream인 <code class="language-plaintext highlighter-rouge">Flow</code>의 <code class="language-plaintext highlighter-rouge">collect</code>를 호출</li>
  <li>flow를 생성할 때 전달했던 <code class="language-plaintext highlighter-rouge">block</code> 함수를 호출</li>
  <li><code class="language-plaintext highlighter-rouge">block</code> 함수에서 <code class="language-plaintext highlighter-rouge">emit</code>을 호출했다면, 인자로 전달받은 <code class="language-plaintext highlighter-rouge">collector</code>의 오버라이드된 <code class="language-plaintext highlighter-rouge">emit</code> 함수가 호출됨</li>
</ol>

<h1 id="sharedflow">SharedFlow</h1>

<p>이번에는 Hot Stream인 <code class="language-plaintext highlighter-rouge">SharedFlow</code>를 파헤치기 앞서서, 전체적인 동작 과정을 설명해보겠다.</p>

<p><img src="/assets/images/shared_flow.png" alt="shared_flow" /></p>

<ul>
  <li>Buffer: 생성자에 의해 방출된 값들이 구독자들에게 읽히기 위해 저장되는 곳이다. 또한 Emitter가 일시중단될 때도 이곳에 저장된다.</li>
  <li>Slot: 구독자를 위해 할당하는 공간. collect가 중단되어 새로 방출되는 값을 대기하는 <code class="language-plaintext highlighter-rouge">Continuation</code>과, 방출될 값의 버퍼 인덱스 등을 저장한다.</li>
</ul>

<p><strong>Collector</strong></p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">FlowCollector</code>를 인자로 넘기며 <code class="language-plaintext highlighter-rouge">SharedFlow.collect</code> 함수 호출</li>
  <li><code class="language-plaintext highlighter-rouge">Slot</code>을 할당받음</li>
  <li>새로 방출된 값이 존재한다면 <code class="language-plaintext highlighter-rouge">FlowCollector.emit</code> 호출, 방출된 값이 없다면 <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code>를 호출하여 <code class="language-plaintext highlighter-rouge">Continuation</code>을 <code class="language-plaintext highlighter-rouge">Slot</code>에 저장하고 await</li>
  <li>3을 계속 반복하다가 예외 발생시 할당 받은 <code class="language-plaintext highlighter-rouge">Slot</code>을 해제</li>
</ol>

<p><strong>Provider</strong></p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">SharedFlow.emit</code>을 호출</li>
  <li>곧바로 방출 가능한 경우 버퍼에 값을 쓰고 <code class="language-plaintext highlighter-rouge">Slot</code> 목록에 저장된 <code class="language-plaintext highlighter-rouge">Continuation</code>의 <code class="language-plaintext highlighter-rouge">resume</code>을 호출 or <code class="language-plaintext highlighter-rouge">Emitter</code>를 대기 큐에 넣음, 이때 버퍼 값 중 하나를 drop 할 수도 있음</li>
  <li>값을 방출할 때 대기 큐에 있는 <code class="language-plaintext highlighter-rouge">Emitter</code>를 <code class="language-plaintext highlighter-rouge">resume</code></li>
</ol>

<p>자 이제 SharedFlow를 파헤쳐보자! <code class="language-plaintext highlighter-rouge">SharedFlow</code>를 만드는 방법은 크게 아래의 두 가지가 존재한다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">MutableSharedFlow</code>를 만들어 사용하기</li>
  <li><code class="language-plaintext highlighter-rouge">sharedIn</code>을 이용하여 Cold Stream을 Hot Stream으로 변환하기</li>
</ul>

<p>이 글에서는 첫 번째 항목으로 설명하겠다. <code class="language-plaintext highlighter-rouge">MutableSharedFlow</code>는 아래와 같이 <code class="language-plaintext highlighter-rouge">MutableSharedFlow()</code> 함수를 이용하여 생성할 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">sharedFlow</span> <span class="p">=</span> <span class="nc">MutableSharedFlow</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;()</span>
<span class="nf">launch</span> <span class="p">{</span>
    <span class="n">sharedFlow</span><span class="p">.</span><span class="nf">collect</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nf">launch</span> <span class="p">{</span>
    <span class="nf">repeat</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">sharedFlow</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 0</span>
<span class="c1">// 1</span>
<span class="c1">// 2</span>
<span class="c1">// 3</span>
<span class="c1">// 4</span>
</code></pre></div></div>

<p>그렇다면 <code class="language-plaintext highlighter-rouge">MutableSharedFlow</code> 함수를 살펴보자. 내부적으로 <code class="language-plaintext highlighter-rouge">SharedFlowImpl</code>를 호출하는 것을 볼 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">MutableSharedFlow</span><span class="p">(</span>
    <span class="n">replay</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">extraBufferCapacity</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">onBufferOverflow</span><span class="p">:</span> <span class="nc">BufferOverflow</span> <span class="p">=</span> <span class="nc">BufferOverflow</span><span class="p">.</span><span class="nc">SUSPEND</span>
<span class="p">):</span> <span class="nc">MutableSharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="c1">// ...</span>
    <span class="kd">val</span> <span class="py">bufferCapacity0</span> <span class="p">=</span> <span class="n">replay</span> <span class="p">+</span> <span class="n">extraBufferCapacity</span>
    <span class="kd">val</span> <span class="py">bufferCapacity</span> <span class="p">=</span> <span class="k">if</span> <span class="p">(</span><span class="n">bufferCapacity0</span> <span class="p">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="nc">Int</span><span class="p">.</span><span class="nc">MAX_VALUE</span> <span class="k">else</span> <span class="n">bufferCapacity0</span> <span class="c1">// coerce to MAX_VALUE on overflow</span>
    <span class="k">return</span> <span class="nc">SharedFlowImpl</span><span class="p">(</span><span class="n">replay</span><span class="p">,</span> <span class="n">bufferCapacity</span><span class="p">,</span> <span class="n">onBufferOverflow</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">SharedFlowImpl</code>는 <code class="language-plaintext highlighter-rouge">MutableSharedFlow</code>를 구현했으며 생성자는 아래와 같다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">internal</span> <span class="k">open</span> <span class="kd">class</span> <span class="nc">SharedFlowImpl</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">replay</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">bufferCapacity</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">onBufferOverflow</span><span class="p">:</span> <span class="nc">BufferOverflow</span>
<span class="p">)</span> <span class="p">:</span> <span class="nc">AbstractSharedFlow</span><span class="p">&lt;</span><span class="nc">SharedFlowSlot</span><span class="p">&gt;(),</span> <span class="nc">MutableSharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">CancellableFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">FusibleFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">MutableSharedFlow</code>를 살펴보기 전에, <code class="language-plaintext highlighter-rouge">SharedFlow</code> 인터페이스를 살펴보자. <code class="language-plaintext highlighter-rouge">SharedFlow</code>는 <code class="language-plaintext highlighter-rouge">Flow</code>에서 <code class="language-plaintext highlighter-rouge">replayCache</code>가 추가로 존재한다.
이 프로퍼티는 새로운 구독자가 등장했을 때 최근 방출된 값을 몇 개를 새로운 구독자에게 전달할지 결정한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">SharedFlow</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">Flow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="kd">val</span> <span class="py">replayCache</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span>

    <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collect</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;):</span> <span class="nc">Nothing</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">MutableSharedFlow</code>는 다음과 같다. 값을 방출 할 수 있도록 <code class="language-plaintext highlighter-rouge">emit</code> 그리고 <code class="language-plaintext highlighter-rouge">tryEmit</code>이 존재한다. 이 두 함수의 차이점은 추후에 살펴볼 것이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">MutableSharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">SharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">emit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span>

    <span class="k">fun</span> <span class="nf">tryEmit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">Boolean</span>

    <span class="kd">val</span> <span class="py">subscriptionCount</span><span class="p">:</span> <span class="nc">StateFlow</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span>

    <span class="k">fun</span> <span class="nf">resetReplayCache</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 대망의 <code class="language-plaintext highlighter-rouge">SharedFlowImpl</code>를 하나씩 파헤쳐보자. 코드 흐름상 <code class="language-plaintext highlighter-rouge">collect</code> 함수를 살펴보자.
이 함수의 핵심은 두 개의 <code class="language-plaintext highlighter-rouge">while</code> 문이다. 먼저 이 함수는 중단함수이기 때문에 코루틴에서 돌아간다.
첫 <code class="language-plaintext highlighter-rouge">while</code> 문을 진입하고 두 번째 <code class="language-plaintext highlighter-rouge">while</code> 문을 진입한다. 두 번째 <code class="language-plaintext highlighter-rouge">while</code> 문에서는 <code class="language-plaintext highlighter-rouge">slot</code>의 값을 가져와본다.
만약 있다면 <code class="language-plaintext highlighter-rouge">while</code> 문을 탈출하여 중단없이 <code class="language-plaintext highlighter-rouge">FlowCollect.emit</code>을 호출하면 된다.
아니라면 <code class="language-plaintext highlighter-rouge">awaitValue</code> 함수를 호출하여 방출될 때까지 코루틴을 일시정지한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collect</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;):</span> <span class="nc">Nothing</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">slot</span> <span class="p">=</span> <span class="nf">allocateSlot</span><span class="p">()</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">collector</span> <span class="k">is</span> <span class="nc">SubscribedFlowCollector</span><span class="p">)</span> <span class="n">collector</span><span class="p">.</span><span class="nf">onSubscription</span><span class="p">()</span>
        <span class="kd">val</span> <span class="py">collectorJob</span> <span class="p">=</span> <span class="nf">currentCoroutineContext</span><span class="p">()[</span><span class="nc">Job</span><span class="p">]</span>
        <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">var</span> <span class="py">newValue</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?</span>
            <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">newValue</span> <span class="p">=</span> <span class="nf">tryTakeValue</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span> <span class="c1">// attempt no-suspend fast path first</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">newValue</span> <span class="p">!==</span> <span class="nc">NO_VALUE</span><span class="p">)</span> <span class="k">break</span>
                <span class="nf">awaitValue</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span> <span class="c1">// await signal that the new value is available</span>
            <span class="p">}</span>
            <span class="n">collectorJob</span><span class="o">?.</span><span class="nf">ensureActive</span><span class="p">()</span>
            <span class="n">collector</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="n">newValue</span> <span class="k">as</span> <span class="nc">T</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
        <span class="nf">freeSlot</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">awaitValue</code> 함수를 살펴보자. <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code>를 이용했기 때문에 <code class="language-plaintext highlighter-rouge">continuation</code>을 전달하여 외부 스코프에서 이 코루틴을 재개할 수 있도록 하였다.
실제로 <code class="language-plaintext highlighter-rouge">slot.cont</code>는 <code class="language-plaintext highlighter-rouge">emit</code>을 호출할때 <code class="language-plaintext highlighter-rouge">resume</code>이 호출되어 재개된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">awaitValue</span><span class="p">(</span><span class="n">slot</span><span class="p">:</span> <span class="nc">SharedFlowSlot</span><span class="p">):</span> <span class="nc">Unit</span> <span class="p">=</span> <span class="nf">suspendCancellableCoroutine</span> <span class="p">{</span> <span class="n">cont</span> <span class="p">-&gt;</span>
    <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="nd">lock@</span><span class="p">{</span>
        <span class="kd">val</span> <span class="py">index</span> <span class="p">=</span> <span class="nf">tryPeekLocked</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span> <span class="c1">// recheck under this lock</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">slot</span><span class="p">.</span><span class="n">cont</span> <span class="p">=</span> <span class="n">cont</span> <span class="c1">// Ok -- suspending</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">cont</span><span class="p">.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span> <span class="c1">// has value, no need to suspend</span>
            <span class="k">return</span><span class="nd">@lock</span>
        <span class="p">}</span>
        <span class="n">slot</span><span class="p">.</span><span class="n">cont</span> <span class="p">=</span> <span class="n">cont</span> <span class="c1">// suspend, waiting</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그렇다면 이제는 <code class="language-plaintext highlighter-rouge">emit</code> 함수를 살펴보자. 중단 없는 방출을 시도해보고, 안된다면 중단하며 방출을 시도한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">emit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nf">tryEmit</span><span class="p">(</span><span class="n">value</span><span class="p">))</span> <span class="k">return</span> <span class="c1">// fast-path</span>
    <span class="nf">emitSuspend</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tryEmit</code> 함수는 <code class="language-plaintext highlighter-rouge">tryEmitLocked</code>를 호출는데 여기서는 <code class="language-plaintext highlighter-rouge">replayCache</code>, <code class="language-plaintext highlighter-rouge">extraBufferSize</code> 그리고 현재 버퍼 크기, 대기 큐에 있는 값들 등을 고려하여 방출을 시도한다.
만약 방출에 성공한다면 <code class="language-plaintext highlighter-rouge">findSlotsToResumeLocked</code>를 호출하여 앞서 설명한 재개될 <code class="language-plaintext highlighter-rouge">Slot</code>들을 가져온다. 그리고 <code class="language-plaintext highlighter-rouge">resume</code>을 통해 값이 실제로 방출된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">override</span> <span class="k">fun</span> <span class="nf">tryEmit</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">resumes</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">Continuation</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;?&gt;</span> <span class="p">=</span> <span class="nc">EMPTY_RESUMES</span>
    <span class="kd">val</span> <span class="py">emitted</span> <span class="p">=</span> <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nf">tryEmitLocked</span><span class="p">(</span><span class="n">value</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">resumes</span> <span class="p">=</span> <span class="nf">findSlotsToResumeLocked</span><span class="p">(</span><span class="n">resumes</span><span class="p">)</span>
            <span class="k">true</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">false</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">cont</span> <span class="k">in</span> <span class="n">resumes</span><span class="p">)</span> <span class="n">cont</span><span class="o">?.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">emitted</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tryEmit</code>에서 방출을 못한 경우 <code class="language-plaintext highlighter-rouge">emitSuspend</code>가 호출되고 대기 큐에 넣어진다. <code class="language-plaintext highlighter-rouge">Emitter</code>를 생성하는 부분을 유심히 살펴보면 된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">emitSuspend</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">)</span> <span class="p">=</span> <span class="n">suspendCancellableCoroutine</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="nd">sc@</span><span class="p">{</span> <span class="n">cont</span> <span class="p">-&gt;</span>
    <span class="kd">var</span> <span class="py">resumes</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">Continuation</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;?&gt;</span> <span class="p">=</span> <span class="nc">EMPTY_RESUMES</span>
    <span class="kd">val</span> <span class="py">emitter</span> <span class="p">=</span> <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="nd">lock@</span><span class="p">{</span>
        <span class="c1">// recheck buffer under lock again (make sure it is really full)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nf">tryEmitLocked</span><span class="p">(</span><span class="n">value</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">cont</span><span class="p">.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span>
            <span class="n">resumes</span> <span class="p">=</span> <span class="nf">findSlotsToResumeLocked</span><span class="p">(</span><span class="n">resumes</span><span class="p">)</span>
            <span class="k">return</span><span class="nd">@lock</span> <span class="k">null</span>
        <span class="p">}</span>
        <span class="c1">// add suspended emitter to the buffer</span>
        <span class="nc">Emitter</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">head</span> <span class="p">+</span> <span class="n">totalSize</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">cont</span><span class="p">).</span><span class="nf">also</span> <span class="p">{</span>
            <span class="nf">enqueueLocked</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
            <span class="n">queueSize</span><span class="p">++</span> <span class="c1">// added to queue of waiting emitters</span>
            <span class="c1">// synchronous shared flow might rendezvous with waiting emitter</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">bufferCapacity</span> <span class="p">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">resumes</span> <span class="p">=</span> <span class="nf">findSlotsToResumeLocked</span><span class="p">(</span><span class="n">resumes</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// outside of the lock: register dispose on cancellation</span>
    <span class="n">emitter</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">cont</span><span class="p">.</span><span class="nf">disposeOnCancellation</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span>
    <span class="c1">// outside of the lock: resume slots if needed</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">r</span> <span class="k">in</span> <span class="n">resumes</span><span class="p">)</span> <span class="n">r</span><span class="o">?.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그렇다면 대기 큐에 넣어진 <code class="language-plaintext highlighter-rouge">Emitter</code>들은 언제 재개될까? 바로 <code class="language-plaintext highlighter-rouge">collect</code>를 하여 값을 수집하고 난 후이다.
물론 항상 값을 수집하고 난 후에 재개되지는 않고 현재 사용되는 버퍼 크기 등을 고려해서 재개된다.</p>

<p>앞선 <code class="language-plaintext highlighter-rouge">collect</code> 함수 설명에서 <code class="language-plaintext highlighter-rouge">tryTakeValue</code>를 호출하는 것을 볼 수 있었다.
이 함수는 아래처럼 PEEK을 성공한 경우 <code class="language-plaintext highlighter-rouge">updateCollectorIndexLocked</code>를 호출한다.
바로 이 함수에서 대기 큐의 <code class="language-plaintext highlighter-rouge">Emitter</code>들이 재개가 되는 것이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// returns NO_VALUE if cannot take value without suspension</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">tryTakeValue</span><span class="p">(</span><span class="n">slot</span><span class="p">:</span> <span class="nc">SharedFlowSlot</span><span class="p">):</span> <span class="nc">Any</span><span class="p">?</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">resumes</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">Continuation</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;?&gt;</span> <span class="p">=</span> <span class="nc">EMPTY_RESUMES</span>
    <span class="kd">val</span> <span class="py">value</span> <span class="p">=</span> <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">index</span> <span class="p">=</span> <span class="nf">tryPeekLocked</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nc">NO_VALUE</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">oldIndex</span> <span class="p">=</span> <span class="n">slot</span><span class="p">.</span><span class="n">index</span>
            <span class="kd">val</span> <span class="py">newValue</span> <span class="p">=</span> <span class="nf">getPeekedValueLockedAt</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
            <span class="n">slot</span><span class="p">.</span><span class="n">index</span> <span class="p">=</span> <span class="n">index</span> <span class="p">+</span> <span class="mi">1</span> <span class="c1">// points to the next index after peeked one</span>
            <span class="n">resumes</span> <span class="p">=</span> <span class="nf">updateCollectorIndexLocked</span><span class="p">(</span><span class="n">oldIndex</span><span class="p">)</span>
            <span class="n">newValue</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">resume</span> <span class="k">in</span> <span class="n">resumes</span><span class="p">)</span> <span class="n">resume</span><span class="o">?.</span><span class="nf">resume</span><span class="p">(</span><span class="nc">Unit</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">value</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="stateflow">StateFlow</h1>

<p>마지막으로 <code class="language-plaintext highlighter-rouge">StateFlow</code>이다. <code class="language-plaintext highlighter-rouge">StateFlow</code>는 <code class="language-plaintext highlighter-rouge">SharedFlow</code> 보다 단순하게 동작하며, 초기값이 존재하고 중복되는 값으로 상태를 수정하면 방출되지 않는다. 상태가 변경된 경우 값이 방출된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">stateFlow</span> <span class="p">=</span> <span class="nc">MutableStateFlow</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nf">launch</span> <span class="p">{</span>
    <span class="n">stateFlow</span><span class="p">.</span><span class="nf">collect</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nf">repeat</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span>
    <span class="n">stateFlow</span><span class="p">.</span><span class="nf">update</span> <span class="p">{</span> <span class="n">it</span> <span class="p">+</span> <span class="mi">1</span> <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 1</span>
<span class="c1">// 2</span>
<span class="c1">// 3</span>
<span class="c1">// 4</span>
<span class="c1">// 5</span>
</code></pre></div></div>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">fun</span> <span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="nf">MutableStateFlow</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">MutableStateFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">StateFlowImpl</span><span class="p">(</span><span class="n">value</span> <span class="o">?:</span> <span class="nc">NULL</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">StateFlowImpl</code>을 살펴보기 전에 <code class="language-plaintext highlighter-rouge">StateFlow</code>, <code class="language-plaintext highlighter-rouge">MutableStateFlow</code> 인터페이스를 확인해보자.
현재 상태인 value를 가지고 있고 <code class="language-plaintext highlighter-rouge">MutableStateFlow</code>의 경우 상태 쓰기를 허용한다.
또한 원자적으로 상태값을 갱신하기 위한 <code class="language-plaintext highlighter-rouge">compareAndSet</code> 함수가 정의되어있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">StateFlow</span><span class="p">&lt;</span><span class="k">out</span> <span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">SharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span>
<span class="p">}</span>

<span class="kd">interface</span> <span class="nc">MutableStateFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="nc">StateFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;,</span> <span class="nc">MutableSharedFlow</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">override</span> <span class="kd">var</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span>

    <span class="k">fun</span> <span class="nf">compareAndSet</span><span class="p">(</span><span class="n">expect</span><span class="p">:</span> <span class="nc">T</span><span class="p">,</span> <span class="n">update</span><span class="p">:</span> <span class="nc">T</span><span class="p">):</span> <span class="nc">Boolean</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 <code class="language-plaintext highlighter-rouge">StateFlowImpl</code>의 <code class="language-plaintext highlighter-rouge">collect</code> 함수를 살펴보자.
<code class="language-plaintext highlighter-rouge">oldState</code>가 <code class="language-plaintext highlighter-rouge">null</code>이라는 뜻은 아직 아무것도 방출되지 않음을 나타내고 있다.
계속해서 반복문을 돌면서 이전 상태와 새로운 상태가 다른지 비교하고 다르다면 값을 방출한다.
그 후 await가 필요한 경우 Slot을 await 한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">suspend</span> <span class="k">fun</span> <span class="nf">collect</span><span class="p">(</span><span class="n">collector</span><span class="p">:</span> <span class="nc">FlowCollector</span><span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;):</span> <span class="nc">Nothing</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">slot</span> <span class="p">=</span> <span class="nf">allocateSlot</span><span class="p">()</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">collector</span> <span class="k">is</span> <span class="nc">SubscribedFlowCollector</span><span class="p">)</span> <span class="n">collector</span><span class="p">.</span><span class="nf">onSubscription</span><span class="p">()</span>
        <span class="kd">val</span> <span class="py">collectorJob</span> <span class="p">=</span> <span class="nf">currentCoroutineContext</span><span class="p">()[</span><span class="nc">Job</span><span class="p">]</span>
        <span class="kd">var</span> <span class="py">oldState</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span> <span class="c1">// previously emitted T!! | NULL (null -- nothing emitted yet)</span>
        <span class="c1">// The loop is arranged so that it starts delivering current value without waiting first</span>
        <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// Here the coroutine could have waited for a while to be dispatched,</span>
            <span class="c1">// so we use the most recent state here to ensure the best possible conflation of stale values</span>
            <span class="kd">val</span> <span class="py">newState</span> <span class="p">=</span> <span class="n">_state</span><span class="p">.</span><span class="n">value</span>
            <span class="c1">// always check for cancellation</span>
            <span class="n">collectorJob</span><span class="o">?.</span><span class="nf">ensureActive</span><span class="p">()</span>
            <span class="c1">// Conflate value emissions using equality</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">oldState</span> <span class="p">==</span> <span class="k">null</span> <span class="p">||</span> <span class="n">oldState</span> <span class="p">!=</span> <span class="n">newState</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">collector</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="nc">NULL</span><span class="p">.</span><span class="nf">unbox</span><span class="p">(</span><span class="n">newState</span><span class="p">))</span>
                <span class="n">oldState</span> <span class="p">=</span> <span class="n">newState</span>
            <span class="p">}</span>
            <span class="c1">// Note: if awaitPending is cancelled, then it bails out of this loop and calls freeSlot</span>
            <span class="k">if</span> <span class="p">(!</span><span class="n">slot</span><span class="p">.</span><span class="nf">takePending</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// try fast-path without suspending first</span>
                <span class="n">slot</span><span class="p">.</span><span class="nf">awaitPending</span><span class="p">()</span> <span class="c1">// only suspend for new values when needed</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
        <span class="nf">freeSlot</span><span class="p">(</span><span class="n">slot</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>값을 새로 갱신하는 경우 아래의 <code class="language-plaintext highlighter-rouge">updateState</code> 함수가 호출된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="kd">var</span> <span class="py">value</span><span class="p">:</span> <span class="nc">T</span>
    <span class="k">get</span><span class="p">()</span> <span class="p">=</span> <span class="nc">NULL</span><span class="p">.</span><span class="nf">unbox</span><span class="p">(</span><span class="n">_state</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
    <span class="k">set</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{</span> <span class="nf">updateState</span><span class="p">(</span><span class="k">null</span><span class="p">,</span> <span class="n">value</span> <span class="o">?:</span> <span class="nc">NULL</span><span class="p">)</span> <span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">updateState</code> 함수는 CAS(Compare And Swap) 알고리즘을 이용하여 <code class="language-plaintext highlighter-rouge">oldState</code>와 <code class="language-plaintext highlighter-rouge">expectedState</code>가 일치하지 않는 경우 함수를 종료한다. 그리고 갱신할 값과 이전 값이 같은 경우 또한 함수를 종료한다.
그리고 상태를 새로운 값으로 갱신한다.</p>

<p>이 함수에서는 <code class="language-plaintext highlighter-rouge">sequence</code>를 이용하여 serializes updates를 구현한다. <code class="language-plaintext highlighter-rouge">sequence</code>가 홀수이면 업데이트가 진행중 인것이고, 짝수이면 업데이트중이 아니다. 이 부분은 무엇을 위한 것인지 더 공부해봐야겠다.(아시는 분 댓글 부탁드립니다.)</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">updateState</span><span class="p">(</span><span class="n">expectedState</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">newState</span><span class="p">:</span> <span class="nc">Any</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">curSequence</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="kd">var</span> <span class="py">curSlots</span><span class="p">:</span> <span class="nc">Array</span><span class="p">&lt;</span><span class="nc">StateFlowSlot</span><span class="p">?&gt;?</span> <span class="p">=</span> <span class="k">this</span><span class="p">.</span><span class="n">slots</span> <span class="c1">// benign race, we will not use it</span>
    <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">oldState</span> <span class="p">=</span> <span class="n">_state</span><span class="p">.</span><span class="n">value</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">expectedState</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">oldState</span> <span class="p">!=</span> <span class="n">expectedState</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span> <span class="c1">// CAS support</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">oldState</span> <span class="p">==</span> <span class="n">newState</span><span class="p">)</span> <span class="k">return</span> <span class="k">true</span> <span class="c1">// Don't do anything if value is not changing, but CAS -&gt; true</span>
        <span class="n">_state</span><span class="p">.</span><span class="n">value</span> <span class="p">=</span> <span class="n">newState</span>
        <span class="n">curSequence</span> <span class="p">=</span> <span class="n">sequence</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">curSequence</span> <span class="n">and</span> <span class="mi">1</span> <span class="p">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// even sequence means quiescent state flow (no ongoing update)</span>
            <span class="n">curSequence</span><span class="p">++</span> <span class="c1">// make it odd</span>
            <span class="n">sequence</span> <span class="p">=</span> <span class="n">curSequence</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// update is already in process, notify it, and return</span>
            <span class="n">sequence</span> <span class="p">=</span> <span class="n">curSequence</span> <span class="p">+</span> <span class="mi">2</span> <span class="c1">// change sequence to notify, keep it odd</span>
            <span class="k">return</span> <span class="k">true</span> <span class="c1">// updated</span>
        <span class="p">}</span>
        <span class="n">curSlots</span> <span class="p">=</span> <span class="n">slots</span> <span class="c1">// read current reference to collectors under lock</span>
    <span class="p">}</span>

    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그리고 난 후 현재 슬롯들 모두 <code class="language-plaintext highlighter-rouge">makePending</code>을 호출하여 await 중인 코루틴을 재개한다.</p>

<p><code class="language-plaintext highlighter-rouge">Unconfined Coroutine</code>으로 발생할 수 있는 데드락을 피하기 위해 lock 바깥에서 아래의 코드를 실행한다고 한다.
lock 안에서 <code class="language-plaintext highlighter-rouge">makePending</code>을 호출하면 왜 데드락이 발생하는지, 아래의 코드가 어떻게 데드락을 막을 수 있는지 잘 모르겠다…(아시는 분 댓글 부탁드립니다.)</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">updateState</span><span class="p">(</span><span class="n">expectedState</span><span class="p">:</span> <span class="nc">Any</span><span class="p">?,</span> <span class="n">newState</span><span class="p">:</span> <span class="nc">Any</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">{</span>
    <span class="c1">// ...</span>

    <span class="cm">/*
        Fire value updates outside of the lock to avoid deadlocks with unconfined coroutines.
        Loop until we're done firing all the changes. This is a sort of simple flat combining that
        ensures sequential firing of concurrent updates and avoids the storm of collector resumes
        when updates happen concurrently from many threads.
    */</span>
    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// Benign race on element read from array</span>
        <span class="n">curSlots</span><span class="o">?.</span><span class="nf">forEach</span> <span class="p">{</span>
            <span class="n">it</span><span class="o">?.</span><span class="nf">makePending</span><span class="p">()</span>
        <span class="p">}</span>
        <span class="c1">// check if the value was updated again while we were updating the old one</span>
        <span class="nf">synchronized</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">sequence</span> <span class="p">==</span> <span class="n">curSequence</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// nothing changed, we are done</span>
                <span class="n">sequence</span> <span class="p">=</span> <span class="n">curSequence</span> <span class="p">+</span> <span class="mi">1</span> <span class="c1">// make sequence even again</span>
                <span class="k">return</span> <span class="k">true</span> <span class="c1">// done, updated</span>
            <span class="p">}</span>
            <span class="c1">// reread everything for the next loop under the lock</span>
            <span class="n">curSequence</span> <span class="p">=</span> <span class="n">sequence</span>
            <span class="n">curSlots</span> <span class="p">=</span> <span class="n">slots</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="배운점">배운점</h1>

<ul>
  <li>Cold Stream과 Hot Stream이 내부적으로 어떻게 달리 동작하는지 알게되었다.</li>
  <li>Hot Stream은 내부적으로 slot과 <code class="language-plaintext highlighter-rouge">suspendCancellableCoroutine</code>을 적극 활용하여 동작한다.</li>
  <li>Flow는 <a href="https://jja08111.github.io/android/live-data-observer-pattern.md/">LiveData</a>와 달리 코루틴의 특성을 잘 활용하여 구현했다.</li>
</ul>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="kotlin" /><category term="kotlin" /><category term="flow" /><summary type="html"><![CDATA[앱 개발에 있어 자주 사용되는 Flow 구현을 파헤쳐보겠다. 버전은 kotlinx-coroutines-core-jvm-1.6.4 기준이다.]]></summary></entry><entry><title type="html">[Kotlin] Coroutine</title><link href="https://jja08111.github.io/kotlin/kotlin-coroutine/" rel="alternate" type="text/html" title="[Kotlin] Coroutine" /><published>2024-02-20T21:12:00+09:00</published><updated>2024-02-20T21:12:00+09:00</updated><id>https://jja08111.github.io/kotlin/kotlin-coroutine</id><content type="html" xml:base="https://jja08111.github.io/kotlin/kotlin-coroutine/"><![CDATA[<p>코루틴은 코틀린에서 제공하는 비동기 솔루션이다.
코드를 실행하는 동시에 다른 코드를 실행하는 점이 경량 스레드라고 생각할 수도 있지만 스레드와는 차이점이 존재한다.
코루틴은 특정 스레드에 속하지 않는다. 즉, 코루틴은 특정 스레드에 실행되고 다른 스레드로부터 재게될 수 있다.</p>

<p>다른 비동기 솔루션에는 RxJava가 존재한다. 하지만 코루틴을 선호하는 이유는 언어 자체에서 지원을 해주기 때문이다.</p>

<h1 id="장점">장점</h1>

<ul>
  <li>경량: 코루틴은 실행 중인 스레드를 차단(block)하지 않는 정지(suspension)을 지원한다. 따라서 단일 스레드에서 많은 코루틴을 실행할 수 있다. 그리고 정지(suspension)은 많은 동시 작업을 지원하면서도 차단(block)보다 메모리를 절약한다.</li>
  <li>메모리 누수 감소: 코루틴은 코루틴 스코프에서만 실행 될 수 있기 때문에 메모리 누수의 위험성을 덜어준다.</li>
  <li>기본으로 제공되는 취소 지원: 실행 중인 코루틴 계층 구조를 통해 자동으로 취소가 전달된다.</li>
</ul>

<h1 id="코루틴-스코프">코루틴 스코프</h1>

<p>코루틴 스코프는 빌더에 의해 생성될 수 있다. 코루틴 스코프는 자식들이 모두 완료되기 전까지 완료되지 않는다.</p>

<p>기본적인 스코프는 <code class="language-plaintext highlighter-rouge">runBlocking</code>과 <code class="language-plaintext highlighter-rouge">coroutineScope</code>가 존재한다. 둘은 내부의 코루틴이 종료될 때까지 대기한다는 점이 동일하나 차이가 존재한다. <code class="language-plaintext highlighter-rouge">runBlocking</code>은 현재 스레드를 <strong>차단(block)</strong>하며, 반면에 <code class="language-plaintext highlighter-rouge">coroutineScope</code>는 코루틴을 <strong>일시 정지(suspend)</strong>하여 스레드가 다른 곳에서 사용할 수 있게 한다.</p>

<p>코드로 살펴보자. 아래는 <code class="language-plaintext highlighter-rouge">runBlocking</code>을 사용하였다. 실행 결과 Hello가 먼저 출력되는 것을 볼 수 있다. 스레드가 차단되었기 때문에 1초 대기 후 <em>“Hello World!”</em>가 출력되는 것이다. <code class="language-plaintext highlighter-rouge">runBlocking</code>은 값비싼 스레드를 차단한다는 것을 확인할 수 있다. 그래서 실제 상용 프로그램 개발을 할때는 많이 사용되지 않는다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">runBlocking</span> <span class="p">{</span>
        <span class="nf">launch</span> <span class="p">{</span>
            <span class="nf">delay</span><span class="p">(</span><span class="mi">1_000</span><span class="p">)</span>
            <span class="nf">println</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"World!"</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// Hello</span>
<span class="c1">// World!</span>
</code></pre></div></div>

<p>이번에는 <code class="language-plaintext highlighter-rouge">coroutineScope</code>를 살펴보자. 실행 결과 World가 먼저 출력된 것을 볼 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">runBlocking</span> <span class="p">{</span>
    <span class="nf">doWorld</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">suspend</span> <span class="k">fun</span> <span class="nf">doWorld</span><span class="p">()</span> <span class="p">=</span> <span class="nf">coroutineScope</span> <span class="p">{</span>
    <span class="nf">launch</span> <span class="p">{</span>
        <span class="nf">delay</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"Hello!"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"World"</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// World</span>
<span class="c1">// Hello!</span>
</code></pre></div></div>

<p>또한 동시에 처리가 가능하다. 아래의 코드를 보면 두개의 코루틴을 생성하는 모습을 볼 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">runBlocking</span> <span class="p">{</span>
    <span class="nf">doWorld</span><span class="p">()</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Done"</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">suspend</span> <span class="k">fun</span> <span class="nf">doWorld</span><span class="p">()</span> <span class="p">=</span> <span class="nf">coroutineScope</span> <span class="p">{</span>
    <span class="nf">launch</span> <span class="p">{</span>
        <span class="nf">delay</span><span class="p">(</span><span class="mi">2000L</span><span class="p">)</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"World 2"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nf">launch</span> <span class="p">{</span>
        <span class="nf">delay</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"World 1"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>
<span class="p">}</span>

<span class="cm">/*
Hello
World 1
World 2
Done
*/</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">launch</code>를 통해 코루틴을 생성하면 <code class="language-plaintext highlighter-rouge">Job</code> 객체가 반환되는데, 이를 통해 <code class="language-plaintext highlighter-rouge">join</code>, <code class="language-plaintext highlighter-rouge">cancel</code>등의 작업을 할 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">runBlocking</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">job</span> <span class="p">=</span> <span class="nf">launch</span> <span class="p">{</span> <span class="c1">// launch a new coroutine and keep a reference to its Job</span>
        <span class="nf">delay</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"World!"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>
    <span class="n">job</span><span class="p">.</span><span class="nf">join</span><span class="p">()</span> <span class="c1">// wait until child coroutine completes</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Done"</span><span class="p">)</span>
<span class="p">}</span>

<span class="cm">/*
Hello
World!
Done
*/</span>
</code></pre></div></div>

<p>안드로이드에서는 흔히 사용되는 코루틴 스코프가 존재한다. <code class="language-plaintext highlighter-rouge">viewModelScope</code>, <code class="language-plaintext highlighter-rouge">lifecycleScope</code>와 같은 <a href="https://developer.android.com/topic/libraries/architecture/coroutines?hl=ko">수명 주기 인식 구성요소와 함께 사용되는 코루틴 스코프</a>가 존재한다. 때문에 생명주기가 존재하는 엑티비티, 프레그먼트, 뷰모델 등에서 안전하게 코루틴을 사용할 수 있다.</p>

<h1 id="async로-병렬-처리">async로 병렬 처리</h1>

<p>만약 실행에 필요한 비동기 요청들이 의존성이 없어 동시에 처리하려면 어떻게 해야할까? <code class="language-plaintext highlighter-rouge">launc</code>를 여러번 수행해도 되지만 <code class="language-plaintext highlighter-rouge">async</code>를 이용하는 방법도 존재한다. <code class="language-plaintext highlighter-rouge">async</code>는 <code class="language-plaintext highlighter-rouge">launch</code>와 유사하게 독립된 코루틴을 실행한다.
다른 부분은 <code class="language-plaintext highlighter-rouge">launch</code>는 <code class="language-plaintext highlighter-rouge">Job</code>을 반환하는데, <code class="language-plaintext highlighter-rouge">async</code>는 결과를 나중에 제공해주는 <code class="language-plaintext highlighter-rouge">Deferred</code>를 반환한다.</p>

<p>아래의 코드를 보면 1초가 걸리는 두 개의 함수를 동시에 실행하여 대기한 후 결과를 반환한다. 만약 <code class="language-plaintext highlighter-rouge">async</code> 없이 호출했다면 첫 번째 함수를 호출하고 1초를 대기하고 다시 두 번째 함수를 호출하고 1초를 대기해 총 2초가 걸릴 것이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="n">runBlocking</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">time</span> <span class="p">=</span> <span class="nf">measureTimeMillis</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">one</span> <span class="p">=</span> <span class="nf">async</span> <span class="p">{</span> <span class="nf">doSomethingUsefulOne</span><span class="p">()</span> <span class="p">}</span>
        <span class="kd">val</span> <span class="py">two</span> <span class="p">=</span> <span class="nf">async</span> <span class="p">{</span> <span class="nf">doSomethingUsefulTwo</span><span class="p">()</span> <span class="p">}</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"The answer is ${one.await() + two.await()}"</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Completed in $time ms"</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">suspend</span> <span class="k">fun</span> <span class="nf">doSomethingUsefulOne</span><span class="p">():</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="nf">delay</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span> <span class="c1">// pretend we are doing something useful here</span>
    <span class="k">return</span> <span class="mi">13</span>
<span class="p">}</span>

<span class="k">suspend</span> <span class="k">fun</span> <span class="nf">doSomethingUsefulTwo</span><span class="p">():</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="nf">delay</span><span class="p">(</span><span class="mi">1000L</span><span class="p">)</span> <span class="c1">// pretend we are doing something useful here, too</span>
    <span class="k">return</span> <span class="mi">29</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이를 이용하여 아래와 같이 async-style 함수를 만들 수 있으나 이처럼 <strong>사용하지 않는</strong> 것을 강력히 권장한다.
그 이유는 예외 발생시 메모리 누수 문제가 있기 때문이다. 예를 들어 첫 번째 함수를 실행하던 도중 예외가 던저졌다고 가정하자. 그렇게 첫 번째 코루틴 스코프는 취소된다. 하지만 두 번재 코루틴은 그대로 백그라운드에서 계속 진행한다. 결과가 의미 없음에도 말이다. 따라서 <strong>절대로 아래와 같이 사용하지 말자</strong>.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@OptIn</span><span class="p">(</span><span class="nc">DelicateCoroutinesApi</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="k">fun</span> <span class="nf">somethingUsefulOneAsync</span><span class="p">()</span> <span class="p">=</span> <span class="nc">GlobalScope</span><span class="p">.</span><span class="nf">async</span> <span class="p">{</span>
    <span class="nf">doSomethingUsefulOne</span><span class="p">()</span>
<span class="p">}</span>

<span class="c1">// The result type of somethingUsefulTwoAsync is Deferred&lt;Int&gt;</span>
<span class="nd">@OptIn</span><span class="p">(</span><span class="nc">DelicateCoroutinesApi</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="k">fun</span> <span class="nf">somethingUsefulTwoAsync</span><span class="p">()</span> <span class="p">=</span> <span class="nc">GlobalScope</span><span class="p">.</span><span class="nf">async</span> <span class="p">{</span>
    <span class="nf">doSomethingUsefulTwo</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// ...</span>
  <span class="kd">val</span> <span class="py">one</span> <span class="p">=</span> <span class="nf">somethingUsefulOneAsync</span><span class="p">()</span>
  <span class="kd">val</span> <span class="py">two</span> <span class="p">=</span> <span class="nf">somethingUsefulTwoAsync</span><span class="p">()</span>
  <span class="c1">// but waiting for a result must involve either suspending or blocking.</span>
  <span class="c1">// here we use `runBlocking { ... }` to block the main thread while waiting for the result</span>
  <span class="nf">runBlocking</span> <span class="p">{</span>
      <span class="nf">println</span><span class="p">(</span><span class="s">"The answer is ${one.await() + two.await()}"</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>반대로 스코프를 아래와 같이 적절히 이용한다면 하나의 예외가 발생하였을 때 예외가 전파되어 스코프 내의 모든 코루틴이 취소된다. 그렇기 때문에 아래의 방법을 이용하자.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">suspend</span> <span class="k">fun</span> <span class="nf">concurrentSum</span><span class="p">():</span> <span class="nc">Int</span> <span class="p">=</span> <span class="nf">coroutineScope</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">one</span> <span class="p">=</span> <span class="nf">async</span> <span class="p">{</span> <span class="nf">doSomethingUsefulOne</span><span class="p">()</span> <span class="p">}</span>
    <span class="kd">val</span> <span class="py">two</span> <span class="p">=</span> <span class="nf">async</span> <span class="p">{</span> <span class="nf">doSomethingUsefulTwo</span><span class="p">()</span> <span class="p">}</span>
    <span class="n">one</span><span class="p">.</span><span class="nf">await</span><span class="p">()</span> <span class="p">+</span> <span class="n">two</span><span class="p">.</span><span class="nf">await</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>한 곳에서 예외가 발생했을 때 실제로 어떤 일이 일어나는 지는 아래의 코드를 통해서 확인할 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="n">runBlocking</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">try</span> <span class="p">{</span>
        <span class="nf">failedConcurrentSum</span><span class="p">()</span>
    <span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">ArithmeticException</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"Computation failed with ArithmeticException"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">suspend</span> <span class="k">fun</span> <span class="nf">failedConcurrentSum</span><span class="p">():</span> <span class="nc">Int</span> <span class="p">=</span> <span class="nf">coroutineScope</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">one</span> <span class="p">=</span> <span class="n">async</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="nf">delay</span><span class="p">(</span><span class="nc">Long</span><span class="p">.</span><span class="nc">MAX_VALUE</span><span class="p">)</span> <span class="c1">// Emulates very long computation</span>
            <span class="mi">42</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="nf">println</span><span class="p">(</span><span class="s">"First child was cancelled"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="kd">val</span> <span class="py">two</span> <span class="p">=</span> <span class="n">async</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"Second child throws an exception"</span><span class="p">)</span>
        <span class="k">throw</span> <span class="nc">ArithmeticException</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="n">one</span><span class="p">.</span><span class="nf">await</span><span class="p">()</span> <span class="p">+</span> <span class="n">two</span><span class="p">.</span><span class="nf">await</span><span class="p">()</span>
<span class="p">}</span>

<span class="cm">/*
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
*/</span>
</code></pre></div></div>

<h1 id="flow">Flow</h1>

<p>Flow는 <a href="https://jja08111.github.io/kotlin/kotlin-flow/">다른 글</a>에 정리해놓았다.</p>

<h1 id="channel">Channel</h1>

<p><code class="language-plaintext highlighter-rouge">Channel</code>은 코루틴간의 통신을 위해 사용될 수 있다. 이는 <code class="language-plaintext highlighter-rouge">BlockingQueue</code>와 매우 유사하다.</p>

<p>아래의 코드는 코루틴에서 5개의 데이터를 전송하고 다른 쪽에서 5개를 수신하고 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">=</span> <span class="nf">runBlocking</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">Channel</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;()</span>
    <span class="nf">launch</span> <span class="p">{</span>
        <span class="c1">// this might be heavy CPU-consuming computation or async logic, we'll just send five squares</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">x</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="mi">5</span><span class="p">)</span> <span class="n">channel</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">x</span> <span class="p">*</span> <span class="n">x</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="c1">// here we print five received integers:</span>
    <span class="nf">repeat</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">channel</span><span class="p">.</span><span class="nf">receive</span><span class="p">())</span> <span class="p">}</span>
    <span class="nf">println</span><span class="p">(</span><span class="s">"Done!"</span><span class="p">)</span>
<span class="p">}</span>

<span class="cm">/*
1
4
9
16
25
Done!
*/</span>
</code></pre></div></div>

<p>queue와 다르게 channel은 닫혀질 수 있다. 반복문에서 iteratorable처럼 사용될 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">Channel</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;()</span>
<span class="nf">launch</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">x</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="mi">5</span><span class="p">)</span> <span class="n">channel</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="n">x</span> <span class="p">*</span> <span class="n">x</span><span class="p">)</span>
    <span class="n">channel</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span> <span class="c1">// we're done sending</span>
<span class="p">}</span>
<span class="c1">// here we print received values using `for` loop (until the channel is closed)</span>
<span class="k">for</span> <span class="p">(</span><span class="n">y</span> <span class="k">in</span> <span class="n">channel</span><span class="p">)</span> <span class="nf">println</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"Done!"</span><span class="p">)</span>

<span class="cm">/*
1
4
9
16
25
Done!
*/</span>
</code></pre></div></div>

<h1 id="내부-동작">내부 동작</h1>

<p>Kotlin compiler가 coroutine 코드를 어떻게 변환하는지 살펴보자.</p>

<p>코루틴은 suspension 함수당 하나의 상태 머신을 만들어 동작한다. 아래의 코드는 두 개의 suspension 지점이 있어 초기 상태를 포함한 총 세 개의 상태를 가진다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">a</span> <span class="p">=</span> <span class="nf">a</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">y</span> <span class="p">=</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">).</span><span class="nf">await</span><span class="p">()</span> <span class="c1">// suspension point #1</span>
<span class="nf">b</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">z</span> <span class="p">=</span> <span class="nf">bar</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">y</span><span class="p">).</span><span class="nf">await</span><span class="p">()</span> <span class="c1">// suspension point #2</span>
<span class="nf">c</span><span class="p">(</span><span class="n">z</span><span class="p">)</span>
</code></pre></div></div>

<p>위의 코드는 아래의 pseudo-Java 코드와 유사하게 컴파일된다. 상태 머신을 구현한 익명 객체가 생성된다. 필드는 현재 머신의 상태를 가지며, 각 상태들에 공유된다. 상태를 <code class="language-plaintext highlighter-rouge">label</code>이라는 필드로 나타내고 있으며 반복적으로 다음 상태로 바뀌는 것을 확인할 수 있다. 중간에 중단되고 다시 <code class="language-plaintext highlighter-rouge">resumeWith</code>가 호출되면 재개된다. 마지막 상태인 <code class="language-plaintext highlighter-rouge">2</code>에 도달하면 마지막 <code class="language-plaintext highlighter-rouge">c(z)</code>를 호출하고 상태를 <code class="language-plaintext highlighter-rouge">-1</code>로 바꾸어 더이상 반복하지 않는다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="err">&lt;anonymous_for_state_machine&gt; extends </span><span class="nc">SuspendLambda</span><span class="p">&lt;</span><span class="o">..</span><span class="p">.&gt;</span> <span class="p">{</span>
    <span class="c1">// The current state of the state machine</span>
    <span class="n">int</span> <span class="n">label</span> <span class="p">=</span> <span class="mi">0</span>

    <span class="c1">// local variables of the coroutine</span>
    <span class="nc">A</span> <span class="n">a</span> <span class="p">=</span> <span class="k">null</span>
    <span class="nc">Y</span> <span class="n">y</span> <span class="p">=</span> <span class="k">null</span>

    <span class="n">void</span> <span class="nf">resumeWith</span><span class="p">(</span><span class="nc">Object</span> <span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">label</span> <span class="p">==</span> <span class="mi">0</span><span class="p">)</span> <span class="n">goto</span> <span class="nc">L0</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">label</span> <span class="p">==</span> <span class="mi">1</span><span class="p">)</span> <span class="n">goto</span> <span class="nc">L1</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">label</span> <span class="p">==</span> <span class="mi">2</span><span class="p">)</span> <span class="n">goto</span> <span class="nc">L2</span>
        <span class="k">else</span> <span class="k">throw</span> <span class="nc">IllegalStateException</span><span class="p">()</span>

      <span class="nc">L0</span><span class="p">:</span>
        <span class="c1">// result is expected to be `null` at this invocation</span>
        <span class="n">a</span> <span class="p">=</span> <span class="nf">a</span><span class="p">()</span>
        <span class="n">label</span> <span class="p">=</span> <span class="mi">1</span>
        <span class="n">result</span> <span class="p">=</span> <span class="nf">foo</span><span class="p">(</span><span class="n">a</span><span class="p">).</span><span class="nf">await</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="c1">// 'this' is passed as a continuation</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">==</span> <span class="nc">COROUTINE_SUSPENDED</span><span class="p">)</span> <span class="k">return</span> <span class="c1">// return if await had suspended execution</span>
      <span class="nc">L1</span><span class="p">:</span>
        <span class="c1">// external code has resumed this coroutine passing the result of .await()</span>
        <span class="n">y</span> <span class="p">=</span> <span class="p">(</span><span class="nc">Y</span><span class="p">)</span> <span class="n">result</span>
        <span class="nf">b</span><span class="p">()</span>
        <span class="n">label</span> <span class="p">=</span> <span class="mi">2</span>
        <span class="n">result</span> <span class="p">=</span> <span class="nf">bar</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">y</span><span class="p">).</span><span class="nf">await</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="c1">// 'this' is passed as a continuation</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">==</span> <span class="nc">COROUTINE_SUSPENDED</span><span class="p">)</span> <span class="k">return</span> <span class="c1">// return if await had suspended execution</span>
      <span class="nc">L2</span><span class="p">:</span>
        <span class="c1">// external code has resumed this coroutine passing the result of .await()</span>
        <span class="nc">Z</span> <span class="n">z</span> <span class="p">=</span> <span class="p">(</span><span class="nc">Z</span><span class="p">)</span> <span class="n">result</span>
        <span class="nf">c</span><span class="p">(</span><span class="n">z</span><span class="p">)</span>
        <span class="n">label</span> <span class="p">=</span> <span class="p">-</span><span class="mi">1</span> <span class="c1">// No more steps are allowed</span>
        <span class="k">return</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 객체는 JVM 힙 영역에 할당되어 여러 스레드에게서 재개될 수 있다. 그렇기 때문에 context-switcing 오버헤드가 상당히 적다.
스레드는 스레드마다 스택 영역을 가지고 있기 때문에 코루틴에 비해 context-switcing 오버헤드가 크고 무겁다.</p>

<h1 id="참조">참조</h1>

<ul>
  <li><a href="https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md">Kotlin/KEEP/proposals/coroutines.md</a></li>
  <li><a href="https://developer.android.com/kotlin/coroutines?hl=ko">Android의 Kotlin 코루틴</a></li>
  <li><a href="https://velog.io/@haero_kim/Thread-vs-Coroutine-비교해보기">Thread vs Coroutine 비교해보기 by haero_kim</a></li>
</ul>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="kotlin" /><category term="kotlin" /><category term="coroutine" /><summary type="html"><![CDATA[코루틴은 코틀린에서 제공하는 비동기 솔루션이다. 코드를 실행하는 동시에 다른 코드를 실행하는 점이 경량 스레드라고 생각할 수도 있지만 스레드와는 차이점이 존재한다. 코루틴은 특정 스레드에 속하지 않는다. 즉, 코루틴은 특정 스레드에 실행되고 다른 스레드로부터 재게될 수 있다.]]></summary></entry><entry><title type="html">[Typescript] any vs unknown</title><link href="https://jja08111.github.io/typescript/any-vs-unknown-in-typescript/" rel="alternate" type="text/html" title="[Typescript] any vs unknown" /><published>2024-01-06T11:00:00+09:00</published><updated>2024-01-06T11:00:00+09:00</updated><id>https://jja08111.github.io/typescript/any-vs-unknown-in-typescript</id><content type="html" xml:base="https://jja08111.github.io/typescript/any-vs-unknown-in-typescript/"><![CDATA[<p>Typescript에는 any와 unknown 타입이 존재한다. 처음 이 둘을 마주했을때는 마냥 비슷한 줄 알았지만 사실은 아니었다.</p>

<p>결론부터 말하자면 any 대신 unknown을 사용하는 것이 안전하다. 그 이유를 알아보자.</p>

<h1 id="any">any</h1>

<p>any는 타입체크를 전혀 하지 않고 메소드 호출, 프로퍼티 접근을 모두 할 수 있다.
집합으로 표현하자면 어떤 집합도 될 수 있고, 어떤 부분 집합되 될 수 있는 마스터 키 같은 녀석이다.
때문에 any를 남발하는 경우 런타임에 문제가 발생할 위험성이 높다.</p>

<p>아래의 코드를 보자. <code class="language-plaintext highlighter-rouge">buyBook</code> 함수의 매개변수로 book을 any 타입으로 선언했다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Book</span> <span class="p">{</span>
  <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">buyBook</span><span class="p">(</span><span class="nx">book</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Buying the </span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">titel</span><span class="p">}</span><span class="s2">! (</span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">price</span><span class="p">}</span><span class="s2"> won)`</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">buyBook</span><span class="p">({</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Nice Book</span><span class="dl">"</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="mi">12000</span> <span class="p">});</span> <span class="c1">// 출력 결과: Buying the undefined! (12000 won)</span>
</code></pre></div></div>

<p>실행을 해보니 엉뚱한 결과가 출력된다. 그 이유는 <code class="language-plaintext highlighter-rouge">title</code>을 <code class="language-plaintext highlighter-rouge">titel</code>로 잘못 작성했기 때문이다.</p>

<p>협업을 하는 경우 다른 사람의 코드를 이해하고 사용해야하는 경우가 많다. 이러한 any 타입의 남발은 협업을 할 때 어려움을 제공한다.
대부분의 타입들이 any로 선언되어있다면? 벌써부터 머리가 아프다. “이 값은 무엇을 담고 있는거지?”, “이 함수는 무엇을 반환하는 거지?”
이럴때마다 코드를 역추적하며 해당 값이 어떤 타입인지 직접 찾아야한다. 이는 큰 시간 낭비이며 런타임 오류를 만들어내기 쉽다.</p>

<p>또한 변화에 취약하다. 만약 특정 타입의 프로퍼티 이름을 수정하는 경우 하나씩 찾아가며 수정을 진행해야한다. 실수로 하나를 놓치게 되는 경우 프로그램에 문제가 생긴다.</p>

<h1 id="unknown">unknown</h1>

<p>unknown은 any와 다르다. unknown은 전체 집합이라고 표현할 수 있으며, 타입이 특정되지 않는다면 메소드와 프로퍼티 접근이 제한된다.</p>

<p>앞선 any 대신 예제를 unknown으로 바꿔보겠다. 그러자 컴파일러가 book은 unknown 타입이라 title, price 프로퍼티에 접근할 수 없다고 경고를 보여준다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Book</span> <span class="p">{</span>
  <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="nl">price</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">buyBook</span><span class="p">(</span><span class="nx">book</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// compile error! 'book' is of type 'unknown'.</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Buying the </span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">titel</span><span class="p">}</span><span class="s2">! (</span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">price</span><span class="p">}</span><span class="s2"> won)`</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">buyBook</span><span class="p">({</span> <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Nice Book</span><span class="dl">"</span><span class="p">,</span> <span class="na">price</span><span class="p">:</span> <span class="mi">12000</span> <span class="p">});</span>
</code></pre></div></div>

<p>이러한 경고를 제거하기 위해서는 type-guard를 통해 타입 범위를 좁히는 방법이 있다. <code class="language-plaintext highlighter-rouge">typeof</code>, <code class="language-plaintext highlighter-rouge">instaceof</code>부터, 아래와 같이 <code class="language-plaintext highlighter-rouge">isBookType</code> 함수를 만들 수도 있다.
<code class="language-plaintext highlighter-rouge">isBookType</code>을 사용하니 컴파일러에서 titel이 아닌 title이라고 경고를 보내준다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">isBookType</span><span class="p">(</span><span class="nx">arg</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="nx">arg</span> <span class="k">is</span> <span class="nx">Book</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">arg</span><span class="p">.</span><span class="nx">title</span> <span class="o">!==</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">arg</span><span class="p">.</span><span class="nx">price</span> <span class="o">!==</span> <span class="kc">undefined</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">buyBook</span><span class="p">(</span><span class="nx">book</span><span class="p">:</span> <span class="nx">unknown</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">isBookType</span><span class="p">(</span><span class="nx">book</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// Property 'titel' does not exist on type 'Book'. Did you mean 'title'?</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Buying the </span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">titel</span><span class="p">}</span><span class="s2">! (</span><span class="p">${</span><span class="nx">book</span><span class="p">.</span><span class="nx">price</span><span class="p">}</span><span class="s2"> won)`</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그렇다면 언제 unknown이 쓰이게 될까? 대표적으로 외부 반환 값에 대해 검증이 필요한 경우가 있다. 예를 들면 외부 라이브러리의 반환 값이 any인 경우이다.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="typescript" /><category term="typescript" /><summary type="html"><![CDATA[Typescript에는 any와 unknown 타입이 존재한다. 처음 이 둘을 마주했을때는 마냥 비슷한 줄 알았지만 사실은 아니었다.]]></summary></entry><entry><title type="html">Compose의 안정성 시스템</title><link href="https://jja08111.github.io/android/stable-system-of-compose/" rel="alternate" type="text/html" title="Compose의 안정성 시스템" /><published>2023-08-21T11:00:00+09:00</published><updated>2023-08-21T11:00:00+09:00</updated><id>https://jja08111.github.io/android/stable-system-of-compose</id><content type="html" xml:base="https://jja08111.github.io/android/stable-system-of-compose/"><![CDATA[<p>Jetpack Compose에는 안정성 시스템이 존재한다. 이는 리컴포지션을 생략 가능한지 판단할 때 사용된다.
리컴포지션이 발생하여 컴포저블 함수의 스냅샷 상태가 변경되었다면 해당 컴포저블은 리컴포지션이 필요하다.
만약 변경되지 않았다면 불필요하게 리컴포지션을 진행할 필요가 없다. 타입이 안정적이지 않다면 항상 리컴포지션을 진행한다.</p>

<h1 id="stable">Stable</h1>

<p>값의 변경을 어떤 기준으로 확인할까? 아래의 안정성(Stable) 기준이 필요하다.</p>

<ol>
  <li>동일한 두 인스턴스로 equals를 수행하였을 때 <strong>항상</strong> 동일한 결과가 주어진다.</li>
  <li>모든 공개 프로퍼티는 변경될 때 Composition에 알려진다.</li>
  <li>모든 공개 프로퍼티는 Stable하다.</li>
</ol>

<p>첫 번째 항목을 생각해보자. 두 인스턴스가 존재하고 equals를 수행할 때마다 다른 결과가 나온다고 가정하자.
이 경우 캐시된 값과 현재 값의 동등성을 보장할 수 없기 때문에 캐시된 값을 재사용할 수 없다.
때문에 이는 unstable 하다고 판정되어 리컴포지션을 생략할 수 없다.</p>

<p>두 번째 항목을 생각해보자.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Person</span><span class="p">(</span>
    <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
    <span class="kd">val</span> <span class="py">address</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">)</span>
</code></pre></div></div>

<p>위의 <code class="language-plaintext highlighter-rouge">Person</code> 클래스를 보면 모든 프로퍼티가 쓰기 가능하지만 Compose에 <a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/MutableState">관찰될 수 있는 값</a>은 아니다.
때문에 아래와 같이 name이 변경 되어도 리컴포지션은 발생하지 않는다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">Text</span><span class="p">(</span>
        <span class="n">modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">.</span><span class="nf">clickable</span> <span class="p">{</span> <span class="n">person</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"new name"</span> <span class="p">},</span>
        <span class="n">text</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">name</span>
    <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그리고 아래와 같이 <code class="language-plaintext highlighter-rouge">person</code>이 변경되지 않는 경우에도 <code class="language-plaintext highlighter-rouge">checked</code>가 변경될 때마다 항상 <code class="language-plaintext highlighter-rouge">PersonDetail</code> 컴포저블에서 리컴포지션이 발생한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetailRow</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">,</span> <span class="n">modifier</span><span class="p">:</span> <span class="nc">Modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">checked</span> <span class="k">by</span> <span class="nf">remember</span> <span class="p">{</span> <span class="nf">mutableStateOf</span><span class="p">(</span><span class="k">false</span><span class="p">)</span> <span class="p">}</span>

    <span class="nc">Row</span><span class="p">(</span><span class="n">modifier</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
        <span class="nc">Switch</span><span class="p">(</span><span class="n">checked</span> <span class="p">=</span> <span class="n">checked</span><span class="p">,</span> <span class="n">onCheckedChange</span> <span class="p">=</span> <span class="p">{</span> <span class="n">checked</span> <span class="p">=</span> <span class="p">!</span><span class="n">checked</span> <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>궁금한점이 생겼다. 메모이제이션된 <code class="language-plaintext highlighter-rouge">Person</code> 객체로 equals를 수행하면 항상 동일한 값이 나오기 때문에 재사용해도 문제가 없지 않나?
그 이유는 다음과 같다. 먼저 <code class="language-plaintext highlighter-rouge">var name</code>을 수정하면 Compose는 해당 수정 사실을 모른다.
그 후 다른 이유로 리컴포지션이 발생했을 때 동일한 인스턴스끼리 equals를 수행하게 되지만 리컴포지션이 생략되어버린다.
때문에 이러한 경우 미묘한 UI 버그가 될 수 있기 때문에 unstable로 취급한다.</p>

<p>세 번째 항목은 공개 프로퍼티 중 하나라도 unstable하다면 두 인스턴스의 동등성을 보장할 수 없기 때문에 필요하다.</p>

<h1 id="stable-immutable을-잘못-사용하면"><code class="language-plaintext highlighter-rouge">@Stable</code>, <code class="language-plaintext highlighter-rouge">@Immutable</code>을 잘못 사용하면?</h1>

<p>두 어노테이션은 주의해서 사용해야한다. 리컴포지션이 발생해야하는 시점에서 누락되는 문제가 발생할 수 있기 때문이다.</p>

<p>예를 들어 stable이 아닌 <code class="language-plaintext highlighter-rouge">Person</code> 객체에 <code class="language-plaintext highlighter-rouge">@Stable</code>을 사용한 아래의 경우를 살펴보자.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Stable</span>
<span class="kd">data class</span> <span class="nc">Person</span><span class="p">(</span>
    <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
    <span class="kd">val</span> <span class="py">address</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">)</span>

<span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">Text</span><span class="p">(</span><span class="n">text</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">name</span><span class="p">)</span>
<span class="p">}</span>

<span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetailRow</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">,</span> <span class="n">modifier</span><span class="p">:</span> <span class="nc">Modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">checked</span> <span class="k">by</span> <span class="nf">remember</span> <span class="p">{</span> <span class="nf">mutableStateOf</span><span class="p">(</span><span class="k">false</span><span class="p">)</span> <span class="p">}</span>

    <span class="nc">Row</span><span class="p">(</span><span class="n">modifier</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
        <span class="nc">Switch</span><span class="p">(</span>
            <span class="n">checked</span> <span class="p">=</span> <span class="n">checked</span><span class="p">,</span>
            <span class="n">onCheckedChange</span> <span class="p">=</span> <span class="p">{</span>
                <span class="n">checked</span> <span class="p">=</span> <span class="p">!</span><span class="n">checked</span>
                <span class="n">person</span><span class="p">.</span><span class="n">name</span> <span class="p">=</span> <span class="s">"wow"</span>
            <span class="p">}</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>스위치를 누르면 <code class="language-plaintext highlighter-rouge">checked</code> 값이 갱신되고 <code class="language-plaintext highlighter-rouge">person</code>의 <code class="language-plaintext highlighter-rouge">name</code>은 <code class="language-plaintext highlighter-rouge">wow</code>가 된다.
이때 State 값인 <code class="language-plaintext highlighter-rouge">checked</code>가 갱신되었기 때문에 리컴포지션이 발생된다.</p>

<p>하지만 <code class="language-plaintext highlighter-rouge">Person</code>의 <code class="language-plaintext highlighter-rouge">name</code>이 변경되었음에도 UI에는 변경된 <code class="language-plaintext highlighter-rouge">name</code>이 표시되지 않는다. LayoutInspector를 살펴보면 <code class="language-plaintext highlighter-rouge">PersonDetail</code>은 리컴포지션이 생략된 것을 볼 수 있다.
그 이유는 <code class="language-plaintext highlighter-rouge">Person</code> 객체가 <code class="language-plaintext highlighter-rouge">@Stable</code>로 마킹되었으나, equals를 동일한 인스턴스로 수행했기 때문이다. 실제로 equals를 오버라이딩하여 출력하면 동일한 <code class="language-plaintext highlighter-rouge">name</code>을 갖고 있다.(의문이 드는 부분은 equals를 항상 false만 반환하도록 오버라이딩 하여도 바뀐 <code class="language-plaintext highlighter-rouge">name</code>이 UI에 보이지 않는다… 왜일까? equals 말고도 다른 연산이 수행되나? hashCode로 비교를 수행하나? 내부를 더 확인해봐야겠다)</p>

<p>이는 아래와 같이 <code class="language-plaintext highlighter-rouge">mutableStateOf</code>를 사용하여 <code class="language-plaintext highlighter-rouge">Person</code>을 안정적으로 만들어 해결할 수 있다.
<code class="language-plaintext highlighter-rouge">name</code> 자체를 관찰 가능한 값으로 만들어 Text에서 변경을 알아 낼 수 있도록 하는 것이다.</p>

<p><em>(2023/12/11 내용 수정됨)</em></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Stable</span>
<span class="kd">class</span> <span class="nc">Person</span><span class="p">(</span>
    <span class="n">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
    <span class="kd">val</span> <span class="py">address</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span> <span class="k">by</span> <span class="nf">mutableStateOf</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="람다-함수">람다 함수</h1>

<p>람다 함수는 다음으로 나눠진다.</p>

<ul>
  <li>값을 캡쳐하지 않는 람다 함수</li>
  <li>안정적인 값을 캡쳐하는 람다 함수</li>
  <li>안정적이지 않은 값을 캡쳐하는 람다 함수</li>
</ul>

<p>값을 캡쳐하지 않는 람다 함수의 경우는 아래와 같다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">foo</span> <span class="p">=</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"bye, world!"</span><span class="p">)</span> <span class="p">}</span>
</code></pre></div></div>

<p>이는 컴파일 후 아래와 같이 싱글톤으로 만들어진다. 아래의 싱글톤 클래스는 안정성 조건을 모두 만족하므로 stable하다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Foo</span> <span class="p">:</span> <span class="nc">Function0</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">invoke</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"bye, world!"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">val</span> <span class="py">fooInstance</span> <span class="p">=</span> <span class="nc">Foo</span><span class="p">()</span>
</code></pre></div></div>

<p>안정적인 값을 캡쳐하는 람다 함수의 경우는 아래와 같다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">greeting</span> <span class="p">=</span> <span class="s">"Hello"</span>
<span class="kd">val</span> <span class="py">bar</span> <span class="p">=</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="s">"$greeting world!"</span><span class="p">)</span> <span class="p">}</span>
</code></pre></div></div>

<p>이는 컴파일 후 아래와 같은 클래스로 변형된다. 안정성 조건을 모두 만족하므로 stable하다.
싱글톤이 아닌 경우 컴포즈 컴파일러에 의해 <code class="language-plaintext highlighter-rouge">remember</code>를 이용하여 항상 인스턴스를 생성하지 않도록 최적화된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">greeting</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">:</span> <span class="nc">Function0</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">invoke</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">println</span><span class="p">(</span><span class="s">"$greeting world!"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>아래처럼 안정적이지 않은 값을 캡쳐하는 람다 함수의 경우는 안정성 3번 조건을 만족하지 않기 때문에 unstable하다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 컴파일 전</span>
<span class="kd">val</span> <span class="py">list</span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span><span class="s">"hi"</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">bar</span> <span class="p">=</span> <span class="p">{</span> <span class="n">list</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span>

<span class="c1">// 컴파일 후</span>
<span class="kd">class</span> <span class="nc">Bar</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">list</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">&gt;)</span> <span class="p">:</span> <span class="nc">Function0</span><span class="p">&lt;</span><span class="nc">Unit</span><span class="p">&gt;</span> <span class="p">{</span>
    <span class="k">override</span> <span class="k">operator</span> <span class="k">fun</span> <span class="nf">invoke</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>그런데 아래와 같이 안정적이지 않은 클래스의 함수 레퍼런스를 사용하는 경우는 함수 레퍼런스가 안정적으로 취급되기 때문에 리컴포지션을 막을 수 있다.
정확하지는 않지만 함수 레퍼런스는 리플랙션을 이용하여 고정된 함수 위치를 반환하기 때문이 아닐까 추측한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">Content</span><span class="p">(</span><span class="n">unstableClass</span><span class="p">:</span> <span class="nc">UnstableClass</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">LambdaComposable</span><span class="p">(</span><span class="n">unstableClass</span><span class="o">::</span><span class="n">doSomthing</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="성능-문제가-발생했을-때-디버깅하는-법">성능 문제가 발생했을 때 디버깅하는 법</h1>

<p>먼저 어디서 리컴포지션이 빈번하게 발생하는지 확인해야 한다. 이는 <a href="https://developer.android.com/jetpack/compose/tooling/layout-inspector">Layout Inspector</a>를 이용하면 된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Immutable</span> <span class="c1">// WARNING to use!</span>
<span class="kd">data class</span> <span class="nc">Person</span><span class="p">(</span>
    <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
    <span class="kd">val</span> <span class="py">address</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
    <span class="kd">val</span> <span class="py">someList</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">&gt;</span>
<span class="p">)</span>

<span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">Text</span><span class="p">(</span><span class="n">text</span> <span class="p">=</span> <span class="n">person</span><span class="p">.</span><span class="n">name</span><span class="p">)</span>
<span class="p">}</span>

<span class="nd">@Composable</span>
<span class="k">fun</span> <span class="nf">PersonDetailRow</span><span class="p">(</span><span class="n">person</span><span class="p">:</span> <span class="nc">Person</span><span class="p">,</span> <span class="n">modifier</span><span class="p">:</span> <span class="nc">Modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">checked</span> <span class="k">by</span> <span class="nf">remember</span> <span class="p">{</span> <span class="nf">mutableStateOf</span><span class="p">(</span><span class="k">false</span><span class="p">)</span> <span class="p">}</span>

    <span class="nc">Row</span><span class="p">(</span><span class="n">modifier</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
        <span class="nc">Switch</span><span class="p">(</span>
            <span class="n">checked</span> <span class="p">=</span> <span class="n">checked</span><span class="p">,</span>
            <span class="n">onCheckedChange</span> <span class="p">=</span> <span class="p">{</span> <span class="n">checked</span> <span class="p">=</span> <span class="p">!</span><span class="n">checked</span> <span class="p">}</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 예제 코드를 보면 스위치를 토글하였을 때 <code class="language-plaintext highlighter-rouge">PersonDetail</code>는 리컴포지션이 발생하지 않는다. 하지만 <code class="language-plaintext highlighter-rouge">@Immutable</code>을 제거하면 리컴포지션이 발생한다.
그 이유는 <code class="language-plaintext highlighter-rouge">MutableList</code>가 <code class="language-plaintext highlighter-rouge">List</code>를 구현해서 <code class="language-plaintext highlighter-rouge">List</code>는 unstable하기 때문이다.</p>

<blockquote>
  <p>For example, you could write <code class="language-plaintext highlighter-rouge">val set: Set&lt;String&gt; = mutableSetOf("foo")</code>. The variable is constant and its declared type is not mutable, but its implementation is still mutable. The Compose compiler cannot be sure of the immutability of this class as it only sees the declared type. It therefore marks tags as unstable.</p>
</blockquote>

<p><strong>위 예제는 “<code class="language-plaintext highlighter-rouge">@Stable</code>, <code class="language-plaintext highlighter-rouge">@Immutable</code>을 잘못 사용하면?”처럼 문제가 있기 때문에 사용에 주의할 필요가 있다.</strong></p>

<p>때문에 위 방법보다는 아래처럼 wrapper 방식 혹은 <a href="https://github.com/Kotlin/kotlinx.collections.immutable">ImmutableCollection</a>을 사용하는 것을 권장한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Immutable</span>
<span class="kd">data class</span> <span class="nc">SnackCollection</span><span class="p">(</span>
   <span class="kd">val</span> <span class="py">snacks</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">Snack</span><span class="p">&gt;</span>
<span class="p">)</span>
</code></pre></div></div>

<h1 id="참조">참조</h1>

<ul>
  <li><a href="https://developer.android.com/jetpack/compose/performance/stability">https://developer.android.com/jetpack/compose/performance/stability</a></li>
  <li><a href="https://developer.android.com/jetpack/compose/performance/stability/diagnose">https://developer.android.com/jetpack/compose/performance/stability/diagnose</a></li>
  <li><a href="https://developer.android.com/jetpack/compose/performance/stability/fix">https://developer.android.com/jetpack/compose/performance/stability/fix</a></li>
  <li><a href="https://sungbin.land/jetpack-compose-람다-최적화에-대한-고찰-b8854e38067a">https://sungbin.land/jetpack-compose-람다-최적화에-대한-고찰-b8854e38067a</a></li>
</ul>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="android" /><category term="android" /><category term="compose" /><summary type="html"><![CDATA[Jetpack Compose에는 안정성 시스템이 존재한다. 이는 리컴포지션을 생략 가능한지 판단할 때 사용된다. 리컴포지션이 발생하여 컴포저블 함수의 스냅샷 상태가 변경되었다면 해당 컴포저블은 리컴포지션이 필요하다. 만약 변경되지 않았다면 불필요하게 리컴포지션을 진행할 필요가 없다. 타입이 안정적이지 않다면 항상 리컴포지션을 진행한다.]]></summary></entry><entry><title type="html">LRU cache 구현</title><link href="https://jja08111.github.io/algorithm/lru-cache/" rel="alternate" type="text/html" title="LRU cache 구현" /><published>2023-06-29T21:00:00+09:00</published><updated>2023-06-29T21:00:00+09:00</updated><id>https://jja08111.github.io/algorithm/lru-cache</id><content type="html" xml:base="https://jja08111.github.io/algorithm/lru-cache/"><![CDATA[<p>페이지 교체 알고리즘에 사용되는 LRU 알고리즘을 double linked list 방식으로 구현해보겠다.</p>

<p>페이지 교체시 LRU 알고리즘은 가장 오래전에 참조한 페이지를 희생 페이지로 선택한다.
이를 구현하기 위해서는 여러 방법이 있겠으나 필자는 아래와 같은 방법으로 구현할 것이다.</p>

<ul>
  <li>get 연산: 데이터를 참조하는 연산이다. 참조된 데이터는 가장 최근에 참조되었다고 표시해야한다.</li>
  <li>put 연산: 데이터를 삽입하는 연산이다. 새로 삽입한 데이터는 가장 최근에 참조되었다고 표시해야한다.</li>
</ul>

<h1 id="linkedhashmap">LinkedHashMap</h1>

<p>이를 구현하기 위해서 어떤 자료구조가 적절할까? 바로 <code class="language-plaintext highlighter-rouge">LinkedHashMap</code>이다.
<code class="language-plaintext highlighter-rouge">LinkedHashMap</code>는 입력된 순서대로 key가 보장된다. 또한 get 연산을 수행하면 해당 노드를 가장 후미에 위치시킨다.
실제로 <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/LinkedHashMap.java#L300">jdk7u-jdk에서 LinkedHashMap 코드</a>를 보면 아래와 같이 get 연산 수행 후 <code class="language-plaintext highlighter-rouge">e.recordAccess(this)</code>를 호출한다. 안드로이드 SDK에서는 구체적인 구현사항은 조금 다를 뿐 알고리즘은 동일하다.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="nc">Object</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">Entry</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">e</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Entry</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span><span class="no">V</span><span class="o">&gt;)</span><span class="n">getEntry</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>
        <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
    <span class="n">e</span><span class="o">.</span><span class="na">recordAccess</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">e</span><span class="o">.</span><span class="na">value</span><span class="o">;</span>
<span class="o">}</span>

<span class="c1">// ...</span>

<span class="kt">void</span> <span class="nf">recordAccess</span><span class="o">(</span><span class="nc">HashMap</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">m</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">LinkedHashMap</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">lm</span> <span class="o">=</span> <span class="o">(</span><span class="nc">LinkedHashMap</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span><span class="no">V</span><span class="o">&gt;)</span><span class="n">m</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">lm</span><span class="o">.</span><span class="na">accessOrder</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">lm</span><span class="o">.</span><span class="na">modCount</span><span class="o">++;</span>
        <span class="n">remove</span><span class="o">();</span>
        <span class="n">addBefore</span><span class="o">(</span><span class="n">lm</span><span class="o">.</span><span class="na">header</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="구현">구현</h1>

<p>먼저 선언부이다. 최대 크기를 가져야 하기 때문에 <code class="language-plaintext highlighter-rouge">maxSize</code>가 존재하고, 현재 데이터들의 크기를 상수 시간만에 얻기 위해 <code class="language-plaintext highlighter-rouge">size</code>가 있으며, key, value 데이터를 저장하기 위해 <code class="language-plaintext highlighter-rouge">LinkedHashMap</code>이 있다.
이때 꼭 <code class="language-plaintext highlighter-rouge">LinkedHashMap</code>의 <code class="language-plaintext highlighter-rouge">accessOrder</code> 인자로 <code class="language-plaintext highlighter-rouge">true</code>를 전달해야한다. 그래야 순서가 보장된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">open</span> <span class="kd">class</span> <span class="nc">LruCache</span><span class="p">&lt;</span><span class="nc">K</span><span class="p">,</span> <span class="nc">V</span><span class="p">&gt;(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">maxSize</span><span class="p">:</span> <span class="nc">Int</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">size</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">map</span><span class="p">:</span> <span class="nc">LinkedHashMap</span><span class="p">&lt;</span><span class="nc">K</span><span class="p">,</span> <span class="nc">V</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">LinkedHashMap</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mf">0.75f</span><span class="p">,</span> <span class="k">true</span><span class="p">)</span>

    <span class="nf">init</span> <span class="p">{</span>
        <span class="nf">require</span><span class="p">(</span><span class="n">maxSize</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>get 연산은 <code class="language-plaintext highlighter-rouge">LinkedHashMap</code>의 도움을 받아 간단히 구현할 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">get</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">):</span> <span class="nc">V</span><span class="p">?</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">map</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>put 연산은 고민이 필요하다. 데이터를 추가하였을 때 최대 크기를 초과하는 경우 최대 크기를 넘지 않을 때까지 가장 참조한지 오래된 데이터를 제거해야한다.
이는 <code class="language-plaintext highlighter-rouge">trimToSize</code>를 구현하여 해결할 수 있다. <code class="language-plaintext highlighter-rouge">trimToSize</code>는 <code class="language-plaintext highlighter-rouge">map</code>에서 가장 앞쪽의 데이터를 가장 참조한지 오래된 데이터로 취급하여 제거한다.
그 이유는 앞서 설명한대로 특정 데이터는 get 혹은 put 연산 후 리스트의 가장 후미에 위치되기 때문이다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">put</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">previous</span> <span class="p">=</span> <span class="n">map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">previous</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">size</span> <span class="p">-=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">previous</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="n">size</span> <span class="p">+=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
    <span class="nf">trimToSize</span><span class="p">(</span><span class="n">maxSize</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">private</span> <span class="k">fun</span> <span class="nf">trimToSize</span><span class="p">(</span><span class="n">maxSize</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">check</span><span class="p">(</span><span class="n">size</span> <span class="p">&gt;=</span> <span class="mi">0</span><span class="p">)</span>
        <span class="nf">check</span><span class="p">(!(</span><span class="n">map</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">()</span> <span class="p">&amp;&amp;</span> <span class="n">size</span> <span class="p">!=</span> <span class="mi">0</span><span class="p">))</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="p">&lt;=</span> <span class="n">maxSize</span> <span class="p">||</span> <span class="n">map</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">())</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>

        <span class="kd">val</span> <span class="py">iterator</span> <span class="p">=</span> <span class="n">map</span><span class="p">.</span><span class="nf">iterator</span><span class="p">().</span><span class="nf">next</span><span class="p">()</span>
        <span class="kd">val</span> <span class="py">key</span> <span class="p">=</span> <span class="n">iterator</span><span class="p">.</span><span class="n">key</span>
        <span class="kd">val</span> <span class="py">value</span> <span class="p">=</span> <span class="n">iterator</span><span class="p">.</span><span class="n">value</span>
        <span class="kd">val</span> <span class="py">sizeOfKey</span> <span class="p">=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
        <span class="n">size</span> <span class="p">-=</span> <span class="n">sizeOfKey</span>
        <span class="n">map</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 코드를 보면 <code class="language-plaintext highlighter-rouge">safeSizeOf</code> 함수를 볼 수 있는데, 이는 특정 key, value를 가진 데이터의 크기를 개발자가 특정할 수 있도록 도와준다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">fun</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">sizeOfKey</span> <span class="p">=</span> <span class="nf">sizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
    <span class="nf">check</span><span class="p">(</span><span class="n">sizeOfKey</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">sizeOfKey</span>
<span class="p">}</span>

<span class="k">open</span> <span class="k">fun</span> <span class="nf">sizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span>
    <span class="k">return</span> <span class="mi">1</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 코드를 모아 아래와 같은 클래스를 만들 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">open</span> <span class="kd">class</span> <span class="nc">LruCache</span><span class="p">&lt;</span><span class="nc">K</span><span class="p">,</span> <span class="nc">V</span><span class="p">&gt;(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">maxSize</span><span class="p">:</span> <span class="nc">Int</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">var</span> <span class="py">size</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">map</span><span class="p">:</span> <span class="nc">LinkedHashMap</span><span class="p">&lt;</span><span class="nc">K</span><span class="p">,</span> <span class="nc">V</span><span class="p">&gt;</span> <span class="p">=</span> <span class="nc">LinkedHashMap</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mf">0.75f</span><span class="p">,</span> <span class="k">true</span><span class="p">)</span>

    <span class="nf">init</span> <span class="p">{</span>
        <span class="nf">require</span><span class="p">(</span><span class="n">maxSize</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">get</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">):</span> <span class="nc">V</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">map</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">put</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">previous</span> <span class="p">=</span> <span class="n">map</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">previous</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">size</span> <span class="p">-=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">previous</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="n">size</span> <span class="p">+=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
        <span class="nf">trimToSize</span><span class="p">(</span><span class="n">maxSize</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">trimToSize</span><span class="p">(</span><span class="n">maxSize</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">while</span> <span class="p">(</span><span class="k">true</span><span class="p">)</span> <span class="p">{</span>
            <span class="nf">check</span><span class="p">(</span><span class="n">size</span> <span class="p">&gt;=</span> <span class="mi">0</span><span class="p">)</span>
            <span class="nf">check</span><span class="p">(!(</span><span class="n">map</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">()</span> <span class="p">&amp;&amp;</span> <span class="n">size</span> <span class="p">!=</span> <span class="mi">0</span><span class="p">))</span>

            <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="p">&lt;=</span> <span class="n">maxSize</span> <span class="p">||</span> <span class="n">map</span><span class="p">.</span><span class="nf">isEmpty</span><span class="p">())</span> <span class="p">{</span>
                <span class="k">return</span>
            <span class="p">}</span>

            <span class="kd">val</span> <span class="py">iterator</span> <span class="p">=</span> <span class="n">map</span><span class="p">.</span><span class="nf">iterator</span><span class="p">().</span><span class="nf">next</span><span class="p">()</span>
            <span class="kd">val</span> <span class="py">key</span> <span class="p">=</span> <span class="n">iterator</span><span class="p">.</span><span class="n">key</span>
            <span class="kd">val</span> <span class="py">value</span> <span class="p">=</span> <span class="n">iterator</span><span class="p">.</span><span class="n">value</span>
            <span class="kd">val</span> <span class="py">sizeOfKey</span> <span class="p">=</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
            <span class="n">size</span> <span class="p">-=</span> <span class="n">sizeOfKey</span>
            <span class="n">map</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">safeSizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">sizeOfKey</span> <span class="p">=</span> <span class="nf">sizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
        <span class="nf">check</span><span class="p">(</span><span class="n">sizeOfKey</span> <span class="p">&gt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">sizeOfKey</span>
    <span class="p">}</span>

    <span class="k">open</span> <span class="k">fun</span> <span class="nf">sizeOf</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nc">K</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nc">V</span><span class="p">):</span> <span class="nc">Int</span> <span class="p">{</span>
        <span class="k">return</span> <span class="mi">1</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>코드를 테스트해보자. 최대 크기 3인 캐시에 a, b, c, d를 순차적으로 넣으니 a가 가장 오래전에 참조되었기 때문에 삭제된 모습이다.
그 후 b를 참조하고 e를 넣으면 캐시에는 가장 오래전에 참조된 c가 삭제된다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">val</span> <span class="py">cache</span> <span class="p">=</span> <span class="nc">LruCache</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Int</span><span class="p">&gt;(</span><span class="mi">3</span><span class="p">)</span>

    <span class="n">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"a"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"b"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="n">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"c"</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
    <span class="n">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"d"</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>

    <span class="nf">println</span><span class="p">(</span><span class="n">cache</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"a"</span><span class="p">))</span> <span class="c1">// null</span>

    <span class="n">cache</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"b"</span><span class="p">)</span>
    <span class="n">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"e"</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>

    <span class="nf">println</span><span class="p">(</span><span class="n">cache</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"b"</span><span class="p">))</span> <span class="c1">// 2</span>
    <span class="nf">println</span><span class="p">(</span><span class="n">cache</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"c"</span><span class="p">))</span> <span class="c1">// null</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="활용">활용</h1>

<p>안드로이드에서는 Glide가 이미지 캐시를 위해 이 알고리즘을 사용한다고 한다. 앱에서 그리드 뷰나 리스트 뷰와 같은 곳에서 이미지가 보일 때 해당 이미지는 다시 로드될 가능성이 높다.
이때마다 새롭게 비트맵을 로드하기 보다는 앞서 설명한 LRU cache를 사용한다면 더 빠르게 이미지를 로드할 수 있다.</p>

<p>절대적인 캐시 크기는 없으며 앱마다 <a href="https://developer.android.com/topic/performance/graphics/cache-bitmap?hl=ko">아래와 같은 사항</a>을 고민하여 캐시 크기를 결정해야한다.</p>

<ul>
  <li>액티비티 및 애플리케이션의 나머지 부분이 얼마나 많은 메모리를 사용하나?</li>
  <li>한 번에 몇 개의 이미지가 화면에 표시되나? 화면에 표시할 준비가 되어야 할 이미지 수는 몇 개인가?</li>
  <li>기기의 화면 크기와 밀도는 어떻게 되나?</li>
  <li>비트맵의 크기와 구성은 어떻게 되며 그에 따른 각각의 메모리 사용량은 어떻게 되나?</li>
  <li>이미지는 얼마나 자주 액세스되나? 다른 이미지보다 더 자주 액세스되는 이미지가 있나? 그런 경우 특정 항목을 항상 메모리에 두거나 서로 다른 그룹의 비트맵에 여러 개의 LruCache 객체를 둘 수 있다.</li>
  <li>품질과 수량의 균형을 맞출 수 있나? 때로 더 낮은 품질의 비트맵을 다수 저장하여 잠재적으로 다른 백그라운드 작업에 더 높은 품질 버전을 로드할 수 있도록 하는 것이 더 유용할 수 있다.</li>
</ul>

<p>캐시가 너무 작으면 아무런 이점 없이 오버헤드만 발생하고, 반대로 캐시가 너무 크면 <code class="language-plaintext highlighter-rouge">OutOfMemory</code> 예외가 발생할 수 있고 앱에 필요한 메모리가 부족할 수 있다.</p>

<h2 id="os에서는">OS에서는?</h2>

<p>운영체제에서는 페이지 교체 알고리즘인 LRU를 구현할 때 double linked list 방식을 사용하지 않는다.
그 이유는 double linked list로 페이지 교체 알고리즘인 LRU를 구현하기 위해서는 TLB 이상의 하드웨어 지원이 있어야 하다. 메모리 참조가 발생할 때마다 node를 이동시키기 위해서 페이징 테이블에 쓰기 연산이 필요하다. 이러한 작업을 소프트웨어로 구현하기 위해 인터럽트를 사용한다면 오버헤드가 상당하다.</p>

<p>따라서 운영체제는 참조 비트를 둔 LRU 근사 알고리즘을 사용한다.
참조 비트는 페이지 테이블에 있는 각 항목과 대응되며 시스템에서 하드웨어 지원을 해준다. 때문에 double linked list 방식에 비해 속도가 빠르다.</p>

<p>대표적으로 Clock 알고리즘이 있다. 참조 비트가 0이면 페이지를 교체하고 1이면 참조 비트를 0으로 수정하고 다음 페이지로 넘어간다. 이를 순환 큐로 구현한다. 최악의 경우 모든 참조 비트가 1인 경우 포인터는 큐를 한 바퀴 돈다.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="algorithm" /><category term="cache" /><category term="lru" /><category term="data_structure" /><summary type="html"><![CDATA[페이지 교체 알고리즘에 사용되는 LRU 알고리즘을 double linked list 방식으로 구현해보겠다.]]></summary></entry><entry><title type="html">Hash에 관하여</title><link href="https://jja08111.github.io/data_structure/datastructure-hash/" rel="alternate" type="text/html" title="Hash에 관하여" /><published>2023-06-17T08:12:00+09:00</published><updated>2023-06-17T08:12:00+09:00</updated><id>https://jja08111.github.io/data_structure/datastructure-hash</id><content type="html" xml:base="https://jja08111.github.io/data_structure/datastructure-hash/"><![CDATA[<blockquote>
  <p>해시(Hash)가 무엇인가요?</p>
</blockquote>

<p>해시란 데이터를 고정된 크기의 고유한 값으로 매핑하는 것을 의미합니다. 임의의 길이를 가진 데이터를 입력하면 고정된 길이의 값을 출력하는 함수를 해시 함수라고 합니다.</p>

<p>해시 함수의 특징은 단방향성입니다. 입력 데이터에서 해시값으로 변환은 쉽지만, 해시값에서 원래 데이터로 역변환은 거의 불가능합니다. 이는 해시 함수가 데이터의 무결성을 보장하고 보안 용도로 활용되는 데에 주요한 특징입니다. 이러한 단방향성을 평가하는 척도 중 하나가 역상 저항성입니다. 역상 저항성이 우수하다는 것은 특정한 값을 출력하는 입력 값을 찾기 어려움을 의미합니다.</p>

<p>해시를 활용한 HashMap은 검색을 수행할 때 O(1)만에 동작하여 굉장히 빠릅니다.</p>

<p>다른 입력 값이 동일한 출력을 보이는 경우 해시 충돌이라고 합니다.</p>

<blockquote>
  <p>좋은 해시 함수는 어떤 함수일까요?</p>
</blockquote>

<p>좋은 해시 함수는 단방향성, 고유성, 일관성, 고속성이 뛰어난 해시 함수입니다. 출력 값으로 입력 값을 역추적하기 어려워야 하고, 입력마다 최대한 고유한 출력을 보여야 하고, 동일한 입력에는 동일한 출력을 보여야 합니다. 그리고 연산 속도가 빨라야 합니다.</p>

<blockquote>
  <p>해시 충돌을 어떻게 해결할까요?</p>
</blockquote>

<p>해시 충돌을 해결하는 방법은 크게 두 가지로 분류됩니다.</p>

<p>첫 번째는 개방 주소법(Open Addressing)입니다. 이 방법은 충돌이 발생했을 때 다른 해시 버킷에 데이터를 넣는 방식입니다. 단순한 방식으로는 선형 탐사 방법이 있습니다. 한 버킷이 이미 사용중인 경우 다음 인덱스의 버킷을 확인하는 방식입니다. 이 방식은 primary clustering 문제가 있습니다. 이 문제는 충돌이 한 번 발생하면 계속하여 충돌이 발생하는 것을 의미합니다. 이로 인하여 평균 검색 시간은 느려집니다.
쿼드라틱 프로빙 방식은 선형 프로빙보다 충돌이 적습니다. 이는 i^2 만큼 버킷을 건너 뛰며 탐사를 하는 방식입니다.</p>

<p>두 번째는 체이닝(Seperate Channing) 방법입니다. 충돌이 발생한 버킷의 연결리스트에 데이터를 저장하는 방식입니다. 이 방식은 최악의 경우 수행 시간이 O(N)이 될 수 있습니다. 이는 연결리스트 대신 트리를 두어 시간 복잡도를 O(logN)으로 개선할 수 있습니다.</p>

<p>배열의 크기가 작을 때는 개방 주소법이 캐시 효율이 더 높습니다. 그 이유는 충돌이 발생할 때 체이닝 방식이 별도의 공간에 데이터를 저장하는 반면, 개방 주소법은 연속된 공간에 저장하기 때문입니다. 그런데 배열의 크기가 커지는 경우 캐시 히트율이 감소하기 때문에 개방 주소법의 장점은 사라지게 됩니다.</p>

<blockquote>
  <p>자바의 HashMap에서는 어떤 방법으로 충돌을 해결하나요?</p>
</blockquote>

<p>체이닝 방식을 사용합니다. 그 이유는 개방 주소법에서는 삭제 연산을 효율적으로 구현하기 어려운데, remove 함수는 매우 빈번하게 호출될 수 있기 때문입니다. 또한 데이터의 갯수가 일정 갯수를 넘어가면 개방 주소법은 Worst Case 발생 빈도가 높습니다. 반면 체이닝 방식은 Worst Case 또는 Worst Case에 가까운 일이 발생하는 것을 줄일 수 있는 방법이 있습니다.</p>

<p>HashMap은 체이닝 방식에서 트리와 연결리스트 모두 사용합니다. 데이터 삽입 후 데이터가 8개 이상이면 트리로 변환하며, 데이터 삭제 후 데이터가 6개 이하이면 연결리스트로 변환합니다.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="data_structure" /><category term="hash" /><category term="data_structure" /><summary type="html"><![CDATA[해시(Hash)가 무엇인가요?]]></summary></entry><entry><title type="html">Google Korea Internship 지원 후기</title><link href="https://jja08111.github.io/interview/google-korea-internship-application/" rel="alternate" type="text/html" title="Google Korea Internship 지원 후기" /><published>2023-04-19T22:12:00+09:00</published><updated>2023-04-19T22:12:00+09:00</updated><id>https://jja08111.github.io/interview/google-korea-internship-application</id><content type="html" xml:base="https://jja08111.github.io/interview/google-korea-internship-application/"><![CDATA[<p>2023 구글 코리아 인턴십에 지원했고 코딩테스트를 거쳐 면접을 진행했다.</p>

<h1 id="지원">지원</h1>

<p>올해 1월, 구글에서 대학생을 대상으로 인턴십을 진행한다고 공고가 올라왔다. 나는 코딩테스트와 면접 경험을 쌓기 위해서 바로 지원했다.
영문 이력서가 필요했고 이력서에는 내가 진행한 프로젝트들을 간결히 작성하여 제출했다.</p>

<h1 id="코딩테스트">코딩테스트</h1>

<p>2월 말, 구글에서 코딩테스트 이메일이 왔다. Google online challange라고 메일이 왔고 특정한 날에 주어진 문제를 풀면 된다고 했다.</p>

<p>테스트를 보며 기억에 남는 점은 IDE를 사용할 수 없고, 외부 코드 복사가 안되며, 브라우저를 벗어나서 작업을 수행하면 경고를 띄운다는 것이었다.
경고가 누적되면 테스트가 종료된다고 했던 것 같다. 제한시간은 1시간이었으며 2문제가 나왔다. 문제 난이도는 높지 않았으나 힘겹게 2문제를 풀고 제출했다.</p>

<h1 id="면접">면접</h1>

<p>3월 말, 구글 리크루터분에게 이메일을 받았다. 면접을 진행할 예정이니 가능한 시간을 입력하라는 메일이었다.
그 후 인터뷰는 어떤식으로 진행되는지 설명받았다. 2번의 라운드가 진행되며 하나당 45분이 소요된다고 안내받었다. 두 라운드 모두 같은날에 진행했다.
그리고 구글 면접에 어떻게 준비하면 좋을지가 적혀있는 안내 설명서도 함께 받았다.</p>

<p>첫 번째 면접은 한국 면접관님이 진행해주셨다. 문제는 어렵지 않았다. 하지만 나는 초반에 시간복잡도가 정확히 생각나지 않아 횡설수설했다.
문제는 정확히 이해했어서 문제를 풀기 시작했다. 면접관님께 계속해서 나의 생각을 전달하며 문제를 해결했다. 코드를 작성을 완료하니 “그렇다면 ~한 경우는 어떻게 해야할까요?”식으로 문제를 변경하셨다.
그에 맞게 코드를 수정했고 시간복잡도와 공간복잡도를 설명하였다. 그 후 면접관님께서 다른 방법으로 해결할 수 있냐는 질문에 잘 모르겠다고 대답했다.
그러자 힌트를 주셨고 “~하게 해결할 수 있겠네요”라고 대답했다. 그리고 내가 생각해낸 방법과 어떤 차이가 있는지 비교했다. 그렇게 질의응답을 거치고 마무리 되었다.</p>

<p>두 번째 면접은 <strong>영어로 진행하는 면접</strong>이었다. 초반에는 간단한 자기소개를 했고 그 후 문제 풀이를 시작했다. 문제를 구글 독스에 붙여넣기 해주셨어서 리스닝이 부족했던 나에게는 다행이었다.
문제는 쉽게 이해할 수 있었다. 하지만 영어로 진행했기 때문에 소통이 잘 안됐다. 내가 코드를 작성하면서 면접관 분이 질문을 던지셨는데 이해하지 못했다.
면접관님께서 중요한 부분은 아니라며 넘어가셨다. 그렇게 문제가 3번정도 바뀌면서 문제를 풀었다. 마지막에 시간복잡도와 공간복잡도를 물어보셨다.
시간복잡도는 맞았던것 같으나 공간복잡도는 틀렸다.</p>

<p>한국어로 진행한 면접은 수월했던 것 같은데 영어 면접이 문제였다. 영어 면접에서 의사소통이 꽤 안되었기 때문에 결과는 기대하기 어려울 것 같다.</p>

<h1 id="결과">결과</h1>

<p>탈락</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="interview" /><category term="google_korea" /><category term="google" /><category term="internship" /><category term="interview" /><summary type="html"><![CDATA[2023 구글 코리아 인턴십에 지원했고 코딩테스트를 거쳐 면접을 진행했다.]]></summary></entry><entry><title type="html">[Naver Deview] Kotlin Multiplatform 적용기 요약</title><link href="https://jja08111.github.io/kotlin/naver-deview-kotlin-multiplaform/" rel="alternate" type="text/html" title="[Naver Deview] Kotlin Multiplatform 적용기 요약" /><published>2023-03-22T10:12:00+09:00</published><updated>2023-03-22T10:12:00+09:00</updated><id>https://jja08111.github.io/kotlin/naver-deview-kotlin-multiplaform</id><content type="html" xml:base="https://jja08111.github.io/kotlin/naver-deview-kotlin-multiplaform/"><![CDATA[<p>졸업 작품에 적용할까 말까 고민했던 코들린 멀티 플랫폼의 적용기를 다룬 <a href="https://www.youtube.com/watch?v=B27Yu9uQvqY">네이버 deview 영상</a>이 올라와서 시청하였다.
주요 부분을 요약하여 정리하고자 한다.</p>

<h1 id="multiplatform을-고려하게된-이유">Multiplatform을 고려하게된 이유</h1>

<p>개발자들이 흔히 Multiplatform(크로스 플랫폼이라고도 불리우는 그것)을 환상이라고 표현한다. 다양한 플랫폼에서 동작하는 앱들을 하나의 코드 베이스로 구현하여 개발 속도를 빠르게 하고 유지보수 하기 편리한 그런 세상이다.
하지만 현실을 녹록치 않다. 네이티브 기능을 지원하지 않는 경우가 꽤 있고…</p>

<p>네이버에서는 60개의 서비스가 PRISM Player를 사용한다고 한다. 서비스가 많아질수록, 스펙이 많아질수록, 지원할 플랫폼이 많아질수록 개발자는 더 필요하다.
한 명의 개발자만 필요한 것이 아니라 플랫폼 개수만큼 개발자가 필요하다.</p>

<p>개발자가 많아지면 각 플랫폼의 개발자마다의 구현 능력이 다르다. 결과적으로 개발자가 많아지면 커뮤니케이션이 증가하고 이슈는 플랫폼 별로 나오는 힘든 부분들이 생긴다.
결국 개발자가 많아지는 것만이 좋은 해결책은 아니다.</p>

<p>그래서 네이버에서는 다양한 플랫폼과 디바이스를 지원하는 동영상 서비스를 위해 멀티 플랫폼을 적용했다고 한다.</p>

<h1 id="왜-kotlin-multiplatform인가">왜 Kotlin Multiplatform인가?</h1>

<p>초반에는 안드로이드 개발자들의 익숙함을 이유로 Kotlin Multiplatform를 생각했다고 한다.
그리고 Android, iOS, Web, Google cast, TV 등을 잘 지원하기 때문에 채택하였다고 한다.</p>

<p>그리고 Android Studio에서 대부분의 개발들이 잘 되었다고 한다.</p>

<p>또한 코틀린은 Kotlin/JVM 외에도 Kotlin/JS, Kotlin/Native를 지원한다. 따라서 멀티 플랫폼에 너무 의존하지 않기 때문에 실무에서도 적용 할 수 있었다고 한다.</p>

<h1 id="적용">적용</h1>

<h2 id="기존-프로젝트를-마이그레이션">기존 프로젝트를 마이그레이션</h2>

<ul>
  <li>1안. 기존 프로젝트의 기능 일부를 Kotlin Multiplatform으로 전환
    <ul>
      <li>리스크 적음</li>
      <li>플랫폼 별로 다른 설계 및 스펙으로 이를 맞추는데 상당한 리소스 소요</li>
    </ul>
  </li>
  <li>2안(채택). 신규 Kotlin Multiplatform 프로젝트에 기존 기능 도입
    <ul>
      <li>차라리 각 플랫폼의 노하우를 취합해 신규 설계하는 것이 나음</li>
      <li>호환성 이슈로 개선하지 못했던 API 개선 가능</li>
    </ul>
  </li>
</ul>

<p>이미 Kotlin으로 작성된 Android 코드를 많이 참고했다고 한다. 그리고 공유 코드를 극대화하기 위해 pure Kotlin을 많이 사용했다고 한다. 어쩔수 없는 경우는 아래와 같이 <code class="language-plaintext highlighter-rouge">expect/actual</code> 키워드를 활용하여 각 플랫폼 별로 코드를 작성했다고 한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// commonMain</span>
<span class="n">expect</span> <span class="k">fun</span> <span class="nf">getPlatform</span><span class="p">():</span> <span class="nc">String</span>

<span class="c1">// androidMain</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">getPlatform</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="s">"Android ${Build.VERSION.RELEASE}"</span>

<span class="c1">// iosMain</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">getPlatform</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="nc">UIDevice</span><span class="p">.</span><span class="n">currentDevice</span><span class="p">.</span><span class="nf">run</span> <span class="p">{</span> <span class="s">"$systemName $systemVersion"</span> <span class="p">}</span>

<span class="c1">// jsMain</span>
<span class="n">actual</span> <span class="k">fun</span> <span class="nf">getPlatform</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="n">window</span><span class="p">.</span><span class="n">navigator</span><span class="p">.</span><span class="n">userAgent</span><span class="p">.</span><span class="nf">toPlatform</span><span class="p">()</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nc">String</span><span class="p">.</span><span class="nf">toPlatform</span><span class="p">():</span> <span class="nc">String</span> <span class="p">=</span> <span class="o">..</span><span class="p">.</span>
</code></pre></div></div>

<h2 id="kotlinjs">Kotlin/JS</h2>

<p>Kotlin/JS 덕분에 여러 웹 브라우저는 물론이고 TV, TIZEN, Google Cast 플랫폼으로 확장할 수 있다.</p>

<p>external modifier를 통한 Javascript 코드를 접근할 수 있다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">external</span> <span class="kd">interface</span> <span class="nc">Console</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">}</span>

<span class="k">public</span> <span class="k">external</span> <span class="kd">val</span> <span class="py">console</span><span class="p">:</span> <span class="nc">Console</span>
</code></pre></div></div>

<p>그리고 <code class="language-plaintext highlighter-rouge">js</code> 함수로 Javascript 코드를 직접 호출할 수 있다고 한다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">shakaPlayer</span> <span class="p">=</span> <span class="nf">js</span><span class="p">(</span><span class="s">"new shaka.Player(element)"</span><span class="p">)</span>
</code></pre></div></div>

<p>또한 NPM 의존성을 사용할 수 있다. Dukat을 사용하면 자동으로 external 코드를 생성해준다.</p>

<p>제대로 지원되지 않는 문자열 디코딩을 위해 직접 구현했다고 한다. 이는 SDK에서는 라이브러리를 선택할 때 신중해야 했기 때문이라고 한다. 왜냐하면 여러 플랫폼을 지원해야 했고 버전 충돌 등으로 예기치 못한 이슈가 종종 발생하기 때문이다.</p>

<h2 id="테스트-코드">테스트 코드</h2>

<p>하나의 테스트 코드로 <strong>모든 플랫폼에서 테스트</strong>를 진행할 수 있다.</p>

<h2 id="swift가-아닌-objective-c">Swift가 아닌 Objective-C</h2>

<p>Kotlin/Native는 Swift가 아닌 Objective-C로 컴파일된다. sealed class나 default argument를 지원하지 않아 불편하다.</p>

<p>네이버는 sealed class를 IceRock의 MOKO KSwift 플러그인의 도움을 받았다고 한다.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="kotlin" /><category term="kotlin" /><category term="kotlin_multiplatform" /><summary type="html"><![CDATA[졸업 작품에 적용할까 말까 고민했던 코들린 멀티 플랫폼의 적용기를 다룬 네이버 deview 영상이 올라와서 시청하였다. 주요 부분을 요약하여 정리하고자 한다.]]></summary></entry><entry><title type="html">작업 단위에 관하여</title><link href="https://jja08111.github.io/collaboration/about-task-size/" rel="alternate" type="text/html" title="작업 단위에 관하여" /><published>2023-02-08T12:12:00+09:00</published><updated>2023-02-08T12:12:00+09:00</updated><id>https://jja08111.github.io/collaboration/about-task-size</id><content type="html" xml:base="https://jja08111.github.io/collaboration/about-task-size/"><![CDATA[<p>요즘 졸업작품을 진행하며 3명이 같이 작업을 하고 있다. 작업 단위를 잘 나누는 것에 대한 중요성을 간과하며 초반을 진행했다.
작업 단위를 왜 잘 나눠야 하는지 생각해보려고 한다.</p>

<p>일단 왜 작업 단위를 잘 나눠야 할까? 아래의 이유들이 존재한다.</p>

<h1 id="코드-병합의-어려움">코드 병합의 어려움</h1>

<p>만약 내가 진행하던 작업의 단위가 너무 크고 꽤 오랜기간 작업을 진행해온 경우 상당한 커밋이 쌓였을 것이다.
그러다 다른 사람의 PR이 main 브런치에 병합되었다고 가정해보자.
내 작업 브런치와 main 브런치와 충돌이 발생한 경우 머지 혹은 리베이스를 진행해야 한다.
이때 모든 커밋마다 충돌을 제거해야 할 수도 있다. 이는 여간 번거로운 일이 아니다.</p>

<p>물론 작업하는 영역을 잘 나눈다면 충돌을 피할 수 있다. 하지만 코드 충돌은 완벽히 피하기 어렵다.</p>

<h1 id="리뷰의-어려움">리뷰의 어려움</h1>

<p>작업의 단위가 엄청 큰 PR을 만든다고 생각해보자.
이렇게 되는 경우 코드의 변경량이 1000줄 이상으로 많아질 것이며 이는 리뷰하기 어렵게 만든다.</p>

<p>그리고 작업의 단위가 큰 경우 보통 여러 이슈를 동시에 해결하는 PR이 되기 마련이다.
때문에 리뷰어는 이 코드가 어느 작업을 처리하는지 계속 생각하며 리뷰를 진행해야 해서 리뷰 몰입도가 떨어진다.</p>

<h1 id="어떻게-작업을-나눌까">어떻게 작업을 나눌까?</h1>

<p>그렇다면 작업을 어떻게 나눠야 할까? 개인적인 생각으로는 하루 혹은 이틀 안에 끝낼 수 있도록 작업을 나누는 게 좋다고 생각한다.
코드 변경량으로 따진다면 500줄 이내이다.</p>

<p>작업을 잘 나누기 위해서는 더욱 중요한 점은 <strong>요구사항 분석을 철저히 진행</strong>하는 것이다.
해당 이슈의 요구사항을 분석한 뒤 구현해야할 항목이 너무 많아보인다면 하위 이슈로 쪼개면 된다.
혹은 너무 작다면 더 큰 단위로 작업을 만들 수 있다.</p>

<p>하나의 브런치는 하나의 이슈를 해결해야한다. 하나의 브런치에서 여러 이슈를 동시에 해결하지 말자.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="collaboration" /><category term="collaboration" /><category term="github" /><category term="jira" /><summary type="html"><![CDATA[요즘 졸업작품을 진행하며 3명이 같이 작업을 하고 있다. 작업 단위를 잘 나누는 것에 대한 중요성을 간과하며 초반을 진행했다. 작업 단위를 왜 잘 나눠야 하는지 생각해보려고 한다.]]></summary></entry><entry><title type="html">CPU 작동원리</title><link href="https://jja08111.github.io/computer_architecture/what-does-cpu-do/" rel="alternate" type="text/html" title="CPU 작동원리" /><published>2023-01-27T20:12:00+09:00</published><updated>2023-01-27T20:12:00+09:00</updated><id>https://jja08111.github.io/computer_architecture/what-does-cpu-do</id><content type="html" xml:base="https://jja08111.github.io/computer_architecture/what-does-cpu-do/"><![CDATA[<p>CPU는 컴퓨터의 두뇌 역할을 하며 아래와 같이 크게 3가지로 구성된다.</p>

<h1 id="연산-장치">연산 장치</h1>

<p>산술연산과 논리연산을 수행한다. 덧셈, 뺄셈 등이 산술연산이고 <a href="https://ko.wikipedia.org/wiki/논리곱">논리곱</a>, <a href="https://ko.wikipedia.org/wiki/논리합">논리합</a>, 부정 등이 논리연산이다.<br />
연산에 필요한 데이터를 레지스터에서 가져오고, 연산 결과를 다시 레지스터로 보낸다.</p>

<h1 id="제어-장치">제어 장치</h1>

<p>명령어를 순서대로 실행 할 수 있도록 제어하는 장치이다.<br />
주기억장치에서 프로그램 명령어를 꺼내 해독하고, 그 결과에 따라 명렁어 실행에 필요한 제어 신호를 기억 장치, 연산 장치, 입출력 장치로 보낸다.<br />
또한 이들 장치가 보낸 신호를 받아, 다음에 수행할 동작을 결정한다.</p>

<h1 id="레지스터">레지스터</h1>

<p>고속 기억 장치이다. 명령어 주소, 코드 연산에 필요한 데이터, 연산 결과 등을 임시로 저장한다.<br />
용도에 따라 범용 레지스터와 특수목적 레지스터로 구분된다. 범용 레지스터는 연산에 필요한 데이터나 연산 결과를 임시 저장한다. 특수목적 레지스터는 특별한 용도로 사용하는 레지스터이다. 예를 들어 MAR(메모리 주소 레지스터, 읽기와 쓰기 연산을 수행할 주기억장치 주소 저장), PC(프로그램 카운터, 다음에 수행할 명령어 주소 저장), IR(명령어 레지스터, 현재 실행 중인 명령어 저장) 등이 있다.</p>

<h1 id="동작-과정">동작 과정</h1>

<ol>
  <li>주기억장치는 입력장치에서 입력 받은 데이터 또는 보조기억장치에 저장된 프로그램을 읽어온다.</li>
  <li>CPU는 프로그램을 실행하기 위해 주기억장치에 저장된 프로그램 명령어와 데이터를 읽어와 처리하고 결과를 다시 주기억장치에 저장한다.</li>
  <li>주기억장치는 처리 결과를 보조기억장치에 저장하거나 출력장치로 보낸다.</li>
  <li>제어장치는 1~3 과정에서 명령어가 순서대로 실행되도록 각 장치를 제어한다.</li>
</ol>

<h1 id="명령어-세트">명령어 세트</h1>

<p>CPU가 실행할 명령어의 집합을 명령어 세트라고 한다. <em>연산코드 + 피연산자</em>로 이루어진다.
연산코드는 연산, 제어, 데이터 전달, 입출력 기능을 가지고, 피연산자는 주소, 숫자, 문자, 논리 데이터 등을 저장한다.</p>]]></content><author><name>Minseong Kim</name><email>jja08111@gmail.com</email></author><category term="computer_architecture" /><category term="computer_architecture" /><category term="cpu" /><category term="processor" /><summary type="html"><![CDATA[CPU는 컴퓨터의 두뇌 역할을 하며 아래와 같이 크게 3가지로 구성된다.]]></summary></entry></feed>