<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Shuu&amp;rsquo;s archive</title>
    <link>https://helloshuu.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 2 Nov 2026 11:30:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>킹숮</managingEditor>
    <item>
      <title>코틀린 코루틴 정석 2</title>
      <link>https://helloshuu.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;섹션3. CoroutineDispatcher 완전 정복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;Dispatchers.IO는&amp;nbsp;입출력&amp;nbsp;작업을&amp;nbsp;위한&amp;nbsp;CoroutineDispatcher이다. &lt;br /&gt;2.&amp;nbsp;Dispatchers.Default는&amp;nbsp;CPU&amp;nbsp;바운드&amp;nbsp;작업을&amp;nbsp;위한&amp;nbsp;CoroutineDispatcher이다. &lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;Dispatchers.IO와&amp;nbsp;Dispatchers.Default는&amp;nbsp;코루틴&amp;nbsp;라이브러리의&amp;nbsp;공유&amp;nbsp;스레드풀을&amp;nbsp;사용한다. &lt;br /&gt;4.&amp;nbsp;Dispatchers.Default의&amp;nbsp;limitedParallelism&amp;nbsp;함수를&amp;nbsp;사용하면,&amp;nbsp;Dispatchers.Default의&amp;nbsp;스레드&amp;nbsp;중&amp;nbsp;일부만을&amp;nbsp;사용하는&amp;nbsp;CoroutineDispatcher를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;5.&amp;nbsp;Dispatchers.IO의&amp;nbsp;limitedParallelism&amp;nbsp;함수를&amp;nbsp;사용하면&amp;nbsp;공유&amp;nbsp;스레드풀의&amp;nbsp;별도&amp;nbsp;스레드를&amp;nbsp;사용하는&amp;nbsp;CoroutineDispatcher를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;&lt;br /&gt;6.&amp;nbsp;Dispatchers.Main은&amp;nbsp;메인&amp;nbsp;스레드를&amp;nbsp;사용하기&amp;nbsp;위한&amp;nbsp;디스패처이다. &lt;br /&gt;7.&amp;nbsp;Dispatchers.Main.immediate는&amp;nbsp;코루틴을&amp;nbsp;요청하는&amp;nbsp;스레드가&amp;nbsp;메인&amp;nbsp;스레드일&amp;nbsp;경우&amp;nbsp;작업&amp;nbsp;대기열에&amp;nbsp;적재되지&amp;nbsp;않고&amp;nbsp;곧바로&amp;nbsp;메인&amp;nbsp;스레드에서&amp;nbsp;실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVfi9a/btsPPkN0UVC/9x0ytPFLA0yGv1nBTkEI3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVfi9a/btsPPkN0UVC/9x0ytPFLA0yGv1nBTkEI3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVfi9a/btsPPkN0UVC/9x0ytPFLA0yGv1nBTkEI3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVfi9a%2FbtsPPkN0UVC%2F9x0ytPFLA0yGv1nBTkEI3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;428&quot; height=&quot;842&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;63&quot; data-start=&quot;34&quot; data-ke-size=&quot;size23&quot;&gt;7. CoroutineContext의 구성요소&lt;/h3&gt;
&lt;p data-end=&quot;91&quot; data-start=&quot;65&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CoroutineContext의 구성요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;135&quot; data-start=&quot;93&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;135&quot; data-start=&quot;93&quot;&gt;CoroutineContext 객체는 네 가지 주요한 구성요소를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;715&quot; data-start=&quot;137&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;272&quot; data-start=&quot;137&quot;&gt;&lt;b&gt;CoroutineName&lt;/b&gt;: 코루틴의 이름 설정에 사용되는 객체&lt;/li&gt;
&lt;li data-end=&quot;272&quot; data-start=&quot;184&quot;&gt;&amp;nbsp;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;복사&lt;span data-state=&quot;closed&quot;&gt;편집&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;val&lt;/span&gt;&lt;/span&gt;&lt;span&gt; coroutineName: CoroutineName = CoroutineName(name = &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;코루틴 이름 설정&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;kotlin&lt;/li&gt;
&lt;li data-end=&quot;401&quot; data-start=&quot;274&quot;&gt;&lt;b&gt;CoroutineDispatcher&lt;/b&gt;: 코루틴을 스레드에 보내서 실행하는 객체&lt;/li&gt;
&lt;li data-end=&quot;401&quot; data-start=&quot;329&quot;&gt;
&lt;div&gt;kotlin&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;복사&lt;span data-state=&quot;closed&quot;&gt;편집&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;val&lt;/span&gt;&lt;/span&gt;&lt;span&gt; dispatcher: CoroutineDispatcher = Dispatchers.IO &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-end=&quot;489&quot; data-start=&quot;403&quot;&gt;&lt;b&gt;Job&lt;/b&gt;: 코루틴의 추상체로 코루틴을 조작하는데 사용되는 객체&lt;/li&gt;
&lt;li data-end=&quot;489&quot; data-start=&quot;449&quot;&gt;
&lt;div&gt;kotlin&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;복사&lt;span data-state=&quot;closed&quot;&gt;편집&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;val&lt;/span&gt;&lt;/span&gt;&lt;span&gt; job: Job = Job() &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-end=&quot;715&quot; data-start=&quot;491&quot;&gt;&lt;b&gt;CoroutineExceptionHandler&lt;/b&gt;: 코루틴에서 발생된 예외를 처리&lt;/li&gt;
&lt;li data-end=&quot;715&quot; data-start=&quot;547&quot;&gt;
&lt;div&gt;kotlin&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;복사&lt;span data-state=&quot;closed&quot;&gt;편집&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;val&lt;/span&gt;&lt;/span&gt;&lt;span&gt; coroutineExceptionHandler: CoroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable -&amp;gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;// 예외 처리 &lt;/span&gt;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. CoroutineContext&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: CoroutineName, CoroutineDispatcher, Job, CoroutineExceptionHandler&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;815&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVT9uc/btsPN5cWBoE/ZZAl0VMDkgbgfCBckcMzmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVT9uc/btsPN5cWBoE/ZZAl0VMDkgbgfCBckcMzmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVT9uc/btsPN5cWBoE/ZZAl0VMDkgbgfCBckcMzmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVT9uc%2FbtsPN5cWBoE%2FZZAl0VMDkgbgfCBckcMzmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;815&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;815&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/18</guid>
      <comments>https://helloshuu.tistory.com/18#entry18comment</comments>
      <pubDate>Mon, 11 Aug 2025 13:17:00 +0900</pubDate>
    </item>
    <item>
      <title>코틀린 코루틴의 정석</title>
      <link>https://helloshuu.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 학습 및 기록용입니다.&amp;nbsp;책의 전반적인 내용보다는 제가 기억할만한 내용 위주로 정리되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 내용의 출처는&amp;nbsp;'코틀린 코루틴의 정석' (조세영 지음) 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Chapter 1.&amp;nbsp;&lt;/b&gt;스레드 기반 작업의 한계와 코루틴의 등장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 코루틴에 알아보기 전에 멀티스레드 프로그래밍이 어떻게 발전해왔고, 멀티스레드 프로그래밍의 단점을 코루틴이 어떻게 극복하였는지를 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.1 JVM 프로세스와 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션이 실행되면 JVM 은 프로세스를 시작하고 메인 스레드를 생성하며 main 함수 내부 코드를 모두 실행하면 애플리케이션은 종료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1717294257719&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	println(&quot;Hello world&quot;)
}

/* 결과 :
Hello world

Process finished with exit code 0
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0 :&amp;nbsp; 프로세스가 정상적으로 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717294406193&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	println(&quot;Hello world&quot;)
    throw Exception(&quot;Dummy exception&quot;)
    println(&quot;main thread 종료&quot;)
}
/* 결과 :
Hello world
Exception in thread &quot;main&quot; java.lang.Exception : Dummy Exception
	at chapter1.code1.Code1_2Kt.main
    ..

Process finished with exit code 1
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 : 프로세스가 비정상적으로 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 의 프로세스는 기본적으로 Main 쓰레드를 단일 쓰레드로 실행되며 메인 스레드가 종료되면 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 쓰레드 애플리케이션은 동시에 다른 작업을 수행하지 못하기 때문에 응답성에 문제를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 애플리케이션의 경우, 멈추거나 버벅이는 주요 이유가 될 수 있으며 서버사이드 작업의 경우도 처리 속도와 응답 속도에 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.2.2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 프로그래밍을 통한 단일 스레드의 한계 극복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 프로그래밍 기본 예시&lt;/p&gt;
&lt;pre id=&quot;code_1717295107141&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	println(&quot;[${Thread.currentThread().name}] 메인 스레드 시작&quot;)
    ExampleThread().start()
    Thread.sleep(1000L)
    println(&quot;[${Thread.currentThread().name}] 메인 스레드 종료&quot;)
}

class ExampleThread: Thread() {
	override fun run() {
    	println(&quot;[${Thread.currentThread().name}] 새로운 스레드 시작&quot;)
        Thread.sleep(2000L)
    	println(&quot;[${Thread.currentThread().name}] 새로운 스레드 종료&quot;)
    }
}

/* 결과 :
[main] 메인 스레드 시작
[Thread-0] 새로운 스레드 시작
[main] 메인 스레드 종료
[Thread-0] 새로운 스레드 종료
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자 스레드와 데몬 스레드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- User thread : 우선도가 높은 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Demon thread : 우선도가 낮은 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Single thread 를 사용하는 경우, 메인 스레드만 사용자 스레드이기 때문에 메인 스레드가 종료될 때 프로세스가 종료된다. 하지만 멀티 스레드를 사용하는 프로세스에서는 thread 중 사용자 스레드가 모두 종료되는 시점에 프로세스가 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717295447377&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	println(&quot;[${Thread.currentThread().name}] 메인 스레드 시작&quot;)
    ExampleThread().apply {
    	isDaemon = true
    }.start()
    Thread.sleep(1000L)
    println(&quot;[${Thread.currentThread().name}] 메인 스레드 종료&quot;)
}
/* 결과 : 
[main] 메인 스레드 시작
[Thread-0] 새로운 스레드 시작
[main] 메인 스레드 종료

Process finished with exit code 0
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 스레드인 main 스레드가 종료될 때 프로세스가 종료되어 Thread-0은 실행 중에 강제 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데몬 스레드는 중요한 스레드가 아니기 때문에 강제 종료되더라도 프로세스가 정상 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.3.1.2. Thread 클래스를 직접 다루는 방법의 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Thread를 상속한 클래스를 인스턴스화해 실행할 때마다 새 Thread가 생성된다. (Thread의 생성 비용은 비싸다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 프로그램이 크고 복잡해지면 Memory leak 등의 문제를 발생시킬 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.3.2 Executor 프레임웍을 통해 스레드풀 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Executor&amp;nbsp; 프레임웍을 사용하여 개발자가 스레드를 직접 관리하는 문제를 해결하고 생성된 Thread 를 재사용 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Executor 프레임웍을 이용한 스레드풀 예시&lt;/p&gt;
&lt;pre id=&quot;code_1717297601311&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	val startTime = System.currentTimeMillis()
    val executorService: ExecutorService = Executors.newFixedThreadPool(2)
    
    executorService.submit {
    	println(&quot;[${Thread.currentThread().name}][${getElapsedTime(startTime)}] 작업 1 시작&quot;)
        Thread.sleep(1000L)
        println(&quot;[${Thread.currentThread().name}][${getElapsedTime(startTime)}] 작업 1 완료&quot;)
    }
    
	executorService.submit {
    	println(&quot;[${Thread.currentThread().name}][${getElapsedTime(startTime)}] 작업 2 시작&quot;)
        Thread.sleep(1000L)
        println(&quot;[${Thread.currentThread().name}][${getElapsedTime(startTime)}] 작업 2 완료&quot;)
    }
    
    fun getElapsedTime(startTime: Long): Seting =
    &quot;지난 시간 : ${System.currentTimeMillis() - startTime}ms&quot;
}

/* 결과 : 서로 다른 스레드에서 실행되기 때문에, 출력 순서, 사용 스레드는 다를 수 있다.
[pool-1-thread-1][지난 시간 : 4ms] 작업1 시작
[pool-1-thread-2][지난 시간 : 4ms] 작업2 시작
[pool-1-thread-1][지난 시간 : 1009ms] 작업1 완료
[pool-1-thread-2][지난 시간 : 1009ms] 작업2 완료
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.3.2.3 Executor 프레임웍의 의의와 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 알아서 Thread를 관래해주는 Executor 프레임웍 또한 한계점을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 바로, 스레드 블로킹 &lt;b&gt;Thread Blocking&lt;/b&gt; (스레드가 아무것도 하지 못하고 멈춰있는 상태)로&amp;nbsp;여러 Thread가 synchronized 블록에 동시에 접근하는 경우, Mutex나 Semaphore로 인해 공유되는 자원에 접근할 수 있는 스레드가 제한 되는 경우 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex. Future 객체는 미래에 언제 올지 모르는 값을 기다리는 함수인 get 함수를 갖고 있고, get gㅏㅁ수를 호출하면 get 함수를 호출한 스레드가 결과값이 반환될 때까지 블로킹 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.3.3 이후의 멀티 스레드 프로그래밍과 한계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 1.8에서는 기존 Future 객체의 단점을 보완하기 위해 CompletableFuture를 개발하기도 하고, 리액티브 프로그래밍 패러다임을 제공하는 RxJava가 등장해 결과값을 데이터 스트림으로 처리하여 스레드 블로킹을 방지하고 작업이 실행되는 스레드풀을 손쉽게 전환할 수 있게 만들기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 여전히 &lt;u&gt;멀티스레드 프로그래밍은 스레드 기반이라는 한계점을 가지고 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드는 생성 비용과 작업을 전환하는 비용이 비싸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.4.2. 코루틴은 스레드 블로킹 문제를 어떻게 극복하는가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 작업 단위 코루틴을 통해 스레드 블로킹 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 단위 코루틴은 스레드에서 작업 실행 도중 일시 중단할 수 있는 작업 단위이다. 코루틴은 작업이 일시 중단되면 더 이상 스레드 사용이 필요하지 않으므로 스레드의 사용 권한을 양보하며, 양보된 스레드는 다른 작업을 실행하는 데 사용할 수 있고, 재개 시점에 새로 스레드를 할당하여 실행한다.( 새로 할당된 스레드는 다른 스레드일 수도 있고, 그 스레드가 될 수도 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Chapter2.&lt;/b&gt; 코루틴 개발 환경 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4.2. 실행중인 코루틴 이름 출력하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Edit configurations... - Run/Debug configuration 창이 뜨면 VM options에 -Dkotlinx.coroutines.debug를 입력후 OK&lt;/p&gt;
&lt;pre id=&quot;code_1717298309754&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() {
	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
}
/* 결과 : 
[main @coroutine#1] 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4.4 CoroutineName 사용해 코루틴에 이름 추가하기&lt;/p&gt;
&lt;pre id=&quot;code_1717298559232&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt;(CoroutineName(&quot;main&quot;)) {
	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
	launch(CoroutineName(&quot;Co1&quot;)) {
    	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
    }
    launch(CoroutineName(&quot;Co2&quot;)) {
    	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
    }
}
/* 결과 : 
[main @Main#1] 실행
[main @Co1#2] 실행
[main @Co2#2] 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chapter 3. CoroutineDispatcher&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine Dispatcher 의 동작 방식은 책 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.2 제한된 디스패처 vs. 무제한 디스패처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무제한 디스패처는 사용할 수 있는 스레드나 스레드 풀이 제한되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 제한된 디스패처를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.3 제한된 디스패처 생성하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.3.1 단일 스레드 디스패처&lt;/p&gt;
&lt;pre id=&quot;code_1717321676055&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val dispatcher: CoroutineDispatcher = newSingleThreadContext(name = &quot;SingleThread&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.3.2 멀티 스레드 디스패처&lt;/p&gt;
&lt;pre id=&quot;code_1717321762254&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val multiThreadDispatcher : CoroutineDispatcher = newFixedThreadPoolContext(
	nThread = 2,
    name = &quot;MultiThread&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 thread의 이름은 MultiThread-1, MultiThread-2가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4.1.&amp;nbsp;CoroutineDispatcher를 통해 코루틴 실행하기&lt;/p&gt;
&lt;pre id=&quot;code_1717322132505&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
	val multiThreadDispatcher = newFixedThreadPoolContext(
    	nThread = 2,
        name = &quot;MultiThread&quot;
    )
    launch(context = multiThreadDispatcher) {
    	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
    }
    launch(context = multiThreadDispatcher) {
    	println(&quot;[${Thread.currentThread().name}] 실행&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.4.2 부모 코루틴의 CoroutineDispatcher를 사용해 자식 코루틴 실행&lt;/p&gt;
&lt;pre id=&quot;code_1717322358198&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
	val multiThreadDispatcher = newFixedThreadPoolContext(
    	nThread = 2,
        name = &quot;MultiThread&quot;
    )
    launch(context = multiThreadDispatcher) {
    	println(&quot;[${Thread.currentThread().name}] 부모 코루틴 실행&quot;)
        launch(context = multiThreadDispatcher) {
    		println(&quot;[${Thread.currentThread().name}] 자식 코루틴 실행&quot;)
  	  	}
        launch(context = multiThreadDispatcher) {
    		println(&quot;[${Thread.currentThread().name}] 자식 코루틴 실행&quot;)
  	  	}
    }
}

/* 결과 :
[MultiThread-1 @coroutine#2] 부모 코루틴 실행
[MultiThread-2 @coroutine#3] 자식 코루틴 실행
[MultiThread-1 @coroutine#4] 자식 코루틴 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴이 어떤 쓰레드에서 실행될지는 실행시마다 달라질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.5 미리 정의된 CoroutineDispatcher&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;newFixedThreadPoolContext()를 사용하여 CoroutineDispatcher 객체를 생성하는건 비효율적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 직접 생성하는 대신 우리는 미리 정의된 CoroutineDispatcher를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Dispatcher.IO : 네트워크 요청이나 파일 입출력 등의 입출력(I/O) 작업을 위한 CoroutineDispatcher&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Dispatcher.Default: CPU를 많이 사용하는 연산 작업을 위한 CoroutineDispatcher&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Dispatcher.Main : 메인 스레드를 사용하기 위한 CoroutineDispatcher&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.5.1 Dispatcher.IO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 통신을 위한 HTTP 요청, DB 작업 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717322804749&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
	launch(Dispatcher.IO) {
    	println(&quot;[${Thread.currentThread().name}] 코루틴 실행&quot;)
    }
}

/* 결과 실행 :
[DefaultDispatcher-worker-1 @coroutine#2] 코루틴 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DefaultDispatcher-worker- ㅇㅇㅇ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Dispatcher.IO 사용 시 공유 Thread pool 에 속한 스레드를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.5.2 Dispatcher.Default&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 데이터 처리처럼 CPU 연산이 필요한 작업을 CPU 바운드 작업이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatcher.Default는 CPU 바운드 작업에 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1717323048259&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking&amp;lt;Unit&amp;gt; {
	launch(Dispatcher.Default) {
    	println(&quot;[${Thread.currentThread().name}] 코루틴 실행&quot;)
    }
}

/*
[DefaultDispatcher-worker-1 @coroutine#2] 코루틴 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 작업 vs. CPU 바운드 작업&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 작업과 CPU 바운드 작업의 중요한 차이는 작업이 실행됐을 때 스레드를 지속적으로 사용하는지 여부이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 I/O 작업은 작을 실행 후 결과를 받을 때까지 스레드를 사용하지 않고, CPU 바운드 작업은 작업을 하는 동안 지속적으로 스레드를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 I/O 작업에 코루틴 사용하면 훨씬 효율적으로 작업을 처리할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 바운드 작업은 스레드나 코루틴을 사용할 때나 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.5.3 limitedParallelism&amp;nbsp; 사용해 Dispatcher.Default 스레드 사용 제한하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dispatcher.Default를 사용해 무거운 연산을 처리하다 특정 연산을 위해 Dispatcher.Default의 모든 스레드가 사용될 수 있다. 이를 방지하기 위해 limitedParallelism() 을 사용하여 Dispatcher.Default의 스레드 중 일부만 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1717323530129&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main = runBlocking&amp;lt;Unit&amp;gt; {
	launch(Dispathtcher.Default.limitedParallelism(2)) {
    	repeat(10) {
        	println(&quot;[${Thread.currentThread().name}] 코루틴 실행&quot;)
        }
    }
}

/* 결과 :
[DefaultDispatcher-worker-2 @coroutine#3] 코루틴 실행
[DefaultDispatcher-worker-1 @coroutine#4] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#5] 코루틴 실행
...
[DefaultDispatcher-worker-2 @coroutine#11] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#12] 코루틴 실행
*/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatcher.IO의 limitedParallellism&lt;/b&gt;은 완전히 다른 동작을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 스레드 풀 상에서 Dispatcher.IO 나 Dispatcher.Default 와 관계없는 스레드로 구성된 새 스레드풀을 만들며 개수의 제한도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 작업이 다른 작업에 영향을 받지 않아야 할 때 사용할 수 있으나 새 스레드를 만드는건 비싼 작업이므로 남용해서는 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.5.5 Dispatcher.Main&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 가 있는 앱에서 메인 스레드의 사용을 위한 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Kotlin 이 아닌 별도 라이브러리를 추가해야 사용 가능하다.&lt;/p&gt;</description>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/17</guid>
      <comments>https://helloshuu.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 28 May 2024 01:09:29 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] Coroutine 코루틴</title>
      <link>https://helloshuu.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin Flow, Live date 를 보다가 문득 깨달은 것.. &quot;나는 아직 Coroutine 도 잘 모르는구나..!&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기초부터 다시 차근차근 쌓아보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 포스팅은 개인 학습용 정리 자료입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고한 내용에 대해서 각 내용 상단에 출처를 기입 하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine&quot;&gt;Coroutines basics | Kotlin Documentation (kotlinlang.org)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711423114695&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Coroutines basics | Kotlin&quot; data-og-description=&quot; &quot; data-og-host=&quot;kotlinlang.org&quot; data-og-source-url=&quot;https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine&quot; data-og-url=&quot;https://kotlinlang.org/docs/coroutines-basics.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Cwu1c/hyVDGrExSP/rPBoyDKz6aEjqNztfhU2kk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Cwu1c/hyVDGrExSP/rPBoyDKz6aEjqNztfhU2kk/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Coroutines basics | Kotlin&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinlang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine 은 컨셉적으로 하나의 thread 랑 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 Coroutine은 특정 thread 에 binding 되는게 아니라서 한 thread에서 실행을 suspend하고 다른 thread에서 실행될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711423220035&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println(&quot;World!&quot;) // print after delay
    }
    println(&quot;Hello&quot;) // main coroutine continues while a previous one is delayed
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Hello&lt;br /&gt;World!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- launch : Coroutine builder.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- delay : &lt;b&gt;정지 함수&lt;/b&gt;. 일정 시간동안 정지하게 하며 정지하고 있는 동안 thread를 잡고 있지 않는다(다른 coroutine이 thread 사용 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- runBlocking : runBlocking 또한 Coroutine builder 이며 &lt;b&gt;CoroutineScope를 만든다&lt;/b&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;structured concurrency&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine 은 structed concurrency를 원칙으로 하기 때문에 CoroutineScope 안에서만 새 Coroutine을 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 runBlocking {} 없이 launch를 호출 시 Coroutine scope이 아니기 때문에 아래처럼 에러가 발생한다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Unresolved reference: launch&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;suspending function&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot;&gt;IDE 에서 &quot;Extract function&quot; 기능을 통해 함수 추출을 해보면 suspend 키워드가 붙은 함수가 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot;&gt;정지함수는 Coroutine 안에서 일반 함수처럼 사용할 수 있고, delay 처럼 정지함수를 호출할 수 있다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Scope builder&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;runBlocking&lt;/span&gt;, coroutineScope와 같은 builder를 통해 coroutine scope를 만들 수 있고, 생성된 coroutine scope는 모든 child가 실행될 때까지 종료되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;runBlocking 과 coroutineScope의 차이점&lt;/u&gt;은&amp;nbsp;runBlocking 은 현재 thread를 잡고 block 하고 있고,&amp;nbsp;coroutineScope는 suspend 하고 다른 작업에 쓸 수 있도록 thread를 release한다. 그렇기 때문에 runBlocking 은 regular function, coroutineScope는 suspend function 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;an-explicit-job&quot; style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot; data-toc=&quot;an-explicit-job&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;An explicit job&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;launch coroutine builder는 Job을 리턴한다. job은 실행된 Coroutine 에 대한 handle 이며 명시적으로 작업이 끝날때까지 기다리게 코드를 짤 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 실행시켜보면 job.join() 에서&amp;nbsp; child 코루틴이 완료될때까지 기다렸다가 Done을 찍는 걸 확인 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1711425615553&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val job = launch { // launch a new coroutine and keep a reference to its Job
    delay(1000L)
    println(&quot;World!&quot;)
}
println(&quot;Hello&quot;)
job.join() // wait until child coroutine completes
println(&quot;Done&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Hello&lt;br /&gt;World!&lt;br /&gt;Done&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot; data-toc=&quot;coroutines-are-light-weight&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot; data-toc=&quot;coroutines-are-light-weight&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;Coroutines are light-weight&lt;/b&gt;&lt;br /&gt;아래 예시처럼 50000개의 Coroutine을 만들어도 적은 memory 를 소비한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711425995248&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(50_000) { // launch a lot of coroutines
        launch {
            delay(5000L)
            print(&quot;.&quot;)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;coroutines-are-light-weight&quot; style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot; data-toc=&quot;coroutines-are-light-weight&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot;&gt;같은 코드를 Thread로 만들면 매우 많은 memory consumption을 만든다.&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #19191c; text-align: start;&quot; data-toc=&quot;coroutines-are-light-weight&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot;&gt;Kotlin coroutines on Android &amp;nbsp;|&amp;nbsp; Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711421543360&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android의 Kotlin 코루틴 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Android의 Kotlin 코루틴 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 코루틴은 비동기적으로 실행되는&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/kotlin/coroutines&quot; data-og-url=&quot;https://developer.android.com/kotlin/coroutines?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/014WS/hyVGGDyOaC/jsvodLh73wOhDpDKvrYjw1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/kotlin/coroutines&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/kotlin/coroutines&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/014WS/hyVGGDyOaC/jsvodLh73wOhDpDKvrYjw1/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android의 Kotlin 코루틴 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Android의 Kotlin 코루틴 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 코루틴은 비동기적으로 실행되는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine은 여러가지 언어에서 여러가지 작업을 동시에 처리하기 위한 사용하며 Kotlin에서는 Kotlin 1.3에 추가되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine을 사용하면 오래 걸리는 작업이 main thread를 잡아 앱이 반응하지 않는 문제를 방지할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Noteworthy features &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;주목할만한 특징&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 경량&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 적은 Memory leak 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Built-in cancellation support&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Jetpack 지원&lt;/p&gt;</description>
      <category>Android</category>
      <category>coroutine</category>
      <category>kotlin</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/16</guid>
      <comments>https://helloshuu.tistory.com/16#entry16comment</comments>
      <pubDate>Tue, 26 Mar 2024 12:59:49 +0900</pubDate>
    </item>
    <item>
      <title>Live data와 Mutable Live data</title>
      <link>https://helloshuu.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 이 곳은 개인 학습을 위한 정리용 공간입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 내용 상단에 내용의 출처를 기입 해두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://onlyfor-me-blog.tistory.com/310&quot;&gt;[MVVM] MutableLiveData와 LiveData 차이 (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711419416920&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[MVVM] MutableLiveData와 LiveData 차이&quot; data-og-description=&quot;MVVM으로 코드를 리팩토링하던 중 LiveData와 MutableLiveData라는 이름이 비슷한 두 클래스를 알게 됐다. 앞에 Mutable이 붙었을 뿐이고 사용법도 비슷하지만 둘의 차이를 알고 넘어가고 싶어서 이 포스&quot; data-og-host=&quot;onlyfor-me-blog.tistory.com&quot; data-og-source-url=&quot;https://onlyfor-me-blog.tistory.com/310&quot; data-og-url=&quot;https://onlyfor-me-blog.tistory.com/310&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/copXIh/hyVDxnZtHt/h67dka9orxOoRVjjY1kdp0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/d1GMyD/hyVGJfZJbt/8M1HUxeAWORadZHZpHvSSK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cfx0CL/hyVDwWSV4T/TAxuK7SpynK1yfpb6QbJ81/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://onlyfor-me-blog.tistory.com/310&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://onlyfor-me-blog.tistory.com/310&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/copXIh/hyVDxnZtHt/h67dka9orxOoRVjjY1kdp0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/d1GMyD/hyVGJfZJbt/8M1HUxeAWORadZHZpHvSSK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cfx0CL/hyVDwWSV4T/TAxuK7SpynK1yfpb6QbJ81/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[MVVM] MutableLiveData와 LiveData 차이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MVVM으로 코드를 리팩토링하던 중 LiveData와 MutableLiveData라는 이름이 비슷한 두 클래스를 알게 됐다. 앞에 Mutable이 붙었을 뿐이고 사용법도 비슷하지만 둘의 차이를 알고 넘어가고 싶어서 이 포스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;onlyfor-me-blog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData를 다루기 전에 Observer pattern과 MVVM(Model-View-ViewModel)의 ViewModel이 뭔지부터 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Observer pattern?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독을 등록한 Subscriber들에게 변경이 있을때마다 Notify 해주는 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://refactoring.guru/design-patterns/observer&quot;&gt;Observer (refactoring.guru)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EZnty/btsF7SNtDkZ/0FTiNpk7KgkwmWxgTHG5Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EZnty/btsF7SNtDkZ/0FTiNpk7KgkwmWxgTHG5Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EZnty/btsF7SNtDkZ/0FTiNpk7KgkwmWxgTHG5Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEZnty%2FbtsF7SNtDkZ%2F0FTiNpk7KgkwmWxgTHG5Yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;653&quot; height=&quot;263&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Customer 가 상품 정보를 알기 위해서는 수시로 상점에 들러서 확인했어야 하는 번거로움이 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Customer가 행사 정보를 구독해놓으면 updates가 있을 때마다 구독자들에게 정보를 받아볼 수 있게 개선 가능하다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 103.139%; height: 401px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 371px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 371px;&quot;&gt;&lt;span&gt;AS-IS.&lt;/span&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clL6zd/btsF78JjlOs/NhHfFiSofWHisjwZuhEKk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clL6zd/btsF78JjlOs/NhHfFiSofWHisjwZuhEKk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clL6zd/btsF78JjlOs/NhHfFiSofWHisjwZuhEKk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclL6zd%2FbtsF78JjlOs%2FNhHfFiSofWHisjwZuhEKk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;263&quot; height=&quot;256&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 371px;&quot;&gt;&lt;br /&gt;&lt;br /&gt;TO-BE&lt;br /&gt;observer pattern.&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drjDiH/btsF6JcukjY/xZCJyVkx0ofHlAxvrWB1F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drjDiH/btsF6JcukjY/xZCJyVkx0ofHlAxvrWB1F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drjDiH/btsF6JcukjY/xZCJyVkx0ofHlAxvrWB1F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrjDiH%2FbtsF6JcukjY%2FxZCJyVkx0ofHlAxvrWB1F0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;279&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신문을 구독하는 것과 비슷한 Observer pattern.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ViewModel 이란?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko&quot;&gt;ViewModel 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18&quot;&gt;ViewModel이란 무엇인가? ViewModel 초보를 위한 가이드 | by Charlezz | Medium&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711442687138&quot; style=&quot;color: #333333; text-align: start;&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/NqZcq/hyVGSYmDFO/Epen6ICTPCJKKYycd24Pk0/img.png?width=522&amp;amp;height=543&amp;amp;face=0_0_522_543,https://scrap.kakaocdn.net/dn/lp7uY/hyVDyHgazs/7IMZtF03ZNqYkoEBKS0TH0/img.png?width=1024&amp;amp;height=339&amp;amp;face=0_0_1024_339,https://scrap.kakaocdn.net/dn/by6SOF/hyVGQzs9uX/b6OcB0AKLXpxpzmR4MsCgk/img.jpg?width=550&amp;amp;height=279&amp;amp;face=0_0_550_279&quot; data-og-url=&quot;https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18&quot; data-og-source-url=&quot;https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18&quot; data-og-host=&quot;charlezz.medium.com&quot; data-og-description=&quot;ViewModel이란?&quot; data-og-title=&quot;ViewModel이란 무엇인가? ViewModel 초보를 위한 가이드&quot; data-og-type=&quot;article&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18&quot; data-source-url=&quot;https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/NqZcq/hyVGSYmDFO/Epen6ICTPCJKKYycd24Pk0/img.png?width=522&amp;amp;height=543&amp;amp;face=0_0_522_543,https://scrap.kakaocdn.net/dn/lp7uY/hyVDyHgazs/7IMZtF03ZNqYkoEBKS0TH0/img.png?width=1024&amp;amp;height=339&amp;amp;face=0_0_1024_339,https://scrap.kakaocdn.net/dn/by6SOF/hyVGQzs9uX/b6OcB0AKLXpxpzmR4MsCgk/img.jpg?width=550&amp;amp;height=279&amp;amp;face=0_0_550_279');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel이란 무엇인가? ViewModel 초보를 위한 가이드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;ViewModel이란?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;charlezz.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MVVM 패턴 관점에서 ViewModel는 View로 부터 독립적이며, View 가 필요로 하는 데이터를 가지고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MVVM 패턴을 적용하면 ViewModel이 Activity나 Fragment 와 같은 UI controller 의 역할을 분담하여 클래스가 방대해지는 것을 막을 수 있고 유지보수, 재사용성, 테스트에 유리하다는 장점이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android Jetpack 에서 ViewModel 클래스를 제공한다. (AAC ViewModel. Android Architecture ViewModel)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Android Jetpack Compose : 구글에서 발표한 개발자를 돕는 Library 모음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LiveData란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData는 Observe 가능한 Data holder이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lifecycle와 관련이 있어 Activity, Fragment, Service 등 Lifecycle이 있는 앱 구성요소들이 LiveData에 Observer로 등록 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot;&gt;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711439655899&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxkqNY/hyVDEgmEw7/LwudzXONPe8szWmoyttI1K/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxkqNY/hyVDEgmEw7/LwudzXONPe8szWmoyttI1K/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Live Data 의 장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ensures your UI matches your data state&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;LiveData는 observe pattern 을 따르고, Data의 변경이 있으면 Observer에게 알려주고 Observer 객체에서 UI를 업데이트 하여 항상 Data 와 UI sync 됨 (상태 일치 보장)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;No memory leaks&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Lifecycle이 종료되면 알아서 삭제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;No crashes due to stopped activities&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;stopped activities는 어떠한 LiveData event도 받지 않음. 이로 인한 비정상 종료도 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;N&lt;/b&gt;&lt;b&gt;o more manual lifecycle handling&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;lifecycle 상태를 인지하고 알아서 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Always up to date data&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;항상 최신 데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Proper configuration changes&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Screen ratate 등 configuration 이 바뀌면 즉시 최신 Data를 수신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sharing resources&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;Singleton 패턴을 활용하여 데이터를 공유하도록 구현 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LiveData 사용해보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData는 일반적으로 ViewModel 객체 내에 저장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1711442005955&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData&amp;lt;String&amp;gt; by lazy {
        MutableLiveData&amp;lt;String&amp;gt;()
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onCreate은 LiveData 의 observe를 시작하기에 적절한 시점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(왜냐하면 onResume 에서 중복 호출 방지 + Actiivity나 Fragment가 활성화되는 즉시 표시할 데이터를 포함하기 위해)&lt;/p&gt;
&lt;pre id=&quot;code_1711443171447&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class NameActivity : AppCompatActivity() {

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Create the observer which updates the UI.
        val nameObserver = Observer&amp;lt;String&amp;gt; { newName -&amp;gt;
            // Update the UI, in this case, a TextView.
            nameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.currentName.observe(this, nameObserver)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 LiveData는 활성상태의 observer에만 업데이트를 보내지만, 예외적으로 비활성상태에서 활성상태로 변환될 때 Observer는 업데이트를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비활성상태 &amp;gt; 활성상태 &amp;gt; 비활성상태 &amp;gt; 활성상태로 변경되면 마지막 활성상태 이후 변경된 경우에만 업데이트를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 이 글의 시작점이 된 LiveData와 Mutable LiveData의 차이점에 대해 알아보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LiveData vs. Mutable LiveData&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/52772884/difference-between-livedata-mutablelivedata&quot;&gt;android - Difference between LiveData, MutableLiveData - Stack Overflow&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711443940608&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Difference between LiveData, MutableLiveData&quot; data-og-description=&quot;I know that the MutableLiveData extends LiveData, but what is the difference between them (in usage). What are appropriate use cases, means when to use the correct one from the two?&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/52772884/difference-between-livedata-mutablelivedata&quot; data-og-url=&quot;https://stackoverflow.com/questions/52772884/difference-between-livedata-mutablelivedata&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hFtlA/hyVGLrpfEi/YvMyOLKKJ2Ztkbk9eKrZnk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/52772884/difference-between-livedata-mutablelivedata&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/52772884/difference-between-livedata-mutablelivedata&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hFtlA/hyVGLrpfEi/YvMyOLKKJ2Ztkbk9eKrZnk/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Difference between LiveData, MutableLiveData&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I know that the MutableLiveData extends LiveData, but what is the difference between them (in usage). What are appropriate use cases, means when to use the correct one from the two?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveData는 immutable 하지만(변하지않음) Mutable LiveData는 변경 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mutable LiveData는 LiveData 를 상속받으며 추가로 &lt;u&gt;setValue(), postValue()같은 메서드를 제공&lt;/u&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#update_livedata_objects&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#update_livedata_objects&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711443644106&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#update_livedata_objects&quot; data-og-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X5oEs/hyVDBKKu1O/7OKzqS8MI6OxKOVqOkR1XK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#update_livedata_objects&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/topic/libraries/architecture/livedata?hl=ko#update_livedata_objects&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X5oEs/hyVDBKKu1O/7OKzqS8MI6OxKOVqOkR1XK/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;LiveData 개요 &amp;nbsp;|&amp;nbsp; Android 개발자 &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;LiveData를 사용하여 수명 주기를 인식하는 방식으로 데이터를 처리합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1711443745959&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;button.setOnClickListener {
    val anotherName = &quot;John Doe&quot;
    model.currentName.setValue(anotherName)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 버튼을 클릭해서 name값을 변경하기 위해 Mutable LiveData를 사용했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB load 완료, 네트워크 통신에 의한 data 업데이트 등 다양한 상황에서 사용된다.&lt;/p&gt;</description>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/15</guid>
      <comments>https://helloshuu.tistory.com/15#entry15comment</comments>
      <pubDate>Tue, 26 Mar 2024 11:45:51 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin - Coroutine flow</title>
      <link>https://helloshuu.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 프로그래밍&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/125&quot;&gt;[RxJava] 반응형 프로그래밍 이해하기 &amp;mdash; 조세영의 Kotlin World&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708575732382&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[RxJava] 반응형 프로그래밍 이해하기&quot; data-og-description=&quot;예제 링크: https://github.com/seyoungcho2/RxJavaExample 경로 : example/example1 명령형 프로그래밍과 반응형 프로그래밍 명령형 프로그래밍 명령형 프로그래밍은 컴퓨터에게 무엇을 하라고 명령하는 것이다. &quot; data-og-host=&quot;kotlinworld.com&quot; data-og-source-url=&quot;https://kotlinworld.com/125&quot; data-og-url=&quot;https://kotlinworld.com/125&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k4Je5/hyVm0w42ZH/Fpm0cSZrVkHKxJl9keakfk/img.png?width=188&amp;amp;height=140&amp;amp;face=0_0_188_140,https://scrap.kakaocdn.net/dn/phwlM/hyVqjIwu2E/HovQZywU9IPTkmpJY5Cqs0/img.png?width=188&amp;amp;height=140&amp;amp;face=0_0_188_140,https://scrap.kakaocdn.net/dn/kLbx9/hyVqsyHChc/DKaxqVlnbsKrmjqSqLHkJK/img.png?width=3982&amp;amp;height=1952&amp;amp;face=0_0_3982_1952&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/125&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinworld.com/125&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k4Je5/hyVm0w42ZH/Fpm0cSZrVkHKxJl9keakfk/img.png?width=188&amp;amp;height=140&amp;amp;face=0_0_188_140,https://scrap.kakaocdn.net/dn/phwlM/hyVqjIwu2E/HovQZywU9IPTkmpJY5Cqs0/img.png?width=188&amp;amp;height=140&amp;amp;face=0_0_188_140,https://scrap.kakaocdn.net/dn/kLbx9/hyVqsyHChc/DKaxqVlnbsKrmjqSqLHkJK/img.png?width=3982&amp;amp;height=1952&amp;amp;face=0_0_3982_1952');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[RxJava] 반응형 프로그래밍 이해하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;예제 링크: https://github.com/seyoungcho2/RxJavaExample 경로 : example/example1 명령형 프로그래밍과 반응형 프로그래밍 명령형 프로그래밍 명령형 프로그래밍은 컴퓨터에게 무엇을 하라고 명령하는 것이다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinworld.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 공부를 위한 정리 문서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 내용의 출처는 아래 링크입니다. 좋은 내용 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://kotlinworld.com/175&quot;&gt;[Coroutine Flow] 1. Flow란 무엇인가? &amp;mdash; 조세영의 Kotlin World&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708575805777&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Coroutine Flow] 1. Flow란 무엇인가?&quot; data-og-description=&quot;Coroutine의 Flow는 데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍 지원 하기 위한 구성요소이다. 이를 이해하기 위해서는 먼저 리액티브(반응형) 프로그래밍이 무엇인지, 그리고 Flow가 리&quot; data-og-host=&quot;kotlinworld.com&quot; data-og-source-url=&quot;https://kotlinworld.com/175&quot; data-og-url=&quot;https://kotlinworld.com/175&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b1ffgR/hyVm3Hkhu7/r8scPLk8wfwATQkTL2A0Pk/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/bEXY1E/hyVqgdXw9C/obYGSoynvKk2iM9x1MPnW0/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/bQllQK/hyVmSshjEM/hEBqFkOdOvcnNuxhycaCq1/img.jpg?width=1706&amp;amp;height=1583&amp;amp;face=0_0_1706_1583&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinworld.com/175&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b1ffgR/hyVm3Hkhu7/r8scPLk8wfwATQkTL2A0Pk/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/bEXY1E/hyVqgdXw9C/obYGSoynvKk2iM9x1MPnW0/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/bQllQK/hyVmSshjEM/hEBqFkOdOvcnNuxhycaCq1/img.jpg?width=1706&amp;amp;height=1583&amp;amp;face=0_0_1706_1583');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Coroutine Flow] 1. Flow란 무엇인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Coroutine의 Flow는 데이터 스트림이며, 코루틴 상에서 리액티브 프로그래밍 지원 하기 위한 구성요소이다. 이를 이해하기 위해서는 먼저 리액티브(반응형) 프로그래밍이 무엇인지, 그리고 Flow가 리&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinworld.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 프로그래밍에는 데이터를 발행하는 발행자가 있고 발행자는 데이터의 소비자에게 지속적으로 데이터를 전달하는 역할을 한다. 우리는 이것을 데이터 스트림이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴에서 데이터 스트림을 구현하기 위해서는 Flow를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow의 핵심 구성요소 3개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer생산자 / Intermediary중간 연산자 / Consumer소비자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer 생산자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow{} 블록 내부에서의 emit()을 통해 데이터를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때의 Data source 는 주로 서버의 데이터를 REST API를 통해 가져오거나, DB에서 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1708576125514&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DustREmoteDataSource(
	private val dustApi:DustApi
) {
	fun getDustInfoFlow() : FLow&amp;lt;List&amp;lt;DustInfo&amp;gt;&amp;gt; = flow {
    	while(true) {
        	val dustInfos = dustApi.fetchLastedDustInfo()
            emit(dustInfos)
            delay(INTERVAL_REFRESH)
            // 60초 마다 반복해서 데이터를 생성
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Imtermediary 중간 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 데이터를 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow에서 지원하는 중간 연산자를 이용해 A객체를 B객체로 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map / filter / onEach 등의 중간 연산자가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708577975343&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DustRepository(
	private val dustRemoteDataSource: DustRemoteDataSource,
) {
	fun getDustsInfoOfViewItem(locale: Locale) =
    	dustRemoteDataSource.getDustInfoFlow().map{ it.filter{ this.locale == locale }}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer 소비자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 상에서 Consumer는 주로 UI 구성요소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow에서 collect를 이용해 전달된 데이터를 소비할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1708586823369&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DustViewModel(
	private val dustRepository: DustRepository
) : ViewModel() {
	fun collectDustInfoOf(locale: Locale) =
    	viewModelScope.launch {
        	dustRepository.getDustInfoOf(locale).collect {
            	dustInfors -&amp;gt; /* ... */
            }
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴 공식문서 번역 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seyoungcho2.github.io/CoroutinesKoreanTranslation/&quot;&gt;Kotlin Coroutines 공식 문서 번역을 시작하며 &amp;middot; GitBook (seyoungcho2.github.io)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708586931823&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Kotlin Coroutines 공식 문서 번역을 시작하며 &amp;middot; GitBook&quot; data-og-description=&quot;Kotlin Coroutines 공식 문서 번역을 시작하며 Kotlin Coroutines는 Kotlin을 위한 강력한 비동기 솔루션이다. 안드로이드 실무에서는 한동안 높은 점유율을 자랑한 RxJava를 Coroutines가 대체하고 있으며, 새로&quot; data-og-host=&quot;seyoungcho2.github.io&quot; data-og-source-url=&quot;https://seyoungcho2.github.io/CoroutinesKoreanTranslation/&quot; data-og-url=&quot;https://seyoungcho2.github.io/CoroutinesKoreanTranslation/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://seyoungcho2.github.io/CoroutinesKoreanTranslation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seyoungcho2.github.io/CoroutinesKoreanTranslation/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin Coroutines 공식 문서 번역을 시작하며 &amp;middot; GitBook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin Coroutines 공식 문서 번역을 시작하며 Kotlin Coroutines는 Kotlin을 위한 강력한 비동기 솔루션이다. 안드로이드 실무에서는 한동안 높은 점유율을 자랑한 RxJava를 Coroutines가 대체하고 있으며, 새로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seyoungcho2.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://kotlinworld.com/232?category=973477&quot;&gt;[Coroutine Flow] 2. Flow와 StateFlow의 차이는 무엇인가? &amp;mdash; 조세영의 Kotlin World&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow는 데이터의 흐름이다. Flow는 데이터의 흐름을 발생시키지만 할 뿐 데이터를 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면이 재구성될 때마다 Data를 가져오는건 비효율 적이므로 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;collect 한 데이터를 ViewModel에 저장해놓고 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;((ViewModel은 onDestroy가 불리더라더도 살아있다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ViewModel이 데이터를 저장하고 있으려면 별도의 데이터 홀더 변수를 만들어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그리고 데이터 홀더 변수는 Reactive하지 않기 때문에 UI에서 해당 데이터 홀더 변수를 구독하기 위해 별도의 fetching 로직을 만들어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 위해 ViewModel에서 데이터 홀더 변수와 flow를 같이 사용한다. 이 방법이 가장 정합하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하지만 둘 모두 보일러 플레이트 코드(비슷한 코드가 반복됨)를 만들어 낸다는 문제가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 등장한 것이 바로 &lt;b&gt;StateFlow&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;StateFlow는 데이터 홀더(저장소) 역할과 Data stream 역할을 모두 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;UI단에서 StateFlow를 구독해 UIState를 업데이트하면 화면이 재구성될때마다 서버에 데이터를 요청할 필요가 없어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/233?category=973477&quot;&gt;[Coroutine Flow] 3. stateIn 사용하여 Flow를 StateFlow로 변환하기 &amp;mdash; 조세영의 Kotlin World&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708652361862&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Coroutine Flow] 3. stateIn 사용하여 Flow를 StateFlow로 변환하기&quot; data-og-description=&quot;Flow와 StateFlow 우리는 리액티브 프로그래밍을 할 때 여러 데이터 흐름(flow)를 하나로 합쳐 하나의 데이터 흐름(Flow)으로 만들어낸다. 예를 들어 아래의 그림3에서는 Flow가 3개 있고 이것이 합쳐져 &quot; data-og-host=&quot;kotlinworld.com&quot; data-og-source-url=&quot;https://kotlinworld.com/233?category=973477&quot; data-og-url=&quot;https://kotlinworld.com/233&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bImEYR/hyVqv3qt0w/MKY8ZeSkkCYWUfUvGLlKb1/img.png?width=794&amp;amp;height=1790&amp;amp;face=0_0_794_1790,https://scrap.kakaocdn.net/dn/bBQHWF/hyVqsljMKf/Wa1KAQJtAyKIFDCFHLYO41/img.png?width=794&amp;amp;height=1790&amp;amp;face=0_0_794_1790,https://scrap.kakaocdn.net/dn/ezpf5f/hyVqqVlGVL/7UkeQq4lsmmjCmwXz6UjV1/img.png?width=3982&amp;amp;height=1952&amp;amp;face=0_0_3982_1952&quot;&gt;&lt;a href=&quot;https://kotlinworld.com/233?category=973477&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kotlinworld.com/233?category=973477&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bImEYR/hyVqv3qt0w/MKY8ZeSkkCYWUfUvGLlKb1/img.png?width=794&amp;amp;height=1790&amp;amp;face=0_0_794_1790,https://scrap.kakaocdn.net/dn/bBQHWF/hyVqsljMKf/Wa1KAQJtAyKIFDCFHLYO41/img.png?width=794&amp;amp;height=1790&amp;amp;face=0_0_794_1790,https://scrap.kakaocdn.net/dn/ezpf5f/hyVqqVlGVL/7UkeQq4lsmmjCmwXz6UjV1/img.png?width=3982&amp;amp;height=1952&amp;amp;face=0_0_3982_1952');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Coroutine Flow] 3. stateIn 사용하여 Flow를 StateFlow로 변환하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Flow와 StateFlow 우리는 리액티브 프로그래밍을 할 때 여러 데이터 흐름(flow)를 하나로 합쳐 하나의 데이터 흐름(Flow)으로 만들어낸다. 예를 들어 아래의 그림3에서는 Flow가 3개 있고 이것이 합쳐져&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kotlinworld.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flow를 UI에서 사용하기 위해 StateFlow로 변환해야한다. UI는 StateFlow를 구독하여 항상 최신 데이터를 발행받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateFlow가 항상 Flow를 구독하고 있다면 메모리누수가 생기므로 StateFlow가 살아있어야 하는 Coroutine scope를 명시할 수 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateFlow는 Cold stream 이 아니라 Hot Stream이다. 마지막에 홀딩하고 있는 데이터를 구독하는 구독자에게 전달할 뿐, 구독자가 구독할 때 발행을 위한 로직을 trigger하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sjoongh.tistory.com/entry/Cold-Stream-vs-Hot-Stream-%EC%9D%B4%EB%9E%80&quot;&gt;Cold Stream vs Hot Stream 이란? (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1708652692915&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Cold Stream vs Hot Stream 이란?&quot; data-og-description=&quot;Coroutine Cold Stream vs Hot Stream 코루틴에서 Scope와 몇 가지 연산자 등의 학습을 거쳐 flow와 channel이라는 것을 학습하게 되었는데 이 2개의 정확한 차이점은 무엇일까? stream은 정확히 무엇인가?.. 둘 다&quot; data-og-host=&quot;sjoongh.tistory.com&quot; data-og-source-url=&quot;https://sjoongh.tistory.com/entry/Cold-Stream-vs-Hot-Stream-%EC%9D%B4%EB%9E%80&quot; data-og-url=&quot;https://sjoongh.tistory.com/entry/Cold-Stream-vs-Hot-Stream-%EC%9D%B4%EB%9E%80&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdbiox/hyVqoDeoJt/PAX65kCfCZKx2cVu0o1e50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cwODoI/hyVqsMpBP0/k3OLETM721jQmBLBVlIjb0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gUZ55/hyVqju9JQy/ancRjPKCeMDP8gL8ffGKvK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://sjoongh.tistory.com/entry/Cold-Stream-vs-Hot-Stream-%EC%9D%B4%EB%9E%80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sjoongh.tistory.com/entry/Cold-Stream-vs-Hot-Stream-%EC%9D%B4%EB%9E%80&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdbiox/hyVqoDeoJt/PAX65kCfCZKx2cVu0o1e50/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cwODoI/hyVqsMpBP0/k3OLETM721jQmBLBVlIjb0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/gUZ55/hyVqju9JQy/ancRjPKCeMDP8gL8ffGKvK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Cold Stream vs Hot Stream 이란?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Coroutine Cold Stream vs Hot Stream 코루틴에서 Scope와 몇 가지 연산자 등의 학습을 거쳐 flow와 channel이라는 것을 학습하게 되었는데 이 2개의 정확한 차이점은 무엇일까? stream은 정확히 무엇인가?.. 둘 다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sjoongh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;마지막으로 Cold Stream 은 Lazy Stream (게으른 스트림)이라고 말할 수 있다. 누군가가 구독할 때만 값의 방출이 시작되기 때문이다. 다시 말해서 누군가가 &amp;ldquo;내가 듣고 있으니까 방출해 줘&amp;rdquo;라고 말해줘야만 시작한다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stateIn 사용하여 Flow를 StateFlow로 변환하기&lt;/p&gt;
&lt;pre id=&quot;code_1708652908052&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public fun &amp;lt;T&amp;gt; Flow&amp;lt;T&amp;gt;.stateIn(
	scope: CoroutineScope,
    started: SharingStarted,
    initialValue: T
): StateFlow&amp;lt;T&amp;gt; {
	val config = configureSharing(1)
    val state = MutableStateFlow(initialValue)
    val job = scope.launchSharing(config.context, config.upstream, state, started, initialValue)
    return ReadonlyStateFlow(state, job)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scope : StateFlow가 Flow로 부터 데이터를 구독받을 CoroutineScope을 명시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;started : Flow 로부터 언제부터 구독을 할지 명시할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initialValue : StateFlow에 저장될 초기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sample&lt;/p&gt;
&lt;pre id=&quot;code_1708653073095&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val stringFlow: Flow&amp;lt;String&amp;gt; = flow {
	for (i in 0..1000) {
    	emit(&quot;integer: $i&quot;)
        delay(1000)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/14</guid>
      <comments>https://helloshuu.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 22 Feb 2024 16:22:49 +0900</pubDate>
    </item>
    <item>
      <title>Groovy?</title>
      <link>https://helloshuu.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 봐왔던 gradle 파일은 groovy 로 작성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 gradle 파일을 groovy 또는 kotlin으로 작성할 수 있고 각 차이점, 장단점은 아래 링크에서 자세히 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle, Groovy Gradle vs Kotlin Gradle&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kdhyo98.tistory.com/87&quot;&gt;[Java] Gradle, Groovy Gradle, Kotlin Gradle &amp;mdash; 일단은 내 이야기 (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704267554884&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Java] Gradle, Groovy Gradle, Kotlin Gradle&quot; data-og-description=&quot;  서론 이번 Project 에서 코드는 java를 사용하고, 빌드툴은 Kotlin Gradle DSL 을 사용하기로 하였습니다. Gradle에 대해서도 한 번 정리하면 좋을 것 같아서 글을 적어봅니다.   본론 Gradle 이란? https:&quot; data-og-host=&quot;kdhyo98.tistory.com&quot; data-og-source-url=&quot;https://kdhyo98.tistory.com/87&quot; data-og-url=&quot;https://kdhyo98.tistory.com/87&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/T10pu/hyUXPIYytT/ODUgpvCMCGmFCjGlLwo7MK/img.png?width=526&amp;amp;height=85&amp;amp;face=0_0_526_85,https://scrap.kakaocdn.net/dn/d9NSy6/hyUXMyJ3Mr/1qtV2t9vFOKPKYh0cOUPHk/img.png?width=526&amp;amp;height=85&amp;amp;face=0_0_526_85&quot;&gt;&lt;a href=&quot;https://kdhyo98.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kdhyo98.tistory.com/87&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/T10pu/hyUXPIYytT/ODUgpvCMCGmFCjGlLwo7MK/img.png?width=526&amp;amp;height=85&amp;amp;face=0_0_526_85,https://scrap.kakaocdn.net/dn/d9NSy6/hyUXMyJ3Mr/1qtV2t9vFOKPKYh0cOUPHk/img.png?width=526&amp;amp;height=85&amp;amp;face=0_0_526_85');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Java] Gradle, Groovy Gradle, Kotlin Gradle&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  서론 이번 Project 에서 코드는 java를 사용하고, 빌드툴은 Kotlin Gradle DSL 을 사용하기로 하였습니다. Gradle에 대해서도 한 번 정리하면 좋을 것 같아서 글을 적어봅니다.   본론 Gradle 이란? https:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kdhyo98.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Groovy 기본 문법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://galid1.tistory.com/647&quot;&gt;BuildTool - Groovy란? (Groovy 문법과, 초간단 build.gradle 작성 예제) (tistory.com)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704267584314&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;BuildTool - Groovy란? (Groovy 문법과, 초간단 build.gradle 작성 예제)&quot; data-og-description=&quot;Gradle의 스크립트 파일 구조 Groovy란JVM에서 실행되는 스크립트 언어문법이 Java와 아주 가깝다Java와 호환이 되며 Java 클래스 파일을 그대로 Groovy 클래스로 사용할 수 있다 1. Gradle 설치https://galid1.ti&quot; data-og-host=&quot;galid1.tistory.com&quot; data-og-source-url=&quot;https://galid1.tistory.com/647&quot; data-og-url=&quot;https://galid1.tistory.com/647&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wr3su/hyUXRfIj64/CN2uG6Cr7gDNgkFCplHqnk/img.png?width=316&amp;amp;height=61&amp;amp;face=0_0_316_61,https://scrap.kakaocdn.net/dn/HjOrE/hyUXYTtuww/47ganYl7n6cBPvcJRvf3Xk/img.png?width=316&amp;amp;height=61&amp;amp;face=0_0_316_61,https://scrap.kakaocdn.net/dn/hLAod/hyUXOcfwQm/UPPiDCWUxjAkXBMHPFR9P1/img.png?width=518&amp;amp;height=390&amp;amp;face=0_0_518_390&quot;&gt;&lt;a href=&quot;https://galid1.tistory.com/647&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://galid1.tistory.com/647&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wr3su/hyUXRfIj64/CN2uG6Cr7gDNgkFCplHqnk/img.png?width=316&amp;amp;height=61&amp;amp;face=0_0_316_61,https://scrap.kakaocdn.net/dn/HjOrE/hyUXYTtuww/47ganYl7n6cBPvcJRvf3Xk/img.png?width=316&amp;amp;height=61&amp;amp;face=0_0_316_61,https://scrap.kakaocdn.net/dn/hLAod/hyUXOcfwQm/UPPiDCWUxjAkXBMHPFR9P1/img.png?width=518&amp;amp;height=390&amp;amp;face=0_0_518_390');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;BuildTool - Groovy란? (Groovy 문법과, 초간단 build.gradle 작성 예제)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Gradle의 스크립트 파일 구조 Groovy란JVM에서 실행되는 스크립트 언어문법이 Java와 아주 가깝다Java와 호환이 되며 Java 클래스 파일을 그대로 Groovy 클래스로 사용할 수 있다 1. Gradle 설치https://galid1.ti&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;galid1.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 형 생략&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;def num = 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 메소드 호출&lt;/b&gt; (인자 하나면 괄호 생략)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id('org.jetbrains.kotiln.android')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id 'org.jetbrains.kotiln.android'&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;groovy 에서 한칸 띄고 인자가 나오면 함수 호출이구나 생각해야함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 클로저 Closure&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://groovy-lang.org/closures.html&quot;&gt;The Apache Groovy programming language - Closures (groovy-lang.org)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1704267926079&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The Apache Groovy programming language - Closures&quot; data-og-description=&quot;This chapter covers Groovy Closures. A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope. In opposition to the f&quot; data-og-host=&quot;groovy-lang.org&quot; data-og-source-url=&quot;https://groovy-lang.org/closures.html&quot; data-og-url=&quot;https://groovy-lang.org/closures.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://groovy-lang.org/closures.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://groovy-lang.org/closures.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Apache Groovy programming language - Closures&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This chapter covers Groovy Closures. A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope. In opposition to the f&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;groovy-lang.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Closure는 익명의 코드 블럭으로 아래처럼 쓸 수 있다&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; style=&quot;background-color: #f5f5f5; color: #333333; text-align: start;&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{ item++ }                                          
{ println it }                                                              
{ String x, int y -&amp;gt;                                
    println &quot;hey ${x} the value is ${y}&quot;
}

def closureExample = { println &quot;hello!&quot;}
closureExample()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>gradle</category>
      <category>Groovy</category>
      <category>kotlin</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/13</guid>
      <comments>https://helloshuu.tistory.com/13#entry13comment</comments>
      <pubDate>Wed, 3 Jan 2024 16:50:24 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin | action bar 에 메뉴 표시하기 (MenuItem.setShowAsAction)</title>
      <link>https://helloshuu.tistory.com/12</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/reference/android/view/MenuItem&quot;&gt;MenuItem &amp;nbsp;|&amp;nbsp; Android Developers&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;showAsAction 속성&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;SHOW_AS_ACTION_ALWAYS / SHOW_AS_ACTION_IF_ROOM /SHOW_AS_ACTION_NEVER / SHOW_AS_ACTION_WITH_TEXT&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이거 굉장히 매우 기본적인 내용인데 모르고 최근에 실수를 해서 아주 반성중이다...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Action bar에 메뉴를 추가하기 위해서는 아래 파일을 수정한다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(xml 을 이용한 방식, programmatically 구현도 가능)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eJfs2Y/btsAMo6jo9G/vQK39ve3XBcSdphVdFxR2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eJfs2Y/btsAMo6jo9G/vQK39ve3XBcSdphVdFxR2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eJfs2Y/btsAMo6jo9G/vQK39ve3XBcSdphVdFxR2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeJfs2Y%2FbtsAMo6jo9G%2FvQK39ve3XBcSdphVdFxR2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;252&quot; height=&quot;125&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;first, second, third 3개의 메뉴를 추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egkgTb/btsAOSrNH3P/Tk1bw7YYdJGWKmv5lQTo7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egkgTb/btsAOSrNH3P/Tk1bw7YYdJGWKmv5lQTo7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egkgTb/btsAOSrNH3P/Tk1bw7YYdJGWKmv5lQTo7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegkgTb%2FbtsAOSrNH3P%2FTk1bw7YYdJGWKmv5lQTo7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;375&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;xml 파일에서는 item에 showAsAction 속성을 줄 수 있는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 속성에 따라 action bar에 표시될 수도 있고, 액션바 우측 ':' 버튼 안으로 들어갈 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;always &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;항상 표시 (Action bar에 버튼)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;never&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;':' 버튼 클릭 시 메뉴가 표시됨&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ifRoom&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;action bar 에 공간이 있으면 버튼으로 표시, 충분하지 않으면 ':' 클릭시 메뉴가 표시됨&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드 실행 시, 오른쪽 처럼 표시되는 것을 볼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 109.884%; height: 467px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 53.3127%;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QpTik/btsALVQ7RcQ/ajM19qqiyAUjifMtyRifPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QpTik/btsALVQ7RcQ/ajM19qqiyAUjifMtyRifPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QpTik/btsALVQ7RcQ/ajM19qqiyAUjifMtyRifPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQpTik%2FbtsALVQ7RcQ%2FajM19qqiyAUjifMtyRifPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;445&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 46.6873%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4dxFO/btsAOJu6tyk/2C8YGP8EKIPKm2lm2NCX5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4dxFO/btsAOJu6tyk/2C8YGP8EKIPKm2lm2NCX5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4dxFO/btsAOJu6tyk/2C8YGP8EKIPKm2lm2NCX5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4dxFO%2FbtsAOJu6tyk%2F2C8YGP8EKIPKm2lm2NCX5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;270&quot; height=&quot;497&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;First는 always라서 버튼으로 표시됨&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Second 는 ifRoom 인데 공간이 있어 버튼으로 표시됨&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Third는 ifRoom 인데 더이상 공간이 없어 ':' 하단에 위치함&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Fourth 는 never 이기 때문에 항상 ':' 하단에 위치함&lt;/p&gt;</description>
      <category>actionbar</category>
      <category>kotlin</category>
      <category>Menu</category>
      <category>MenuItem</category>
      <category>setshowasaction</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/12</guid>
      <comments>https://helloshuu.tistory.com/12#entry12comment</comments>
      <pubDate>Thu, 23 Nov 2023 16:50:04 +0900</pubDate>
    </item>
    <item>
      <title>[React] 지도위에 custom marker 표시하기 (Google Map API)</title>
      <link>https://helloshuu.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크에서 볼 수 있듯이 Google map API를 사용하여 다양한 방식으로 Custom marker를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 맞춤&amp;nbsp;그래픽&amp;nbsp;파일&amp;nbsp;사용 &lt;br /&gt;- 인라인 SVG 사용 &lt;br /&gt;- 그래픽 파일을 글리프로 사용 &lt;br /&gt;- 장소 아이콘 이미지를 글리프로 사용 &lt;br /&gt;- 아이콘 글꼴 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/advanced-markers/graphic-markers?hl=ko&amp;amp;authuser=1#javascript&quot;&gt;그래픽으로 마커 만들기 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692345374273&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;그래픽으로 마커 만들기 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;의견 보내기 그래픽으로 마커 만들기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 제품 또는 기능은 미리보기(GA 이전) 버전입니다. GA 이전 제품과 기&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/maps/documentation/javascript/advanced-markers/graphic-markers?hl=ko&amp;amp;authuser=1#javascript&quot; data-og-url=&quot;https://developers.google.com/maps/documentation/javascript/advanced-markers/graphic-markers?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b8jNyy/hyTFiMnJWm/rpRhrAufVP9zeOT4OwBxs0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/advanced-markers/graphic-markers?hl=ko&amp;amp;authuser=1#javascript&quot; data-source-url=&quot;https://developers.google.com/maps/documentation/javascript/advanced-markers/graphic-markers?hl=ko&amp;amp;authuser=1#javascript&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b8jNyy/hyTFiMnJWm/rpRhrAufVP9zeOT4OwBxs0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;그래픽으로 마커 만들기 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;의견 보내기 그래픽으로 마커 만들기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 제품 또는 기능은 미리보기(GA 이전) 버전입니다. GA 이전 제품과 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 가장 간단하게 png 파일을 사용해서 marker를 표시하는 예제를 테스트 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;맞춤&amp;nbsp;그래픽&amp;nbsp;파일&amp;nbsp;사용&lt;/b&gt; &lt;br /&gt;그래픽&amp;nbsp;파일(예:&amp;nbsp;PNG)을&amp;nbsp;참조하는&amp;nbsp;img&amp;nbsp;요소를&amp;nbsp;AdvancedMarkerView.content&amp;nbsp;옵션에&amp;nbsp;전달하여&amp;nbsp;맞춤&amp;nbsp;이미지를&amp;nbsp;사용하여&amp;nbsp;마커를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도에 marker를 표시하는 기본 예제에서 이어서 해보겠습니다. (&lt;a href=&quot;https://helloshuu.tistory.com/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://helloshuu.tistory.com/9)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 경로에 marker 로 쓸 store.png 파일을 추가해주었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.js에서 storeImg를 읽어와 marker에 content 로 설정해주면 완성&lt;/p&gt;
&lt;pre id=&quot;code_1692344950758&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import StoreImage from './store.png'
let map;

async function initMap() {
  const position = { lat: 37.578, lng: 126.976 };

  const { Map } = await window.google.maps.importLibrary(&quot;maps&quot;);
  const { AdvancedMarkerView } = await window.google.maps.importLibrary(&quot;marker&quot;);


  const storeImg = document.createElement(&quot;img&quot;);
  storeImg.src = StoreImage;


  map = new Map(document.getElementById(&quot;map&quot;), {
    zoom: 16,
    center: position,
    mapId: &quot;DEMO_MAP_ID&quot;,
  });

  const marker = new AdvancedMarkerView({
    map: map,
    position: position,
    content: storeImg,
    title: &quot;경복궁&quot;,
  });
}

initMap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3vuM2/btsrsSQj3gK/9Fk3V4XALGXRs3fRpPB2qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3vuM2/btsrsSQj3gK/9Fk3V4XALGXRs3fRpPB2qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3vuM2/btsrsSQj3gK/9Fk3V4XALGXRs3fRpPB2qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3vuM2%2FbtsrsSQj3gK%2F9Fk3V4XALGXRs3fRpPB2qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;618&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Googlemap</category>
      <category>googlemapapi</category>
      <category>React</category>
      <category>구글맵api</category>
      <category>리액트</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/11</guid>
      <comments>https://helloshuu.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 18 Aug 2023 16:57:25 +0900</pubDate>
    </item>
    <item>
      <title>[React] 지도에 클릭 이벤트로 위경도 받기 (Google map api)</title>
      <link>https://helloshuu.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱 내가 해보고 싶었던 예제가 Google developers에 있었고, 예제만 그대로 따라하였을 때 문제없이 구현 가능하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Getting Lat/Lng from a Click Event&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng#maps_event_click_latlng-javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng#maps_event_click_latlng-javascript&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google map 을 띄우고 지도를 클릭하면 marker가 옮겨가면서 클릭한 위치의 위경도를 표시해준다.&lt;/p&gt;
&lt;figure id=&quot;og_1692342429117&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Getting Lat/Lng from a Click Event &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;Send feedback Getting Lat/Lng from a Click Event Stay organized with collections Save and categorize content based on your preferences. This example listens for the click event, gets the latitude and longitude coordinates of the click from google.maps.MapM&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng#maps_event_click_latlng-javascript&quot; data-og-url=&quot;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/liaEd/hyTFiMmT9I/8pESP6HbJT2MX5GkWoSRP0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng#maps_event_click_latlng-javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/maps/documentation/javascript/examples/event-click-latlng#maps_event_click_latlng-javascript&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/liaEd/hyTFiMmT9I/8pESP6HbJT2MX5GkWoSRP0/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Lat/Lng from a Click Event &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Send feedback Getting Lat/Lng from a Click Event Stay organized with collections Save and categorize content based on your preferences. This example listens for the click event, gets the latitude and longitude coordinates of the click from google.maps.MapM&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최종 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html&lt;/p&gt;
&lt;pre id=&quot;code_1692342495272&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Marker on map&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=default&quot;&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;./style.css&quot; /&amp;gt;
    &amp;lt;script type=&quot;module&quot; src=&quot;./index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;


&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;map&quot; style=&quot;height: 400px; width: 500px&quot;&amp;gt;&amp;lt;/div&amp;gt;

          &amp;lt;script
             src=&quot;https://maps.googleapis.com/maps/api/js?key=MY_API_KEY&amp;amp;callback=initMap&amp;amp;v=weekly&quot;
             defer
           &amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.js&lt;/p&gt;
&lt;pre id=&quot;code_1692342560248&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function initMap() {
  const myLatlng = { lat: 37.578, lng: 126.976 };
  const map = new window.google.maps.Map(document.getElementById(&quot;map&quot;), {
    zoom: 10,
    center: myLatlng,
  });
  // Create the initial InfoWindow.
  let infoWindow = new window.google.maps.InfoWindow({
    content: &quot;Click the map to get Lat/Lng!&quot;,
    position: myLatlng,
  });

  infoWindow.open(map);
  // Configure the click listener.
  map.addListener(&quot;click&quot;, (mapsMouseEvent) =&amp;gt; {
    // Close the current InfoWindow.
    infoWindow.close();
    // Create a new InfoWindow.
    infoWindow = new window.google.maps.InfoWindow({
      position: mapsMouseEvent.latLng,
    });
    infoWindow.setContent(
      JSON.stringify(mapsMouseEvent.latLng.toJSON(), null, 2),
    );
    infoWindow.open(map);
  });
}

window.initMap = initMap;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA09ej/btsrB7ShzIf/VyRQsTe4YwylpOErLtKmr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA09ej/btsrB7ShzIf/VyRQsTe4YwylpOErLtKmr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA09ej/btsrB7ShzIf/VyRQsTe4YwylpOErLtKmr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA09ej%2FbtsrB7ShzIf%2FVyRQsTe4YwylpOErLtKmr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;632&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google map API로 받을 수 있는 다양한 이벤트들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/events?authuser=1&amp;amp;hl=ko#EventsOverview&quot;&gt;이벤트 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/a&gt;&lt;/p&gt;</description>
      <category>ClickEvent</category>
      <category>googlemapapi</category>
      <category>LatLng</category>
      <category>React</category>
      <category>구글맵</category>
      <category>구글맵api</category>
      <category>리액트</category>
      <category>위경도</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/10</guid>
      <comments>https://helloshuu.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 18 Aug 2023 16:28:52 +0900</pubDate>
    </item>
    <item>
      <title>[React] Google Map API를 사용하여 지도위에 marker pin 표시하기</title>
      <link>https://helloshuu.tistory.com/9</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google developer 에 나온 예제를 따라 지도에 marker를 표시해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/adding-a-google-map?hl=ko#maps_add_map-html&quot;&gt;웹사이트에 마커가 포함된 Google 지도 추가 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1692342274402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;웹사이트에 마커가 포함된 Google 지도 추가 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;의견 보내기 웹사이트에 마커가 포함된 Google 지도 추가 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소개 이 튜토리얼에서는 마커가 포함된 간단한 Google &quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/maps/documentation/javascript/adding-a-google-map?hl=ko#maps_add_map-html&quot; data-og-url=&quot;https://developers.google.com/maps/documentation/javascript/adding-a-google-map?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bIir0u/hyTFpLrFql/kkVuMNWeLwofJQ0Ht2pV5K/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/adding-a-google-map?hl=ko#maps_add_map-html&quot; data-source-url=&quot;https://developers.google.com/maps/documentation/javascript/adding-a-google-map?hl=ko#maps_add_map-html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bIir0u/hyTFpLrFql/kkVuMNWeLwofJQ0Ht2pV5K/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;웹사이트에 마커가 포함된 Google 지도 추가 &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;의견 보내기 웹사이트에 마커가 포함된 Google 지도 추가 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소개 이 튜토리얼에서는 마커가 포함된 간단한 Google&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;#troubleshooting&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ERROR&lt;/b&gt; &lt;br /&gt;AdvancedMarkerView&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;constructor &lt;br /&gt;TypeError:&amp;nbsp;AdvancedMarkerView&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;constructor &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;at&amp;nbsp;initMap&amp;nbsp;(http://localhost:3000/static/js/bundle.js:37:18)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 :&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 예제에서 map 을 init 하는 index.js 만 수정하고, index.html 수정하는 것을 놓침&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html&amp;nbsp; &amp;lt;body&amp;gt; 에 v: &quot;beta&quot;가 포함된 script 추가 (Advanced Marker는 beta에서만 사용가능)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIiOyA/btsrB3ISz6E/FIBD8lDbetddai2bY5tbR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIiOyA/btsrB3ISz6E/FIBD8lDbetddai2bY5tbR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIiOyA/btsrB3ISz6E/FIBD8lDbetddai2bY5tbR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIiOyA%2FbtsrB3ISz6E%2FFIBD8lDbetddai2bY5tbR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;173&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&quot;&gt;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1692342112715&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Advanced Markers (beta) &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 Advanced Markers (beta) 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. google.maps.m&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&quot; data-og-url=&quot;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ggaBo/hyTFoTjH49/ZfGVafcYhUDZXkUdTpW7QK/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&quot; data-source-url=&quot;https://developers.google.com/maps/documentation/javascript/reference/3.51/advanced-markers?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ggaBo/hyTFoTjH49/ZfGVafcYhUDZXkUdTpW7QK/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Advanced Markers (beta) &amp;nbsp;|&amp;nbsp; Maps JavaScript API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 Advanced Markers (beta) 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. google.maps.m&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;최종 코드&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;index.html&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692335314712&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Add Map&amp;lt;/title&amp;gt;
    &amp;lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=default&quot;&amp;gt;&amp;lt;/script&amp;gt;

    &amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;./style.css&quot; /&amp;gt;
    &amp;lt;script type=&quot;module&quot; src=&quot;./index.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!--The div element for the map --&amp;gt;
    &amp;lt;div id=&quot;map&quot;  style=&quot;height: 400px; width: 500px&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;!-- prettier-ignore --&amp;gt;
    &amp;lt;script&amp;gt;(g=&amp;gt;{var h,a,k,p=&quot;The Google Maps JavaScript API&quot;,c=&quot;google&quot;,l=&quot;importLibrary&quot;,q=&quot;__ib__&quot;,m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=&amp;gt;h||(h=new Promise(async(f,n)=&amp;gt;{await (a=m.createElement(&quot;script&quot;));e.set(&quot;libraries&quot;,[...r]+&quot;&quot;);for(k in g)e.set(k.replace(/[A-Z]/g,t=&amp;gt;&quot;_&quot;+t[0].toLowerCase()),g[k]);e.set(&quot;callback&quot;,c+&quot;.maps.&quot;+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=&amp;gt;h=n(Error(p+&quot; could not load.&quot;));a.nonce=m.querySelector(&quot;script[nonce]&quot;)?.nonce||&quot;&quot;;m.head.append(a)}));d[l]?console.warn(p+&quot; only loads once. Ignoring:&quot;,g):d[l]=(f,...n)=&amp;gt;r.add(f)&amp;amp;&amp;amp;u().then(()=&amp;gt;d[l](f,...n))})
        ({key: &quot;MY_API_KEYYYYYY&quot;, v: &quot;beta&quot;});&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;index.js&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1692335802394&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let map;

async function initMap() {
  // The location of 경복궁
  const position = { lat: 37.578, lng: 126.976 };
  // Request needed libraries.
  //@ts-ignore
  const { Map } = await window.google.maps.importLibrary(&quot;maps&quot;);
  const { AdvancedMarkerView } = await window.google.maps.importLibrary(&quot;marker&quot;);

  // The map, centered at 경복궁
  map = new Map(document.getElementById(&quot;map&quot;), {
    zoom: 16,
    center: position,
    mapId: &quot;DEMO_MAP_ID&quot;,
  });

  // The marker, positioned at Uluru
  const marker = new AdvancedMarkerView({
    map: map,
    position: position,
    title: &quot;경복궁&quot;,
  });
}

initMap();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 지도에 marker 를 표시하고 '경복궁' 이란 태그도 추가하는데 성공하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBlNld/btsrrSbsFzX/QJ4DcAfIWqBck3jx1GOtik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBlNld/btsrrSbsFzX/QJ4DcAfIWqBck3jx1GOtik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBlNld/btsrrSbsFzX/QJ4DcAfIWqBck3jx1GOtik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBlNld%2FbtsrrSbsFzX%2FQJ4DcAfIWqBck3jx1GOtik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;630&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>googlemapapi</category>
      <category>marker</category>
      <category>React</category>
      <category>구글맵</category>
      <category>리액트</category>
      <category>리액트지도</category>
      <category>웹프로그래밍</category>
      <category>지도</category>
      <author>킹숮</author>
      <guid isPermaLink="true">https://helloshuu.tistory.com/9</guid>
      <comments>https://helloshuu.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 18 Aug 2023 16:05:51 +0900</pubDate>
    </item>
  </channel>
</rss>