<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>bslee.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 24 Jul 2025 14:00:46 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>bslee.log</title>
            <url>https://velog.velcdn.com/images/bslee_365/profile/727e0d93-8309-4749-ae64-3e6d7aa083fc/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. bslee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bslee_365" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[swift UI로 모달까지 만들어봄.]]></title>
            <link>https://velog.io/@bslee_365/swift-UI%EB%A1%9C-%EB%AA%A8%EB%8B%AC%EA%B9%8C%EC%A7%80-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B4%84</link>
            <guid>https://velog.io/@bslee_365/swift-UI%EB%A1%9C-%EB%AA%A8%EB%8B%AC%EA%B9%8C%EC%A7%80-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B4%84</guid>
            <pubDate>Thu, 24 Jul 2025 14:00:46 GMT</pubDate>
            <description><![CDATA[<h1 id="swiftui에서-sheet가-동작하는-과정-완전-이해하기">SwiftUI에서 <code>.sheet</code>가 동작하는 과정 완전 이해하기</h1>
<p>SwiftUI로 앱을 만들다 보면, <strong>리스트에서 아이템을 선택하면 상세 화면이 뜨는 패턴</strong>을 자주 구현하게 된다.
이번에는 <code>FrameworkGridView</code>, <code>FrameworkGridViewModel</code>, <code>FrameworkDetailView</code> 세 가지를 기준으로 <strong><code>.sheet</code>가 어떻게 동작하는지</strong> 하나씩 뜯어보자.</p>
<hr>
<h2 id="1-전체-구조-다시-보기">1. 전체 구조 다시 보기</h2>
<h3 id="frameworkgridview">FrameworkGridView</h3>
<pre><code class="language-swift">@StateObject var viewModel = FrameworkGridViewModel()

var body: some View {
    NavigationView {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach(MockData.frameworks, id: \.id) { framework in
                    FrameworkTitleView(framework: framework)
                        .onTapGesture {
                            viewModel.selectedFramework = framework
                        }
                }
            }
        }
        .navigationTitle(&quot;🍎 Frameworks&quot;)
        .sheet(isPresented: $viewModel.isShowingDetailView) {
            FrameworkDetailView(
                frameWork: viewModel.selectedFramework!,
                isShowingDetailView: $viewModel.isShowingDetailView
            )
        }
    }
}</code></pre>
<h3 id="frameworkgridviewmodel">FrameworkGridViewModel</h3>
<pre><code class="language-swift">final class FrameworkGridViewModel: ObservableObject {
    var selectedFramework: Framework? {
        didSet {
            isShowingDetailView = true
        }
    }

    @Published var isShowingDetailView = false
}</code></pre>
<h3 id="frameworkdetailview">FrameworkDetailView</h3>
<pre><code class="language-swift">struct FrameworkDetailView: View {
    var frameWork: Framework
    @Binding var isShowingDetailView: Bool

    var body: some View {
        VStack {
            HStack {
                Spacer()
                Button {
                    isShowingDetailView = false
                } label: {
                    Image(systemName: &quot;xmark&quot;)
                        .foregroundColor(Color(.label))
                        .imageScale(.large)
                        .frame(width: 44, height: 44)
                }
            }
            .padding()

            Spacer()
            FrameworkTitleView(framework: frameWork)
            Text(frameWork.description)
                .font(.body)
                .padding()
            Spacer()
            Button {
                // 더 알아보기 로직
            } label: {
                AFButton(title: &quot;더 알아보기&quot;)
            }
        }
    }
}</code></pre>
<hr>
<h2 id="2-동작-흐름-정의">2. 동작 흐름 정의</h2>
<h3 id="1-ontapgesture로-selectedframework-주입">1) <code>onTapGesture</code>로 <code>selectedFramework</code> 주입</h3>
<p><code>FrameworkGridView</code>에서 <strong>사용자가 특정 아이템을 탭</strong>하면
<code>viewModel.selectedFramework</code>에 선택한 <code>framework</code>가 할당된다.</p>
<pre><code class="language-swift">.onTapGesture {
    viewModel.selectedFramework = framework
}</code></pre>
<p>즉, <code>FrameworkGridView</code>는 <strong>이미 생성된 ViewModel 인스턴스</strong>를 사용하며,
선택한 데이터를 ViewModel에 넘겨주는 역할만 한다.</p>
<hr>
<h3 id="2-didset을-통한-isshowingdetailview-변경">2) <code>didSet</code>을 통한 <code>isShowingDetailView</code> 변경</h3>
<p><code>FrameworkGridViewModel</code>의 핵심 로직은 <code>selectedFramework</code>의 <code>didSet</code>이다.</p>
<pre><code class="language-swift">var selectedFramework: Framework? {
    didSet {
        isShowingDetailView = true
    }
}</code></pre>
<ul>
<li>선택된 데이터가 할당되면</li>
<li><strong>자동으로 <code>isShowingDetailView</code>가 <code>true</code>로 바뀐다.</strong></li>
</ul>
<p>즉, <strong>선택과 동시에 상세 화면을 띄우라는 신호</strong>를 보내는 구조다.</p>
<hr>
<h3 id="3-published와-sheet의-반응">3) <code>@Published</code>와 <code>.sheet</code>의 반응</h3>
<p><code>isShowingDetailView</code>는 <code>@Published</code>이기 때문에,
값이 변경되면 이 값을 감시하고 있는 View가 즉시 반응한다.</p>
<pre><code class="language-swift">.sheet(isPresented: $viewModel.isShowingDetailView) {
    FrameworkDetailView(
        frameWork: viewModel.selectedFramework!,
        isShowingDetailView: $viewModel.isShowingDetailView
    )
}</code></pre>
<p>즉,</p>
<ul>
<li><code>isShowingDetailView = true</code> → <code>.sheet</code>가 뜬다</li>
<li><code>isShowingDetailView = false</code> → <code>.sheet</code>가 닫힌다</li>
</ul>
<p><code>FrameworkDetailView</code> 내부에서 <code>xmark</code> 버튼을 눌러 <code>isShowingDetailView = false</code>로 바꾸면,
자동으로 시트가 사라지는 이유도 여기에 있다.</p>
<hr>
<h3 id="4-선택된-데이터의-전달">4) 선택된 데이터의 전달</h3>
<p><code>.sheet</code>가 실행될 때, <code>selectedFramework</code>에 저장된 데이터가 그대로 전달된다.</p>
<pre><code class="language-swift">FrameworkDetailView(
    frameWork: viewModel.selectedFramework!,
    isShowingDetailView: $viewModel.isShowingDetailView
)</code></pre>
<p>즉, <code>FrameworkDetailView</code>가 보여줄 데이터는
<strong><code>onTapGesture</code>에서 선택된 그 데이터 그대로</strong>다.</p>
<hr>
<h2 id="3-흐름-요약">3. 흐름 요약</h2>
<ol>
<li><strong>탭 이벤트 발생</strong> → <code>selectedFramework</code>에 선택된 데이터가 주입된다.</li>
<li><strong><code>didSet</code> 실행</strong> → <code>isShowingDetailView</code>가 <code>true</code>로 변경된다.</li>
<li><strong>@Published 반응</strong> → <code>.sheet</code>가 자동으로 뜬다.</li>
<li><strong>데이터 전달</strong> → <code>selectedFramework</code>가 <code>FrameworkDetailView</code>에 전달되어 View에 표시된다.</li>
<li><strong>닫기 버튼 동작</strong> → <code>isShowingDetailView = false</code>로 변경되어 <code>.sheet</code>가 닫힌다.</li>
</ol>
<hr>
<h2 id="4-한-줄-정리">4. 한 줄 정리</h2>
<table>
<thead>
<tr>
<th>단계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>데이터 선택</strong></td>
<td><code>onTapGesture</code>로 ViewModel의 <code>selectedFramework</code>가 갱신된다</td>
</tr>
<tr>
<td><strong>상태 변화</strong></td>
<td><code>didSet</code>으로 <code>isShowingDetailView</code>가 <code>true</code>로 바뀐다</td>
</tr>
<tr>
<td><strong>UI 반응</strong></td>
<td><code>@Published</code>가 변화를 감지해 <code>.sheet</code>를 띄운다</td>
</tr>
<tr>
<td><strong>데이터 전달</strong></td>
<td><code>.sheet</code>에서 선택된 Framework를 상세 화면에 넘겨준다</td>
</tr>
</tbody></table>
<p>즉, <strong>View → ViewModel → View</strong>의 단방향 흐름으로 동작하며,
상태 변경은 ViewModel이 책임지고, View는 그 상태를 구독하며 UI를 업데이트한다.</p>
<h2 id="마무리로-정리한-내용">마무리로 정리한 내용</h2>
<p><img src="https://velog.velcdn.com/images/bslee_365/post/d504436e-9c64-4417-a46e-5085e2b4e21a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@ViewBuilder]]></title>
            <link>https://velog.io/@bslee_365/ViewBuilder</link>
            <guid>https://velog.io/@bslee_365/ViewBuilder</guid>
            <pubDate>Sun, 20 Jul 2025 14:54:25 GMT</pubDate>
            <description><![CDATA[<h1 id="viewbuilder-완전-정리--왜-필요한가"><code>@ViewBuilder</code> 완전 정리 — 왜 필요한가?</h1>
<p>SwiftUI로 UI를 작성하다 보면 여러 개의 뷰를 나열하는 상황이 많다.
하지만 SwiftUI의 함수나 <code>body</code>는 <strong>단 하나의 View만 반환</strong>해야 한다.
그럼에도 여러 개의 뷰를 쌓을 수 있는 이유가 뭘까? 바로 <code>@ViewBuilder</code> 덕분이다.</p>
<hr>
<h2 id="1-viewbuilder란">1. <code>@ViewBuilder</code>란?</h2>
<blockquote>
<p><code>@ViewBuilder</code>는 <strong>여러 개의 뷰를 하나의 View로 조합해주는 Function Builder</strong>이다.</p>
</blockquote>
<p>쉽게 말해, <strong>여러 View를 선언적으로 나열하면 Swift가 알아서 하나의 View 트리로 합쳐준다.</strong>
이 덕분에 <code>body</code>나 커스텀 컴포넌트에서 뷰를 자연스럽게 쌓을 수 있다.</p>
<hr>
<h2 id="2-왜-필요한가">2. 왜 필요한가?</h2>
<p>SwiftUI에서 함수나 프로퍼티는 <strong><code>some View</code> → 단일 View만 반환 가능</strong>하다.</p>
<pre><code class="language-swift">func myView() -&gt; some View {
    Text(&quot;Hello&quot;)
    Text(&quot;World&quot;)   // 오류 발생
}</code></pre>
<p>그럼 이렇게 여러 View를 어떻게 쌓을 수 있을까?
<code>@ViewBuilder</code>가 이 문제를 해결해준다.</p>
<pre><code class="language-swift">@ViewBuilder
func myView() -&gt; some View {
    Text(&quot;Hello&quot;)
    Text(&quot;World&quot;)   // 자동으로 TupleView로 묶임
}</code></pre>
<p>Swift는 내부적으로 이 코드를 <strong><code>TupleView&lt;(Text, Text)&gt;</code></strong> 형태로 변환해준다.</p>
<p><code>@ViewBuilder</code>를 쓰다보면 계속해서 아래와 같은 식으로 꼬리에 꼬리를 물면서 하나로 생성이 된다.</p>
<pre><code class="language-console">ModifiedContent&lt;ModifiedContent&lt;ModifiedContent&lt;ModifiedContent&lt;ModifiedContent&lt;Text, _FrameLayout&gt;, _BackgroundStyleModifier&lt;AnyGradient&gt;&gt;, _EnvironmentKeyWritingModifier&lt;Optional&lt;Color&gt;&gt;&gt;, _EnvironmentKeyWritingModifier&lt;Optional&lt;Font&gt;&gt;&gt;, _ClipEffect&lt;RoundedRectangle&gt;&gt;</code></pre>
<hr>
<h2 id="3-내부-원리">3. 내부 원리</h2>
<p><code>@ViewBuilder</code>는 Swift의 <strong>Function Builder</strong> 기능을 사용한다.
<strong>컴파일러가 빌더 안의 여러 반환값을 하나로 합쳐주는 문법적 도우미</strong>다.</p>
<p>덕분에 다음과 같은 일이 가능해진다.</p>
<ul>
<li><code>if/else</code>, <code>switch</code>, <code>ForEach</code> 같은 <strong>제어문 사용</strong></li>
<li>여러 View를 선언적으로 조합</li>
</ul>
<p>즉, SwiftUI의 <strong>선언적 UI 패러다임</strong>을 가능하게 하는 핵심 문법이다.</p>
<hr>
<h2 id="4-활용-예시">4. 활용 예시</h2>
<h3 id="4-1-기본적인-사용">4-1. 기본적인 사용</h3>
<pre><code class="language-swift">@ViewBuilder
func myView() -&gt; some View {
    Text(&quot;Hello&quot;)
    Text(&quot;World&quot;)
}</code></pre>
<p>자동으로 하나의 View로 합쳐지기 때문에 별도의 컨테이너를 명시하지 않아도 된다.</p>
<hr>
<h3 id="4-2-ifelse-활용">4-2. <strong>if/else 활용</strong></h3>
<pre><code class="language-swift">@ViewBuilder
func greetingView(isMorning: Bool) -&gt; some View {
    if isMorning {
        Text(&quot;Good Morning&quot;)
    } else {
        Text(&quot;Good Evening&quot;)
    }
}</code></pre>
<p>빌더 내부에서 조건문을 자유롭게 쓸 수 있다.</p>
<hr>
<h3 id="4-3-foreach-활용">4-3. <strong>ForEach 활용</strong></h3>
<pre><code class="language-swift">@ViewBuilder
func listView(items: [String]) -&gt; some View {
    ForEach(items, id: \.self) { item in
        Text(item)
    }
}</code></pre>
<p>여러 개의 뷰를 동적으로 생성할 때도 유용하다.</p>
<hr>
<h3 id="4-4-커스텀-뷰에서의-사용">4-4. <strong>커스텀 뷰에서의 사용</strong></h3>
<p><code>@ViewBuilder</code>는 <strong>클로저 형태로 View를 받아야 하는 커스텀 컴포넌트</strong>에서 특히 중요하다.</p>
<pre><code class="language-swift">struct MyCard&lt;Content: View&gt;: View {
    let title: String
    let content: Content

    init(title: String, @ViewBuilder content: () -&gt; Content) {
        self.title = title
        self.content = content()
    }

    var body: some View {
        VStack {
            Text(title)
                .font(.headline)
            content
        }
    }
}

struct ContentView: View {
    var body: some View {
        MyCard(title: &quot;SwiftUI&quot;) {
            Text(&quot;ViewBuilder Example&quot;)
            Text(&quot;Hello World&quot;)
        }
    }
}</code></pre>
<hr>
<h2 id="5-body는-왜-여러-view를-쌓을-수-있을까">5. <code>body</code>는 왜 여러 View를 쌓을 수 있을까?</h2>
<p>하나 의문이 생긴다. <strong><code>body</code>에는 @ViewBuilder를 명시하지 않았는데 왜 여러 View를 쌓을 수 있을까?</strong>
정답은, <strong>SwiftUI가 <code>body</code> 프로퍼티에 암묵적으로 @ViewBuilder를 적용</strong>하기 때문이다.</p>
<p>즉, 다음 두 코드는 동일하게 작동한다.</p>
<pre><code class="language-swift">// SwiftUI 내부에서 암묵적으로 이렇게 처리됨
@ViewBuilder
var body: some View {
    Text(&quot;Hello&quot;)
    Text(&quot;World&quot;)
}</code></pre>
<hr>
<h2 id="7-정리--한-줄-요약">7. 정리 — 한 줄 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>@ViewBuilder</code></td>
<td>여러 View를 <strong>하나의 View 트리로 조합</strong>하는 SwiftUI 전용 Function Builder</td>
</tr>
<tr>
<td>필요성</td>
<td><code>some View</code>가 단일 View만 반환할 수 있기 때문</td>
</tr>
<tr>
<td>특징</td>
<td><strong>if/else, ForEach</strong> 등 제어문도 사용 가능</td>
</tr>
<tr>
<td>body</td>
<td><strong>암묵적으로 @ViewBuilder 적용</strong></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[.onAppear]]></title>
            <link>https://velog.io/@bslee_365/.onAppear</link>
            <guid>https://velog.io/@bslee_365/.onAppear</guid>
            <pubDate>Sun, 20 Jul 2025 14:49:27 GMT</pubDate>
            <description><![CDATA[<h1 id="onappear-정리"><code>.onAppear</code> 정리</h1>
<p>SwiftUI로 앱을 만들다 보면 뷰가 <strong>화면에 나타나는 시점</strong>에 특정 작업을 실행해야 할 때가 있다.
이럴 때 사용하는 게 바로 <code>.onAppear</code>이다.</p>
<hr>
<h2 id="1-onappear란"><strong>1. <code>.onAppear</code>란?</strong></h2>
<p>먼저 정의부터 짚고 가보자.</p>
<p><code>.onAppear</code>는 <strong>SwiftUI에서 뷰가 화면에 나타날 때(렌더링될 때) 실행되는 클로저</strong>를 지정하는 <strong>뷰 수명주기(Lifecycle) 이벤트</strong>이다.</p>
<ul>
<li><strong>화면에 그려지는 순간 딱 한 번</strong> 실행된다.</li>
<li>UIKit의 <code>viewDidAppear</code>와 유사한 역할을 한다.</li>
<li>뷰가 사라졌다가 다시 나타나면 <strong>다시 실행된다</strong>.</li>
</ul>
<p>그럼 하나 의문이 생긴다.
<strong>&quot;UI가 다시 그려질 때도 실행될까?&quot;</strong>
답은 <strong>No</strong>다. <strong>단순히 데이터가 바뀌어 UI가 다시 렌더링될 때는 실행되지 않는다.</strong></p>
<hr>
<h2 id="2-특징"><strong>2. 특징</strong></h2>
<p><strong>언제 실행되나?</strong></p>
<ul>
<li>SwiftUI가 <strong>뷰를 실제로 렌더링해서 사용자에게 보이는 순간</strong></li>
<li>예: <code>NavigationLink</code>로 새로운 화면을 열 때 새 화면의 <code>onAppear</code>가 호출됨</li>
</ul>
<p><strong>언제 다시 실행되나?</strong></p>
<ul>
<li>뷰가 <strong>완전히 사라졌다가 다시 나타날 때</strong>
(예: 다른 화면으로 갔다가 다시 돌아오면 실행)</li>
</ul>
<p><strong>반대 개념 – <code>.onDisappear</code></strong></p>
<ul>
<li>뷰가 화면에서 사라질 때 실행된다.</li>
<li>주로 <strong>정리 작업(타이머 중지, 상태 초기화)</strong> 등에 사용된다.</li>
</ul>
<hr>
<h2 id="3-문법"><strong>3. 문법</strong></h2>
<pre><code class="language-swift">.onAppear {
    // 뷰가 나타날 때 실행할 코드
}

.onDisappear {
    // 뷰가 사라질 때 실행할 코드
}</code></pre>
<hr>
<h2 id="4-예시"><strong>4. 예시</strong></h2>
<h3 id="기본-사용-예시"><strong>기본 사용 예시</strong></h3>
<pre><code class="language-swift">struct OnAppearExample: View {
    @State private var message = &quot;로딩 중...&quot;

    var body: some View {
        Text(message)
            .onAppear {
                print(&quot;뷰가 화면에 나타났습니다!&quot;)
                message = &quot;화면에 나타났습니다.&quot;
            }
            .onDisappear {
                print(&quot;뷰가 사라졌습니다!&quot;)
            }
    }
}</code></pre>
<h3 id="실행-흐름"><strong>실행 흐름</strong></h3>
<ol>
<li>화면에 나타나는 순간 → <code>&quot;뷰가 화면에 나타났습니다!&quot;</code> 출력 + <code>message</code> 변경</li>
<li>화면에서 벗어날 때 → <code>&quot;뷰가 사라졌습니다!&quot;</code> 출력</li>
</ol>
<hr>
<h3 id="api-데이터-불러올-때-자주-사용"><strong>API 데이터 불러올 때 자주 사용</strong></h3>
<pre><code class="language-swift">struct UserProfileView: View {
    @State private var username = &quot;불러오는 중...&quot;

    var body: some View {
        Text(username)
            .onAppear {
                loadUserData()
            }
    }

    func loadUserData() {
        // 실제 앱에서는 네트워크 요청 사용
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            username = &quot;홍길동&quot;
        }
    }
}</code></pre>
<p><strong>뷰가 로드되자마자 자동으로 네트워크 요청이나 초기 데이터 세팅</strong>을 할 때 많이 사용된다.</p>
<hr>
<h2 id="5-한-줄-정리"><strong>5. 한 줄 정리</strong></h2>
<table>
<thead>
<tr>
<th>메서드</th>
<th>언제 실행?</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong><code>.onAppear</code></strong></td>
<td>뷰가 화면에 <strong>나타날 때</strong></td>
<td>초기 데이터 로딩, 애니메이션 시작</td>
</tr>
<tr>
<td><strong><code>.onDisappear</code></strong></td>
<td>뷰가 화면에서 <strong>사라질 때</strong></td>
<td>타이머 정리, 상태 초기화</td>
</tr>
</tbody></table>
<p><strong>한 줄 요약</strong>:
<code>.onAppear</code>는 뷰가 <strong>보이는 순간 작업을 시작</strong>하고, <code>.onDisappear</code>는 <strong>사라질 때 정리</strong>하는 데 쓰인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 상태관리]]></title>
            <link>https://velog.io/@bslee_365/Swift-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@bslee_365/Swift-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sun, 20 Jul 2025 14:42:26 GMT</pubDate>
            <description><![CDATA[<h1 id="swiftui-상태-관리-완전-정리">SwiftUI 상태 관리 완전 정리</h1>
<p><code>@State</code>, <code>@Binding</code>, <code>@StateObject</code>, <code>@ObservedObject</code>, <code>@EnvironmentObject</code></p>
<p>SwiftUI로 앱을 만들다 보면 “상태 관리”가 정말 중요하다는 걸 느끼게 된다.
값이 바뀔 때마다 UI가 자동으로 업데이트되니 편리하지만, <strong>상태의 소유권과 생명주기</strong>를 제대로 이해하지 않으면 예상치 못한 버그를 맞이할 수 있다.</p>
<p>그럼 하나씩 정리해보자.</p>
<hr>
<h2 id="1-state--로컬-상태">1. <code>@State</code> — 로컬 상태</h2>
<h3 id="정의"><strong>정의</strong></h3>
<p><code>@State</code>는 <strong>뷰 내부에서만 관리되는 로컬 상태</strong>다.
값 타입(<code>Int</code>, <code>String</code>, <code>struct</code>)에 적합하며, 값이 바뀌면 뷰가 자동으로 다시 그려진다.</p>
<h3 id="의문"><strong>의문</strong></h3>
<p>“이걸 어디에 써야 하지?”
→ <strong>뷰 내부에서만 쓰는 값</strong>일 때 사용하면 된다. 외부와 공유할 필요가 없을 때 가장 적합하다.</p>
<h3 id="예시-코드"><strong>예시 코드</strong></h3>
<pre><code class="language-swift">struct CounterView: View {
    @State private var count = 0  // 로컬 상태

    var body: some View {
        VStack {
            Text(&quot;Count: \(count)&quot;)
            Button(&quot;증가&quot;) {
                count += 1  // 값 변경 → UI 자동 업데이트
            }
        }
    }
}</code></pre>
<hr>
<h2 id="2-binding--상위-상태를-하위에서-수정">2. <code>@Binding</code> — 상위 상태를 하위에서 수정</h2>
<h3 id="정의-1"><strong>정의</strong></h3>
<p><code>@Binding</code>은 <strong>상위 뷰의 @State 값을 하위 뷰에서 읽고 수정할 수 있게 연결</strong>해 준다.</p>
<h3 id="의문-1"><strong>의문</strong></h3>
<p>“하위 뷰가 상태를 직접 소유하면 안 되나?”
→ 하위에서 소유하면 <strong>상위와 값이 따로 놀게 된다.</strong> 상위 상태를 공유해야 한다면 Binding이 정답이다.</p>
<h3 id="예시-코드-1"><strong>예시 코드</strong></h3>
<pre><code class="language-swift">struct ParentView: View {
    @State private var name = &quot;홍길동&quot;

    var body: some View {
        VStack {
            Text(&quot;이름: \(name)&quot;)
            ChildView(name: $name)  // Binding으로 전달
        }
    }
}

struct ChildView: View {
    @Binding var name: String  // 상위의 상태에 직접 연결

    var body: some View {
        TextField(&quot;이름 입력&quot;, text: $name)
            .textFieldStyle(.roundedBorder)
    }
}</code></pre>
<p>하위에서 이름을 바꾸면 부모 뷰의 <code>name</code>도 즉시 바뀐다.</p>
<hr>
<h2 id="3-stateobject--참조-타입-상태의-소유자">3. <code>@StateObject</code> — 참조 타입 상태의 소유자</h2>
<h3 id="정의-2"><strong>정의</strong></h3>
<p><code>@StateObject</code>는 <strong>뷰가 직접 ObservableObject를 소유하고 관리</strong>할 때 사용한다.
SwiftUI가 객체의 생명주기를 관리하며, 값이 바뀌면 뷰가 다시 그려진다.</p>
<h3 id="의문-2"><strong>의문</strong></h3>
<p>“왜 그냥 @State로 클래스 타입을 쓰면 안 돼?”
→ @State는 <strong>값 타입</strong> 전용이다. 클래스 같은 참조 타입은 @StateObject를 써야 한다.</p>
<h3 id="예시-코드-2"><strong>예시 코드</strong></h3>
<pre><code class="language-swift">class UserSettings: ObservableObject {
    @Published var score = 0
}

struct StateObjectExample: View {
    @StateObject private var settings = UserSettings()  // 뷰가 소유

    var body: some View {
        VStack {
            Text(&quot;점수: \(settings.score)&quot;)
            Button(&quot;점수 증가&quot;) {
                settings.score += 1
            }
        }
    }
}</code></pre>
<p><code>@StateObject</code>는 뷰가 새로 그려져도 <strong>기존 객체를 유지</strong>한다.</p>
<hr>
<h2 id="4-observedobject--다른-뷰에서-만든-객체-관찰">4. <code>@ObservedObject</code> — 다른 뷰에서 만든 객체 관찰</h2>
<h3 id="정의-3"><strong>정의</strong></h3>
<p><code>@ObservedObject</code>는 <strong>상위에서 이미 만든 ObservableObject를 관찰</strong>만 할 때 사용한다.
뷰는 이 객체를 소유하지 않는다.</p>
<h3 id="의문-3"><strong>의문</strong></h3>
<p>“그럼 @StateObject랑 뭐가 다른데?”
→ <strong>소유권의 차이</strong>다.</p>
<ul>
<li><code>@StateObject</code>: 내가 소유 → 내가 생성</li>
<li><code>@ObservedObject</code>: 남이 소유 → 나는 관찰만</li>
</ul>
<h3 id="예시-코드-3"><strong>예시 코드</strong></h3>
<pre><code class="language-swift">struct ParentView: View {
    @StateObject private var settings = UserSettings()  // 부모가 소유

    var body: some View {
        VStack {
            Text(&quot;부모 점수: \(settings.score)&quot;)
            ChildView(settings: settings)
        }
    }
}

struct ChildView: View {
    @ObservedObject var settings: UserSettings  // 단순 관찰

    var body: some View {
        VStack {
            Text(&quot;자식 점수: \(settings.score)&quot;)
            Button(&quot;점수 증가&quot;) {
                settings.score += 1
            }
        }
    }
}</code></pre>
<p>부모와 자식이 같은 객체를 공유하며, 값이 바뀌면 둘 다 업데이트된다.</p>
<hr>
<h2 id="5--environmentobject--전역-상태-공유">5.  <code>@EnvironmentObject</code> — 전역 상태 공유</h2>
<h3 id="정의-4"><strong>정의</strong></h3>
<p><code>@EnvironmentObject</code>는 앱 전역에서 공유하는 상태를 주입받을 때 사용한다.
상위에서 <code>.environmentObject(_:)</code>로 한 번만 넣어주면 하위 뷰 어디서든 접근 가능하다.</p>
<h3 id="의문-4"><strong>의문</strong></h3>
<p>“그냥 파라미터로 넘기면 되지 않나?”
→ 뷰 계층이 깊어질수록 파라미터로 계속 넘기는 게 귀찮아진다.
<code>@EnvironmentObject</code>를 쓰면 이 문제를 깔끔하게 해결할 수 있다.</p>
<h3 id="예시-코드-4"><strong>예시 코드</strong></h3>
<pre><code class="language-swift">class AppSettings: ObservableObject {
    @Published var themeColor: Color = .blue
}

@main
struct MyApp: App {
    @StateObject private var settings = AppSettings()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(settings)  // 전역 주입
        }
    }
}

struct RootView: View {
    var body: some View {
        ChildView()  // 별도 파라미터 필요 없음
    }
}

struct ChildView: View {
    @EnvironmentObject var settings: AppSettings

    var body: some View {
        VStack {
            Text(&quot;테마 색상&quot;)
                .foregroundColor(settings.themeColor)
            Button(&quot;색상 변경&quot;) {
                settings.themeColor = .red
            }
        }
    }
}</code></pre>
<p>단, <strong>주입하지 않으면 런타임 에러</strong>가 난다.</p>
<hr>
<h2 id="한눈에-비교">한눈에 비교</h2>
<table>
<thead>
<tr>
<th>래퍼</th>
<th>소유권</th>
<th>대상</th>
<th>사용 위치</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>@State</strong></td>
<td>뷰가 소유</td>
<td>값 타입</td>
<td>로컬</td>
<td>뷰 내부 상태</td>
</tr>
<tr>
<td><strong>@Binding</strong></td>
<td>상위가 소유</td>
<td>값 타입</td>
<td>하위</td>
<td>상위 상태 수정</td>
</tr>
<tr>
<td><strong>@StateObject</strong></td>
<td>뷰가 소유</td>
<td>참조 타입</td>
<td>초기 생성</td>
<td>뷰가 직접 관리</td>
</tr>
<tr>
<td><strong>@ObservedObject</strong></td>
<td>상위가 소유</td>
<td>참조 타입</td>
<td>하위</td>
<td>상위 객체 관찰</td>
</tr>
<tr>
<td><strong>@EnvironmentObject</strong></td>
<td>전역</td>
<td>참조 타입</td>
<td>전역 하위 뷰</td>
<td>전역 상태 공유</td>
</tr>
</tbody></table>
<hr>
<h2 id="결론--한-줄-요약">결론 — 한 줄 요약</h2>
<ul>
<li><strong>소유권</strong>이 핵심이다.</li>
<li>내가 만든 값 → <code>@State</code> / <code>@StateObject</code></li>
<li>남이 준 값 → <code>@Binding</code> / <code>@ObservedObject</code></li>
<li>전역 공유 → <code>@EnvironmentObject</code></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[async/await와 @Sendable]]></title>
            <link>https://velog.io/@bslee_365/asyncawait%EC%99%80-Sendable</link>
            <guid>https://velog.io/@bslee_365/asyncawait%EC%99%80-Sendable</guid>
            <pubDate>Sun, 13 Jul 2025 11:41:53 GMT</pubDate>
            <description><![CDATA[<h1 id="asyncawait와-sendable"><code>async/await</code>와 <code>@Sendable</code></h1>
<p>Swift로 비동기 작업을 하다 보면 <code>async/await</code>는 자연스럽게 접하게 되는데,
조금만 깊게 들어가 보면 <code>@Sendable</code> 같은 개념도 함께 따라온다.</p>
<h2 id="async와-await란"><code>async</code>와 <code>await</code>란?</h2>
<p>먼저 정의부터 짚고 가보자.</p>
<p><code>async</code>는 <strong>비동기 함수임을 나타내는 키워드</strong>고,
<code>await</code>는 <strong>비동기 함수의 결과를 기다리기 위한 키워드</strong>다.</p>
<p>즉, <code>async</code>는 약속, <code>await</code>는 그 약속을 기다리는 역할이라고 보면 된다.</p>
<pre><code class="language-swift">func fetchName() async -&gt; String {
    return &quot;Swift User&quot;
}

Task {
    let name = await fetchName()
    print(name)  // Swift User
}</code></pre>
<p>Swift 5.5부터 도입된 이 문법은
<strong>비동기 코드도 동기처럼</strong> 읽기 쉽게 만들어주는 강력한 도구다.</p>
<hr>
<h2 id="async-await-선언방법">async await 선언방법</h2>
<p><strong>비동기 함수 정의</strong> </p>
<pre><code class="language-swift">func getUserInfo() async -&gt; String {
    try? await Task.sleep(for: .seconds(1))
    return &quot;User Info&quot;
}</code></pre>
<p><strong>호출 예시</strong></p>
<pre><code class="language-swift">Task {
    let info = await getUserInfo()
    print(info)
}</code></pre>
<p><code>await</code>는 반드시 <strong>비동기 컨텍스트</strong>에서만 사용 가능하다.
즉, <code>Task {}</code> 같은 비동기 환경 안에서만 쓸 수 있다.</p>
<blockquote>
<p><code>Task {}</code>는 새로운 비동기 실행 컨텍스트를 열어주는 문이다.</p>
</blockquote>
<hr>
<h2 id="sendable은-뭐가-다른-걸까"><code>@Sendable</code>은 뭐가 다른 걸까?</h2>
<p><code>@Sendable</code>은 뭔가 생소한 느낌이 있는데,
이것도 사실 <code>async</code>와 함께 쓰이다 보면 자주 마주치게 된다.</p>
<h3 id="정의부터-정리해보자">정의부터 정리해보자</h3>
<blockquote>
<p><code>@Sendable</code>은 클로저가 <strong>스레드 안전하게 실행될 수 있음</strong>을 컴파일러에 보장해주는 키워드다.</p>
</blockquote>
<p>다시 말해, 비동기 환경에서 <strong>다른 스레드로 넘어갈 수 있는 클로저</strong>인지 아닌지를 판단하는 기준인 셈.</p>
<hr>
<h2 id="의문점이-생긴다">의문점이 생긴다</h2>
<p>“왜 꼭 <code>@Sendable</code>을 붙이라고 할까?”</p>
<p>이유는 명확하다.
Swift의 동시성 모델은 <strong>스레드 안전성(thread safety)</strong>을 기본으로 하기 때문이다.</p>
<p>예를 들어 이런 코드가 있다고 해보자:</p>
<pre><code class="language-swift">var name = &quot;Foo&quot;

Task {
    await getFullName(delay: .seconds(1)) {
        print(name) // ❌ 외부 변수 캡처 → 컴파일 에러 가능성
        return &quot;Result&quot;
    }
}</code></pre>
<p>이 코드는 <code>name</code>이라는 외부 변수를 참조하고 있어서
동시성 환경에서는 <strong>데이터 충돌이나 레이스 컨디션</strong>이 생길 수 있다.</p>
<p>그래서 Swift는 컴파일 타임에 미리 알려준다.
“이 클로저는 <code>@Sendable</code>이 아닌데, 스레드 안전하지 않을 수 있어!”</p>
<hr>
<h2 id="sendable을-비유로-쉽게-이해해보자"><code>@Sendable</code>을 비유로 쉽게 이해해보자</h2>
<p>이 개념이 어렵게 느껴진다면, 이렇게 비유해보자.</p>
<ul>
<li>클로저는 &quot;메모지에 적힌 일&quot;</li>
<li><code>Task</code>는 &quot;그 메모를 친구에게 넘겨서 대신 처리해달라는 상황&quot;</li>
</ul>
<p>근데 메모에 이렇게 써 있다면?</p>
<blockquote>
<p>“내 방 책상 위에 있는 파일을 꺼내서 정리해줘!”</p>
</blockquote>
<p>친구는 내 방에 들어올 수 없는데…?
결국 그 일은 실패하고 만다. ⚠️</p>
<p>Swift에서도 똑같은 상황이 벌어진다.</p>
<ul>
<li>클로저가 외부 값을 참조하고 있다면</li>
<li>다른 스레드에서는 그 값에 접근할 수 없거나 위험할 수 있다.</li>
</ul>
<p>그래서 Swift는 <strong>명시적으로 <code>@Sendable</code>을 붙이라고 요구</strong>하는 것이다.
해당 클로저가 사용하는 동안은 다른 스레드에서는 사용할 수 없도록 제한을 해버리는 것이다.</p>
<hr>
<h2 id="정리하며-한-줄-요약">정리하며 한 줄 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>async</code></td>
<td>비동기 함수를 정의할 때 사용</td>
</tr>
<tr>
<td><code>await</code></td>
<td>비동기 함수의 실행을 기다릴 때 사용</td>
</tr>
<tr>
<td><code>Task {}</code></td>
<td>비동기 작업을 실행할 새로운 컨텍스트 생성</td>
</tr>
<tr>
<td><code>@Sendable</code></td>
<td>클로저가 다른 스레드에서 안전하게 실행될 수 있다는 것을 보장하는 키워드</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[연산자 커스터마이징]]></title>
            <link>https://velog.io/@bslee_365/%EC%97%B0%EC%82%B0%EC%9E%90-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95</link>
            <guid>https://velog.io/@bslee_365/%EC%97%B0%EC%82%B0%EC%9E%90-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95</guid>
            <pubDate>Sun, 13 Jul 2025 10:02:49 GMT</pubDate>
            <description><![CDATA[<h1 id="swift-연산자-오버로딩과-사용자-정의-연산자-정리">Swift 연산자 오버로딩과 사용자 정의 연산자 정리</h1>
<p>Swift에서는 기본 제공되는 연산자뿐만 아니라 <strong>연산자를 직접 정의</strong>하거나 <strong>옵셔널 타입에도 오버로딩</strong>을 통해 다양한 동작을 지정할 수 있다. 오늘은 아래와 같은 내용을 중심으로 Swift의 연산자 활용법을 정리해본다</p>
<ul>
<li>옵셔널 문자열을 더하는 <code>+</code> 연산자</li>
<li>사용자 정의 전위/후위 연산자</li>
<li>커스텀 타입(<code>Person</code>, <code>Family</code>) 간의 연산자 정의</li>
</ul>
<hr>
<h2 id="옵셔널-문자열에-대한--연산자-오버로딩">옵셔널 문자열에 대한 <code>+</code> 연산자 오버로딩</h2>
<pre><code class="language-swift">func + (
    lhs: String?,
    rhs: String?
) -&gt; String? {
    switch(lhs, rhs) {
    case(.none, .none):
        return nil
    case let (.some(value), .none),
         let (.none, .some(value)):
        return value
    case let (.some(lhs), .some(rhs)):
        return lhs + rhs
    }
}</code></pre>
<h3 id="개념-설명">개념 설명</h3>
<ul>
<li>옵셔널 <code>String</code> 타입도 <code>+</code> 연산을 사용할 수 있도록 오버로딩 함.</li>
<li>두 값 모두 <code>nil</code>이면 결과도 <code>nil</code>.</li>
<li>한 쪽이 <code>nil</code>이면, 다른 쪽 값을 반환.</li>
<li>둘 다 값이 있으면 일반 문자열처럼 이어붙임.</li>
</ul>
<pre><code class="language-swift">let firstName: String? = &quot;Foo&quot;
let lastName: String? = &quot; Bar&quot;
let fullName = firstName + lastName  // &quot;Foo Bar&quot;</code></pre>
<hr>
<h2 id="사용자-정의-연산자">사용자 정의 연산자</h2>
<h3 id="전위-연산자-prefix">전위 연산자 (<code>prefix</code>)</h3>
<pre><code class="language-swift">prefix operator ^
prefix func ^ (value: String) -&gt; String {
    value.uppercased()
}</code></pre>
<pre><code class="language-swift">let lowercaseName = &quot;Foo Bar&quot;
let uppercaseName = ^lowercaseName  // &quot;FOO BAR&quot;</code></pre>
<ul>
<li><code>^&quot;hello&quot;</code> → <code>&quot;HELLO&quot;</code></li>
<li>문자열을 전부 대문자로 바꾸는 사용자 정의 연산자</li>
</ul>
<h3 id="후위-연산자-postfix">후위 연산자 (<code>postfix</code>)</h3>
<pre><code class="language-swift">postfix operator *
postfix func * (value: String) -&gt; String {
    &quot;*** \(value) ***&quot;
}</code></pre>
<pre><code class="language-swift">let widthStars = lowercaseName*  // &quot;*** Foo Bar ***&quot;</code></pre>
<ul>
<li><code>&quot;hello&quot;*</code> → <code>&quot;*** hello ***&quot;</code></li>
<li>문자열 양옆에 별표를 붙이는 연산자</li>
</ul>
<hr>
<h2 id="커스텀-타입-간의-연산자-정의">커스텀 타입 간의 연산자 정의</h2>
<pre><code class="language-swift">struct Person {
    let name: String
}
struct Family {
    let members: [Person]
}</code></pre>
<h3 id="🔹-person--person-→-family">🔹 Person + Person → Family</h3>
<pre><code class="language-swift">func + (lhs: Person, rhs: Person) -&gt; Family {
    Family(members: [lhs, rhs])
}</code></pre>
<h3 id="🔹-family--person-→-family">🔹 Family + Person → Family</h3>
<pre><code class="language-swift">func + (lhs: Family, rhs: Person) -&gt; Family {
    var members = lhs.members
    members.append(rhs)
    return Family(members: members)
}</code></pre>
<h3 id="🔹-family--person-→-family-1">🔹 Family + [Person] → Family</h3>
<pre><code class="language-swift">func + (lhs: Family, rhs: [Person]) -&gt; Family {
    var members = lhs.members
    members.append(contentsOf: rhs)
    return Family(members: members)
}</code></pre>
<h3 id="사용-예">사용 예</h3>
<pre><code class="language-swift">let mom = Person(name: &quot;Mom&quot;)
let dad = Person(name: &quot;Dad&quot;)
let son = Person(name: &quot;Son&quot;)
let daughter1 = Person(name: &quot;Daughter1&quot;)
let daughter2 = Person(name: &quot;Daughter2&quot;)

let family = mom + dad                         // Family with mom and dad
let familyWithChildren = family + son         // Family with mom, dad, son
let familyWithDaughters = familyWithChildren + [daughter1, daughter2]</code></pre>
<ul>
<li>연산자 오버로딩을 통해 자연어처럼 <strong>family + son</strong> 또는 <strong>mom + dad</strong> 형태로 코드를 구성할 수 있어 가독성이 높아짐.</li>
</ul>
<hr>
<h2 id="오늘의-핵심-요약">오늘의 핵심 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>옵셔널 <code>+</code> 오버로딩</td>
<td>nil 처리까지 고려한 안전한 문자열 결합 방식 제공</td>
</tr>
<tr>
<td>전위/후위 연산자</td>
<td>개발자가 의도한 방식으로 표현력 있는 코드 구성 가능</td>
</tr>
<tr>
<td>커스텀 타입 연산자 오버로딩</td>
<td>연산자 문법을 활용해 DSL처럼 표현 가능함</td>
</tr>
</tbody></table>
<hr>
<h2 id="마무리">마무리</h2>
<p>Swift에서는 연산자 오버로딩과 사용자 정의 연산자 기능을 통해 <strong>표현력 있는 코드</strong>, <strong>안전한 로직 처리</strong>, <strong>가독성 좋은 DSL 형태의 인터페이스</strong>를 만들 수 있다. 단, 과한 오버로딩은 오히려 가독성을 해칠 수 있으므로 <strong>적절한 수준에서 명확한 의미를 갖도록 정의하는 것이 중요</strong>하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hash와 equal(==)]]></title>
            <link>https://velog.io/@bslee_365/Hash%EC%99%80-equal</link>
            <guid>https://velog.io/@bslee_365/Hash%EC%99%80-equal</guid>
            <pubDate>Sun, 13 Jul 2025 08:57:39 GMT</pubDate>
            <description><![CDATA[<h2 id="hashvalue란"><code>hashValue</code>란?</h2>
<p><code>hashValue</code>는 어떤 객체를 빠르게 식별할 수 있도록 만들어주는 <strong>고유한 정수값</strong>이다.
객체가 <code>Hashable</code>을 채택하면 자동으로 사용할 수 있게 된다.</p>
<p>예를 들어서:</p>
<pre><code class="language-swift">struct Person: Hashable {
    let name: String
    let age: Int
}

let foo = Person(name: &quot;Foo&quot;, age: 30)
print(foo.hashValue)  // 고유한 정수값 출력</code></pre>
<p>이 값은 주로 <strong>Set이나 Dictionary처럼 해시 기반 컬렉션에서 빠른 검색</strong>을 위해 쓰인다.</p>
<blockquote>
<p>정리하자면: <code>hashValue</code>는 객체를 빠르게 찾기 위한 식별 번호다.</p>
</blockquote>
<hr>
<h2 id="hashvalue가-같으면-같은-객체인가">hashValue가 같으면 같은 객체인가?</h2>
<p>아니다.</p>
<p>두 객체의 <code>hashValue</code>가 같아도 <code>==</code> 비교 결과는 다를 수 있다.
하지만 중요한 규칙이 하나 있다.</p>
<blockquote>
<p><code>==</code>가 <code>true</code>라면, 반드시 <code>hashValue</code>도 같아야 한다.</p>
</blockquote>
<p>이건 <code>Hashable</code>을 제대로 구현하기 위한 필수 조건이다.</p>
<hr>
<h2 id="그럼-실제로-예를-하나-보자">그럼 실제로 예를 하나 보자</h2>
<pre><code class="language-swift">struct House: Hashable {
    let number: Int
    let numberOfBedrooms: Int
}

let house1 = House(number: 123, numberOfBedrooms: 2)
let house2 = House(number: 123, numberOfBedrooms: 2)

print(house1 == house2)       // true
print(house1.hashValue == house2.hashValue)  // true</code></pre>
<p>왜 같을까?
Swift는 <code>struct</code>가 <code>Hashable</code>을 채택하면, 내부 속성 기준으로 <code>==</code>와 <code>hashValue</code>를 <strong>자동으로 만들어준다.</strong>
즉, <code>number</code>와 <code>numberOfBedrooms</code>가 같으니까 둘 다 true가 되는 것.</p>
<hr>
<h2 id="그럼-수동으로-구현하면">그럼 수동으로 구현하면?</h2>
<p>이번엔 우리가 <code>hash(into:)</code>와 <code>==</code>을 직접 정의해보자.</p>
<pre><code class="language-swift">struct NumberedHouse: Hashable {
    let number: Int
    let numberOfBedrooms: Int

    func hash(into hasher: inout Hasher) {
        hasher.combine(number)
    }

    static func == (lhs: Self, rhs: Self) -&gt; Bool {
        lhs.number == rhs.number
    }
}</code></pre>
<p>이렇게 되면 <strong>오직 <code>number</code>만을 기준으로</strong> 동등성과 해시가 판단된다.</p>
<pre><code class="language-swift">let h1 = NumberedHouse(number: 123, numberOfBedrooms: 2)
let h2 = NumberedHouse(number: 123, numberOfBedrooms: 100)

let set = Set([h1, h2])
print(set.count)  // 1</code></pre>
<p>침실 수가 달라도 <strong>번호가 같으면 같은 객체</strong>로 취급된다.
Set은 중복을 허용하지 않으니까 하나만 들어가는 것.</p>
<hr>
<h2 id="그럼-set에-넣을-때-내부적으로-어떤-일이-일어날까">그럼 <code>Set</code>에 넣을 때 내부적으로 어떤 일이 일어날까?</h2>
<p>Swift는 <code>Set</code>, <code>Dictionary</code> 등에 값을 넣을 때 자동으로 다음을 수행한다.</p>
<ol>
<li><code>hash(into:)</code>를 호출해서 해시값 계산</li>
<li>해시값이 같으면 <code>==</code>로 진짜 같은지 비교</li>
</ol>
<p>즉, 우리는 함수만 정의하면 되고 <strong>언제 호출할지는 Swift가 알아서 처리</strong>해준다.</p>
<hr>
<h2 id="정리하면">정리하면</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>호출되는 함수</th>
</tr>
</thead>
<tbody><tr>
<td><code>a == b</code></td>
<td><code>==</code></td>
</tr>
<tr>
<td><code>Set.contains(a)</code></td>
<td><code>hash(into:)</code> → <code>==</code> (필요시)</td>
</tr>
<tr>
<td><code>Set.insert(a)</code></td>
<td><code>hash(into:)</code> → <code>==</code> (필요시)</td>
</tr>
<tr>
<td><code>Dictionary[key] = value</code></td>
<td><code>hash(into:)</code> → <code>==</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="한-줄-정리">한 줄 정리</h2>
<blockquote>
<p><code>hashValue</code>는 빠른 탐색용 정수값, <code>==</code>는 논리적 동등성 비교. 둘은 <strong>짝궁처럼 같이 작동</strong>해야 한다.</p>
</blockquote>
<hr>
<h2 id="추가-팁">추가 팁</h2>
<ul>
<li>Swift 4.2부터는 <code>hashValue</code>보다 <code>hash(into:)</code> 구현이 권장된다.</li>
<li><code>==</code>가 <code>true</code>면 반드시 해시도 같아야 한다 (반대는 아님).</li>
<li><code>Set</code>, <code>Dictionary</code>에서 <code>Hashable</code>을 잘못 구현하면 중복 탐지 실패나 검색 오류 같은 버그가 생길 수 있다.</li>
<li>자동 합성도 편하지만, 커스텀 기준이 필요할 땐 직접 구현하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[예외처리와 콜렉션]]></title>
            <link>https://velog.io/@bslee_365/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%EC%99%80-%EC%BD%9C%EB%A0%89%EC%85%98</link>
            <guid>https://velog.io/@bslee_365/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%EC%99%80-%EC%BD%9C%EB%A0%89%EC%85%98</guid>
            <pubDate>Sun, 13 Jul 2025 07:51:18 GMT</pubDate>
            <description><![CDATA[<h2 id="throws-try-do-catch-그리고-rethrows"><code>throws</code>, <code>try</code>, <code>do-catch</code>, 그리고 <code>rethrows</code></h2>
<h2 id="throws는-뭘까"><code>throws</code>는 뭘까?</h2>
<p>Swift에서 <code>throws</code>는 <strong>에러를 던질 수 있는 함수</strong>를 의미한다.
즉, 이 함수는 문제가 생기면 직접 에러를 호출한 쪽으로 &quot;던질 수 있다&quot;.</p>
<pre><code class="language-swift">func someFunc() throws -&gt; String</code></pre>
<p>바로 <code>try</code>를 붙여서 호출해야 한다.</p>
<pre><code class="language-swift">do {
    let result = try someFunc()
} catch {
    print(&quot;에러 발생: \(error)&quot;)
}</code></pre>
<p>또는 <code>try?</code>를 쓰면 실패 시 <code>nil</code>을 반환하고, <code>try!</code>는 실패 시 런타임 에러를 낸다.</p>
<hr>
<h2 id="rethrows란"><code>rethrows</code>란?</h2>
<p><code>rethrows</code>는 이렇게 정의할 수 있다.</p>
<blockquote>
<p><strong>자신은 에러를 던지지 않지만, 전달받은 클로저가 에러를 던지면 그걸 대신 던질 수 있게 해주는 키워드</strong>다.</p>
</blockquote>
<p>예를 들어 다음과 같은 함수가 있다고 하자</p>
<pre><code class="language-swift">func fullName(
    firstName: String?, 
    lastName: String?,
    calculator: (String?, String?) throws -&gt; String?
) rethrows -&gt; String? {
    try calculator(firstName, lastName)
}</code></pre>
<p><code>fullName</code> 함수는 직접 에러를 만들지는 않지만,
<code>calculator</code>라는 클로저가 에러를 던질 수 있다면, 그 에러를 <strong>호출한 쪽으로 다시 던질 수 있다.</strong></p>
<p>그럼 실제로 어떻게 동작하는지 예제를 보자.</p>
<h3 id="💡-예제-에러를-던지는-클로저를-넘기기">💡 예제: 에러를 던지는 클로저를 넘기기</h3>
<p>먼저, 에러 타입부터 정의해보자.</p>
<pre><code class="language-swift">enum NameErrors: Error {
    case firstNameIsInvalid
    case lastNameIsInvalid
}</code></pre>
<p>이제 <code>+</code>라는 함수(클로저로 넘길 수 있는 형태)를 만들어보자.
이 함수는 에러를 던질 수 있다.</p>
<pre><code class="language-swift">func + (
    firstName: String?,
    lastName: String?
) throws -&gt; String? {
    guard let firstName, !firstName.isEmpty else {
        throw NameErrors.firstNameIsInvalid
    }
    guard let lastName, !lastName.isEmpty else {
        throw NameErrors.lastNameIsInvalid
    }
    return &quot;\(firstName) \(lastName)&quot;
}</code></pre>
<p>그리고 이 <code>+</code> 함수를 <code>fullName</code> 함수에 넘겨서 사용해보자.</p>
<pre><code class="language-swift">do {
    let fooBar = try fullName(firstName: &quot;foo&quot;, lastName: nil, calculator: +)
    print(fooBar ?? &quot;&quot;)
} catch NameErrors.firstNameIsInvalid {
    print(&quot;First name is invalid&quot;)
} catch NameErrors.lastNameIsInvalid {
    print(&quot;Last name is invalid&quot;)
} catch {
    print(&quot;Something went wrong&quot;)
}</code></pre>
<h3 id="그러면-여기서-생기는-의문-하나">그러면 여기서 생기는 의문 하나</h3>
<p><strong><code>fullName</code> 함수는 에러를 직접 던지지 않는데, 왜 <code>do-catch</code>로 감싸야 하지?</strong></p>
<p>바로 이 지점이 <code>rethrows</code>의 핵심이다.</p>
<ul>
<li><code>fullName</code>은 에러를 직접 던지지 않는다.</li>
<li>하지만 전달받은 클로저(<code>+</code> 함수)가 <code>throws</code> 함수이기 때문에,</li>
<li><strong>해당 클로저에서 에러가 발생하면, <code>fullName</code>은 그 에러를 &quot;다시 던질 수&quot; 있다.</strong></li>
</ul>
<h3 id="🧠-한-줄-요약">🧠 한 줄 요약</h3>
<blockquote>
<p><code>rethrows</code>는 &quot;나는 에러를 만들진 않지만, 받은 클로저가 던진다면 그걸 대신 던질게&quot;라는 선언이다.</p>
</blockquote>
<p>Swift는 이렇게 <strong>정적 타입</strong>과 <strong>명시적 에러 처리</strong>를 중요하게 여기는 언어다.
그래서 <code>throws</code>와 <code>rethrows</code>의 쓰임새를 구분해서 설계하게 만든다.
이런 부분이 처음엔 번거롭지만, 점점 더 코드가 안정적으로 느껴지는 이유이기도 하다.</p>
<hr>
<h2 id="에러-처리와-옵셔널-어떤-차이가-있을까">에러 처리와 옵셔널, 어떤 차이가 있을까?</h2>
<p>이건 정말 자주 헷갈렸다.
<code>throw</code>와 <code>Optional</code>은 모두 실패 상황을 표현할 수 있는데, 어떤 차이점이 있을까?</p>
<ul>
<li><code>throws</code>는 <strong>예외 상황을 에러 타입으로 전달</strong>한다.</li>
<li><code>Optional</code>은 <strong>값이 없을 수도 있는 정상적인 흐름</strong>을 표현한다.</li>
</ul>
<pre><code class="language-swift">func getName() throws -&gt; String?</code></pre>
<p>이렇게 쓰면, &quot;에러&quot;와 &quot;값 없음(nil)&quot;을 명확히 구분할 수 있어서 더 유용한 경우가 많다.</p>
<hr>
<h2 id="compactmap은-그냥-map이랑-뭐가-다를까"><code>compactMap</code>은 그냥 map이랑 뭐가 다를까?</h2>
<p><code>compactMap</code>은 map의 기능 + <code>nil</code> 제거 기능이 붙어 있다.
즉, <strong>변환을 하고 나서 nil은 제거</strong>하는 기능까지 포함이다.</p>
<pre><code class="language-swift">let numbers = [1, 2, 3, 4]
let evenStrings = numbers.compactMap {
    $0 % 2 == 0 ? String($0) : nil
}
// 결과: [&quot;2&quot;, &quot;4&quot;]</code></pre>
<p>단순히 <code>map</code>만 쓰면 <code>Optional</code>이 남는데, <code>compactMap</code>은 그런 거 없이 깔끔하게 나온다.</p>
<hr>
<h2 id="anyhashable은-언제-쓰는-걸까"><code>AnyHashable</code>은 언제 쓰는 걸까?</h2>
<p>다양한 타입이 하나의 컬렉션(Set, Dictionary 등)에 있어야 할 때 사용한다.
단, 이 타입들은 전부 <code>Hashable</code>이어야 한다.</p>
<pre><code class="language-swift">let items: Set&lt;AnyHashable&gt; = [1, &quot;hi&quot;, true]</code></pre>
<p>각 아이템의 원래 타입은 내부적으로 유지되지만, 외부에서는 <code>AnyHashable</code>로 동작한다.</p>
<hr>
<h2 id="hashable-hashinto-그리고-"><code>Hashable</code>, <code>hash(into:)</code>, 그리고 <code>==</code></h2>
<p>해시 기반 컬렉션(Set, Dictionary 등)을 쓰려면, 타입이 <code>Hashable</code>을 따라야 한다.
그럼 두 가지 필수 메서드가 생긴다.</p>
<h3 id="hashinto"><code>hash(into:)</code></h3>
<ul>
<li>이건 해시값을 계산하는 메서드다.</li>
<li>보통 <code>id</code> 같은 고유값을 이용해서 구현한다.</li>
</ul>
<h3 id=""><code>==</code></h3>
<ul>
<li>두 객체가 같은지 비교할 때 기준이 되는 연산자다.</li>
</ul>
<pre><code class="language-swift">struct Person: Hashable {
    let id: UUID
    let name: String
    let age: Int

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    static func == (lhs: Person, rhs: Person) -&gt; Bool {
        lhs.id == rhs.id
    }
}</code></pre>
<hr>
<h2 id="switch에서-let을-꼭-써야-하는-이유"><code>switch</code>에서 <code>let</code>을 꼭 써야 하는 이유</h2>
<p>예를 들어 이런 코드가 있다고 하자.</p>
<pre><code class="language-swift">switch result {
case .success(let value):
    print(value)
case .failure(let error):
    print(error)
}</code></pre>
<p>여기서 <code>let</code>은 뭐냐면, <strong>case 패턴에서 값을 꺼내서 변수에 바인딩하는 역할</strong>이다.
Swift는 명시적인 언어라서, 바인딩 여부도 확실하게 표시해줘야 한다.</p>
<p>그래서 <code>case let .success(value)</code>처럼 써도 된다. 같은 의미다.</p>
<hr>
<h2 id="string-any-왜-이렇게-캐스팅할까"><code>[String: Any]</code> 왜 이렇게 캐스팅할까?</h2>
<p>딕셔너리에 여러 타입의 값이 섞여 있으면, Swift는 타입을 제대로 추론하지 못한다.
그래서 강제로 <code>[String: Any]</code>로 명시해주는 것이다.</p>
<pre><code class="language-swift">let userInfo = [
    &quot;name&quot;: &quot;Foo&quot;,
    &quot;age&quot;: 20,
    &quot;address&quot;: [
        &quot;line&quot;: &quot;Dongjak&quot;,
        &quot;postCode&quot;: &quot;123&quot;
    ]
] as [String: Any]</code></pre>
<p>안 그러면 에러가 난다.</p>
<hr>
<h2 id="as-string-string은-어떤-상황에서-쓰지"><code>as? [String: String]</code>은 어떤 상황에서 쓰지?</h2>
<p>위에서 본 <code>userInfo[&quot;address&quot;]</code>는 타입이 <code>Any</code>다.
근데 실제론 <code>[String: String]</code>이니까, 그걸 다시 안전하게 다운캐스팅해서 꺼내야 한다.</p>
<pre><code class="language-swift">if let address = userInfo[&quot;address&quot;] as? [String: String] {
    print(address[&quot;line&quot;] ?? &quot;&quot;)
}</code></pre>
<p>안전하게 타입을 확인하고 꺼내는 데 필요한 문법이다.</p>
<hr>
<h2 id="핵심-한-줄-요약">핵심 한 줄 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>throws</code> / <code>try</code></td>
<td>함수 내에서 에러를 던지고, 호출자는 반드시 처리</td>
</tr>
<tr>
<td><code>rethrows</code></td>
<td>클로저가 에러를 던지면 그걸 다시 전달</td>
</tr>
<tr>
<td><code>compactMap</code></td>
<td>nil 제거 + 변환 map</td>
</tr>
<tr>
<td><code>AnyHashable</code></td>
<td>서로 다른 타입을 하나의 Hashable 컬렉션에 넣을 수 있음</td>
</tr>
<tr>
<td><code>hash(into:)</code> / <code>==</code></td>
<td>해시 기반 비교/저장을 위한 필수 요소</td>
</tr>
<tr>
<td><code>switch let</code></td>
<td>바인딩 시 <code>let</code>은 필수</td>
</tr>
<tr>
<td><code>as [String: Any]</code></td>
<td>혼합 타입 딕셔너리에서 명시적 타입 선언</td>
</tr>
<tr>
<td><code>as? [String: String]</code></td>
<td>다운캐스팅을 통한 안전한 타입 접근</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[옵셔널(Optional)]]></title>
            <link>https://velog.io/@bslee_365/%EC%98%B5%EC%85%94%EB%84%90Optional</link>
            <guid>https://velog.io/@bslee_365/%EC%98%B5%EC%85%94%EB%84%90Optional</guid>
            <pubDate>Tue, 08 Jul 2025 13:26:26 GMT</pubDate>
            <description><![CDATA[<h2 id="옵셔널optional이란">옵셔널(Optional)이란?</h2>
<p>간단하게 말해서 이거다.</p>
<blockquote>
<p><strong>Optional은 값이 있을 수도, 없을 수도 있는 타입이다.</strong></p>
</blockquote>
<p>Swift는 타입 안정성을 굉장히 중요하게 생각한다.
그래서 <code>nil</code>이라는 개념을 아무 변수에나 막 쓸 수 없다.</p>
<pre><code class="language-swift">var name: String = nil       // 에러
var name: String? = nil      // 에러 안남</code></pre>
<p>즉, <code>nil</code>을 쓸 수 있으려면 명시적으로 <code>?</code>를 붙여서 옵셔널 타입이라고 선언해줘야 한다.</p>
<hr>
<h2 id="다른-언어에서는-어떻게-처리할까">다른 언어에서는 어떻게 처리할까?</h2>
<p>여기서 궁금한 게 하나 생긴다.
<strong>다른 언어들도 이런 옵셔널 개념이 있을까?</strong></p>
<p>한번 비교해보자.</p>
<table>
<thead>
<tr>
<th>언어</th>
<th>null 표현</th>
<th>옵셔널 처리 방식</th>
</tr>
</thead>
<tbody><tr>
<td>JavaScript</td>
<td><code>null</code>, <code>undefined</code></td>
<td>아무 변수나 null 가능</td>
</tr>
<tr>
<td>Python</td>
<td><code>None</code></td>
<td>역시 아무 변수나 None 가능</td>
</tr>
<tr>
<td>Java</td>
<td><code>null</code>, <code>Optional&lt;T&gt;</code></td>
<td>옵셔널은 선택사항</td>
</tr>
<tr>
<td><strong>Swift</strong></td>
<td><code>nil</code>, <code>Optional&lt;T&gt;</code></td>
<td>옵셔널 타입만 nil 가능</td>
</tr>
</tbody></table>
<p><strong>옵셔널이 아니면 nil을 절대 못 넣는다.</strong></p>
<hr>
<h2 id="옵셔널-비교연산">옵셔널 비교연산</h2>
<pre><code class="language-swift">var age: Int? = nil

if age == nil {
    print(&quot;age는 nil입니다&quot;)
}</code></pre>
<p>물론 가능하다. <code>== nil</code>을 직접 비교하는 것도 된다.
하지만 Swift에서는 더 깔끔하고 안전한 방식인 **<code>if let</code>이나 <code>guard let</code>**을 권장한다.</p>
<hr>
<h2 id="if-let-vs-guard-let"><code>if let</code> vs <code>guard let</code></h2>
<p>옵셔널을 안전하게 꺼내 쓰는 대표적인 방식이 이 두 가지다.</p>
<h3 id="if-let"><code>if let</code></h3>
<pre><code class="language-swift">if let value = optional {
    // value 사용 가능
    print(value)
}
print(value) // 에러발생</code></pre>
<p>조건이 <code>true</code>일 때만 실행된다.
하지만 블록 내부에서만 <code>value</code>를 쓸 수 있다는 단점이 있다.</p>
<h3 id="guard-let"><code>guard let</code></h3>
<pre><code class="language-swift">guard let value = optional 
else {
    print(value)
    return
}
print(value)
// 여기부터는 value가 nil이 아닐경우  구분을 타게 되어이음</code></pre>
<p>이건 다르다.
조건이 <strong>false일 때 탈출</strong>하기 때문에 이후 코드에서는 언래핑된 <code>value</code>를 계속 쓸 수 있다.
<strong>중첩 코드 없이 플랫하게 유지</strong>할 수 있어서 훨씬 가독성이 좋다.</p>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th><code>if let</code></th>
<th><code>guard let</code></th>
</tr>
</thead>
<tbody><tr>
<td>조건 실패 시 처리</td>
<td>블록 실행 안됨</td>
<td>else 블록 실행 후 탈출</td>
</tr>
<tr>
<td>바인딩된 값 사용 가능 범위</td>
<td>if 블록 내부만 가능</td>
<td>이후 전체 코드에서 사용 가능</td>
</tr>
<tr>
<td>코드 구조</td>
<td>중첩되기 쉬움</td>
<td>플랫하게 깔끔하게 유지됨</td>
</tr>
</tbody></table>
<hr>
<h2 id="옵셔널-체이닝">옵셔널 체이닝</h2>
<pre><code class="language-swift">foo.address?.firstLine</code></pre>
<p>옵셔널 체이닝은
중간의 어떤 값이 nil이라도 에러가 나지 않고, <strong>전체가 nil</strong>이 되도록 안전하게 처리하는 방식이다.</p>
<p>예를 들어 아래와 같은 중첩된 구조를 생각해보자.</p>
<pre><code class="language-swift">if let address = foo.address {
    if let line = address.firstLine {
        print(line)
    }
}</code></pre>
<p>이걸 한 줄로 줄이면 다음과 같은 코드로 정리 할 수 있다.</p>
<pre><code class="language-swift">if let line = foo.address?.firstLine {
    print(line)
}</code></pre>
<p>엄청 깔끔해진다.</p>
<hr>
<h2 id="여러-개를-한-번에-바인딩하려면">여러 개를 한 번에 바인딩하려면?</h2>
<p>옵셔널 바인딩은 하나씩만 하는 게 아니다.</p>
<pre><code class="language-swift">if let address = foo.address,            // 중간 값 바인딩
   let firstLine = address.firstLine {    // 최종 값 바인딩
    print(firstLine)    // 최종 값 사용가능
    print(address)        // 중간 값 사용가능
}</code></pre>
<p>이렇게 <code>,</code>로 이어서 <strong>순차적으로 바인딩</strong>할 수 있다.
중간 값을 쓰고 싶다면 이 방식이 유용하다.
옵셔널 체이닝은 결과만 얻을 수 있지만, 바인딩은 <strong>중간 값까지 활용 가능</strong>하다.</p>
<hr>
<h2 id="옵셔널의-내부-구조">옵셔널의 내부 구조?</h2>
<p>사실 옵셔널은 enum이다.</p>
<pre><code class="language-swift">// optional 내부 구조
enum Optional&lt;Wrapped&gt; {
    case none           // == nil
    case some(Wrapped)  // 값이 있을 때
}</code></pre>
<p>그래서 이런 코드도 완전히 가능하다.</p>
<pre><code class="language-swift">switch age {
case .none:
    print(&quot;Age is nil&quot;)
case let .some(value):
    print(&quot;Age is \(value)&quot;)
}</code></pre>
<p>즉, 옵셔널도 그냥 열거형일 뿐이다.
다만 Swift가 너무 잘 숨겨줘서 그런 줄 몰랐던 거다.</p>
<blockquote>
<p>옵셔널을 선언할 때, 자료형 뒤에 ?를 붙여주는데 이건 optional자료형으로 wrapping하는 거라고 보면 된다.</p>
</blockquote>
<hr>
<h2 id="guard-lastname--nil-else---는-무슨-의미"><code>guard lastName == nil else { ... }</code>는 무슨 의미?</h2>
<p>여기서 궁금한 게 또 하나 생긴다.
<strong>조건에 부정을 넣는 것도 가능할까?</strong></p>
<pre><code class="language-swift">guard lastName == nil else {
    // lastName이 nil일 경우 실행
    return
}
// nil이 아니면 실행</code></pre>
<p>이건 <strong>lastName이 nil이 아니면 return</strong>, 즉
<strong>nil일 경우에만 계속 진행하겠다</strong>는 의미다.</p>
<p><code>guard</code>는 결국 false일 때 탈출하는 거니까
조건을 부정으로 써도 전혀 문제없다.</p>
<hr>
<h2 id="한-줄-정리">한 줄 정리</h2>
<blockquote>
<p>옵셔널은 값이 있을 수도 없을 수도 있는 Swift만의 안전한 타입 시스템이며,
옵셔널 바인딩(<code>if let</code>, <code>guard let</code>), 체이닝, nil 병합(<code>??</code>) 등 다양한 방식으로 다룰 수 있다.</p>
</blockquote>
<hr>
<h2 id="마무리하며">마무리하며....</h2>
<p>옵셔널..... 자주 쓰이는 방식이니 잘 알아둬야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭]]></title>
            <link>https://velog.io/@bslee_365/%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@bslee_365/%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Mon, 07 Jul 2025 13:21:47 GMT</pubDate>
            <description><![CDATA[<h1 id="제네릭">제네릭</h1>
<p>Swift에서 프로토콜, 제네릭, 그리고 확장(extension)이 섞인 코드를 보다 보면 뭔가 어려운 듯하면서도, 익숙한 듯하면서도… 이상하게 머리에 잘 안 들어온다.
나도 그랬다. 특히 <code>associatedtype</code>이 처음 등장했을 때는 대체 얘는 왜 필요한 건지부터가 궁금했다.</p>
<p>그럼, 처음부터 차근차근 짚어보자.</p>
<hr>
<h2 id="제네릭이란">제네릭이란?</h2>
<p><strong>제네릭(Generic)</strong>은 “타입을 고정하지 않고, 나중에 사용할 때 결정하도록 만드는 프로그래밍 기법”이다.
예를 들어 <code>Array&lt;T&gt;</code>는 어떤 타입이든 저장할 수 있다. <code>Array&lt;Int&gt;</code>, <code>Array&lt;String&gt;</code>처럼.</p>
<p>Swift에서 제네릭은 다음과 같은 곳에 쓰인다.</p>
<pre><code class="language-swift">func swapValues&lt;T&gt;(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}</code></pre>
<p>같은 로직을 여러 타입에 대해 재사용할 수 있게 해주니, <strong>유연하고 타입 안정적인 코드</strong>를 만들 수 있다.</p>
<hr>
<p>다음 코드를 봐보자. 역시 아무것도 모르는게 당연하다. 이 코드가 예제가 되어줄거다.
(주석으로 달은건 무시해도 된다. 강의를 보면서 대충 이해한대로 적은 것이니....)</p>
<pre><code class="language-swift">rotocol View {
    func addSubView(_ view: View)
}

extension View {
    func addSubView(_ view: View) {
    }
}

struct Button: View {}

protocol PresentableAsView {
    associatedtype ViewType: View // generic 역할
    func produceView() -&gt; ViewType // ViewType을 지정해줄 애
    func configure(superView: View, thisView: ViewType)
    func present(view: ViewType, on superView: View)
}

extension PresentableAsView {
    func present(view: ViewType, on superView: View) {
        superView.addSubView(view) // 이건 잘 모르겠음
    }
}

struct MyButton: PresentableAsView { // MyButton 구조체를 만들어 줌
    func produceView() -&gt; Button { Button() }   // 구현체로부터 받은 produceView를 Button()으로 초기화 여기서 ViewType 자동추론이 이루어진다.
    func configure(superView: View, thisView: Button) {} // 의미 없음
}

extension PresentableAsView where ViewType == Button { // Veiw타입이 Button하고 같으면 아래 함수를 실행
    func doSomethingWithButton() {
        &quot;This is a button&quot;
    }
}

let button = MyButton()
button.doSomethingWithButton()</code></pre>
<h2 id="그럼-여기서-궁금한-게-하나-생긴다">그럼 여기서 궁금한 게 하나 생긴다.</h2>
<p><strong>Swift 프로토콜에서는 왜 직접 제네릭을 못 쓰고 <code>associatedtype</code>을 써야 할까?</strong></p>
<hr>
<h2 id="associatedtype이란">associatedtype이란?</h2>
<p>Swift에서는 프로토콜에 직접 <code>&lt;T&gt;</code> 같은 제네릭을 붙일 수 없다.
대신 <strong><code>associatedtype</code>을 이용해서 추상적인 타입을 정의</strong>한다.</p>
<pre><code class="language-swift">protocol PresentableAsView {
    associatedtype ViewType: View
}</code></pre>
<p>이건 “이 프로토콜을 따르는 애는 <code>ViewType</code>이라는 타입을 나중에 정하게 될 건데, 그 타입은 최소한 <code>View</code>를 따라야 해”라는 뜻이다.</p>
<p>즉, 프로토콜은 타입을 알 수 없으니 대충 틀만 잡고,
<strong>실제 타입은 나중에 이 프로토콜을 채택하는 쪽에서 정해줘야 한다</strong>는 거다.</p>
<hr>
<h2 id="그럼-또-하나">그럼 또 하나.</h2>
<p>아래처럼 쓴 <code>present(view: Button, ...)</code>는 과연 프로토콜 요구사항을 만족할까?</p>
<pre><code class="language-swift">extension PresentableAsView {
    func present(view: Button, on superView: View) {
        superView.addSubView(view)
    }
}</code></pre>
<p>아니다 ❌
이건 프로토콜에 정의된 <code>func present(view: ViewType, on:)</code>와는 <strong>다른 함수</strong>다.
<strong><code>ViewType</code>과 <code>Button</code>은 같은 타입이 아니기 때문</strong>이다. 이름만 같아 보여도, Swift는 타입까지 엄격하게 본다.</p>
<p>따라서 정확하게 구현하려면 이렇게 써야 한다:</p>
<pre><code class="language-swift">extension PresentableAsView {
    func present(view: ViewType, on superView: View) {
        superView.addSubView(view)
    }
}</code></pre>
<hr>
<p>그럼 <code>where ViewType == Button</code> 조건은 언제 만족할까?</p>
<pre><code class="language-swift">extension PresentableAsView where ViewType == Button {
    func doSomethingWithButton() {
        print(&quot;This is a button&quot;)
    }
}</code></pre>
<p>이건 <strong>조건부 확장</strong>이다.
말 그대로, ViewType이 <code>Button</code>일 때만 저 확장이 붙는다.</p>
<p>그럼 <code>ViewType</code>은 언제 <code>Button</code>으로 정해질까?</p>
<pre><code class="language-swift">struct MyButton: PresentableAsView {
    func produceView() -&gt; Button { Button() } // 여기서 정해진다.
    func configure(superView: View, thisView: Button) {} // produceView()가 없었다면 여기서 정해졌을것이다.
}</code></pre>
<p>여기서 Swift는 추론한다:</p>
<ul>
<li><code>produceView()</code>의 반환값이 <code>Button</code>이니까 → <code>ViewType = Button</code></li>
<li><code>configure()</code>도 <code>thisView: Button</code>이니까 → 역시 <code>ViewType = Button</code></li>
</ul>
<p>즉, 컴파일러는 &quot;아 너 ViewType = Button이구나&quot; 하고 알아서 추론해준다.
그래서 아래 같은 코드도 실행된다:</p>
<pre><code class="language-swift">let my = MyButton()
my.doSomethingWithButton()</code></pre>
<hr>
<h2 id="마지막-퍼즐--produceview-configure-present는-왜-필요한가">마지막 퍼즐 – <code>produceView</code>, <code>configure</code>, <code>present</code>는 왜 필요한가?</h2>
<p>지금 당장 보기엔 아무 일도 안 하는 함수처럼 보일 수 있다.
하지만 이들은 <strong>설계상의 인터페이스</strong> 역할을 한다.</p>
<ul>
<li><code>produceView()</code> → 뷰를 만들고</li>
<li><code>configure()</code> → 뷰를 설정하고</li>
<li><code>present()</code> → 부모 뷰에 뷰를 추가한다</li>
</ul>
<p>이렇게 만들면 어떤 뷰 타입이 오든 <strong>같은 흐름으로 처리</strong>할 수 있다.
→ 매우 <strong>확장성과 재사용성</strong>이 높은 구조다.</p>
<hr>
<h2 id="요약-정리">요약 정리</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>associatedtype</code></td>
<td>프로토콜 내부에서 타입을 일반화하는 키워드. 제네릭처럼 작동함</td>
</tr>
<tr>
<td><code>ViewType: View</code></td>
<td>ViewType은 최소한 View를 따라야 함</td>
</tr>
<tr>
<td><code>produceView() -&gt; Button</code></td>
<td>이걸 통해 ViewType이 Button으로 추론됨</td>
</tr>
<tr>
<td><code>extension where ViewType == Button</code></td>
<td>조건부 확장. 오직 Button일 때만 실행</td>
</tr>
<tr>
<td><code>present(view: Button, ...)</code></td>
<td>이건 ViewType이 Button일 때만 맞는 함수이고, 일반 요구사항과는 별개</td>
</tr>
<tr>
<td>인터페이스 메서드들</td>
<td>구조화된 설계를 위해 사용됨. 재사용성과 유연성을 높임</td>
</tr>
</tbody></table>
<hr>
<h2 id="마무리하며">마무리하며....</h2>
<p>이 코드는 Swift 제네릭 프로그래밍, 프로토콜 중심 설계, 타입 추론, 조건부 확장의 정수가 다 들어 있는 예제다.
처음엔 헷갈릴 수밖에 없다.
하지만 흐름을 알고 나면, Swift가 왜 이렇게 설계되었는지 조금씩 이해가 간다.</p>
<p>그리고 이제 나도 이렇게 말할 수 있다:
<strong>associatedtype은 프로토콜 속 제네릭이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토콜 & 익스텐션]]></title>
            <link>https://velog.io/@bslee_365/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%9D%B5%EC%8A%A4%ED%85%90%EC%85%98</link>
            <guid>https://velog.io/@bslee_365/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-%EC%9D%B5%EC%8A%A4%ED%85%90%EC%85%98</guid>
            <pubDate>Mon, 30 Jun 2025 14:52:33 GMT</pubDate>
            <description><![CDATA[<p>오늘은 Swift의 핵심 개념인 <strong>Protocol</strong>, <strong>Extension</strong>, 그리고 <strong>타입 캐스팅</strong>에 대해서 하나씩 정리해보려고 한다.
이 세 가지는 Swift에서 꽤 자주 마주치게 되는데, 처음 보면 뭔가 추상적이고, 런타임에 따라 달라지고...
조금 어렵게 느껴질 수 있다. 그래서 오늘은 찬찬히 정리해봤다.</p>
<hr>
<h1 id="프로토콜protocol이란">프로토콜(Protocol)이란?</h1>
<p>먼저 정의부터 확실하게 짚고 가자.</p>
<p><strong>Protocol은 어떤 속성이나 메서드가 필요하다는 ‘규약’을 정리해놓은 것</strong>이다.
쉽게 말하면 &quot;이걸 따르는 타입은 이 기능을 반드시 구현해야 해!&quot; 라는 일종의 계약서 같은 느낌이다.
자바로 치면 인터페이스 역할을 하는 친구라고 보면된다.</p>
<pre><code class="language-swift">protocol Greetable {
    var name: String { get }
    func greet()
}</code></pre>
<p>이걸 보면, <code>name</code>이라는 속성이 있어야 하고, <code>greet()</code>라는 메서드도 구현해야 한다.</p>
<hr>
<h2 id="프로토콜로-인스턴스를-만들-수-있을까">프로토콜로 인스턴스를 만들 수 있을까?</h2>
<pre><code class="language-swift">let someone = Greetable() // 응 안됨.</code></pre>
<p>에러가 난다.
왜냐하면 <strong>프로토콜은 구현체가 아니라 설계도이기 때문</strong>이다.
직접 쓸 수 없고, 반드시 해당 프로토콜을 &quot;채택한&quot; 타입(예: struct, class)을 통해서 사용해야 한다.</p>
<pre><code class="language-swift">struct Human: Greetable {
    var name: String
    func greet() {
        print(&quot;Hello, \(name)&quot;)
    }
}

let tom = Human(name: &quot;Tom&quot;)
tom.greet() // Hello, Tom</code></pre>
<hr>
<h2 id="extension">extension?</h2>
<p>Swift에서는 기존 타입에 기능을 붙일 수 있다.
이게 바로 <strong>extension(확장)</strong>이다.</p>
<pre><code class="language-swift">extension Int {
    func squared() -&gt; Int {
        return self * self
    }
}</code></pre>
<p>이제 <code>5.squared()</code> =&gt; 25가 출력된다.</p>
<hr>
<h2 id="프로토콜에도-extension을-쓸-수-있다">프로토콜에도 extension을 쓸 수 있다.</h2>
<pre><code class="language-swift">protocol CanJump {
    func jump()
}
extension CanJump {
    func jump() {
        print(&quot;Jumping...&quot;)
    }
}</code></pre>
<p>만약 어떤 타입이 <code>CanJump</code>만 채택하고 <code>jump()</code>를 직접 구현하지 않으면?
→ <strong>extension에 있는 기본 구현이 자동으로 사용된다.</strong></p>
<pre><code class="language-swift">struct Cat: CanJump {}
Cat().jump() // Jumping...</code></pre>
<hr>
<h2 id="프로토콜-속성에--get---get-set-이-붙는-이유는">프로토콜 속성에 <code>{ get }</code>, <code>{ get set }</code>이 붙는 이유는?</h2>
<p>이건 처음 보면 문법이 헷갈릴 수 있다.</p>
<pre><code class="language-swift">protocol HasName {
    var name: String { get }
    var age: Int { get set }
}</code></pre>
<ul>
<li><code>{ get }</code>: 읽기만 가능 (let이나 var 모두 OK)</li>
<li><code>{ get set }</code>: 읽기 + 쓰기 (→ 반드시 <code>var</code>만 가능)</li>
</ul>
<p>예를 들어서 이렇게 구현할 수 있다.</p>
<pre><code class="language-swift">struct Person: HasName {
    let name: String   // 읽기 전용
    var age: Int       // 읽고 쓸 수 있음
}</code></pre>
<p>자바에서 생각하면 getter와 setter를 생각하면 쉬울듯하다...</p>
<hr>
<h2 id="그럼-privateset과-뭐가-다를까">그럼 <code>private(set)</code>과 뭐가 다를까?</h2>
<p>또 헷갈릴 수 있는 게 <code>private(set)</code>이다.</p>
<pre><code class="language-swift">struct User {
    private(set) var name: String
}</code></pre>
<p>이건 <strong>외부에서는 읽기만 가능하지만 내부에서는 수정 가능</strong>하다.</p>
<table>
<thead>
<tr>
<th>문법</th>
<th>위치</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>{ get set }</code></td>
<td>프로토콜</td>
<td>외부에서도 읽고 쓸 수 있어야 함</td>
</tr>
<tr>
<td><code>private(set)</code></td>
<td>구조체 정의</td>
<td>외부는 읽기만 가능, 내부는 읽고 쓸 수 있음</td>
</tr>
</tbody></table>
<p>결국 목적은 비슷하지만, <strong>프로토콜은 ‘요구 조건’, 구조체는 ‘접근 제어’</strong> 관점인 거다.</p>
<hr>
<h2 id="any-is-as는-언제-써야-할까"><code>Any</code>, <code>is</code>, <code>as?</code>는 언제 써야 할까?</h2>
<p>여기서 타입 캐스팅 개념으로 넘어가 보자.</p>
<pre><code class="language-swift">func describe(obj: Any) {
    if obj is Vehicle {
        print(&quot;obj conforms to Vehicle&quot;)
    } else {
        print(&quot;nope&quot;)
    }
}</code></pre>
<ul>
<li><code>Any</code>: 모든 타입을 받을 수 있다. (Int, String, Struct, Class 기타 등등...., 자바에서 <code>Object클래스</code>와 비슷한 개념)</li>
<li><code>is</code>: 런타임에 타입이 맞는지 체크 (java에서 <code>instance of</code> 와 비슷한 개념)</li>
</ul>
<hr>
<h2 id="그럼-as는-뭘까">그럼 <code>as?</code>는 뭘까?</h2>
<pre><code class="language-swift">if let vehicle = obj as? Vehicle {
    vehicle.increaseSpeed(by: 10)
}</code></pre>
<p>여기서 <code>as?</code>는 <strong>조건부 타입 캐스팅</strong>이다.</p>
<table>
<thead>
<tr>
<th>연산자</th>
<th>설명</th>
<th>실패 시</th>
</tr>
</thead>
<tbody><tr>
<td><code>as?</code></td>
<td>조건부 캐스팅</td>
<td><code>nil</code> 반환</td>
</tr>
<tr>
<td><code>as!</code></td>
<td>강제 캐스팅 (실패 시 앱 죽음)</td>
<td>런타임 에러</td>
</tr>
<tr>
<td><code>as</code></td>
<td>확정 타입으로 캐스팅</td>
<td>컴파일 시 타입 체크됨</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-그런데-구조체에서-as-썼더니-왜-값이-안-바뀔까">🧠 그런데 구조체에서 <code>as?</code> 썼더니 왜 값이 안 바뀔까?</h2>
<pre><code class="language-swift">struct Car: Vehicle {
    var speed = 0
    mutating func increaseSpeed(by amount: Int) {
        speed += amount
    }
}

let car = Car()
increaseSpeedIfVehicle(obj: car)

print(car.speed) // 0</code></pre>
<p>헷갈릴 수 있다. 분명 <code>increaseSpeed</code> 했는데 왜 0이지?</p>
<p>→ Swift의 <code>struct</code>는 <strong>값 타입</strong>이다.
즉, <code>obj as? Vehicle</code>에서 복사본이 넘어가기 때문에,
원래 <code>car</code>는 그대로인 것이다.</p>
<ul>
<li>해결하려면 <code>class</code>를 쓰거나</li>
<li><code>inout</code> 매개변수로 직접 전달해야 한다.</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>한 줄 요약</th>
</tr>
</thead>
<tbody><tr>
<td><code>protocol</code></td>
<td>타입이 따라야 할 규약이다</td>
</tr>
<tr>
<td><code>extension</code></td>
<td>기존 타입이나 프로토콜에 기능을 추가한다</td>
</tr>
<tr>
<td><code>{ get set }</code></td>
<td>읽기와 쓰기 둘 다 가능해야 한다는 의미</td>
</tr>
<tr>
<td><code>private(set)</code></td>
<td>외부는 읽기만 가능, 내부는 수정 가능</td>
</tr>
<tr>
<td><code>Any</code></td>
<td>모든 타입을 받을 수 있는 타입</td>
</tr>
<tr>
<td><code>is</code></td>
<td>타입 체크 연산자</td>
</tr>
<tr>
<td><code>as?</code></td>
<td>조건부 캐스팅 (옵셔널로 반환)</td>
</tr>
<tr>
<td>값 타입 캐스팅 문제</td>
<td>복사본을 바꾸는 거라 원본은 변하지 않음</td>
</tr>
</tbody></table>
<hr>
<p>Swift는 한 번 이해하면 정말 강력한 문법들이 많은 언어다.
궁금한 게 생기면 또 정리해서 기록해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[class]]></title>
            <link>https://velog.io/@bslee_365/class</link>
            <guid>https://velog.io/@bslee_365/class</guid>
            <pubDate>Mon, 30 Jun 2025 12:11:01 GMT</pubDate>
            <description><![CDATA[<p>Swift의 class는 struct와 다른 점이 참 많다.
오늘은 class의 개념부터 초기화 방식, 접근 제어자, 메모리 관리까지 정리해본다.</p>
<hr>
<h2 id="class란">class란?</h2>
<p>Swift에서 <code>class</code>는 <strong>참조 타입(reference type)</strong>이다.
같은 인스턴스를 여러 변수가 공유할 수 있고, 값이 바뀌면 모든 참조가 영향을 받는다.</p>
<pre><code class="language-swift">class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sayHello() {
        print(&quot;Hello, my name is \(name)&quot;)
    }
}</code></pre>
<p>그럼 여기서 궁금한 게 하나 생긴다.
<strong>값을 비교하고 싶을 때는 어떻게 해야 할까?</strong>
클래스는 참조 타입인데, <code>==</code>만 써도 되는 걸까?</p>
<hr>
<h2 id="-vs--값-비교와-참조-비교"><code>==</code> vs <code>===</code> (값 비교와 참조 비교)</h2>
<ul>
<li><code>==</code>는 값을 비교하는 연산자다. <code>Equatable</code> 프로토콜을 따르지 않으면 쓸 수 없다.</li>
<li><code>===</code>는 클래스 인스턴스가 <strong>동일한 객체인지</strong> 비교한다. 즉, 주소값 비교한다.</li>
</ul>
<pre><code class="language-swift">class Dog {
    var name: String
    init(name: String) { self.name = name }
}

let a = Dog(name: &quot;Buddy&quot;)
let b = Dog(name: &quot;Buddy&quot;)
let c = a

print(a === b) // false
print(a === c) // true</code></pre>
<p>쉽게 말하자면, <code>==</code>는 값이 같냐를 보는 것이고,
<code>===</code>는 같은 물건이냐를 보는 것이다.</p>
<hr>
<h2 id="privateset은-왜-쓸까">private(set)은 왜 쓸까?</h2>
<p>Swift에서는 변수에 <code>private(set)</code>을 붙일 수 있다.
<strong>읽을 수는 있지만, 바깥에서는 수정할 수 없다.</strong></p>
<pre><code class="language-swift">class Person {
    private(set) var age: Int

    init(age: Int) {
        self.age = age
    }

    func growOlder() {
        age += 1
    }
}</code></pre>
<p>그럼 왜 이렇게 만들까?
<strong>캡슐화</strong> 때문이다.
외부에서는 값이 어떻게 바뀌는지 알 필요 없고,
내부에서만 규칙적으로 바뀌게 하고 싶을 때 사용한다.</p>
<hr>
<h2 id="convenience-init이란">convenience init이란?</h2>
<p>보통 클래스에 여러 종류의 초기화 방법이 필요할 때가 있다.
이럴 때 <strong>보조 생성자</strong> 역할을 하는 게 <code>convenience init</code>이다.</p>
<pre><code class="language-swift">class Car {
    var manufacturer: String = &quot;Tesla&quot;
    var model: String
    var year: Int

    init(model: String, year: Int) {
        self.model = model
        self.year = year
    }

    convenience init(model: String) {
        self.init(model: model, year: 2025)
    }

    func startEngine() {
        print(&quot;start Engine.&quot;)
    }

    func drive() {
        print(&quot;I&#39;m driving.&quot;)
    }
}</code></pre>
<p>근데 여기서 또 질문이 생긴다.
<strong>그냥 init 여러 개 만들면 되는 거 아닌가?</strong></p>
<hr>
<h2 id="init-오버로딩-vs-convenience-init">init 오버로딩 vs convenience init</h2>
<p>물론 Swift도 <code>init</code> 오버로딩이 가능하다.
그치만 <strong>중복 코드</strong>가 생긴다.</p>
<pre><code class="language-swift">init(model: String) {
    self.model = model
    self.year = 2025
    startEngine()
    drive()
}

init(model: String, year: Int) {
    self.model = model
    self.year = year
    startEngine()
    drive()

}
// startEngine과 drive의 함수의 중복코드 발생!</code></pre>
<p>반면 <code>convenience init</code>을 쓰면
<strong>중복 없이 기존 init을 재활용할 수 있다.</strong></p>
<pre><code class="language-swift">convenience init(model: String) {
    self.init(model: model, year: 2025)
    startEngine()
    drive()
}</code></pre>
<p>결국, <strong>편리하고 중복 없는 초기화를 위해</strong> <code>convenience init</code>이 존재하는 것이다.</p>
<hr>
<h2 id="그럼-자식-클래스에서-superinit은-어떻게-써야-할까">그럼 자식 클래스에서 <code>super.init()</code>은 어떻게 써야 할까?</h2>
<p>아래 코드는 에러가 난다.</p>
<pre><code class="language-swift">class TeslaY: Car {
    override init() {
        super.init(model: &quot;Y&quot;) // 에러
    }
}</code></pre>
<p>왜냐면 부모의 <code>convenience init</code>은
자식에서 <code>super.init</code>로 호출할 수 없다.</p>
<p>해결하려면 반드시 <strong>지정 생성자</strong>를 호출해야 한다.</p>
<pre><code class="language-swift">override init() {
    super.init(model: &quot;Y&quot;, year: 2025) // OK!
}</code></pre>
<p>Swift는 상속 구조에서 초기화를 명확하게 통제한다.
<code>super.init()</code>은 꼭 <strong>지정 생성자</strong>만 호출할 수 있다.
보조 생성자는 내부에서 <code>self.init()</code>로만 호출 가능하다.</p>
<hr>
<h2 id="deinit은-언제-호출될까">deinit은 언제 호출될까?</h2>
<p><code>deinit</code>은 클래스 인스턴스가 <strong>메모리에서 해제될 때</strong> 자동으로 호출된다.</p>
<pre><code class="language-swift">class MyClass {
    init() {
        print(&quot;Initialized&quot;)
    }

    func doSomething() {}

    deinit {
        print(&quot;Deinitialized&quot;)
    }
}</code></pre>
<p>예를 들어 이런 코드가 있다면?</p>
<pre><code class="language-swift">let myClosure = {
    let obj = MyClass()
    obj.doSomething()
}
myClosure()

// 출력:
// Initialized
// Deinitialized</code></pre>
<p>클로저 블록이 끝나면서 <code>obj</code>의 참조 카운트가 0이 되고,
그 순간 <code>deinit</code>이 호출된다.</p>
<p>이게 바로 ARC (Automatic Reference Counting)의 힘이다.
Swift는 메모리를 자동으로 관리해준다.
단, 순환 참조는 주의해야한다.</p>
<hr>
<h2 id="마무리-한-줄-요약">마무리 한 줄 요약</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>class</td>
<td>참조 타입, 상속/소멸자 지원, ARC 동작</td>
</tr>
<tr>
<td>== / ===</td>
<td>값 비교 / 참조 비교</td>
</tr>
<tr>
<td>private(set)</td>
<td>외부에서는 읽기만, 내부에서는 수정 가능</td>
</tr>
<tr>
<td>convenience init</td>
<td>지정 생성자 위임용 보조 생성자</td>
</tr>
<tr>
<td>super.init()</td>
<td>지정 생성자만 호출 가능, convenience는 호출 불가</td>
</tr>
<tr>
<td>deinit</td>
<td>인스턴스가 메모리에서 해제될 때 호출됨 (struct에는 없음)</td>
</tr>
</tbody></table>
<hr>
<p>Swift에서 class를 제대로 이해하려면
참조 타입의 특성과 ARC를 잘 알아야 한다.
그리고 생성자와 소멸자의 규칙도 깔끔하게 정리해두면
실전에서 훨씬 유연하게 대응할 수 있다.</p>
<p>필요하면 다음 포스팅에서
<code>required init</code>, <code>weak/unowned</code>, <code>순환 참조 방지</code>도 다뤄보자!</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[이놈(enum).... 이놈의 자식 넌 누구냐]]></title>
            <link>https://velog.io/@bslee_365/%EC%9D%B4%EB%86%88enum....-%EC%9D%B4%EB%86%88%EC%9D%98-%EC%9E%90%EC%8B%9D-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</link>
            <guid>https://velog.io/@bslee_365/%EC%9D%B4%EB%86%88enum....-%EC%9D%B4%EB%86%88%EC%9D%98-%EC%9E%90%EC%8B%9D-%EB%84%8C-%EB%88%84%EA%B5%AC%EB%83%90</guid>
            <pubDate>Wed, 25 Jun 2025 15:00:36 GMT</pubDate>
            <description><![CDATA[<h2 id="1-enum이란">1. enum이란?</h2>
<p><strong>enum은 한정된 값의 집합을 표현하는 타입이다.</strong>
예를 들어, 동물의 종류처럼 &quot;정해진 선택지&quot;를 다룰 때 쓰기 좋다.</p>
<pre><code class="language-swift">enum Animals {
    case cat
    case dog
    case rabbit
}</code></pre>
<p>이렇게 <code>Animals.cat</code>처럼 쓰면, 이것 자체가 enum 타입의 값이 된다.
출력하면 &quot;cat&quot;처럼 보일 수도 있지만, 실제로는 문자열이 아니다.</p>
<h3 id="그럼-여기서-궁금한-게-하나-생긴다">그럼 여기서 궁금한 게 하나 생긴다.</h3>
<p><strong>문자열처럼 출력하고 싶으면 어떻게 하지?</strong></p>
<p>그럴 땐 <code>RawRepresentable</code>을 활용하면 된다.
뒤에 붙는거는 프로토콜인데 이건 스웨덴 형님이 나중에 알려주신다고 참고만 하시란다.
(지금은 클로저 후유증이 좀 남아있으니 더 안파고 넘어가보겠다.)</p>
<pre><code class="language-swift">enum Animals: String {
    case cat, dog, rabbit
}

print(Animals.cat.rawValue)  // &quot;cat&quot;</code></pre>
<hr>
<h2 id="2-enum을-언제-쓸까">2. enum을 언제 쓸까?</h2>
<p><strong>정해진 상태나 선택지를 표현하고 싶을 때</strong> enum이 제격이다.</p>
<p>예를 들어:</p>
<ul>
<li>로그인 상태: <code>.loggedIn</code>, <code>.loggedOut</code></li>
<li>네트워크 상태: <code>.loading</code>, <code>.success(data)</code>, <code>.failure(error)</code></li>
<li>방향: <code>.left</code>, <code>.right</code></li>
</ul>
<p>왜 좋냐면
<em>switch문과 찰떡*이고, 연관 값을 붙여서 다양한 상태를 *안전하게</em> 표현할 수 있기 때문이다.</p>
<hr>
<h2 id="3-enum에서--비교가-안-되는-이유는">3. enum에서 <code>==</code> 비교가 안 되는 이유는?</h2>
<pre><code class="language-swift">enum Shortcut {
    case fileOrFolder(path: URL, name: String)
    case wwwUrl(path: URL)
}

let a = Shortcut.wwwUrl(path: URL(string: &quot;https://apple.com&quot;)!)

if a == Shortcut.wwwUrl(path: URL(string: &quot;https://apple.com&quot;)!) {
    // Error 발생
}</code></pre>
<p>왜 안 될까?</p>
<p>바로, <strong>연관 값이 있는 enum은 자동으로 <code>Equatable</code>을 채택하지 않기 때문이다.</strong></p>
<h3 id="해결-방법">해결 방법:</h3>
<pre><code class="language-swift">enum Shortcut: Equatable {
    case fileOrFolder(path: URL, name: String)
    case wwwUrl(path: URL)
}</code></pre>
<hr>
<h2 id="4-그럼-switch는-왜-잘-작동할까">4. 그럼 <code>switch</code>는 왜 잘 작동할까?</h2>
<pre><code class="language-swift">switch a {
case .wwwUrl(let path):
    print(path)
default:
    break
}</code></pre>
<p><code>switch</code>는 <strong>패턴 매칭 기반</strong>이라, <code>Equatable</code>이 없어도 내부 case 구조를 직접 분해해서 비교한다.
즉, <code>==</code> 연산이 아니라 구조 자체를 비교하는 방식이다.
<code>==</code>는 값을 비교하는거라고 한다면 <code>switch</code>같은 경우에는, 값이 <code>특정 구조</code>, <code>모양</code>, <code>조건</code>을 만족하는지 검사하는 방식</p>
<hr>
<h2 id="5-if-case-let-문법">5. if case let 문법</h2>
<pre><code class="language-swift">if case let .wwwUrl(path) = a {
    print(path)
}

// 이거랑 똑같다고 보면 된다
switch a {
    case let .wwwUrl(path):
        print(path)
        break
    defualt:
        &quot;somthing else...&quot;
}</code></pre>
<p>이건 <code>switch</code>를 줄여 쓴 문법이다.
<strong>특정 케이스만 확인하고, 연관 값도 꺼내고 싶을 때</strong> 아주 편리하다.</p>
<p>비슷하게 <code>guard case</code>, <code>for case</code>, <code>while case</code>도 쓸 수 있다.</p>
<hr>
<h2 id="6-재귀-enum이란-indirect">6. 재귀 enum이란? <code>indirect</code>?</h2>
<p>이게 조금 난이도 있는 부분이다.</p>
<pre><code class="language-swift">indirect enum IntOperation {
    case add(Int, Int)
    case subtract(Int, Int)
    case freeHand(IntOperation)
}</code></pre>
<p>여기서 <code>freeHand</code>는 다시 <code>IntOperation</code>을 포함하고 있다.
이건 재귀 구조인데, Swift의 enum은 <strong>값 타입이라 스스로를 가질 수 없다.</strong>
그래서 <code>indirect</code> 키워드를 써서 <strong>힙에 저장되도록</strong> 한다.</p>
<hr>
<h2 id="7-재귀-enum을-이용한-계산-예제">7. 재귀 enum을 이용한 계산 예제</h2>
<pre><code class="language-swift">indirect enum IntOperation {
    case add(Int, Int)
    case subtract(Int, Int)
    case freeHand(IntOperation)

    func calculateResult(
        of operation: IntOperation? = nil
    ) -&gt; Int {
        switch operation ?? self {
        case let .add(lhs, rhs):
            return lhs + rhs
        case let .subtract(lhs, rhs):
            return lhs + rhs
        case let .freeHand(operation):
            return calculateResult(of: operation)
        }
    }
}</code></pre>
<p>여기서 핵심은 <code>operation ?? self</code>다.
함수 호출 시 매개변수가 없으면 <code>self</code>를 쓰는 구조다.
즉, 이런 식으로도 가능하다:</p>
<pre><code class="language-swift">let freeHand = IntOperation.freeHand(.add(20, 30))
freeHand.calculateResult()  // 50</code></pre>
<hr>
<h2 id="8-그럼-struct와의-차이는-뭐야">8. 그럼 struct와의 차이는 뭐야?</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>struct</th>
<th>enum</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>여러 속성을 가진 데이터 구조 표현</td>
<td>상태나 선택지를 표현</td>
</tr>
<tr>
<td>구성</td>
<td>여러 프로퍼티 가능</td>
<td>한 번에 하나의 case만 존재</td>
</tr>
<tr>
<td>연관값</td>
<td>없음 (모든 속성이 고정 필드로 존재)</td>
<td>특정 case에만 연관값 가능</td>
</tr>
<tr>
<td>switch 사용</td>
<td>❌</td>
<td>(switch, if case 등 패턴 매칭 가능)</td>
</tr>
<tr>
<td>상속</td>
<td>❌</td>
<td>❌</td>
</tr>
</tbody></table>
<hr>
<h2 id="한-줄-정리">한 줄 정리</h2>
<p><strong>enum은 상태를 표현하고</strong>,
<strong>struct는 데이터를 담는다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Struct 구조체..... (C에서만 보던 녀석이 또 튀어나왔다)]]></title>
            <link>https://velog.io/@bslee_365/Struct-%EA%B5%AC%EC%A1%B0%EC%B2%B4.....-C%EC%97%90%EC%84%9C%EB%A7%8C-%EB%B3%B4%EB%8D%98-%EB%85%80%EC%84%9D%EC%9D%B4-%EB%98%90-%ED%8A%80%EC%96%B4%EB%82%98%EC%99%94%EB%8B%A4</link>
            <guid>https://velog.io/@bslee_365/Struct-%EA%B5%AC%EC%A1%B0%EC%B2%B4.....-C%EC%97%90%EC%84%9C%EB%A7%8C-%EB%B3%B4%EB%8D%98-%EB%85%80%EC%84%9D%EC%9D%B4-%EB%98%90-%ED%8A%80%EC%96%B4%EB%82%98%EC%99%94%EB%8B%A4</guid>
            <pubDate>Tue, 24 Jun 2025 14:09:26 GMT</pubDate>
            <description><![CDATA[<p>Swift에서 <code>struct</code>는 굉장히 자주 쓰이는데, 클래스랑 비슷해 보이지만 사실 꽤 다르다.
딱 두 단어로 정리하면 <strong>값 타입</strong>이고, <strong>상속 불가</strong>이다.</p>
<p>그래서 오늘은 Swift의 구조체에 대해 차근차근 정리해보려 한다.</p>
<hr>
<h2 id="구조체">구조체?</h2>
<pre><code class="language-swift">struct Person {
    let name: String
    let age: Int
}

let foo = Person(name: &quot;Foo&quot;, age: 20)</code></pre>
<p>Swift의 구조체는 내부에 저장된 속성들을 기준으로 자동으로 생성자(<code>init</code>)를 만들어준다.
이런 걸 <strong>멤버와이즈 이니셜라이저</strong>라고 부른다.</p>
<p>즉, 따로 <code>init</code> 안 만들어도 이렇게 <code>Person(name:age:)</code>처럼 바로 만들 수 있다는 얘기다.</p>
<hr>
<h2 id="그럼-언제-사용자-정의-init이-필요할까">그럼 언제 사용자 정의 init이 필요할까?</h2>
<pre><code class="language-swift">struct CommodoreComputer {
    let name: String
    let manufacturer: String

    init(name: String) {
        self.name = name
        self.manufacturer = &quot;Commodore&quot;
    }
}</code></pre>
<p>여기서 궁금한 게 하나 생긴다.
&quot;기본 생성자도 있는데 굳이 init을 왜 만들지?&quot;</p>
<p>그 이유는 <code>manufacturer</code>처럼 내부에서 고정 값을 넣고 싶을 때!
외부에서 굳이 넣을 필요 없는 값을 내부에서 세팅해줄 수 있어서다.</p>
<hr>
<h2 id="기본값을-활용하면-더-간단해진다">기본값을 활용하면 더 간단해진다</h2>
<pre><code class="language-swift">struct CommodoreComputer2 {
    let name: String
    let manufacturer = &quot;Commodore&quot;
}</code></pre>
<p>이렇게 속성에 기본값을 주면?
Swift는 알아서 생성자를 만들어준다.
그래서 <code>CommodoreComputer2(name:)</code>만 있어도 된다.</p>
<hr>
<h2 id="fullname을-계산해주는-두-가지-방식">fullName을 계산해주는 두 가지 방식</h2>
<pre><code class="language-swift">struct Person2 {
    let firstName: String
    let lastName: String
    let fullName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
        self.fullName = &quot;\(firstName) \(lastName)&quot;
    }
}</code></pre>
<p>이렇게 <code>init</code>에서 fullName을 지정해줄 수도 있지만,
사실 더 Swift스러운 방법은 따로 있다.</p>
<pre><code class="language-swift">struct Person22 {
    let firstName: String
    let lastName: String

    var fullName: String {
        &quot;\(firstName) \(lastName)&quot;
    }
}</code></pre>
<h3 id="왜-이게-더-좋을까">왜 이게 더 좋을까?</h3>
<ul>
<li>fullName은 실제로 저장된 값이 아니라 <strong>계산 가능한 값</strong>이다.</li>
<li>이렇게 하면 속성이 바뀌더라도 항상 최신 상태를 유지할 수 있다.</li>
</ul>
<hr>
<h2 id="구조체-내부-값을-바꾸고-싶다면-→-mutating">구조체 내부 값을 바꾸고 싶다면? → <code>mutating</code></h2>
<pre><code class="language-swift">struct Car {
    var currentSpeed: Int

    mutating func drive(speed: Int) {
        currentSpeed = speed
    }
}</code></pre>
<p>여기서 궁금한 점.
&quot;왜 굳이 <code>mutating</code>이라는 키워드가 필요한 걸까?&quot;</p>
<p>그 이유는 구조체는 <strong>값 타입</strong>이라 내부에서 값을 바꾸려면 스스로를 다시 바꿔야 하기 때문.
그래서 명시적으로 <code>mutating</code>을 붙여줘야 한다.</p>
<pre><code class="language-swift">// currentSpeed가 let이면 당연히 안바뀜.
var car = Car(currentSpeed: 100)
car.drive(speed: 300)</code></pre>
<hr>
<h2 id="구조체는-복사된다-값-타입-특징">구조체는 복사된다 (값 타입 특징)</h2>
<pre><code class="language-swift">let copy = car
car.drive(speed: 500)
print(copy.currentSpeed)  // 300</code></pre>
<p>이 예제를 보면 명확하다.
<code>copy</code>는 <code>car</code>를 복사한 값이라, 이후 <code>car</code>가 바뀌어도 <code>copy</code>는 그대로다.</p>
<hr>
<h2 id="구조체는-상속이-안-된다">구조체는 상속이 안 된다</h2>
<pre><code class="language-swift">struct LivingThing {
    init() {
        print(&quot;I&#39;m living thing&quot;)
    }
}

// 에러 발생: 구조체는 상속이 안 된다
// struct Animal: LivingThing { }</code></pre>
<p>이쯤에서 이런 의문이 생긴다.
&quot;왜 struct는 상속이 안 되는 거지?&quot;</p>
<p>Swift는 <strong>컴포지션(구성)</strong>을 더 중요하게 본다.
클래스처럼 복잡하게 상속보단, 작은 구조체들을 조립해서 쓰는 걸 더 선호한다.
그래서 swiftUI 잠깐 배웠을 때, 데이터는 struct. 데이터를 제어하는건 class를 사용한다고 들었던거같다.</p>
<hr>
<h2 id="copy-메서드-패턴으로-새-인스턴스-만들기"><code>copy</code> 메서드 패턴으로 새 인스턴스 만들기</h2>
<pre><code class="language-swift">struct Bike {
    let manufacturer: String
    let currentSpeed: Int

    func copy(currentSpeed: Int) -&gt; Bike {
        Bike(manufacturer: self.manufacturer, currentSpeed: currentSpeed)
    }
}</code></pre>
<p>값 타입에서 가장 많이 쓰이는 패턴 중 하나가 바로 이거다.</p>
<p>기존 값을 유지하면서 <strong>일부 속성만 바꾼 새로운 인스턴스</strong>를 만들고 싶을 때 유용하다.</p>
<pre><code class="language-swift">let bike1 = Bike(manufacturer: &quot;HD&quot;, currentSpeed: 20)
let bike2 = bike1.copy(currentSpeed: 30)</code></pre>
<p>다른 방법도 있다.</p>
<pre><code class="language-swift">struct Bike {
    let manufacturer: String
    let currentSpeed: int

    mutating func moveToOrigin() {
        // 이런식으로 작성하면 self 전체를 다른 값으로 통째로 바꾸는 것도 가능.
        self = Point(manufacturer: &quot;YD&quot;, currentSpeed: 30)
    }
}</code></pre>
<hr>
<h2 id="정리하자면">정리하자면</h2>
<ul>
<li>구조체는 <strong>값 타입</strong>이고 <strong>상속 불가</strong></li>
<li>생성자는 자동으로 만들어지지만, 필요에 따라 <code>init</code> 정의 가능</li>
<li>계산 속성(computed property) 활용은 코드 간결성과 최신 상태 유지에 좋다</li>
<li>내부 값 수정은 <code>mutating</code> 키워드로 명시</li>
<li>복사(copy) 패턴은 상태 변경을 안전하게 다룰 수 있는 방법</li>
</ul>
<hr>
<h2 id="한-줄-요약">한 줄 요약</h2>
<p><strong>Swift의 struct는 값 기반 프로그래밍을 위한 핵심 도구다.</strong></p>
<p><code>불변성</code>, <code>복사</code>, <code>컴포지션</code>을 잘 이해하고 쓰면, Swift가 훨씬 더 명확하고 안전한 언어로 다가온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저.....]]></title>
            <link>https://velog.io/@bslee_365/%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@bslee_365/%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Tue, 24 Jun 2025 12:47:48 GMT</pubDate>
            <description><![CDATA[<p>Swift를 배우면서 가장 많이 마주치는 개념 중 하나가 바로 <strong>클로저(Closure)</strong>이다.
근데 이게 생긴 것도 낯설고, 문법도 헷갈리고, 대체 왜 쓰는지도 잘 모르겠다.
(이게 무슨소리인지 이해하는데 대략 2일정도 소요된거같다.... 허허)
(사실 완전히 이해하지는 못했지만, 계속 마주치면서 이해해보려고 한다.)</p>
<hr>
<h2 id="클로저란">클로저란?</h2>
<p>클로저는 <strong>이름 없는 함수</strong>이다.
그리고 이 함수는 변수처럼 저장하거나, 함수의 인자로 전달하거나, 함수에서 리턴할 수도 있다.</p>
<p>Swift에서는 아래처럼 쓴다:</p>
<pre><code class="language-swift">let greet = {
    print(&quot;안녕하세요&quot;)
}
greet()  // 출력: 안녕하세요</code></pre>
<p>그럼 여기서 궁금한 게 하나 생긴다.
<strong>&quot;굳이 이렇게까지 해야 돼? 그냥 함수 쓰면 되는 거 아니야?&quot;</strong></p>
<hr>
<h2 id="클로저를-왜-써야-할까">클로저를 왜 써야 할까?</h2>
<p><strong>클로저는 실전에서 정말 많이 쓰인다.</strong> 그 이유는 이렇다.</p>
<ul>
<li>콜백 전달<ul>
<li>네트워크 요청 끝났을 때, 애니메이션 완료 시 등등</li>
</ul>
</li>
<li>상태 유지<ul>
<li>외부 변수 캡처해서 값 기억 가능 (→ 클로저 캡처)</li>
</ul>
</li>
<li>코드 간결<ul>
<li>짧고 일회성 로직에 딱 좋음</li>
</ul>
</li>
<li>지연 실행<ul>
<li>필요할 때만 실행되도록 할 수 있음</li>
</ul>
</li>
</ul>
<p>예를 들어 이런 함수가 있다고 하자.</p>
<pre><code class="language-swift">func makeCounter() -&gt; () -&gt; Int {
    var count = 0
    return {
        count += 1
        return count
    }
}</code></pre>
<p>이건 <code>count</code>라는 값을 내부에서 유지하면서 점점 늘려주는 클로저를 리턴한다.</p>
<pre><code class="language-swift">let counter = makeCounter()
counter()  // 1
counter()  // 2</code></pre>
<p>이처럼 클로저는 함수 이상의 역할을 할 수 있다.
<strong>‘기억하는 함수’</strong>라고 생각하면 된다.</p>
<hr>
<h2 id="클로저를-어떻게-쓸까">클로저를 어떻게 쓸까?</h2>
<p>Swift에서 클로저는 여러 방식으로 정의할 수 있다.
(예시를 쭉 보면 감이 올지도.......?)</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>예시 코드</th>
</tr>
</thead>
<tbody><tr>
<td>기본 정의</td>
<td><code>let greet = { print(&quot;hi&quot;) }</code></td>
</tr>
<tr>
<td>파라미터 &amp; 리턴</td>
<td><code>let add = { (a: Int, b: Int) -&gt; Int in return a + b }</code></td>
</tr>
<tr>
<td>타입 추론</td>
<td><code>let add = { a, b in a + b }</code></td>
</tr>
<tr>
<td>축약 표현</td>
<td><code>let add = { $0 + $1 }</code></td>
</tr>
<tr>
<td>함수 인자로 전달</td>
<td><code>sorted(by: { $0 &lt; $1 })</code> 또는 <code>sorted { $0 &lt; $1 }</code></td>
</tr>
<tr>
<td>함수에서 반환</td>
<td><code>func makeAdder(x: Int) -&gt; (Int) -&gt; Int</code></td>
</tr>
<tr>
<td>변수 캡처</td>
<td>클로저 안에서 외부 변수 사용 가능</td>
</tr>
<tr>
<td>@escaping</td>
<td>비동기 작업 시 클로저를 나중에 실행할 때 사용</td>
</tr>
<tr>
<td>@autoclosure</td>
<td>표현식 자체를 넘겨서 나중에 평가할 때 사용</td>
</tr>
</tbody></table>
<p>예를 들어 이런 식으로도 쓸 수 있다:</p>
<pre><code class="language-swift">func perform(action: () -&gt; Void) {
    action()
}

perform {
    print(&quot;실행됨!&quot;)
}</code></pre>
<p>→ 이런 방식은 trailing closure 문법이라고 부른다. 가독성이 훨씬 좋다.</p>
<hr>
<h2 id="클로저-예제-모음">클로저 예제 모음</h2>
<h3 id="함수에서-클로저-반환">함수에서 클로저 반환</h3>
<pre><code class="language-swift">func makeAdder(_ x: Int) -&gt; (Int) -&gt; Int {
    return { y in x + y }
}

let add5 = makeAdder(5)
add5(3)  // 결과: 8</code></pre>
<h3 id="고차-함수-사용">고차 함수 사용</h3>
<pre><code class="language-swift">let nums = [1, 2, 3]
let squared = nums.map { $0 * $0 }  // [1, 4, 9]</code></pre>
<h3 id="escaping-사용-예시">@escaping 사용 예시</h3>
<pre><code class="language-swift">func asyncTask(completion: @escaping () -&gt; Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        completion()
    }
}</code></pre>
<p>→ 비동기에서 나중에 클로저를 실행해야 하니 <code>@escaping</code>을 붙여줘야 한다.</p>
<hr>
<h2 id="정리-한-줄-요약">정리 한 줄 요약</h2>
<p><strong>클로저는 이름 없는 함수로, 콜백 전달이나 상태 유지, 코드 축약 등에 매우 유용하다.</strong>
Swift에서는 클로저 문법이 매우 강력하고 다양하기 때문에
<strong>map, filter, sorted 같은 고차 함수</strong>에서도 자주 활용된다.</p>
<hr>
<ul>
<li>JS의 <code>() =&gt; {}</code>, Python의 <code>lambda</code>, Java의 <code>() -&gt; {}</code>와 개념적으로 비슷하다.</li>
<li>하지만 Swift는 <strong>타입 안정성</strong>과 <strong>캡처 기능</strong>이 더 강력하다.</li>
<li>단, 클로저가 변수/상수를 캡처하다가 <strong>메모리 누수(참조 순환)</strong>가 생길 수 있으니
<code>[weak self]</code> 같은 문법도 꼭 알아두자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift 함수 문법 정리]]></title>
            <link>https://velog.io/@bslee_365/Swift-%ED%95%A8%EC%88%98-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@bslee_365/Swift-%ED%95%A8%EC%88%98-%EB%AC%B8%EB%B2%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 20 Jun 2025 14:36:51 GMT</pubDate>
            <description><![CDATA[<p>Swift로 코딩하다 보면 함수 하나 짜는 것도 은근히 헷갈린다.
특히 <em>리턴은 꼭 적어야 하나?</em> <em>파라미터 앞에 언더바(_)는 왜 붙이는 걸까?</em> <em>함수 안에 또 함수를 넣어도 되나?</em> 같은 의문이 계속 생긴다.</p>
<p>그래서 오늘은 함수 관련해서 헷갈릴만한 포인트를 쫙 정리해봤다.
나중에 블로그 다시 보면서 기억도 되살릴 겸 써놓는 기록이다.</p>
<hr>
<h2 id="리턴이-있는-함수">리턴이 있는 함수</h2>
<p>Swift에서 리턴이 없으면 그냥 <code>Void</code>다.
정확히 말하면 <code>Void == ()</code> 빈 튜플이다.</p>
<pre><code class="language-swift">func sayHello(name: String) {
    print(&quot;Hello, \(name)!&quot;)
}</code></pre>
<p>이 함수는 아무것도 안 돌려준다. 그냥 출력만 하고 끝.
그럼 여기서 궁금한 게 하나 생긴다.</p>
<p><strong>리턴이 있어야 좋은 함수일까?</strong></p>
<p>결론은 그때마다 다르다.
값을 밖으로 전달할 필요가 있으면 <code>-&gt; 타입</code>을 명시하고 <code>return</code>을 써주면 된다.</p>
<pre><code class="language-swift">func plusTwo(value: Int) -&gt; Int {
    return value + 2
}</code></pre>
<p>이건 명확히 <code>Int</code>를 돌려주는 함수. 이렇게 어떤 타입을 리턴받는지 확인하기 좋다.</p>
<hr>
<h2 id="리턴이-없는-함수-생략이-가능함">리턴이 없는 함수 (생략이 가능함)</h2>
<p>Swift에서는 한 줄짜리 계산식이면 <strong><code>return</code>을 생략</strong>할 수 있다.</p>
<pre><code class="language-swift">func plus(_ a: Int, _ b: Int) -&gt; Int {
    a + b
}</code></pre>
<p>이런 식으로 간단한 함수는 깔끔하게 쓸 수 있다.
그런데 여러 줄이 된다면 무조건 <code>return</code>을 명시해야 한다.</p>
<pre><code class="language-swift">func plus(_ a: Int, _ b: Int) -&gt; Int {
    let result = a + b
    return result
}</code></pre>
<p>한 줄이면 생략, 두 줄 이상이면 <code>return</code> 꼭 써줘야 된다.
<br>
<strong>그런데.... 이런 식으로 리턴은 했는데 타입을 안 썼다?</strong></p>
<pre><code class="language-swift">func wrongReturn(value: Int) {
    return value + 2 // ❌ 오류
}</code></pre>
<p>에러가 난다. 왜냐면 <code>Void</code> 함수는 값을 돌려줄 수 없기 때문.
<strong>리턴할 거면 타입도 꼭 같이 써야 한다.</strong></p>
<hr>
<h2 id="리턴값-안-써도-되게-해주는-어노테이션">리턴값 안 써도 되게 해주는 어노테이션</h2>
<p><code>@discardableResult</code>라는 게 있다.
이건 뭐냐면, <strong>리턴값을 무시해도 경고 안 뜨게 해주는</strong> 키워드다.
쉽게 말해서 리턴값이 있지만, 쓰이지 않았다는 <strong>경고를 무시</strong>하기 위한 어노테이션이라고 보면된다.</p>
<pre><code class="language-swift">@discardableResult
func logAction(_ message: String) -&gt; Bool {
    print(&quot;LOG: \(message)&quot;)
    return true
}</code></pre>
<p>Swift에서는 안정성과 명확성을 중요시하기 때문에 리턴을 무시할 때도 명시적으로 작성해줘야 된단다...</p>
<hr>
<h2 id="파라미터-앞에-_">파라미터 앞에 <code>_</code></h2>
<pre><code class="language-swift">func add(_ a: Int, _ b: Int) -&gt; Int {
    a + b
}</code></pre>
<p>이런 식으로 <code>_</code>를 붙이면 호출할 때 외부 파라미터 이름을 생략할 수 있다.</p>
<pre><code class="language-swift">add(3, 5) // 깔끔하게 호출 가능</code></pre>
<p>반면, 이건 좀 다르다</p>
<pre><code class="language-swift">func addNumbers(a: Int, b: Int) -&gt; Int {
    a + b
}</code></pre>
<pre><code class="language-swift">addNumbers(a: 3, b: 5) // 외부 이름 붙여야 함</code></pre>
<p>즉, <code>_</code>는 <strong>외부 이름 생략하겠다</strong>는 선언이다.</p>
<hr>
<h2 id="함수-안에-함수">함수 안에 함수</h2>
<p>Swift는 <strong>중첩 함수(nested function)</strong>도 가능하다.</p>
<pre><code class="language-swift">func complicatedStuff(with value: Int) -&gt; Int {
    func subLogic(value: Int) -&gt; Int {
        value + 2
    }
    return subLogic(value: value + 3)
}</code></pre>
<p>여기서 <code>subLogic</code>은 <code>complicatedStuff</code> 안에서만 쓸 수 있다.
이걸 잘 쓰면 복잡한 로직을 깔끔하게 나눌 수 있다. 
설명해줄 때 클로저랑 비슷하다는데.... 클로저는 다음 시간에 가르쳐준단다. 기다려보자.</p>
<hr>
<h2 id="with-by-from은-왜-붙이는-거지"><code>with</code>, <code>by</code>, <code>from</code>은 왜 붙이는 거지?</h2>
<pre><code class="language-swift">func fetchUserData(
    with id: Int,
    by your: String, 
    from me: String) { ... }</code></pre>
<p>이런식으로 쓰는 것은 js, pyhon, java에서는 못보던 형태일 것이다.
<code>with</code>, <code>by</code>, <code>from</code> 등은 그냥 <strong>외부 파라미터 이름</strong>일 뿐이다.
반대로 <code>id</code>, <code>your</code>, <code>me</code>는 <strong>내부 파라미터</strong> 이름이다.</p>
<p>코드 외부에서 파라미터 넣을 때는 <code>with</code>, <code>by</code>, <code>from</code>를 사용해 주지만,</p>
<pre><code class="language-swift">fetchUserData(with: 123) // 외부에서 파라미터 넣을 경우</code></pre>
<p>내부에서 사용할 때는 <code>id</code>, <code>your</code>, <code>me</code>를 사용해준다.</p>
<pre><code class="language-swift">func fetchUserData(
    with id: Int,
    by your: String, 
    from me: String) -&gt; Int {
        retrun &quot;\(your)&quot; + &quot;\(me)&quot; // 내부에서 파라미터를 사용할 경우
    }</code></pre>
<p>생략하고 싶으면 <code>_</code>를 쓰면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NSArray를 NSMutableArray로 바꿔보자]]></title>
            <link>https://velog.io/@bslee_365/NSArray%EB%A5%BC-NSMutableArray%EB%A1%9C-%EB%B0%94%EA%BF%94%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@bslee_365/NSArray%EB%A5%BC-NSMutableArray%EB%A1%9C-%EB%B0%94%EA%BF%94%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 19 Jun 2025 13:12:53 GMT</pubDate>
            <description><![CDATA[<h1 id="nsarray를-nsmutablearray로-바꿔보자">NSArray를 NSMutableArray로 바꿔보자</h1>
<p>Swift를 쓰다 보면 <code>NSArray</code>나 <code>NSMutableArray</code> 같은 Objective-C 기반 클래스를 만날 때가 있다.
특히 다른 프레임워크나 레거시 코드랑 연동할 때 자주 보이는데…</p>
<pre><code class="language-swift">func changeTheArray(_ array: NSArray) {
    let copy = array as! NSMutableArray
}</code></pre>
<p>이런 코드 보면 한 가지 궁금증이 생긴다.</p>
<p><strong><code>NSArray</code> 타입인데 왜 <code>NSMutableArray</code>로 바꿀 수 있는 걸까?</strong></p>
<p>사실 Swift에서 <code>NSArray</code>는 &quot;불변&quot; 배열이고,
<code>NSMutableArray</code>는 &quot;가변&quot; 배열이다.</p>
<p>근데 이 둘은 <strong>클래스 계층상</strong> 관계가 있다.
<code>NSMutableArray</code>는 <code>NSArray</code>의 하위 클래스다.</p>
<p>즉, <code>NSMutableArray</code>는 자동으로 <code>NSArray</code>로 <strong>업캐스팅</strong>되고,
필요하면 다시 <code>as!</code>나 <code>as?</code>를 써서 <strong>다운캐스팅</strong>할 수 있는 거다.</p>
<hr>
<h2 id="그럼-실제로-동작은-어떻게-될까">그럼 실제로 동작은 어떻게 될까?</h2>
<p>이 코드를 보자.</p>
<pre><code class="language-swift">let someNames = NSMutableArray(array: [&quot;Foo&quot;, &quot;Bar&quot;])

func changeTheArray(_ array: NSArray) {
    let copy = array as! NSMutableArray
    copy.add(&quot;Baz&quot;)
}

changeTheArray(someNames)
print(someNames) // [&quot;Foo&quot;, &quot;Bar&quot;, &quot;Baz&quot;]</code></pre>
<p>여기서 핵심은 이거다:
<strong>함수 인자는 <code>NSArray</code>지만 실제 객체는 <code>NSMutableArray</code>이기 때문에 캐스팅이 가능하다.</strong></p>
<p>그리고 중요한 사실 하나!
<strong><code>NSMutableArray</code>는 참조 타입이라, 함수 내부에서 추가한 값이 밖에서도 반영된다.</strong></p>
<h3 id="추가로-강의-가르쳐준-스웨덴-유튜버는-이렇게-타입을-강제로-바꿔서-쓰는-것은-매우-위험하다고한다">추가로 강의 가르쳐준 스웨덴 유튜버는 이렇게 타입을 강제로 바꿔서 쓰는 것은 매우 위험하다고한다.</h3>
<hr>
<h2 id="마지막으로-정리하면-이렇게-된다">마지막으로 정리하면 이렇게 된다.</h2>
<ol>
<li><code>NSArray</code>는 불변 배열이고, <code>NSMutableArray</code>는 그 하위 클래스인 가변 배열이다.</li>
<li>함수에서 <code>NSArray</code>로 받아도, 실제로는 <code>NSMutableArray</code>일 수 있으니 <code>as!</code>로 다운캐스팅이 가능하다.</li>
<li><code>NSMutableArray</code>는 참조 타입이라 내부 값을 바꾸면 원본도 바뀐다.</li>
<li><code>let</code>으로 선언해도 인스턴스 내부 수정은 막지 않는다.</li>
</ol>
<hr>
<h2 id="한-줄-요약">한 줄 요약</h2>
<p><strong><code>NSMutableArray</code>는 <code>NSArray</code>로 업캐스팅되었다가 다운캐스팅으로 다시 가변 배열처럼 쓸 수 있다. 하지만 항상 타입 확인을 하고 안전하게 써야 한다.</strong></p>
<p><img src="https://velog.velcdn.com/images/bslee_365/post/41be1a76-095a-42dd-a7fb-2cebc1d4d446/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[try와 await]]></title>
            <link>https://velog.io/@bslee_365/try%EC%99%80-await</link>
            <guid>https://velog.io/@bslee_365/try%EC%99%80-await</guid>
            <pubDate>Wed, 18 Jun 2025 14:55:17 GMT</pubDate>
            <description><![CDATA[<p>SwiftUI로 앱을 만들다 보면 데이터를 외부에서 받아오는 일이 꼭 생긴다.
예를 들어 JSON 데이터를 받아서 리스트로 보여주는 상황.
이럴 때 흔히 쓰는 코드가 바로 이거다.</p>
<pre><code class="language-swift">let (data, _) = try! await URLSession.shared.data(from: url!)</code></pre>
<p>이 한 줄에 <code>!</code>가 두 개나 들어간다.
근데 try! 이렇게 쓰는 방식은 <strong>권장은 안한다고 한다.</strong></p>
<hr>
<h2 id="try-await-urlsessionshareddatafrom-이-한-줄이-뭘-의미할까"><code>try! await URLSession.shared.data(from:)</code> 이 한 줄이 뭘 의미할까?</h2>
<p>이 코드의 동작을 찬찬히 뜯어보자.</p>
<ul>
<li><code>URLSession.shared.data(from:)</code>: 네트워크 요청을 보낸다.</li>
<li><code>await</code>: 네트워크 응답이 올 때까지 기다린다.</li>
<li><code>try!</code>: 오류가 나더라도 무시하고 실행한다.</li>
<li><code>(data, _)</code>: 받은 응답 중 <code>data</code>만 쓰고, 메타정보는 무시한다.</li>
</ul>
<p>그럼 여기서 궁금한 게 하나 생긴다.
<strong>왜 !를 이렇게 두 번이나 쓰는 걸까....</strong></p>
<p>Swift에서 <code>!</code>는 두 가지 상황에서 자주 등장한다.</p>
<h3 id="1-url-옵셔널-강제-해제">1. <code>url!</code>: 옵셔널 강제 해제</h3>
<pre><code class="language-swift">let url = URL(string: &quot;https://example.com&quot;)!</code></pre>
<p><code>URL(string:)</code>은 옵셔널을 반환한다.
왜냐면 문자열이 진짜 URL일지 아닐지 알 수 없기 때문.
그래서 저렇게 <code>!</code>를 붙이면 &quot;무조건 URL이 맞을 거야!&quot; 하고 강제로 열어버린다.</p>
<p>근데 만약 문자열이 잘못돼서 <code>nil</code>이면
앱크래시 문구가 뜬다.</p>
<h3 id="2-try-에러-강제-무시">2. <code>try!</code>: 에러 강제 무시</h3>
<pre><code class="language-swift">let (data, _) = try! await URLSession.shared.data(from: url!)</code></pre>
<p>이 함수는 실패할 수도 있다.
예를 들어 인터넷이 안 된다든가, URL이 잘못됐다든가.</p>
<p>그럴 때 보통은 <code>try?</code>나 <code>do-catch</code>를 써야 하는데,
<code>try!</code>를 사용하게 되면 앱크래시가 발생하게 된다.</p>
<hr>
<h2 id="그럼-어떻게-처리하는-게-좋은-걸까">그럼 어떻게 처리하는 게 좋은 걸까?</h2>
<p>테스트할 때는 <code>try!</code>, <code>url!</code>을 써도 괜찮다.
데이터가 무조건 있다는 걸 알고 있고, 빠르게 확인하고 싶을 때.</p>
<p>하지만 실서비스 앱이라면, </p>
<p>✅ <code>guard let</code>으로 URL 안전하게 열고
✅ <code>do-catch</code>로 네트워크 요청 에러도 잡아줘야 한다.
                            - gpt 피셜 -</p>
<hr>
<h2 id="codable-decodable-encodable은-차이점">Codable, Decodable, Encodable은 차이점?</h2>
<p>네트워크 응답으로 JSON을 받았다면
그걸 모델 객체로 바꾸는 작업이 필요하다.
그때 사용하는 게 바로 이 세 가지 프로토콜이다.</p>
<table>
<thead>
<tr>
<th>프로토콜</th>
<th>역할</th>
<th>언제 쓰나</th>
</tr>
</thead>
<tbody><tr>
<td><code>Decodable</code></td>
<td>JSON → 객체</td>
<td>서버 응답 디코딩할 때</td>
</tr>
<tr>
<td><code>Encodable</code></td>
<td>객체 → JSON</td>
<td>서버에 요청 보낼 때</td>
</tr>
<tr>
<td><code>Codable</code></td>
<td>둘 다 가능</td>
<td>대부분의 경우 이걸 씀</td>
</tr>
</tbody></table>
<p>예를 들어 이런 모델이 있다</p>
<pre><code class="language-swift">struct Drama: Codable {
    let title: String
    let year: Int
}</code></pre>
<p>근데 JSON 키가 <code>year</code>가 아니라 <code>releaseYear</code>라면?</p>
<pre><code class="language-swift">enum CodingKeys: String, CodingKey {
    case title
    case year = &quot;releaseYear&quot;
}</code></pre>
<p>이렇게 키 매핑을 해주면 된다.
Swift에서는 구조체의 프로퍼티 이름과 JSON 키가 다르면 반드시 <code>CodingKeys</code>를 지정해줘야 제대로 파싱된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이게 무슨 주니어야...]]></title>
            <link>https://velog.io/@bslee_365/%EC%9D%B4%EA%B2%8C-%EB%AC%B4%EC%8A%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%95%BC</link>
            <guid>https://velog.io/@bslee_365/%EC%9D%B4%EA%B2%8C-%EB%AC%B4%EC%8A%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%95%BC</guid>
            <pubDate>Tue, 17 Jun 2025 12:42:22 GMT</pubDate>
            <description><![CDATA[<p>나는 현재 웹개발을 하고 있지만, 추후 앱개발자로서 전향도 해볼수 있지 않을까 해서
gpt한테 로드맵을 짜달라고 했다.</p>
<br>
<br>

<h3 id="주니어-ios개발자로-취업을-하려면-어떤-기술과-역량이-필요할까">&quot;주니어 iOS개발자로 취업을 하려면 어떤 기술과 역량이 필요할까?&quot;</h3>
<br>
<br>
<br>

<p>아주 중요한 질문입니다.
iOS 개발자로 <strong>취업을 목표</strong>로 한다면, 단순히 &quot;앱을 만들 수 있다&quot; 수준을 넘어서서 <strong>개발 실력 + 협업 능력 + 실무 경험의 간접 증명</strong>이 중요합니다.</p>
<p>다음은 <strong>iOS 주니어 개발자가 취업을 위해 반드시 알아야 할 기술/지식</strong>을 실무 기준으로 정리한 것입니다.</p>
<hr>
<h2 id="✅-ios-취업을-위한-필수-역량-정리">✅ iOS 취업을 위한 필수 역량 정리</h2>
<h3 id="1-기본-개발-역량">1. <strong>기본 개발 역량</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>✅ Swift 언어</td>
<td>옵셔널, 클로저, ARC, 프로토콜, 제네릭, 타입 시스템 등</td>
</tr>
<tr>
<td>✅ UIKit</td>
<td>ViewController, LifeCycle, AutoLayout, TableView, CollectionView</td>
</tr>
<tr>
<td>✅ 비동기 처리</td>
<td><code>DispatchQueue</code>, <code>GCD</code>, <code>async/await</code>, 클로저 이스케이핑</td>
</tr>
<tr>
<td>✅ 앱 생명주기</td>
<td>SceneDelegate, AppDelegate 이해</td>
</tr>
<tr>
<td>✅ JSON 파싱</td>
<td><code>Codable</code>, 네트워크 통신(<code>URLSession</code>)</td>
</tr>
<tr>
<td>✅ 앱 구조화</td>
<td>MVC 기본, MVVM 구조 이해 및 적용</td>
</tr>
<tr>
<td>✅ 저장소 사용</td>
<td><code>UserDefaults</code>, <code>FileManager</code>, <code>CoreData</code> (또는 SwiftData)</td>
</tr>
<tr>
<td>✅ 메모리 관리</td>
<td>ARC, strong/weak, retain cycle, 캡처 리스트</td>
</tr>
<tr>
<td>✅ 스레드와 동시성</td>
<td>UI는 메인스레드, 백그라운드 처리 흐름 이해</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-실전-앱-구현-능력">2. <strong>실전 앱 구현 능력</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>✅ 앱 하나 끝까지 만들기</td>
<td>디자인 → 기능 구현 → 버그 수정 → 배포</td>
</tr>
<tr>
<td>✅ UI/UX 신경 쓰기</td>
<td>다크모드 대응, 접근성 고려, UX 흐름 자연스럽게</td>
</tr>
<tr>
<td>✅ Firebase 연동</td>
<td>인증, DB, 푸시 알림 등 실전 대응</td>
</tr>
<tr>
<td>✅ 버전 관리</td>
<td>Git으로 프로젝트 관리, 커밋 로그 작성 습관</td>
</tr>
<tr>
<td>✅ 배포 경험</td>
<td>TestFlight, 앱스토어 등록 (있으면 강점)</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-협업과-소통-역량">3. <strong>협업과 소통 역량</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>✅ Git &amp; Github</td>
<td>협업용 Branch 전략, PR 리뷰 경험</td>
</tr>
<tr>
<td>✅ 문서화</td>
<td>README, Wiki, Notion에 기능 정리 및 이슈 관리</td>
</tr>
<tr>
<td>✅ 코드 정리 습관</td>
<td>함수 분리, 네이밍, 주석, SwiftLint 활용 등</td>
</tr>
<tr>
<td>✅ 디자인 툴 연동</td>
<td>Figma, Zeplin 등 UI 스펙 받아서 구현 가능해야 함</td>
</tr>
</tbody></table>
<hr>
<h3 id="4-기초-cs-및-면접-준비">4. <strong>기초 CS 및 면접 준비</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>✅ 자료구조/알고리즘</td>
<td>배열, 해시, 트리, 정렬, 탐색, 스택/큐 등</td>
</tr>
<tr>
<td>✅ OOP, SOLID</td>
<td>객체지향, 의존성, 클래스 분리</td>
</tr>
<tr>
<td>✅ 네트워크 기초</td>
<td>HTTP, REST, StatusCode, JSON, CORS 등</td>
</tr>
<tr>
<td>✅ iOS 면접 단골</td>
<td>ARC, GCD, 클로저, delegate vs closure, MVC vs MVVM, 앱 생명주기, retain cycle, diffable data source 등</td>
</tr>
</tbody></table>
<hr>
<h3 id="5-기타-경쟁력-있는-항목">5. <strong>기타 (경쟁력 있는 항목)</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>✅ SwiftUI 가능</td>
<td>최신 트렌드에 관심 보이고 있음 (비중은 아직 낮지만 +α로 강점)</td>
</tr>
<tr>
<td>✅ 디자인 패턴</td>
<td>Factory, Singleton, Observer, Coordinator 등 이해</td>
</tr>
<tr>
<td>✅ RxSwift / Combine</td>
<td>실무에 따라 요구되기도 함 (비동기 흐름 처리에 유용)</td>
</tr>
<tr>
<td>✅ 포트폴리오 사이트</td>
<td>본인의 앱, 프로젝트 구조, 고민한 흔적을 정리해 공개하면 매우 긍정적 인상</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-정리-취업용-ios-개발-역량-3대-포인트">🧠 정리: “취업용 iOS 개발 역량 3대 포인트”</h2>
<ol>
<li><strong>UIKit 기반 앱 하나 이상 구현 + MVVM 구조화 가능</strong></li>
<li><strong>Swift 기반 문법과 메모리/비동기/구조화에 대한 기본 이해</strong></li>
<li><strong>협업/문서화/형상관리/Git 경험 + Firebase 같은 외부 연동 경험</strong></li>
</ol>
<hr>
<p>필요하시면 다음도 드릴 수 있어요:</p>
<ul>
<li>주니어용 이력서/포트폴리오 구성 팁</li>
<li>실제 면접 질문 리스트</li>
<li>위 항목들을 기준으로 한 체크리스트 PDF</li>
</ul>
<p>어떤 게 필요하신가요? 또는 지금 어느 정도까지 준비되셨는지도 알려주시면 맞춤형으로 안내드리겠습니다.</p>
<br>
<br>
<br>
<br>
<br>


<p>취미로만 하는것도 나쁘지 않을지도....ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[# SwiftData는 언제 자동으로 저장될까?]]></title>
            <link>https://velog.io/@bslee_365/SwiftData%EB%8A%94-%EC%96%B8%EC%A0%9C-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%A0%80%EC%9E%A5%EB%90%A0%EA%B9%8C</link>
            <guid>https://velog.io/@bslee_365/SwiftData%EB%8A%94-%EC%96%B8%EC%A0%9C-%EC%9E%90%EB%8F%99%EC%9C%BC%EB%A1%9C-%EC%A0%80%EC%9E%A5%EB%90%A0%EA%B9%8C</guid>
            <pubDate>Tue, 17 Jun 2025 11:53:05 GMT</pubDate>
            <description><![CDATA[<h1 id="swiftdata는-언제-자동으로-저장될까">SwiftData는 언제 자동으로 저장될까?</h1>
<p>SwiftData를 처음 써보면 정말 놀라는 포인트가 하나 있다.
<strong><code>modelContext.save()</code>를 호출하지 않았는데도 데이터가 저장되는 현상을 밝견했다.</strong></p>
<p>문제를 발견한 코드는 다음과 같다.</p>
<pre><code class="language-swift">struct TodoDetail: View {

    @Binding var todo: TodoList

    @Environment(\.modelContext) private var modelContext

        ...생략

        .padding()
        .navigationTitle(&quot;할 일을 작성하세요.&quot;)
        .toolbar{
            ToolbarItem{
                Button(&quot;저장&quot;) {
                    do {
                       // 주석처리 했음에도 불구하고, 저장이 된다.
                      // try modelContext.save()
                    }
                    catch {
                        print(&quot;Error Context Save: \(error)&quot;)
                    }

                }
            }
        }
    }
}
... 생략</code></pre>
<p><strong>SwiftData는 과연 언제, 어떤 조건에서 데이터를 자동 저장하는 걸까?</strong></p>
<hr>
<h2 id="swiftdata는-자동-저장-기능을-갖고-있다">SwiftData는 자동 저장 기능을 갖고 있다.</h2>
<p>SwiftData는 Core Data 위에 만들어진 새로운 추상화 계층이다.
그리고 SwiftUI와 찰떡같이 맞춰주기 위해 자동 저장 기능이 추가됐다.
그러니까 핵심은 이거다:</p>
<blockquote>
<p><strong>SwiftData는 특정 시점에 자동으로 데이터를 저장한다.</strong></p>
</blockquote>
<p>그럼 여기서 궁금한 게 하나 생긴다.
<strong>그 특정 시점이 언제냐는 거다.</strong></p>
<hr>
<h2 id="언제-자동-저장이-될까">언제 자동 저장이 될까?</h2>
<p>SwiftData에서 자동 저장은 다음 조건에서 &#39;발생할 수 있다&#39;고 한다.
여기서 중요한 건 <strong>&quot;항상 발생한다&quot;가 아니라 &quot;발생할 수 있다&quot;</strong>라는 점이다.</p>
<table>
<thead>
<tr>
<th>상황</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>뷰가 pop되거나 dismiss될 때</td>
<td>NavigationStack이나 Sheet가 닫힐 때</td>
</tr>
<tr>
<td>앱이 백그라운드로 전환될 때</td>
<td>AppDelegate 수준에서 저장 트리거</td>
</tr>
<tr>
<td>앱이 종료될 때</td>
<td>OS 상황에 따라 다름</td>
</tr>
<tr>
<td>일정 시간 경과</td>
<td>&quot;주기적 저장&quot;은 보장되지 않음</td>
</tr>
<tr>
<td>메모리 압박</td>
<td>오히려 저장 없이 종료될 위험</td>
</tr>
</tbody></table>
<p>딱 정리하자면,
<strong>자동 저장은 &quot;뷰가 사라질 때&quot; 가장 확실하게 일어난다.</strong>
그 외의 경우는 절대적으로 믿으면 안 된다. 앱이 종료되거나 메모리가 부족해지면 데이터가 날아갈 수도 있다.</p>
<hr>
<h2 id="그러면-왜-저장이-된-걸까">그러면 왜 저장이 된 걸까?</h2>
<p>내가 작성한 코드에서 <code>modelContext.save()</code>는 없었다.
근데 TextField와 TextEditor에 <code>@Binding var todo: TodoList</code>가 직접 연결돼 있었고,
이 상태에서 값을 입력하면 SwiftData의 <code>ModelContext</code>는 자동으로 변화(<code>hasChanges</code>)를 감지한다.</p>
<p>그리고 이 뷰가 pop되거나 dismiss되는 순간,
SwiftData는 알아서 <code>context.save()</code>를 호출한다.</p>
<blockquote>
<p>결국 이 말은,
<strong>자동 저장은 내가 뷰를 닫는 그 타이밍에 발생한 것</strong>이다.</p>
</blockquote>
<hr>
<h2 id="자동-저장을-막고-수동-저장만-하고-싶다면">자동 저장을 막고 수동 저장만 하고 싶다면?</h2>
<p>이럴 땐 이렇게 하면 된다:</p>
<pre><code class="language-swift">@State private var tempTitle: String = &quot;&quot;
@State private var tempDesc: String = &quot;&quot;

.onAppear {
    tempTitle = todo.title
    tempDesc = todo.todoDesc
}

Button(&quot;저장&quot;) {
    todo.title = tempTitle
    todo.todoDesc = tempDesc
    try? modelContext.save()
}</code></pre>
<p><code>@Binding</code> 대신 <code>@State</code>에 임시 값을 담아두고,
사용자가 직접 저장 버튼을 눌렀을 때만 모델을 수정하는 방식이다.</p>
<p>이러면 자동 저장을 피하고 <strong>내가 원하는 타이밍에만 저장</strong>할 수 있다.
SwiftData를 제어하려면 이런 식으로 한 번 더 래핑해주는 게 좋다.</p>
<hr>
<h2 id="정리하자면">정리하자면</h2>
<p>한 줄 요약:
<strong>SwiftData는 뷰가 사라질 때 자동 저장이 일어난다. 하지만 그 외 상황은 보장되지 않으므로 수동 저장 흐름이 필요할 수 있다.</strong></p>
<table>
<thead>
<tr>
<th>핵심 정리</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Binding</code>은 즉시 모델을 수정한다</td>
</tr>
<tr>
<td>뷰가 사라질 때 SwiftData가 자동 저장한다</td>
</tr>
<tr>
<td>다른 상황에서는 자동 저장이 보장되지 않는다</td>
</tr>
<tr>
<td>안전하게 저장하려면 <code>@State</code>를 활용해 수동 저장 흐름을 만들자</td>
</tr>
</tbody></table>
<hr>
<p>SwiftData가 정말 매력적인 이유는 SwiftUI와 자연스럽게 동작하기 때문이다.
하지만 반대로, 자동으로 너무 많은 걸 처리해줘서
개발자가 직접 제어하고 싶을 땐 조금 까다로워질 수도 있다.</p>
<hr>
<p>필요하면 이 글을 기반으로 추가적인 실험 결과나 예제를 더 작성해도 좋겠다.
다음엔 <code>@Model</code>, <code>@Query</code>, 그리고 관계 설정 쪽도 정리해볼 예정이다.</p>
]]></description>
        </item>
    </channel>
</rss>