<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Archive for Samuel : Samuel 의 기록 보관소</title>
    <link>https://afs0201.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 4 Apr 2026 10:51:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Samuel_0201</managingEditor>
    <item>
      <title>Android 의 생명주기 (Lifecycle)</title>
      <link>https://afs0201.tistory.com/81</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 개발에서 &lt;b&gt;생명주기(Lifecycle)&lt;/b&gt;를 이해하는 것은 단순히 화면 전환을 처리하는 것을 넘어, &lt;br /&gt;한정된 리소스(메모리, 배터리)를 효율적으로 관리하고 사용자 경험을 최적화하는 핵심 역량입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;안드로이드 시스템은 메모리가 부족하면 언제든 백그라운드에 있는 앱을 강제 종료할 수 있기 때문에, &lt;br /&gt;각 단계에서 어떤 데이터를 저장하고 복구할지 결정하는 것이 매우 중요합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;안드로이드 액티비티 생명주기 흐름&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;액티비티의 상태 변화는 피라미드 구조와 비슷합니다.&lt;br /&gt;사용자가 앱을 실행하면 피라미드의 꼭대기(Resumed)로 올라가고, &lt;br /&gt;앱을 종료하거나 다른 앱으로 넘어가면 다시 바닥(Destroyed)으로 내려옵니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;핵심 콜백 메서드의 역할과 용도&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;1. onCreate()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 처음 생성될 때 시스템에 의해 딱 한 번 호출됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;용도:&lt;/b&gt; 사용자 인터페이스(UI) 설정(레이아웃 인플레이션), 뷰 바인딩, 클래스 범위 변수 초기화 등 &lt;b data-index-in-node=&quot;57&quot; data-path-to-node=&quot;8,1,0&quot;&gt;정적인 설정&lt;/b&gt;을 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;특징:&lt;/b&gt; 이 단계가 끝나면 액티비티는 곧바로 onStart()로 넘어갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;2. onStart()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 사용자에게 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;10,0,0&quot;&gt;보이기 시작&lt;/b&gt;할 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;용도:&lt;/b&gt; UI를 유지하기 위해 필요한 리소스를 할당하거나, 애니메이션을 시작할 준비를 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;특징:&lt;/b&gt; 매우 빠르게 실행되며 바로 onResume()으로 이어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;3. onResume()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 **포그라운드(Foreground)**에 위치하며 사용자와 상호작용이 가능한 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;용도:&lt;/b&gt; 카메라 실행, 애니메이션 시작, 데이터 업데이트 등 사용자가 보고 듣는 모든 활동을 활성화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;특징:&lt;/b&gt; 앱이 실행 중인 대부분의 시간을 이 상태에서 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;4. onPause()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 여전히 화면에 보이지만(예: 반투명 다이얼로그가 앞에 뜸), 사용자와의 상호작용은 중단된 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;용도:&lt;/b&gt; 센서 중단, 애니메이션 일시 정지 등 CPU를 많이 점유하는 작업을 멈춥니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;주의:&lt;/b&gt; 이 메서드는 매우 짧은 시간 내에 끝나야 합니다. 여기서 데이터를 저장하거나 네트워크 요청을 하면 다음 화면으로 넘어가는 속도가 느려집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;5. onStop()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 사용자에게 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;16,0,0&quot;&gt;더 이상 보이지 않는&lt;/b&gt; 상태입니다. (예: 홈 버튼 누름, 다른 액티비티 실행)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;용도:&lt;/b&gt; 데이터베이스 저장, 네트워크 연결 해제, 애니메이션 중지 등 무거운 리소스를 해제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;특징:&lt;/b&gt; 시스템이 메모리가 부족하면 onStop 상태의 앱을 우선적으로 종료합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;6. onDestroy()&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;역할:&lt;/b&gt; 액티비티가 완전히 소멸되기 직전에 호출됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;용도:&lt;/b&gt; onCreate에서 생성한 모든 리소스를 해제(Cleanup)합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0&quot;&gt;특징:&lt;/b&gt; 개발자가 직접 finish()를 호출하거나, 시스템이 메모리 확보를 위해 앱을 종료할 때 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;생명주기 활용 시 장단점 및 주의사항&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;21&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;단계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;장점 (권장 사항)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;단점 (주의 사항)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0,0&quot;&gt;onCreate&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,1,1,0&quot;&gt;한 번만 실행되므로 일관된 초기화 가능.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,1,2,0&quot;&gt;너무 많은 작업을 수행하면 앱 구동 속도가 현저히 느려짐 (ANR 위험).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0,0&quot;&gt;onResume&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,2,1,0&quot;&gt;상호작용 직전에 최신 데이터를 갱신하기 좋음.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,2,2,0&quot;&gt;매번 호출되므로 가벼운 로직만 배치해야 함.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,3,0,0&quot;&gt;onPause&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,3,1,0&quot;&gt;시스템 자원을 즉시 반납하여 배터리 절약 가능.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,3,2,0&quot;&gt;여기서 데이터를 저장(I/O)하려다 손실될 위험이 있음.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,4,0,0&quot;&gt;onStop&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,4,1,0&quot;&gt;앱이 안 보일 때 무거운 작업을 멈춰 효율적임.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;21,4,2,0&quot;&gt;사용자가 다시 돌아올 때 복구 로직(onRestart)이 필요함.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;실무에서 자주 겪는 시나리오: 화면 회전&lt;/h2&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;가장 당황스러운 지점 중 하나는 &lt;b&gt;화면 회전(Landscape/Portrait)&lt;/b&gt;입니다. &lt;br /&gt;안드로이드에서는 화면이 회전하면 기본적으로 액티비티를 &lt;b data-index-in-node=&quot;82&quot; data-path-to-node=&quot;24&quot;&gt;onDestroy 시킨 후 onCreate로 다시 만듭니다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;이유:&lt;/b&gt; 새로운 화면 비율에 맞는 레이아웃 리소스를 다시 불러오기 위해서입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;해결책:&lt;/b&gt; 이때 입력 중이던 데이터가 날아가지 않도록 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;25,1,0&quot;&gt;ViewModel&lt;/b&gt;을 사용하거나, &lt;br /&gt;onSaveInstanceState() 콜백을 사용하여 데이터를 임시 저장해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;C#이나 C++ 환경에서는 객체의 생명주기를 개발자가 명시적으로 관리하는 경우가 많지만, &lt;br /&gt;안드로이드는 &lt;b&gt;&quot;시스템이 언제든 내 앱을 죽일 수 있다&quot;&lt;/b&gt;는 전제하에 각 콜백에서 리소스를 뺏고 돌려받는 연습이 필요합니다.&lt;/p&gt;</description>
      <category>Programming/Android</category>
      <category>Android</category>
      <category>Lifecycle</category>
      <category>생명주기</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/81</guid>
      <comments>https://afs0201.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 3 Apr 2026 19:49:57 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin의 Null Safety와 람다식 리스트</title>
      <link>https://afs0201.tistory.com/80</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin에서 가장 중요하게 여겨지는 두 가지 핵심 기능인 &lt;br /&gt;&lt;b&gt;Null Safety(널 안정성)&lt;/b&gt;와 &lt;b&gt;람다식(Lambda Expressions)&lt;/b&gt;에 대한 주요 개념과 문법 리스트 정리입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;1. Null Safety (널 안정성)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin은 NullPointerException(NPE)을 컴파일 단계에서 방지할 수 있도록 설계되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;Nullable(? ) vs Non-Nullable:&lt;/b&gt; 기본적으로 모든 타입은 null을 허용하지 않습니다. &lt;br /&gt;null을 허용하려면 타입 뒤에 ?를 붙여야 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;var name: String = &quot;Kotlin&quot; (null 불가능)&lt;/li&gt;
&lt;li&gt;var name: String? = null (null 가능)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;Safe Call (?.):&lt;/b&gt; 참조 변수가 null이 아닐 때만 멤버에 접근하고, null이면 null을 반환합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val length = name?.length&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;Elvis Operator (?:):&lt;/b&gt; 객체가 null일 경우 사용할 기본값을 지정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val length = name?.length ?: 0 (null이면 0 반환)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,3,0&quot;&gt;Not-null Assertion (!!):&lt;/b&gt; 해당 변수가 절대 null이 아님을 단언합니다. 만약 null일 경우 NPE가 발생하므로 주의해서 사용해야 합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val length = name!!.length&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,4,0&quot;&gt;Safe Cast (as?):&lt;/b&gt; 캐스팅에 실패하면 ClassCastException 대신 null을 반환합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,4,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val str = obj as? String&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,5,0&quot;&gt;let 함수:&lt;/b&gt; 객체가 null이 아닐 때만 특정 코드 블록을 실행할 때 유용합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4,5,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name?.let { println(it) }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;2. Lambda Expressions (람다식)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;람다식은 함수를 값처럼 다루어 변수에 저장하거나 인자로 전달할 수 있는 익명 함수입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;기본 문법:&lt;/b&gt; { 매개변수 -&amp;gt; 본문 } 형태를 가집니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val sum = { x: Int, y: Int -&amp;gt; x + y }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;it (암시적 이름):&lt;/b&gt; 람다의 매개변수가 하나뿐일 경우 이름을 생략하고 it으로 참조할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val numbers = listOf(1, 2, 3)&lt;/li&gt;
&lt;li&gt;numbers.forEach { println(it) }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;Trailing Lambda (마지막 인자가 람다일 때):&lt;/b&gt; 함수의 마지막 인자가 람다식이라면 괄호 바깥으로 뺄 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;list.filter({ it &amp;gt; 0 }) &amp;rarr; list.filter { it &amp;gt; 0 }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,3,0&quot;&gt;고차 함수 (Higher-Order Functions):&lt;/b&gt; 람다를 인자로 받거나 반환하는 함수입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map, filter, reduce 등이 대표적입니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,4,0&quot;&gt;함수 타입 지정:&lt;/b&gt; 변수에 람다를 저장할 때 타입을 명시할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,4,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val isPositive: (Int) -&amp;gt; Boolean = { it &amp;gt; 0 }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;요약 리스트&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 84.3023%;&quot; border=&quot;1&quot; data-path-to-node=&quot;11&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1395%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;b&gt;주요 키워드/연산자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.3488%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1395%;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0,0&quot;&gt;Null Safety&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,1,0&quot;&gt;?, ?., ?:, !!, as?, let&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,2,0&quot;&gt;컴파일 시점에 Null 참조 오류 방지&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1395%;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0,0&quot;&gt;Lambda&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,1,0&quot;&gt;{ }, -&amp;gt;, it&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,2,0&quot;&gt;간결한 코드 작성 및 함수형 프로그래밍 지원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 개념들은 Android 앱 개발 시 데이터를 처리하거나 UI 이벤트를 리스닝할 때 필수적으로 사용됩니다.&lt;/p&gt;</description>
      <category>Programming/Android</category>
      <category>Android</category>
      <category>Lambda Expressions</category>
      <category>null safety</category>
      <category>널 안정성</category>
      <category>람다식</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/80</guid>
      <comments>https://afs0201.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 3 Apr 2026 17:10:05 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 4대 컴포넌트</title>
      <link>https://afs0201.tistory.com/79</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 앱 개발의 근간을 이루는 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;0&quot;&gt;4대 컴포넌트&lt;/b&gt;는 독립적인 형태로 존재하며, &lt;br /&gt;시스템에 의해 관리되는 핵심 구성 요소입니다. &lt;br /&gt;각 컴포넌트는 고유의 목적을 가지며 &lt;b&gt;Intent(인텐트)&lt;/b&gt;라는 메시지 객체를 통해 서로 통신합니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;1. Activity (액티비티)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용자와 상호작용하는 하나의 '화면'&lt;/b&gt;을 의미합니다. &lt;br /&gt;앱에 여러 화면이 있다면 화면마다 하나의 액티비티가 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;특징:&lt;/b&gt; 모든 안드로이드 앱은 최소 하나 이상의 액티비티를 가져야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;생명주기:&lt;/b&gt; 시스템에 의해 생성되고 소멸되는 과정(onCreate &amp;rarr; onStart &amp;rarr; onResume 등)을 직접 관리해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,2,0&quot;&gt;UI 구성:&lt;/b&gt; 일반적으로 XML 레이아웃 파일이나 Jetpack Compose 코드를 통해 화면을 그립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;2. Service (서비스)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;사용자 눈에 보이지 않는 '백그라운드'에서 실행되는 컴포넌트&lt;/b&gt;입니다. &lt;br /&gt;사용자가 다른 앱을 사용하더라도 계속 실행되어야 하는 작업에 사용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;유형:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,1,0,0&quot;&gt;Foreground Service:&lt;/b&gt; 음악 재생, 운동 경로 기록 등 사용자에게 알림(Notification)을 통해 실행 중임을 알리는 서비스.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,1,1,0&quot;&gt;Background Service:&lt;/b&gt; 사용자가 직접 인지하지 못하는 작업(데이터 동기화 등)을 수행. &lt;br /&gt;(최신 안드로이드 버전에서는 배터리 절약을 위해 제한이 많습니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,1,2,0&quot;&gt;Bound Service:&lt;/b&gt; 특정 컴포넌트(액티비티 등)와 연결되어 클라이언트-서버처럼 동작하는 서비스.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;주의점:&lt;/b&gt; 기본적으로 메인 스레드에서 실행되므로, 무거운 작업은 별도의 스레드를 생성하여 처리해야 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;3. Broadcast Receiver (브로드캐스트 수신기)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;안드로이드 시스템이나 다른 앱에서 발생하는 신호(Event)를 수신&lt;/b&gt;하여 반응하는 컴포넌트입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;역할:&lt;/b&gt; &quot;배터리 부족&quot;, &quot;Wi-Fi 연결됨&quot;, &quot;화면 켜짐&quot;, &quot;문자 수신&quot; 등 시스템의 특정 상태 변화를 감지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;동작 방식:&lt;/b&gt; 수신기는 평소엔 대기 상태이다가, 관심 있는 이벤트가 발생하면 onReceive() 메서드를 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;UI 없음:&lt;/b&gt; 자체적인 화면은 없으며, 이벤트를 받으면 알림(Notification)을 띄우거나 &lt;br /&gt;다른 액티비티를 실행하는 등의 동작을 수행합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;4. Content Provider (콘텐츠 제공자)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;앱 간의 데이터를 공유하기 위한 표준 인터페이스&lt;/b&gt;입니다. &lt;br /&gt;보안상 앱은 자신의 데이터 저장소(DB, 파일 등)에만 접근할 수 있지만, &lt;br /&gt;이를 통해 외부 앱이 안전하게 데이터에 접근하도록 허용할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;특징:&lt;/b&gt; 데이터의 실제 저장 방식(SQLite, 파일, 네트워크 등)에 상관없이 동일한 인터페이스(CRUD: Create, Read, Update, Delete)를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;활용 예시:&lt;/b&gt; 전화번호부 앱의 연락처 데이터, 갤러리 앱의 사진 데이터 등을 내 앱으로 가져올 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;SQL 유사성:&lt;/b&gt; 데이터를 쿼리하는 방식이 SQL 문법과 매우 유사합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;요약 및 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 96.8605%;&quot; border=&quot;1&quot; data-path-to-node=&quot;19&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.3488%;&quot;&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%;&quot;&gt;&lt;b&gt;핵심 역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.7907%;&quot;&gt;&lt;b&gt;사용자 가시성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;b&gt;주요 통신 수단&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;19,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0,0&quot;&gt;Activity&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%;&quot;&gt;&lt;span data-path-to-node=&quot;19,1,1,0&quot;&gt;UI 화면 제공 및 사용자 입력 처리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.7907%;&quot;&gt;&lt;span data-path-to-node=&quot;19,1,2,0&quot;&gt;상 (화면 있음)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;span data-path-to-node=&quot;19,1,3,0&quot;&gt;Intent&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;19,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0,0&quot;&gt;Service&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%;&quot;&gt;&lt;span data-path-to-node=&quot;19,2,1,0&quot;&gt;백그라운드 장기 작업 수행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.7907%;&quot;&gt;&lt;span data-path-to-node=&quot;19,2,2,0&quot;&gt;하 (화면 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;span data-path-to-node=&quot;19,2,3,0&quot;&gt;Intent&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;19,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,3,0,0&quot;&gt;Broadcast Receiver&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%;&quot;&gt;&lt;span data-path-to-node=&quot;19,3,1,0&quot;&gt;시스템/앱 이벤트 수신 및 반응&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.7907%;&quot;&gt;&lt;span data-path-to-node=&quot;19,3,2,0&quot;&gt;하 (화면 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;span data-path-to-node=&quot;19,3,3,0&quot;&gt;Intent&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25.3488%;&quot;&gt;&lt;span data-path-to-node=&quot;19,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,4,0,0&quot;&gt;Content Provider&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.2326%;&quot;&gt;&lt;span data-path-to-node=&quot;19,4,1,0&quot;&gt;앱 데이터 공유 및 관리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 22.7907%;&quot;&gt;&lt;span data-path-to-node=&quot;19,4,2,0&quot;&gt;하 (화면 없음)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 18.3721%;&quot;&gt;&lt;span data-path-to-node=&quot;19,4,3,0&quot;&gt;Content Resolver&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;핵심 개념: Intent (인텐트)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;위의 컴포넌트들을 연결해주는 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;21&quot;&gt;'우편 배달부'&lt;/b&gt; 역할을 합니다. 예를 들어, 액티비티에서 다른 액티비티를 띄우거나, 서비스에 명령을 내릴 때 모두 인텐트를 사용합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;안드로이드 앱을 개발할 때는 이 4가지 컴포넌트를 &lt;br /&gt;반드시 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;23&quot;&gt;AndroidManifest.xml&lt;/b&gt; 파일에 등록해야 시스템이 인지하고 실행할 수 있습니다.&lt;/p&gt;</description>
      <category>Programming/Android</category>
      <category>4대 컴포넌트</category>
      <category>activity</category>
      <category>Android</category>
      <category>broadcast receiver</category>
      <category>content provider</category>
      <category>Service</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/79</guid>
      <comments>https://afs0201.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 3 Apr 2026 17:04:57 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin 의 주요 문법 기능 리스트</title>
      <link>https://afs0201.tistory.com/78</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin은 현대적이고 간결한 문법을 지향하며, Java와의 상호운용성을 완벽하게 유지하면서도 &lt;br /&gt;개발자의 생산성을 높이는 다양한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;주요 문법 기능을 카테고리별로 정리해 드립니다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;1. 변수 선언 (Variables)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin은 타입 추론을 지원하며, 가변성(mutability)을 엄격히 구분합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;val (Value):&lt;/b&gt; 읽기 전용 변수 (C#의 readonly와 유사). 한 번 할당하면 값을 변경할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;var (Variable):&lt;/b&gt; 변경 가능한 변수.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;타입 추론:&lt;/b&gt; 타입을 명시하지 않아도 초기화 값에 따라 자동으로 결정됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val age = 30 (Int로 추론)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;2. 함수 (Functions)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;함수 정의가 매우 간결하며, 파라미터 기본값 설정을 지원합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;기본 구조:&lt;/b&gt; fun 함수명(파라미터: 타입): 리턴타입 { ... }&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;단일 표현식 함수:&lt;/b&gt; 본문이 하나인 경우 =를 사용해 간결하게 작성 가능합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fun sum(a: Int, b: Int) = a + b&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;디폴트 파라미터:&lt;/b&gt; C#처럼 인자에 기본값을 줄 수 있어 오버로딩을 줄여줍니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fun greet(name: String = &quot;Guest&quot;) { ... }&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;3. 제어 흐름 (Control Flow)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin에서는 if와 when이 문장(Statement)이 아닌 &lt;b&gt;식(Expression)&lt;/b&gt;으로 사용될 수 있어 결과를 변수에 바로 대입 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;when 식:&lt;/b&gt; C#의 switch와 유사하지만 훨씬 강력합니다. 타입 체크, 범위 체크 등이 가능합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li data-ved=&quot;0CAAQhtANahgKEwiY_e-1_9CTAxUAAAAAHQAAAAAQoQs&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Kotlin&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;val result = when (x) {
    1 -&amp;gt; &quot;One&quot;
    in 2..10 -&amp;gt; &quot;Small number&quot;
    is String -&amp;gt; &quot;It's a string&quot;
    else -&amp;gt; &quot;Unknown&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Smart Casts:&lt;/b&gt; is를 사용하여 타입을 확인하면, &lt;br /&gt;해당 블록 내에서는 명시적 캐스팅 없이 해당 타입의 멤버에 접근할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;4. 클래스와 프로퍼티 (Classes &amp;amp; Properties)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;상속과 프로퍼티 정의가 매우 직관적입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;기본 생성자:&lt;/b&gt; 클래스 이름 옆에 바로 선언하며, 별도의 필드 선언 없이 프로퍼티를 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;Data Class:&lt;/b&gt; 데이터를 담는 목적으로 사용되며, equals(), hashCode(), toString(), copy() 등을 자동으로 생성합니다. &lt;br /&gt;(C#의 record와 유사)
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data class User(val name: String, val age: Int)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;5. 확장 함수 (Extension Functions)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;상속을 받지 않고도 기존 클래스에 새로운 메서드를 추가할 수 있는 기능입니다. &lt;br /&gt;(C#의 Extension Methods와 개념이 같습니다.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fun String.lastChar(): Char = this[length - 1]&lt;/li&gt;
&lt;li&gt;이제 모든 String 객체에서 .lastChar()를 호출할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;6. 스코프 함수 (Scope Functions)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;객체의 컨텍스트 내에서 코드 블록을 실행할 수 있게 해주는 함수들입니다. 코드가 훨씬 읽기 쉬워집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;let:&lt;/b&gt; null 체크 후 실행하거나 결과를 변환할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;apply:&lt;/b&gt; 객체 생성 후 초기화 설정을 할 때 사용 (객체 자신을 반환).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;with, run, also:&lt;/b&gt; 상황에 따라 컨텍스트 객체를 다루는 방식이 조금씩 다릅니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;7. 기타 주요 기능&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;String Interpolation:&lt;/b&gt; 문자열 내에 변수나 식을 직접 삽입합니다. (&quot;Hello, $name!&quot;)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;Sealed Classes:&lt;/b&gt; 클래스 계층 구조를 제한하여 when 식에서 모든 경우의 수를 처리하도록 강제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;Object (Singleton):&lt;/b&gt; 별도의 인스턴스화 과정 없이 단 하나의 객체(싱글톤)를 만들 때 사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;C# 개발자 관점에서의 팁&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,0,0&quot;&gt;C#의 var vs Kotlin의 var:&lt;/b&gt; C#의 var는 타입 추론일 뿐이지만, Kotlin은 val(불변)과 var(가변)을 명확히 구분하여 코드의 안정성을 높입니다. 가능하면 val을 기본으로 사용하는 것이 권장됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25,1,0&quot;&gt;Properties:&lt;/b&gt; Kotlin은 별도의 getter/setter를 만들지 않아도 내부적으로 생성됩니다. 커스텀이 필요할 때만 필드 아래에 get(), set()을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Android</category>
      <category>Android</category>
      <category>kotlin</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/78</guid>
      <comments>https://afs0201.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 3 Apr 2026 16:59:45 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 개발 및 학습 핵심 개념</title>
      <link>https://afs0201.tistory.com/77</link>
      <description>&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;1. 개발 언어: Kotlin (코틀린)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;현재 안드로이드 공식 개발 언어는 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;4&quot;&gt;Kotlin&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;C#과의 유사성:&lt;/b&gt; Kotlin은 C#과 문법적으로나 기능적으로 유사한 점이 많습니다.&lt;br /&gt;(Property, Extension Methods, Null Safety 등). C# 경험이 있으시다면 빠르게 적응하실 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;Java 상호운용성:&lt;/b&gt; 안드로이드 생태계의 뿌리는 Java이므로, Java 코드도 읽을 줄 알면 매우 유리합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;2. 안드로이드 4대 컴포넌트&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 앱은 단순히 main() 함수에서 시작하는 프로그램이 아니라, 시스템에 의해 관리되는 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;7&quot;&gt;4가지 구성 요소&lt;/b&gt;의 결합체입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;8&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0,0&quot;&gt;Activity (액티비티)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,1,1,0&quot;&gt;사용자와 상호작용하는 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;8,1,1,0&quot;&gt;화면&lt;/b&gt;을 담당합니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0,0&quot;&gt;Service (서비스)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,2,1,0&quot;&gt;백그라운드에서 오래 실행되는 작업을 수행합니다 (예: 음악 재생).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,3,0,0&quot;&gt;Broadcast Receiver&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,3,1,0&quot;&gt;시스템이나 다른 앱으로부터 전달되는 신호를 수신합니다 (예: 배터리 부족 알림).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,4,0,0&quot;&gt;Content Provider&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;8,4,1,0&quot;&gt;앱 간의 데이터를 공유할 수 있도록 해줍니다 (예: 연락처 데이터 접근).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;3. 생명주기 (Lifecycle)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;모바일 기기는 리소스(메모리, 배터리)가 제한적입니다. 사용자가 전화를 받거나 다른 앱으로 전환할 때, &lt;br /&gt;내 앱이 &lt;b&gt;어떤 상태(생성, 중지, 소멸 등)&lt;/b&gt;에 있는지를 관리하는 것이 매우 중요합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;핵심 콜백:&lt;/b&gt; onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy()&lt;/li&gt;
&lt;li&gt;이를 제대로 이해하지 못하면 데이터 유실이나 앱 강제 종료(Crash)가 빈번하게 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;4. UI 개발: Jetpack Compose (최신 트렌드)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 XML 파일을 이용해 UI를 설계했지만, 현재는 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;13&quot;&gt;Jetpack Compose&lt;/b&gt;라는 선언형 UI 프레임워크가 대세입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;Python/React 스타일:&lt;/b&gt; Python의 간결함이나 웹의 React/Flutter 방식과 유사하게 코드로 UI를 직접 작성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;장점:&lt;/b&gt; 재사용성이 높고 UI 상태 관리가 직관적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;5. 아키텍처 패턴 (MVVM)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드 앱은 복잡해지기 쉽기 때문에 역할을 분리하는 것이 필수입니다. &lt;br /&gt;Google은 &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;16&quot;&gt;MVVM(Model-View-ViewModel)&lt;/b&gt; 패턴을 권장합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;View:&lt;/b&gt; UI를 표시 (Activity/Fragment)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;ViewModel:&lt;/b&gt; UI에 필요한 데이터를 보유하고 로직을 처리&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;Model:&lt;/b&gt; 데이터 소스 (DB, 네트워크 서버)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;6. 데이터 관리와 네트워크&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;Room (Local DB):&lt;/b&gt; SQL과 유사합니다. SQLite를 추상화한 라이브러리로, 앱 내부 DB 구축에 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;Retrofit (Network):&lt;/b&gt; 서버와 통신(REST API)하여 JSON 데이터를 주고받을 때 사용하는 표준 라이브러리입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;Coroutines (비동기 처리):&lt;/b&gt; 네트워크 작업 등 시간이 걸리는 작업을 처리할 때 UI가 멈추지 않도록 비동기 처리를 하는 &lt;br /&gt;Kotlin의 핵심 기능입니다. (C#의 async/await와 유사합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;학습 추천 경로&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;Kotlin 기초:&lt;/b&gt; Kotlin의 Null Safety와 람다식을 빠르게 훑어보세요.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;안드로이드 스튜디오 설치:&lt;/b&gt; 공식 IDE를 설치하고 &quot;Hello World&quot;를 찍어봅니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;Jetpack Compose 튜토리얼:&lt;/b&gt; 공식 문서의 코드랩(Codelabs)을 통해 UI 구성을 연습하세요.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,3,0&quot;&gt;작은 프로젝트:&lt;/b&gt; 간단한 '메모장 앱'이나 '날씨 API 앱'을 만들며 생명주기와 네트워크를 익히는 것을 추천합니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Programming/Android</category>
      <category>Android</category>
      <category>핵심 개념</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/77</guid>
      <comments>https://afs0201.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 3 Apr 2026 16:51:56 +0900</pubDate>
    </item>
    <item>
      <title>동기/비동기와 블로킹/논블로킹의 차이</title>
      <link>https://afs0201.tistory.com/76</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;동기/비동기와 블로킹/논블로킹은 혼용되어 사용되는 경우가 많지만, 서로 &lt;b data-index-in-node=&quot;40&quot; data-path-to-node=&quot;0&quot;&gt;관점을 두는 지점&lt;/b&gt;이 명확히 다릅니다. &lt;br /&gt;이 둘의 차이를 이해하는 가장 좋은 방법은 '대상'과 '관심사'를 나누어 보는 것입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;1. 동기(Sync) vs 비동기(Async): &quot;시간과 순서&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;이 개념은 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;3&quot;&gt;작업의 순서와 완료 여부를 누가 신경 쓰느냐&lt;/b&gt;에 초점이 맞춰져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;동기 (Synchronous):&lt;/b&gt; 요청한 작업의 결과가 나올 때까지 기다리거나, 끝나는 시점을 계속 확인하며 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;4,0,0&quot;&gt;순차적으로&lt;/b&gt; 실행합니다. 호출하는 함수와 호출되는 함수의 시간이 일치해야 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;비동기 (Asynchronous):&lt;/b&gt; 작업을 요청해두고 결과가 나오든 말든 신경 쓰지 않고 다음 일을 합니다. 결과는 나중에 **콜백(Callback)**이나 &lt;b data-index-in-node=&quot;88&quot; data-path-to-node=&quot;4,1,0&quot;&gt;이벤트&lt;/b&gt;를 통해 전달받습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;2. 블로킹(Blocking) vs 논블로킹(Non-blocking): &quot;제어권&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;이 개념은 &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;6&quot;&gt;호출된 함수가 호출한 함수에게 제어권을 바로 돌려주느냐&lt;/b&gt;에 초점이 맞춰져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;블로킹 (Blocking):&lt;/b&gt; 호출된 함수가 자신의 작업을 마칠 때까지 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;7,0,0&quot;&gt;제어권을 넘겨주지 않습니다.&lt;/b&gt; 호출한 쪽은 아무것도 못 하고 그 자리에서 대기(Stop)해야 합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;논블로킹 (Non-blocking):&lt;/b&gt; 호출된 함수가 작업을 다 못 끝냈더라도 &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;7,1,0&quot;&gt;제어권을 바로 반환&lt;/b&gt;합니다. 호출한 쪽은 제어권을 돌려받았으므로 다른 일을 할 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;3. 이 둘의 조합 (2x2 매트릭스)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;실제 시스템에서는 이 두 개념이 조합되어 네 가지 케이스로 나타납니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;11&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.6047%;&quot;&gt;&lt;b&gt;블로킹 (Blocking)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.5814%;&quot;&gt;&lt;b&gt;논블로킹 (Non-blocking)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;&lt;span data-path-to-node=&quot;11,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0,0&quot;&gt;동기 (Sync)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.6047%;&quot;&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,1,0&quot;&gt;Sync-Blocking&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;결과가 나올 때까지 아무것도 못 하고 기다림. &lt;br /&gt;(가장 일반적인 형태)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.5814%;&quot;&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,2,0&quot;&gt;Sync-Non-blocking&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;제어권은 바로 받아서 다른 일은 할 수 있지만, &lt;br /&gt;작업 완료 여부를 계속 물어봄.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 15.6977%;&quot;&gt;&lt;span data-path-to-node=&quot;11,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0,0&quot;&gt;비동기 (Async)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.6047%;&quot;&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,1,0&quot;&gt;Async-Blocking&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;결과를 신경 안 써도 되는데, &lt;br /&gt;제어권을 안 줘서 억지로 기다림. &lt;br /&gt;(비효율적, 드문 케이스)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.5814%;&quot;&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,2,0&quot;&gt;Async-Non-blocking&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;작업 시켜두고 바로 내 할 일 함. &lt;br /&gt;작업이 끝나면 신호가 옴. &lt;br /&gt;(가장 효율적)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;실생활 비유 (커피 주문)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;Sync-Blocking:&lt;/b&gt; 점원에게 주문을 하고, 커피가 나올 때까지 카운터 앞에서 아무것도 안 하고 서서 기다립니다. (가장 답답함)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;Sync-Non-blocking:&lt;/b&gt; 점원에게 주문을 하고 자리에 앉습니다. 하지만 커피가 나왔는지 1분마다 카운터에 가서 물어봅니다. (은근히 바쁨)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;Async-Non-blocking:&lt;/b&gt; 점원에게 주문하고 자리에 앉아 내 할 일을 합니다. 커피가 다 되면 **진동벨(Callback)**이 울립니다. 그때 받으러 갑니다. (가장 효율적)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,0&quot;&gt;Async-Blocking:&lt;/b&gt; 점원에게 주문하고 진동벨도 받았는데, 왠지 모르게 카운터 앞에서 커피가 나올 때까지 멍하니 서 있습니다. (비효율적)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;보통 성능 최적화가 중요한 네트워크 서버나 복잡한 UI 처리에서는 &lt;b data-index-in-node=&quot;37&quot; data-path-to-node=&quot;16&quot;&gt;Async-Non-blocking&lt;/b&gt; 방식을 지향하게 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;C# 기준, Async-Non-blocking 개념의 코드 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;C#에서 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;0&quot;&gt;Async-Non-blocking&lt;/b&gt;은 async와 await 키워드, 그리고 Task 클래스를 통해 구현됩니다. &lt;br /&gt;이 패턴의 핵심은 작업을 요청한 스레드가 결과가 나올 때까지 멈춰(Blocking) 있는 것이 아니라, &lt;br /&gt;제어권을 시스템에 반환하여 다른 일을 할 수 있게 하는 것입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;1. 기본 구조: Task, async, await&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,0,0&quot;&gt;Task / Task&amp;lt;T&amp;gt;:&lt;/b&gt; 미래에 완료될 작업(약속)을 나타내는 객체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,1,0&quot;&gt;async:&lt;/b&gt; 해당 메서드가 비동기 작업을 포함하고 있음을 선언하며, 내부에서 await를 사용할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3,2,0&quot;&gt;await:&lt;/b&gt; 비동기 작업이 완료될 때까지 기다리되, &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;3,2,0&quot;&gt;현재 스레드를 점유하지 않고 제어권을 호출자에게 돌려주는&lt;/b&gt; 핵심 키워드입니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;2. 코드 예시: 데이터베이스 조회 (I/O Bound)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 동기 방식과 비동기(Async-Non-blocking) 방식을 비교해 보겠습니다.&lt;/p&gt;
&lt;div data-hveid=&quot;5&quot;&gt;
&lt;div&gt;&lt;span&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;using System;
using System.Threading.Tasks;

public class SCMService
{
    // 1. 동기 방식 (Blocking)
    public void SyncProcess()
    {
        Console.WriteLine(&quot;1. 데이터 조회 시작...&quot;);
        FetchDataFromDB(); // 여기서 스레드가 3초간 멈춤 (Blocking)
        Console.WriteLine(&quot;3. 모든 작업 완료.&quot;);
    }

    // 2. 비동기 방식 (Async-Non-blocking)
    public async Task AsyncProcess()
    {
        Console.WriteLine(&quot;1. 비동기 데이터 조회 시작...&quot;);
        
        // await를 만나면 제어권을 반환합니다. 
        // 스레드는 자유로워져서 UI 렌더링이나 다른 요청을 처리할 수 있습니다.
        Task&amp;lt;string&amp;gt; dataTask = FetchDataFromDBAsync(); 
        
        Console.WriteLine(&quot;2. 조회 중인 동안 다른 업무(로그 기록 등) 처리 가능!&quot;);

        string result = await dataTask; // 작업이 끝나면 여기서부터 다시 실행
        
        Console.WriteLine($&quot;4. 조회 완료: {result}&quot;);
    }

    private void FetchDataFromDB() { /* 3초 대기 */ Task.Delay(3000).Wait(); }
    
    private async Task&amp;lt;string&amp;gt; FetchDataFromDBAsync() 
    {
        await Task.Delay(3000); // 실제 DB I/O를 시뮬레이션
        return &quot;SCM Data&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;3. Non-blocking의 핵심 메커니즘&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;C#의 await가 호출되는 순간, 다음과 같은 일이 일어납니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;상태 저장:&lt;/b&gt; 현재 메서드의 상태(지역 변수 등)를 '상태 머신(State Machine)'에 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;제어권 반환:&lt;/b&gt; 호출한 쪽(예: UI 스레드 또는 스레드 풀)으로 제어권을 즉시 돌려줍니다. &lt;br /&gt;덕분에 프로그램이 '응답 없음' 상태에 빠지지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;작업 완료 및 복귀:&lt;/b&gt; 비동기 작업(I/O 등)이 끝나면, 저장했던 상태를 복원하여 await 다음 줄부터 코드를 계속 실행합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;4. 주의사항: Task.Run vs I/O Bound&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;I/O Bound (DB, 파일, 네트워크):&lt;/b&gt; 위의 예시처럼 await를 사용하면 스레드 자체가 대기하지 않으므로 &lt;br /&gt;진정한 Non-blocking입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;CPU Bound (복잡한 연산):&lt;/b&gt; Task.Run(() =&amp;gt; { ... })을 사용하여 별도의 백그라운드 스레드에서 실행합니다. &lt;br /&gt;이 경우 호출한 스레드는 Non-blocking이지만, 내부적으로는 다른 스레드 하나를 점유하게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스(Oracle 등) 처리를 비동기로 전환해 보시면, &lt;br /&gt;특히 많은 동시 접속자가 발생하는 환경에서 서버 자원 효율이 급격히 좋아지는 것을 체감하실 수 있습니다.&lt;/p&gt;</description>
      <category>SW Engineering/운영체제</category>
      <category>asynchronous</category>
      <category>blocking</category>
      <category>non-blocking</category>
      <category>synchronous</category>
      <category>논블로킹</category>
      <category>동기</category>
      <category>블로킹</category>
      <category>비동기</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/76</guid>
      <comments>https://afs0201.tistory.com/76#entry76comment</comments>
      <pubDate>Fri, 3 Apr 2026 16:41:34 +0900</pubDate>
    </item>
    <item>
      <title>비동기 코드의 그림자, 콜백 지옥(Callback Hell)</title>
      <link>https://afs0201.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 지옥(Callback Hell)은 개발자들 사이에서는 '멸망의 피라미드(Pyramid of Doom)'라고도 불립니다.&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;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;1. 콜백 지옥(Callback Hell)이란?&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;비동기 처리를 위해 콜백 함수를 연속해서 사용할 때, &lt;br /&gt;코드가 깊게 중첩되어 가독성이 떨어지고 유지보수가 불가능해지는 상태를 말합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;&lt;br /&gt;왜 발생할까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;여러 비동기 작업을 순차적으로 실행해야 할 때 발생합니다. &lt;br /&gt;예를 들어, &quot;로그인 &amp;rarr; 사용자 정보 가져오기 &amp;rarr; 사용자 게시물 가져오기&quot;와 같은 흐름이죠.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiqrYmDiMyTAxUAAAAAHQAAAAAQzwk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;//   전형적인 콜백 지옥의 모습
step1(function(res1) {
    step2(res1, function(res2) {
        step3(res2, function(res3) {
            step4(res3, function(res4) {
                console.log(&quot;드디어 끝!: &quot; + res4);
            });
        });
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;문제점:&lt;/b&gt; 어디서 에러가 났는지 찾기 힘들고, 코드가 오른쪽으로 계속 밀려나서 읽기가 매우 고통스럽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;2. 해결사 1: Promise (약속)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;ES6(2015년)에서 등장한 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;9&quot;&gt;Promise&lt;/b&gt;는 &quot;지금은 없지만, 나중에 성공 혹은 실패 결과를 꼭 줄게!&quot;라는 약속입니다. &lt;br /&gt;콜백의 중첩을 &lt;b data-index-in-node=&quot;77&quot; data-path-to-node=&quot;9&quot;&gt;체이닝(Chaining)&lt;/b&gt; 방식으로 풀어냅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;특징:&lt;/b&gt; .then()을 사용하여 비동기 작업을 한 줄씩 순차적으로 이어 붙입니다. &lt;br /&gt;에러 처리는 마지막에 .catch() 한 번으로 해결 가능하죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiqrYmDiMyTAxUAAAAAHQAAAAAQ0Ak&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;//   Promise를 사용한 개선
step1()
    .then(res1 =&amp;gt; step2(res1))
    .then(res2 =&amp;gt; step3(res2))
    .then(res3 =&amp;gt; step4(res3))
    .catch(error =&amp;gt; console.error(error)); // 에러 처리가 깔끔!
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;3. 최종 보스: async / await&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;ES2017에서 도입된 이 방식은 현재 비동기 코드를 짜는 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;14&quot;&gt;가장 표준적이고 깔끔한 방법&lt;/b&gt;입니다. &lt;br /&gt;Promise를 기반으로 하지만, 마치 &lt;b data-index-in-node=&quot;75&quot; data-path-to-node=&quot;14&quot;&gt;동기 코드(위에서 아래로 흐르는 코드)처럼 보이게&lt;/b&gt; 만들어줍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;특징:&lt;/b&gt; 함수 앞에 async를 붙이고, 비동기 작업 앞에 await을 붙입니다. 코드가 직관적이라 읽기 매우 편합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiqrYmDiMyTAxUAAAAAHQAAAAAQ0Qk&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;//   async/await을 사용한 환상적인 코드
async function runTasks() {
    try {
        const res1 = await step1();
        const res2 = await step2(res1);
        const res3 = await step3(res2);
        console.log(&quot;결과:&quot;, res3);
    } catch (error) {
        console.error(&quot;에러 발생!&quot;, error);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;JS 뿐만 아니라, C# 에서 발생하는 콜백 지옥의 예시 코드도 보여 드리겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;C#은 비동기 처리에 있어 전 세계에서 가장 우아한 문법을 가진 언어 중 하나로 꼽히지만,&lt;br /&gt;과거(async/await 도입 전)에는 C# 도 예외 없이 '지옥'을 맛봐야 했습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;C#에서 대리자(Delegate)나 Action을 사용해 구현한 콜백 지옥과,&lt;br /&gt;이를 현대적인 async/await으로 심폐소생술 하는 과정도 보여드릴게요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1.   C# 버전: 콜백 지옥 (The Callback Hell)&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;과거에는 비동기 작업이 끝난 뒤 실행할 로직을 Action&amp;lt;T&amp;gt; 같은 대리자로 넘겨주었습니다.&lt;br /&gt;작업이 3단계만 깊어져도 코드가 오른쪽으로 무섭게 탈출하기 시작합니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot; data-hveid=&quot;0&quot; data-ved=&quot;0CAAQhtANahgKEwiqrYmDiMyTAxUAAAAAHQAAAAAQ-Ak&quot;&gt;
&lt;div&gt;&lt;span&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;// 로그인 -&amp;gt; 프로필 로드 -&amp;gt; 게시물 로드 순서의 로직
public void LoginAndLoadData()
{
    AuthService.Login(&quot;user_id&quot;, &quot;password&quot;, (user) =&amp;gt; {
        Console.WriteLine($&quot;{user.Name}님 로그인 성공!&quot;);

        ProfileService.GetProfile(user.Id, (profile) =&amp;gt; {
            Console.WriteLine(&quot;프로필 정보를 가져왔습니다.&quot;);

            PostService.GetLatestPosts(profile.Id, (posts) =&amp;gt; {
                Console.WriteLine($&quot;{posts.Count}개의 게시물을 찾았습니다.&quot;);
                
                // 여기서 또 뭔가를 하려면...? (탈출 불가)
            }, (postError) =&amp;gt; Console.WriteLine($&quot;게시물 에러: {postError}&quot;));

        }, (profileError) =&amp;gt; Console.WriteLine($&quot;프로필 에러: {profileError}&quot;));

    }, (loginError) =&amp;gt; Console.WriteLine($&quot;로그인 에러: {loginError}&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;6&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;6,0,0&quot; data-index-in-node=&quot;0&quot;&gt;문제점:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;괄호 })가 중첩되어 닫히는 모습이 흡사 '아뵤~' 하는 이소룡의 옆차기 같습니다.&lt;br /&gt;에러 처리를 위해 매번 실패 콜백을 따로 넘겨야 해서 코드 양도 어마어마하게 늘어납니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;2.   C# 버전: 개선된 코드 (The Async/Await)&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;C#의 Task와 async/await을 사용하면 위 코드가 놀라울 정도로 평온해집니다.&lt;br /&gt;비동기 작업이지만, 마치&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;9&quot; data-index-in-node=&quot;64&quot;&gt;동기(순차적) 코드&lt;/b&gt;를 읽는 것처럼 위에서 아래로 흐릅니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot; data-hveid=&quot;0&quot; data-ved=&quot;0CAAQhtANahgKEwiqrYmDiMyTAxUAAAAAHQAAAAAQ-Qk&quot;&gt;
&lt;div&gt;&lt;span&gt;C#&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;public async Task LoginAndLoadDataAsync()
{
    try
    {
        // await 한 줄로 '나중에 올 결과'를 기다립니다.
        var user = await AuthService.LoginAsync(&quot;user_id&quot;, &quot;password&quot;);
        Console.WriteLine($&quot;{user.Name}님 로그인 성공!&quot;);

        var profile = await ProfileService.GetProfileAsync(user.Id);
        Console.WriteLine(&quot;프로필 정보를 가져왔습니다.&quot;);

        var posts = await PostService.GetLatestPostsAsync(profile.Id);
        Console.WriteLine($&quot;{posts.Count}개의 게시물을 찾았습니다.&quot;);
    }
    catch (Exception ex)
    {
        // 모든 단계의 에러를 한곳에서 우아하게 처리합니다.
        Console.WriteLine($&quot;작업 중 오류 발생: {ex.Message}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;3. 무엇이 달라졌나요?&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-path-to-node=&quot;13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;콜백 방식 (Legacy)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;async / await (Modern)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,1,0,0&quot;&gt;&lt;b data-path-to-node=&quot;13,1,0,0&quot; data-index-in-node=&quot;0&quot;&gt;코드 흐름&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,1,1,0&quot;&gt;안으로 파고드는 중첩 구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,1,2,0&quot;&gt;위에서 아래로 흐르는 선형 구조&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,2,0,0&quot;&gt;&lt;b data-path-to-node=&quot;13,2,0,0&quot; data-index-in-node=&quot;0&quot;&gt;에러 처리&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,2,1,0&quot;&gt;각 함수마다 실패 콜백을 따로 정의&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,2,2,0&quot;&gt;익숙한 try-catch 하나로 해결&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,3,0,0&quot;&gt;&lt;b data-path-to-node=&quot;13,3,0,0&quot; data-index-in-node=&quot;0&quot;&gt;가독성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,3,1,0&quot;&gt;로직 파악이 매우 힘듦&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,3,2,0&quot;&gt;소설 읽듯 로직이 한눈에 들어옴&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,4,0,0&quot;&gt;&lt;b data-path-to-node=&quot;13,4,0,0&quot; data-index-in-node=&quot;0&quot;&gt;반환 타입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,4,1,0&quot;&gt;void (결과를 알기 어려움)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;13,4,2,0&quot;&gt;Task 또는 Task&amp;lt;T&amp;gt; (상태 추적 가능)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;  팁: C#의 비동기 명명 규칙&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;C#에서는 비동기로 작동하는 메서드 이름 뒤에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Async&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 붙이는 것이 국룰(Naming Convention)입니다.&lt;br /&gt;만약 메서드 이름이 DoSomethingAsync()라면, &quot;아, 이 녀석은 await으로 기다려줘야겠구나!&quot;라고 바로 눈치챌 수 있죠.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;요약 및 비교&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;19&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Callback&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Promise&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;async / await&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0,0&quot;&gt;코드 형태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,1,0&quot;&gt;중첩 구조 (피라미드)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,2,0&quot;&gt;체이닝 구조 (선형)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,1,3,0&quot;&gt;동기 코드 방식 (절차적)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0,0&quot;&gt;가독성&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,1,0&quot;&gt;매우 낮음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,2,0&quot;&gt;보통&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,2,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,3,0&quot;&gt;매우 높음&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,3,0,0&quot;&gt;에러 처리&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,3,1,0&quot;&gt;각 단계마다 해줘야 함&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,3,2,0&quot;&gt;.catch()로 통합 관리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;19,3,3,0&quot;&gt;try-catch로 통합 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;결국 비동기 구조에서 콜백으로 시작된 흐름이, 더 읽기 쉽고 관리하기 편한 async/await까지 진화해 온 셈입니다.&lt;/p&gt;</description>
      <category>SW Engineering/운영체제</category>
      <category>async</category>
      <category>await</category>
      <category>callback</category>
      <category>callback hell</category>
      <category>콜백 지옥</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/75</guid>
      <comments>https://afs0201.tistory.com/75#entry75comment</comments>
      <pubDate>Wed, 1 Apr 2026 19:56:19 +0900</pubDate>
    </item>
    <item>
      <title>비동기 구조에서 콜백 함수의 역할</title>
      <link>https://afs0201.tistory.com/74</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;코딩을 하다 보면 &quot;나중에 끝날 테니, 다 되면 이 함수를 실행해줘&quot;라고 부탁해야 할 때가 오는데, &lt;br /&gt;그때 사용하는 '나중에 실행될 실행권'이 바로 콜백(Callback) 함수입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;비동기 구조에서 콜백 함수가 왜 필수적인지, 그 핵심 역할을 쉽게 정리해 드릴게요.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size23&quot;&gt;1. 비동기 작업의 &quot;완료 알림&quot; 역할&lt;/h3&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;비동기 함수(예: 서버에서 데이터 가져오기, 파일 읽기)는 호출 즉시 결과를 반환하지 않습니다. &lt;br /&gt;작업이 배경(Background)에서 돌아가는 동안 메인 프로그램은 다음 코드를 실행하러 떠나버리죠.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;이때 &quot;작업이 끝났어! 이제 이 결과를 봐!&quot;라고 프로그램에 알려주는 신호등 역할을 콜백 함수가 수행합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;2. 콜백 함수의 3가지 핵심 임무&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;비동기 코드 내에서 콜백은 단순히 '끝났다'고 말하는 것 이상의 일을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;결과값 전달:&lt;/b&gt; 서버에서 받아온 데이터나 읽어온 파일의 내용을 다음 로직으로 안전하게 전달합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;실행 순서 보장:&lt;/b&gt; 비동기 작업은 언제 끝날지 알 수 없습니다.&lt;br /&gt;특정 작업(A)이 반드시 끝난 뒤에 다음 작업(B)을 해야 할 때, B를 A의 콜백으로 넣어 순서를 강제합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;에러 처리:&lt;/b&gt; 작업 중 문제가 생겼을 때 &quot;에러가 났으니 이렇게 처리해&quot;라는 지침을 내리는 통로가 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;3. 쉬운 비유: 카페 주문 시스템&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;이 상황을 카페에 비유하면 이해가 한결 빠릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;동기(Sync):&lt;/b&gt; 커피를 주문하고 나올 때까지 카운터 앞에 서서 기다립니다. &lt;br /&gt;뒤에 온 손님들은 줄만 서 있고 아무도 주문을 못 하죠. (비효율적)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;비동기(Async):&lt;/b&gt; 주문을 하고 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;12,1,0&quot;&gt;진동벨&lt;/b&gt;을 받은 뒤 자리에 가서 책을 읽거나 친구와 수다를 떱니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;콜백(Callback):&lt;/b&gt; 여기서 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;12,2,0&quot;&gt;진동벨&lt;/b&gt;이 바로 콜백 함수입니다. 커피가 완성되었을 때 나를 호출(Call)하여 다시 카운터로 오게(Back) 만드는 장치니까요.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;4. 간단한 코드 예시 (JavaScript)&lt;/h3&gt;
&lt;div data-hveid=&quot;5&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function 주문하기(메뉴, 콜백) {
    console.log(`${메뉴} 주문 접수!`);
    
    // 3초가 걸리는 비동기 작업 (커피 제조)
    setTimeout(() =&amp;gt; {
        const 결과 = `${메뉴} 완성!`;
        콜백(결과); // 작업이 끝나면 콜백 함수를 실행하며 결과 전달
    }, 3000);
}

// 여기서 (커피) =&amp;gt; { ... } 부분이 콜백 함수입니다.
주문하기(&quot;아이스 아메리카노&quot;, (커피) =&amp;gt; {
    console.log(`알림: ${커피} 맛있게 드세요!`);
});

console.log(&quot;주문해놓고 딴짓 하는 중...&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16&quot;&gt;&lt;br /&gt;출력 결과:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;아이스 아메리카노 주문 접수!&lt;/li&gt;
&lt;li&gt;주문해놓고 딴짓 하는 중...&lt;/li&gt;
&lt;li&gt;(3초 후) 알림: 아이스 아메리카노 완성! 맛있게 드세요!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;요약하자면&lt;/h3&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;콜백 함수는 비동기 작업이 &quot;제어권을 잠시 내려놓았다가, 작업이 끝난 시점에 다시 그 흐름을 이어가기 위한 연결 고리&quot;라고 보시면 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;요즘은 콜백 지옥(Callback Hell)을 피하기 위해 Promise나 async/await 같은 문법을 더 많이 쓰지만, 그 근간에는 여전히 이 콜백의 원리가 흐르고 있답니다.&lt;/p&gt;</description>
      <category>SW Engineering/운영체제</category>
      <category>callback</category>
      <category>비동기</category>
      <category>비동기 구조</category>
      <category>운영체제</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/74</guid>
      <comments>https://afs0201.tistory.com/74#entry74comment</comments>
      <pubDate>Wed, 1 Apr 2026 18:42:26 +0900</pubDate>
    </item>
    <item>
      <title>완성된 Docker 환경 업로드 및 Docker 내부 에러 로그 확인</title>
      <link>https://afs0201.tistory.com/73</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;완성된 도커 환경을 실제 서버에 올리고, 문제가 생겼을 때 원인을 파악하는 것은 실제 서비스 운영의 핵심입니다. &lt;br /&gt;C# 기반의 자재 관리 시스템이나 라즈베리파이 원격 모터 제어 환경을 외부 우분투(Ubuntu) 서버나 클라우드로 배포한다고 가정하고 전체적인 흐름을 설명해 드리겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;1. 도커 환경을 실제 서버에 배포하는 과정&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;도커로 만든 환경을 다른 장비로 넘기는 방법은 크게 두 가지가 있습니다. 상황에 맞게 선택하시면 됩니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;방법 A: 도커 허브(Registry)를 이용한 배포 (가장 표준적인 방법)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;스마트폰의 '앱 스토어'에 앱을 올리고, 다른 폰에서 다운받아 설치하는 것과 완벽히 같은 원리입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;내 PC에서 이미지 굽기 (Build):&lt;/b&gt; 내 컴퓨터에서 docker-compose build 명령어를 통해 C# 서버 코드가 담긴 도커 이미지를 최종 완성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;저장소로 밀어 넣기 (Push):&lt;/b&gt; 완성된 도커 이미지를 도커 허브(Docker Hub)나 프라이빗 이미지 저장소(AWS ECR 등)에 업로드합니다. (docker push 내계정/scm-backend:v1)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;원격 리눅스 서버 접속:&lt;/b&gt; SSH를 통해 외부 우분투 서버나 라즈베리파이에 접속합니다. (서버에는 도커만 깔려 있으면 됩니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0&quot;&gt;가져와서 실행하기 (Pull &amp;amp; Run):&lt;/b&gt; 저장소에서 이미지를 다운받고(docker pull), docker-compose.yml 파일을 실행하여 컨테이너를 띄웁니다. (docker-compose up -d)&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size20&quot;&gt;방법 B: Git(소스코드) 기반 빌드 배포 (소규모/개인 프로젝트에 편리함)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;이미지를 통째로 옮기지 않고, 소스코드와 '설명서(Dockerfile)'만 서버로 보내서 서버가 직접 빵(이미지)을 굽게 만드는 방식입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내 PC에서 작성한 코드와 Dockerfile, docker-compose.yml을 GitHub 등에 올립니다.&lt;/li&gt;
&lt;li&gt;외부 우분투 서버에서 git clone으로 해당 파일들을 그대로 내려받습니다.&lt;/li&gt;
&lt;li&gt;서버에서 바로 docker-compose up -d --build 명령어를 입력합니다. 서버가 스스로 코드를 읽어 도커 이미지를 만들고 컨테이너를 실행합니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;  배포 시 팁:&lt;/b&gt; Oracle DB처럼 용량이 크고 이미 만들어져 있는 공식 이미지는 '방법 A'처럼 저장소에서 바로 가져다 쓰고, 직접 개발하신 C# 서버나 모터 제어 통신 서버는 '방법 B'처럼 서버에서 코드를 당겨와 바로 빌드하는 방식을 혼합해서 사용하는 것이 일반적입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;br /&gt;2. 도커 환경 내부에서 에러 로그 확인하는 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;서버를 띄웠는데 접속이 안 되거나 DB 연동 에러가 났을 때, 가상 컨테이너 내부로 일일이 들어갈 필요 없이 밖에서 아주 쉽게 로그를 뽑아볼 수 있습니다. 도커는 컨테이너 안에서 출력되는 모든 텍스트(표준 출력)를 자체적으로 수집해 줍니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;단일 컨테이너의 로그를 볼 때&lt;/h4&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;특정 컨테이너(예: C# 백엔드 서버)에서 발생한 에러만 집중적으로 보고 싶을 때 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;기본 로그 확인:&lt;/b&gt; docker logs agri-scm-backend (컨테이너가 시작된 시점부터 지금까지의 모든 로그를 한 번에 출력합니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;실시간 로그 추적 (가장 많이 씀):&lt;/b&gt; docker logs -f agri-scm-backend (-f는 follow의 약자로, 터미널을 끄기 전까지 실시간으로 찍히는 에러나 로그를 계속 보여줍니다. 에러를 재현하면서 원인을 찾을 때 필수입니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;최근 로그만 확인:&lt;/b&gt; docker logs --tail 50 agri-scm-backend (너무 로그가 길 때, 가장 최근에 발생한 50줄의 로그만 보여줍니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;전체 시스템(여러 컨테이너)의 로그를 한 번에 볼 때&lt;/h4&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;Oracle DB 컨테이너와 C# 서버 컨테이너가 서로 통신하는 과정에서 어디서 문제가 터졌는지 흐름을 파악하고 싶을 때는 도커 컴포즈 명령어를 씁니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;전체 실시간 로그 확인:&lt;/b&gt; docker-compose.yml 파일이 있는 폴더에서 아래 명령어를 입력합니다. &lt;br /&gt;&lt;b&gt;docker-compose logs -f&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;이 명령어를 치면 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;19,1,0&quot;&gt;[oracle-db]&lt;/b&gt; 시스템 준비 완료, &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;19,1,0&quot;&gt;[scm-backend]&lt;/b&gt; DB 연결 실패(Timeout) 등 여러 컨테이너의 로그가 각기 다른 색상으로 한 화면에 섞여서 출력되므로 시스템 전체의 문제 상황을 입체적으로 진단할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/기타 팁</category>
      <category>docker</category>
      <category>Docker Error Log</category>
      <category>Docker Upload</category>
      <category>Docker 업로드</category>
      <category>도커 업로드</category>
      <category>도커 에러</category>
      <category>도커 에러 로그</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/73</guid>
      <comments>https://afs0201.tistory.com/73#entry73comment</comments>
      <pubDate>Sat, 28 Mar 2026 16:15:36 +0900</pubDate>
    </item>
    <item>
      <title>Dockerfile 의 내부 구조 및 Docker Volume</title>
      <link>https://afs0201.tistory.com/72</link>
      <description>&lt;p data-path-to-node=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;C# 애플리케이션을 도커 환경으로 감싸는 방법과, 데이터베이스의 생명줄인 볼륨(Volume)에 대한 내용입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;1. C# 앱을 포장하는 설명서: Dockerfile의 내부 구조&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;앞서 docker-compose.yml 파일에서 build: . 옵션을 주면, 도커는 해당 폴더에 있는 Dockerfile이라는 텍스트 문서를 읽어들여 &lt;br /&gt;C# 서버용 이미지를 직접 만들어냅니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;C# (.NET) 애플리케이션을 도커로 만들 때는 보통 '멀티 스테이지 빌드(Multi-stage build)'라는 아주 효율적인 방식을 사용합니다. 개발 및 빌드용 무거운 환경과, 실제 실행에 필요한 가벼운 환경을 분리하는 기법입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;자재 관리 백엔드 서버를 위한 표준적인 Dockerfile 구조는 다음과 같습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiYyZLH28GTAxUAAAAAHQAAAAAQmgU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;# [Stage 1: 빌드 환경]
# 소스코드를 컴파일하기 위해 무거운 .NET SDK(소프트웨어 개발 키트) 이미지를 가져옵니다.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# 프로젝트 파일(.csproj)을 먼저 복사하고 패키지(의존성)를 복원합니다.
# (이 과정을 먼저 하면, 소스코드가 바뀌어도 패키지 다운로드 과정을 건너뛸 수 있어 빌드가 훨씬 빠릅니다.)
COPY *.csproj ./
RUN dotnet restore

# 나머지 모든 소스코드를 복사하고 릴리즈 모드로 빌드하여 결과물을 'out' 폴더에 생성합니다.
COPY . ./
RUN dotnet publish -c Release -o out

# ---------------------------------------------------

# [Stage 2: 실행 환경]
# 실제 구동할 때는 무거운 SDK가 필요 없으므로, 아주 가벼운 ASP.NET 런타임 이미지만 가져옵니다.
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app

# Stage 1에서 빌드 완료된 결과물('out' 폴더)만 가벼운 실행 환경으로 쏙 빼서 복사해 옵니다.
COPY --from=build-env /app/out .

# 컨테이너가 켜질 때 실행할 명령어를 지정합니다. (빌드된 C# 백엔드 서버 실행)
ENTRYPOINT [&quot;dotnet&quot;, &quot;ScmBackend.dll&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;&lt;br /&gt;  핵심 요약:&lt;/b&gt; 소스코드를 굽는 오븐(SDK)과 완성된 빵을 진열하는 쇼케이스(Runtime)를 분리하는 것입니다. 이렇게 하면 최종 도커 이미지의 용량이 획기적으로 줄어들어 서버에 배포하고 실행하는 속도가 매우 빨라집니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;2. 컨테이너의 기억력: 도커 볼륨(Volume)과 데이터 보존&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;도커 컨테이너는 기본적으로 '무상태(Stateless)'를 지향합니다. &lt;br /&gt;즉, 컨테이너가 삭제되면 그 안에 있던 파일도 모두 깔끔하게 증발합니다.&lt;br /&gt;&lt;br /&gt;백엔드 서버 코드는 삭제되더라도 이미지를 다시 실행하면 되니 문제가 없지만, &lt;b data-index-in-node=&quot;128&quot; data-path-to-node=&quot;10&quot;&gt;데이터베이스에 쌓인 자재 재고 기록이나 거래 데이터가 통째로 날아가면 대형 사고&lt;/b&gt;가 발생합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 문제를 해결하는 기술이 바로 '도커 볼륨(Volume)'입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;볼륨은 호스트 PC(내 컴퓨터나 리눅스 서버)의 하드디스크 한구석을 떼어내어, 도커 컨테이너의 특정 내부 폴더와 &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;12&quot;&gt;마법의 터널&lt;/b&gt;을 뚫어 연결하는 기술입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;앞서 보여드린 docker-compose.yml 파일의 하단과 Oracle DB 설정을 다시 떠올려 보겠습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiYyZLH28GTAxUAAAAAHQAAAAAQmwU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;# (docker-compose.yml 파일의 일부)
    volumes:
      - oracle-data:/opt/oracle/oradata 

# 파일 맨 아래 볼륨 정의
volumes:
  oracle-data:
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;oracle-data&lt;/b&gt;: 도커가 호스트 PC의 안전한 공간에 만들어둔 영구 저장소의 이름입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;/opt/oracle/oradata&lt;/b&gt;: Oracle 데이터베이스가 컨테이너 내부에서 실제 데이터를 저장하는 경로입니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16&quot;&gt;데이터 보존 원리:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;콜론(:)을 기준으로 앞뒤를 연결합니다. 오라클 DB가 내부 경로(/opt/oracle/oradata)에 데이터를 쓰면, 그 데이터는 사실 컨테이너 내부가 아니라 호스트 PC의 안전한 공간(oracle-data 볼륨)에 고스란히 저장됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;만약 실수로, 혹은 업데이트를 위해 오라클 DB 컨테이너를 삭제해 버렸다고 가정해 보겠습니다. 컨테이너 껍데기는 날아갔지만, 호스트 PC에 연결해 둔 볼륨 안의 SCM 데이터베이스 파일들은 전혀 손상되지 않고 남아있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;새로운 버전의 오라클 컨테이너를 띄우면서 똑같은 볼륨(oracle-data)을 다시 연결해 주면, 새 컨테이너가 기존 데이터를 그대로 물려받아 아무 일 없었다는 듯이 자재 데이터를 서비스하게 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 도커를 사용할 때 &quot;프로그램(컨테이너)은 언제든 부수고 새로 지을 수 있는 텐트처럼 다루고, 중요한 데이터(볼륨)는 절대 부서지지 않는 금고에 보관한다&quot;고 이해하시면 정확합니다.&lt;/p&gt;</description>
      <category>Programming/기타 팁</category>
      <category>docker</category>
      <category>docker volume</category>
      <category>Dockfile</category>
      <author>Samuel_0201</author>
      <guid isPermaLink="true">https://afs0201.tistory.com/72</guid>
      <comments>https://afs0201.tistory.com/72#entry72comment</comments>
      <pubDate>Sat, 28 Mar 2026 16:07:04 +0900</pubDate>
    </item>
  </channel>
</rss>