<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>제삐(Jebby)</title>
        <link>https://velog.io/</link>
        <description>공부한거 느낌대로 써내려갑니당</description>
        <lastBuildDate>Sat, 21 Sep 2024 13:09:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>제삐(Jebby)</title>
            <url>https://velog.velcdn.com/images/ljh3904_a/profile/813df9d2-11e7-41c7-9007-d4e858ac889d/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 제삐(Jebby). All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ljh3904_a" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[@retroactive]]></title>
            <link>https://velog.io/@ljh3904_a/retroactive</link>
            <guid>https://velog.io/@ljh3904_a/retroactive</guid>
            <pubDate>Sat, 21 Sep 2024 13:09:22 GMT</pubDate>
            <description><![CDATA[<h3 id="retroactive">@retroactive?</h3>
<p>@retroactive은 Swift6에서 도입된 어트리뷰트입니다.<br>@retroactive 채택은 Swift의 기능 중 하나로, 기존에 정의된 클래스나 구조체에 나중에 프로토콜을 추가하는 방식입니다. 이를 통해, 이미 존재하는 타입들에 새로운 프로토콜을 추가할수 있다고 합니다.</p>
<h4 id="프로토콜의-추가">프로토콜의 추가</h4>
<ul>
<li>Swift에서는 타입이 정의된 이후에 프로토콜을 추가할 수 있습니다. 이를 통해 기존 타입이 새로운 기능을 갖도록 할 수 있습니다.</li>
<li>@retroactive를 사용하면, 해당 타입의 구현을 수정하지 않고도 프로토콜을 채택할 수 있습니다.</li>
</ul>
<h4 id="예시">예시</h4>
<p>이 코드는 앱에서 스와이프로 뒤로가기 코드입니다.</p>
<pre><code class="language-swift">extension UINavigationController: @retroactive UIBarPositioningDelegate {}
extension UINavigationController: @retroactive UINavigationBarDelegate, @retroactive UIGestureRecognizerDelegate {
    open override func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -&gt; Bool {
        return viewControllers.count &gt; 1
    }
}</code></pre>
<pre><code class="language-swift">extension UINavigationController: @retroactive UIBarPositioningDelegate {}swift</code></pre>
<p>UINavigationController: 이 클래스를 확장하여 두 개의 프로토콜인 UIGestureRecognizerDelegate와 UINavigationBarDelegate를 추가합니다. 
그리고 UIBarPositioningDelegate는 추가적으로 채택됩니다.
위의 코드에서는 UINavigationController가 이미 정의된 타입이지만, UIBarPositioningDelegate 프로토콜을 나중에 추가하는 방식입니다. 이렇게 하면 UINavigationController가 UIBarPositioningDelegate의 프토토콜을 충족할 수 있습니다.</p>
<h4 id="왜-사용할까">왜 사용할까?</h4>
<ul>
<li>유연성: 기존 클래스의 기능을 확장할 수 있어 코드의 유연성을 높입니다. 예를 들어, 라이브러리나 SDK의 기본 클래스를 수정하지 않고도 새로운 기능을 추가할 수 있습니다.</li>
<li>호환성 유지: 기존 코드와의 호환성을 유지하면서 새로운 기능을 제공할 수 있습니다.</li>
<li>@retroactive를 통해 코드의 명확성과 최적화를 지원합니다.</li>
<li>런타임 에러 방지: 만약 Test 타입에서 Identifiable 프로토콜을 사용 하고 있었는데 그 타입이 swift버전이 업데이트 되면서 Test타입에 Identifiable를 기본적으로 내포하게 된다면 밑에 문제가 생깁니다. <blockquote>
</blockquote>
•    기본 데이터 타입에서 동일한 프로토콜의 중복 준수로 인해 예기치 않은 버그 발생.
•    특히 라이브러리 모듈에서 선언된 경우, 클라이언트 코드에서의 예측 불가성 증가.</li>
</ul>
<p>이렇게 되기에  @retroactive를 사용을 하여 미연에 위와같은 문제를 방지할 수 있습니다. </p>
<h4 id="참고">참고</h4>
<p><a href="https://green1229.tistory.com/507">https://green1229.tistory.com/507</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제스쳐로 뒤로가기 + 특정 부분이면 막기 ]]></title>
            <link>https://velog.io/@ljh3904_a/%EC%A0%9C%EC%8A%A4%EC%B3%90%EB%A1%9C-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%ED%8A%B9%EC%A0%95-%EB%B6%80%EB%B6%84%EC%9D%B4%EB%A9%B4-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/%EC%A0%9C%EC%8A%A4%EC%B3%90%EB%A1%9C-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%ED%8A%B9%EC%A0%95-%EB%B6%80%EB%B6%84%EC%9D%B4%EB%A9%B4-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Sat, 21 Sep 2024 12:51:48 GMT</pubDate>
            <description><![CDATA[<h3 id="제스처를-사용하여-뒤로가기">제스처를 사용하여 뒤로가기</h3>
<p>SWiftUI 에서 제공하는 제스처로 뒤로가기 많이들 보셨을겁니다. 
그것을 구현하도록 하겠습니다. 
코드는 단순해서 밑에 코드만 적도록 하고 특정부분을 막는게 주된 글입니당</p>
<pre><code class="language-swift">extension UINavigationController: @retroactive UIBarPositioningDelegate {}
extension UINavigationController: @retroactive UINavigationBarDelegate, @retroactive UIGestureRecognizerDelegate {
    open override func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -&gt; Bool {
        return viewControllers.count &gt; 1
    }
}</code></pre>
<p>@retroactive 프로토콜 채택 -&gt; <a href="https://velog.io/@ljh3904_a/retroactive">https://velog.io/@ljh3904_a/retroactive</a></p>
<h3 id="특정-뷰로는-제스쳐-막기">특정 뷰로는 제스쳐 막기</h3>
<img src="https://velog.velcdn.com/images/ljh3904_a/post/7bbde3a0-5da9-47de-b6fd-bf9cf9b1f489/image.gif" style="height: 400px;" />

<p>이제 로그인 창으로 가는것을 막아야합니다. </p>
<pre><code class="language-swift">enum TrackingType {
    case home
    case sign
    case vote
    case mypage
    case bookmark
}

class ViewTracker: ObservableObject {
    static let shared = ViewTracker()
    @Published var currentView: TrackingType = .sign

    func updateCurrentView(to newView: TrackingType) {
        jhPrint(newView)
        currentView = newView
    }
}
</code></pre>
<p>뷰를 탐색해서 싱글톤으로 현제 뷰의 상태를 저장해두고 이걸로 비교 하는 방식으로 만들었습니다. </p>
<pre><code class="language-swift"> public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -&gt; Bool {
        var isEnabled: Bool = false
        switch ViewTracker.shared.currentView {
            case .home, .sign, .bookmark:
                isEnabled = false
            case .mypage, .vote:
                isEnabled = true
        }
        return viewControllers.count &gt; 1 &amp;&amp; isEnabled

    }</code></pre>
<p>각 뷰가 생성될 때 type 에맞게 넣어서 조절하였습니다. </p>
<pre><code class="language-swift">.onAppear {
            ViewTracker.shared.updateCurrentView(to: .mypage)
        }</code></pre>
<h4 id="참고">참고</h4>
<p><a href="https://green1229.tistory.com/507">https://green1229.tistory.com/507</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[dismiss() 사용하니깐 앱이 멈추는 현상 ]]></title>
            <link>https://velog.io/@ljh3904_a/dismiss-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8B%88%EA%B9%90-%EC%95%B1%EC%9D%B4-%EB%A9%88%EC%B6%94%EB%8A%94-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@ljh3904_a/dismiss-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8B%88%EA%B9%90-%EC%95%B1%EC%9D%B4-%EB%A9%88%EC%B6%94%EB%8A%94-%ED%98%84%EC%83%81</guid>
            <pubDate>Fri, 13 Sep 2024 05:36:15 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<p>앱이 정상적으로 작동하다가 밑에 코드를 넣은순간부터 앱이 정지 되고 무한루프가 돌았습니다. CPU99%;; </p>
<pre><code class="language-swift">@Environment(\.dismiss) var dismiss

dismiss()</code></pre>
<p>미친듯이 올라가는 메모리 ㄷㄷ;; 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/4a6c1e0c-e975-4024-a8e3-ab9e4661fd6f/image.png" alt=""></p>
<p>추가적으로 뜨는 메시지 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/78c4a8a8-86a7-4859-863c-5a165da15889/image.png" alt="">
이 디버깅 메시지의 뜻은 </p>
<ul>
<li>과도한 네트워크 이벤트: 네트워크 연결에서 많은 이벤트가 발생하고, 각 이벤트에는 타임스탬프가 기록된다 합니다. 하지만 특정 연결에서 기록할 수 있는 타임스탬프의 최대 한도에 도달하면, 더 이상 기록할 수 없기에 이벤트가 무시된다 합니다. </li>
<li>네트워크 부하: 앱에서 많은 네트워크 요청을 하거나 연결이 불안정한 경우, 짧은 시간 내에 다수의 네트워크 이벤트가 발생할 수 있습니다. 이로 인해 발생하는 메시지입니다.</li>
</ul>
<p>라는 의미라고 합니다 . </p>
<p>이렇게 보면 api 쪽의 문제인줄 알고 전부 봐왔지만 .. 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/ff498696-df91-4c18-bb54-311d749389c6/image.png" alt=""></p>
<p>고작 이거 하나 받아오는 코드인데 ?? 알고보니 api의 문제가 아니였습니다.. </p>
<h3 id="원인">원인</h3>
<pre><code class="language-swift">@Environment(\\.dismiss) private var dismiss</code></pre>
<p>문제의 코드 ; 고작 뒤로가기가? </p>
<p>뷰를 이동할 때 @Environment 변수를 사용하는 모든 뷰의 body 내부 코드를 재실행한다고 합니다. 
이렇게 되면 앱 성능과 메모리 등 부정적인 영향을 미치고 앱이 오랫동안 저걸 사용하는 뷰에서 멈춘다면 리소스 사용량이 급증하게 된다고 합니다. </p>
<p>NavigationStack으로 옮긴 후로 iOS 16/17에서 같은 문제가 발생하고 있으며 무한 루프의 원인을 정확히 알 수 없습니다.
무한 루프가 되니 cpu와 메모리가 급증하며 앱이 멈춰버린것입니다..! </p>
<h3 id="해결">해결</h3>
<p>@Environment가 있는 뷰들이 많은데 계속 무한 루프로 하위 뷰들의 body를 그리다가 timestamp 의 count가 맥시멈도 되고 CPU memory도 지구 끝까지 올라가면서 정지된듯해요. 
그래서 iOS 15 전버전에서 사용되던 @Environment(.presentationMode)를 가져와서 사용하니 해결됐씁니다!! </p>
<ul>
<li><pre><code class="language-swift">@Environment(\.presentationMode) var presentationMode
presentationMode.wrappedValue.dismiss()
</code></pre>
</li>
</ul>
<p>```</p>
<h4 id="참고">참고</h4>
<ul>
<li><a href="https://stackoverflow.com/questions/77401804/app-freezes-using-environment-dismiss-variable-swiftui">https://stackoverflow.com/questions/77401804/app-freezes-using-environment-dismiss-variable-swiftui</a></li>
<li><a href="https://huniroom.tistory.com/entry/SwiftUI-Environment%EC%99%80-dismiss%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%98%AC%EB%B0%94%EB%A5%B8-%EB%B0%A9%EB%B2%95">https://huniroom.tistory.com/entry/SwiftUI-Environment%EC%99%80-dismiss%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%98%AC%EB%B0%94%EB%A5%B8-%EB%B0%A9%EB%B2%95</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[네비게이션과 탭제스쳐를 어떻게 구분할까 ]]></title>
            <link>https://velog.io/@ljh3904_a/%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98%EA%B3%BC-%ED%83%AD%EC%A0%9C%EC%8A%A4%EC%B3%90%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B5%AC%EB%B6%84%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@ljh3904_a/%EB%84%A4%EB%B9%84%EA%B2%8C%EC%9D%B4%EC%85%98%EA%B3%BC-%ED%83%AD%EC%A0%9C%EC%8A%A4%EC%B3%90%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EA%B5%AC%EB%B6%84%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Thu, 12 Sep 2024 09:19:54 GMT</pubDate>
            <description><![CDATA[<h2 id="지금-만들고-있는-코드는-한-셀에-이벤트가-여러개를-둬야하는-상황입니다">지금 만들고 있는 코드는 한 셀에 이벤트가 여러개를 둬야하는 상황입니다.</h2>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/1de97aa6-315f-474b-b462-ca0e71e0485a/image.png" alt="">
이것은 편집 버튼을 눌렀을 경우 </p>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/616e2b20-70da-4e74-8bde-77794e415a3d/image.png" alt="">
이것은 편집 버튼이 눌리지 않은 경우 </p>
<h3 id="문제">문제</h3>
<h4 id="네비게이션-링크가-들어가지지-않음">네비게이션 링크가 들어가지지 않음</h4>
<h3 id="이유">이유</h3>
<ul>
<li>한 셀에 이벤트가 여러개가 겹칠경우 네비게이션 링크의 이벤트가 가장 아래로 씹히는 경우가 생깁니다. </li>
<li><ul>
<li>복잡한 레이아웃과 터치 이벤트가 겹치는 경우 발생한다고 합니다. </li>
</ul>
</li>
<li><ul>
<li>zstack의 경우 상위 레이어가 터치 영역을 덮고 있으면 하위 레이어의 네비게이션 링크는 터치 이벤트를 받지 못합니다. </li>
</ul>
</li>
</ul>
<p>문제의 코드 </p>
<pre><code class="language-swift">if selectedButton == &quot;투표&quot; {
                    ScrollView(showsIndicators: false) {
                        ForEach(bookmarkStore.currentUserVotesBookmark) { bookmark in
                            if isEdit {
                                VotesBookmarkCell(categoryStore: categoryStore, voteStore: voteStore, bookmarkStore: bookmarkStore, isEdit: $isEdit,  bookmark: bookmark)
                                    .padding(.horizontal, 20)
                            } else {
                                NavigationLink {
                                    VoteDetailView(voteId: bookmark.voteId, voteStore: voteStore, bookmarkStore: bookmarkStore)
                                } label: {
                                    VotesBookmarkCell(categoryStore: categoryStore, voteStore: voteStore, bookmarkStore: bookmarkStore, isEdit: $isEdit, bookmark: bookmark)
                                        .padding(.horizontal, 20)
                                }
                                .buttonStyle(PlainButtonStyle())

                            }
                        }
                    }</code></pre>
<pre><code class="language-swift">ZStack {
            ZStack {
                RoundedRectangle(cornerRadius: 8)
                    .frame(height: 66)
                    .foregroundStyle(CustomColor.GrayScaleColor.white)
                HStack(spacing: 0) {
                    if isEdit {
                        Image(&quot;checkCircle_\(isTap)&quot;)
                            .frame(width: 16, height: 16)
                            .padding(.leading, 16)
                    }
                    VStack(alignment: .leading, spacing: 0) {
                        HStack(spacing: 0) {

                            Text(bookmark.title)
                                .font(.createFont(weight: .bold, size: 14))
                                .foregroundStyle(CustomColor.GrayScaleColor.black)
                                .lineLimit(1)
                            Spacer()
                        }
                        .padding(.bottom, 10)
                        HStack(spacing: 0) {
                            Text(bookmark.description)
                                .font(.createFont(weight: .bold, size: 12))
                                .foregroundStyle(CustomColor.GrayScaleColor.gs6)
                                .lineLimit(1)
                            Spacer()
                        }
                    }
                    .padding(.horizontal, 16)
                }
            }
            .onTapGesture {
                if isEdit {
                    isTap.toggle()
                }
            }
        }</code></pre>
<h3 id="해결">해결</h3>
<ul>
<li>뷰 계층구조에서 상위 뷰의 터치이벤트가 하위 뷰에 영향을 미치지 않도록 분리 하였습니다. </li>
<li>분기를 나눠서 분기마다의 처리를 해줬습니다. </li>
<li>뷰빌더를 사용하여 코드의 재사용성을 높?인거같아요 호호 </li>
</ul>
<pre><code class="language-swift">//
//  VotesBookmarkCell.swift
//  Sachosaeng
//
//  Created by LJh on 9/11/24.
//

import SwiftUI

struct VotesBookmarkCell: View {
    @StateObject var categoryStore: CategoryStore
    @StateObject var voteStore: VoteStore
    @StateObject var bookmarkStore: BookmarkStore
    @Binding var isEdit: Bool
    @State var isTap: Bool = false
    var bookmark: Bookmark

    var body: some View {
        ZStack {
            if isEdit {
                cellContent
                    .onTapGesture {
                        isTap.toggle()
                    }
            } else {
                NavigationLink {
                    VoteDetailView(voteId: bookmark.voteId, voteStore: voteStore, bookmarkStore: bookmarkStore)
                } label: {
                    cellContent
                } //: navigation
            }
        } //: ZSTACK
    }

    @ViewBuilder
    var cellContent: some View {
        RoundedRectangle(cornerRadius: 8)
            .frame(height: 66)
            .foregroundStyle(CustomColor.GrayScaleColor.white)
            .overlay {
                HStack(spacing: 0) {
                    if isEdit {
                        Image(&quot;checkCircle_\(isTap)&quot;)
                            .frame(width: 16, height: 16)
                            .padding(.leading, 16)
                    }
                    VStack(alignment: .leading, spacing: 0) {
                        HStack(spacing: 0) {

                            Text(bookmark.title)
                                .font(.createFont(weight: .bold, size: 14))
                                .foregroundStyle(CustomColor.GrayScaleColor.black)
                                .lineLimit(1)
                            Spacer()
                        }
                        .padding(.bottom, 10)
                        HStack(spacing: 0) {
                            Text(bookmark.description)
                                .font(.createFont(weight: .bold, size: 12))
                                .foregroundStyle(CustomColor.GrayScaleColor.gs6)
                                .lineLimit(1)
                            Spacer()
                        }
                    }
                    .padding(.horizontal, 16)
                }
            }
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Codable, Hashable, Identifiable]]></title>
            <link>https://velog.io/@ljh3904_a/Codable-Hashable-Identifiable</link>
            <guid>https://velog.io/@ljh3904_a/Codable-Hashable-Identifiable</guid>
            <pubDate>Sun, 25 Aug 2024 14:36:33 GMT</pubDate>
            <description><![CDATA[<p>Codable, Hashable, Identifiable 이 세 가지 프로토콜을 사용하는 이유를 잘 모르고 단순히 ForEach나 디코딩을 위해 채택했습니다. 이제는 제대로 이해하고 싶어 블로그를 작성하면서 공부할 계획입니다.</p>
<h1 id="codable">Codable</h1>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/001f467a-782d-4edd-9d16-3d160d85fab1/image.png" alt="">
/// 스스로 외부 표현으로 변환하거나 외부 표현으로 변환할 수 있는 유형입니다.
///
/// <code>Codable</code>은 <code>Encodable</code>과 <code>Decodable</code> 프로토콜의 타입 별칭입니다.
/// <code>Codable</code>을 타입이나 일반 제약 조건으로 사용하면 다음과 일치합니다.
/// 두 프로토콜을 모두 준수하는 모든 타입과 일치합니다.</p>
<h3 id="역할">역할</h3>
<ul>
<li>codable 프로콜은 swift에서 데이터와 객체 간의 변환을 용이하게 하는 프로토콜입니다. </li>
<li>Json, XML 등 외부 데이터 형식을 Swift 객체로 변환 또는 Swift내의 객체를 외부 데이터 형식으로 변환하는 작업을 도와주는 프로토콜 입니다. </li>
</ul>
<h3 id="주요기능">주요기능</h3>
<ul>
<li>Encoding: 객체 -&gt; 특정형식</li>
<li>Decoding: 특정형식의 데이터 -&gt; Swift내 객체 </li>
</ul>
<h3 id="사용예시">사용예시</h3>
<pre><code class="language-swift">do {
                let decodedData = try JSONDecoder().decode(T.self, from: data)
                completion(.success(decodedData))
            } catch let decodingError {
                if let jsonString = String(data: data, encoding: .utf8) {
                    jhPrint(&quot;&quot;&quot;
                            path: \(path), 
                            code: \(httpResponse.statusCode),
                            Decoding Error: \(decodingError.localizedDescription),
                            Decoding message: \(jsonString)
                            &quot;&quot;&quot; , isWarning: true)
                }
                completion(.failure(.decodingFailed(decodingError)))
            }</code></pre>
<h1 id="hashable">Hashable</h1>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/b14774be-5969-4159-9c50-4c7c8b82907b/image.png" alt=""></p>
<blockquote>
<p>/// 해시 값입니다.
    ///
    /// 해시 값은 프로그램의 여러 실행에서 동일하게 보장되지 않습니다.
    /// 프로그램의 다른 실행에서 동일하다고 보장되지 않습니다. 향후 실행 중에 사용할 해시 값을 저장하지 마세요.
    ///
    /// 중요: <code>해시값</code>은 <code>해시가능</code> 요구사항으로 더 이상 사용되지 않습니다. To
    /// 해시 가능<code>을 준수하려면 대신</code>hash(into:)<code>요구 사항을 구현하세요.
    /// 컴파일러는</code>hashValue<code>에 대한 구현을 제공합니다.
    /// 이 값의 필수 구성 요소를 주어진 해셔에 공급하여
    /// 주어진 해셔에 공급하여 해시합니다.
    ///
    /// 이 메서드를 구현하면</code>Hashable<code>프로토콜을 준수합니다. 해싱에 사용되는
    /// 해싱에 사용되는 컴포넌트는 비교되는 컴포넌트와 동일해야 합니다.
    /// 타입의</code>==<code>연산자 구현에서 비교한 컴포넌트와 동일해야 합니다. 해셔.결합(_:)</code>을 호출합니다.
    /// 를 호출합니다.
    ///
    /// - 중요: <code>hash(into:)</code> 구현에서,
    /// 제공된 <code>해셔</code> 인스턴스에서 <code>finalize()</code>를 호출하지 마세요,
    /// 또는 다른 인스턴스로 대체하세요.
    /// 그렇게 하면 나중에 컴파일 타임 오류가 발생할 수 있습니다.
    ///
    /// - 파라미터 해셔: 이 인스턴스의 컴포넌트를 결합할 때 사용할 해셔입니다.
    ///를 결합할 때 사용할 해시입니다.</p>
</blockquote>
<h1 id="뭐라는거야"><del>뭐라는거야;</del></h1>
<p>자이게 뭐냐 </p>
<h3 id="역할-1">역할</h3>
<ul>
<li>Hashable 프로토콜은 해시 기반 컬렉션에서 객체를 키로 사용할 떄 필요하며, 객체의 동등성을 비교하는데 사용한다고 하네요.</li>
<li>해시 기반 컬렉션에는 Set과 Dictionary가 포함됩니다.</li>
</ul>
<h3 id="주요기능-1">주요기능</h3>
<ul>
<li>해시 값을 생성: 객체의 해시 값을 생성하여 컬렉션의 효율적인 접근과 검색을 가능하게 한다.</li>
<li>동등성 비교: 두 객체가 같은지 비교할 수 있게 한다.</li>
</ul>
<h3 id="사용예시-1">사용예시</h3>
<ul>
<li>Set에서는 객체의 해시 값을 사용하여 중복을 제거하고 빠르게 객체를 검색한다.</li>
<li>DIctionary에서는 해시 값을 사용하여 키와 값을 매핑한다.</li>
<li>딕셔너리나 세트 쓸 때 더 효율적인 데이터 서치를 위해 사용하는거래요</li>
</ul>
<h1 id="identifiable">Identifiable</h1>
<h3 id="역할-2">역할</h3>
<ul>
<li>Identifiable 프로토콜은 SwiftUI와 같은 프레임워크에서 객체를 고유하게 식별하기 위해 사용됩니다. 이 프로토콜을 채택한 객체는 고유한 식별자를 제공해야 하며, 이 식별자는 객체를 구분할 수 있는 값입니다. SwiftUI에서 ForEach와 같은 뷰를 사용할 때, 이 프로토콜을 채택한 배열을 사용할 경우, 별도로 식별자를 지정하지 않아도 됩니다. 그 이유는 Identifiable 프로토콜이 이미 객체의 고유한 식별자를 제공하기 때문입니다. 
<del><em>내가쓴글 지피티가 첨삭해줬는데 깔끔하네</em></del></li>
</ul>
<h3 id="주요기능-2">주요기능</h3>
<ul>
<li>고유 식별자를 제공한다. 이게 끝 사용해보면 고유 ID 값을 구조에 넣으라고 계속 뭐라할거다.
<img src="https://velog.velcdn.com/images/ljh3904_a/post/ee08b46c-f7ba-4dd3-815a-4d33dddab855/image.png" alt=""></li>
</ul>
<p>참고:  GPT </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI 디자이너가 행간의 크기를 주었을 때 아주 쉽게 하는법 ]]></title>
            <link>https://velog.io/@ljh3904_a/SwiftUI-%EB%94%94%EC%9E%90%EC%9D%B4%EB%84%88%EA%B0%80-%ED%96%89%EA%B0%84%EC%9D%98-%ED%81%AC%EA%B8%B0%EB%A5%BC-%EC%A3%BC%EC%97%88%EC%9D%84-%EB%95%8C-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%ED%95%98%EB%8A%94%EB%B2%95</link>
            <guid>https://velog.io/@ljh3904_a/SwiftUI-%EB%94%94%EC%9E%90%EC%9D%B4%EB%84%88%EA%B0%80-%ED%96%89%EA%B0%84%EC%9D%98-%ED%81%AC%EA%B8%B0%EB%A5%BC-%EC%A3%BC%EC%97%88%EC%9D%84-%EB%95%8C-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%ED%95%98%EB%8A%94%EB%B2%95</guid>
            <pubDate>Sat, 17 Aug 2024 13:52:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/66b07a89-b435-4ff2-aed1-0f6e634fdee6/image.png" alt=""></p>
<p>저는 디자이너에게 검수를 받으려 프로토타입을 디자이너 분께 맡겼는데
모든 텍스트에 행간이 피그마상에 디자인명세대로 맞지 않다고 하셨다.. </p>
<pre><code class="language-swift">private let lineHeight: CGFloat = 1.338 //

    var body: some View {
        VStack(spacing: 0) {
            HStack {
                if isSuccessView { Spacer() }
                Text(top)
                    .font(.createFont(weight: topFont, size: 26))

                Spacer()
            }
            .padding(.bottom, 26 * (lineHeight - 1))</code></pre>
<p>맨처음에는 하드코딩으로 좀 더럽게 만들었었는데 하나의 모디파이어를 알고 인생이 달라졌다 </p>
<p>그건 바로 .lineSpacing() 이였따 </p>
<p>&quot;치피치피차파차파두비두비다바다바\n치피치피차파차파두비두비다바다바&quot; 이 있다고 치자</p>
<p>저 폰트의 사이즈는 13이다.
디자이너가 준 행간은 18.2다.</p>
<p>그러면 </p>
<pre><code class="language-swift">Text(&quot;치피치피차파차파두비두비다바다바\n치피치피차파차파두비두비다바다바&quot;)
     .font(.createFont(weight: .semiBold, size: 13))
     .lineSpacing(18.2 - 13)</code></pre>
<p>이런식으로 사용하면 된다 </p>
<p>이 방법은 Line Height 18.2에서 텍스트의 크기인 13를 뺀 값을 행간으로 사용한다는 의미입니다!</p>
<p>다들 행간때문에 삽질하지 마시고 쉽게하시길 바래용 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SwiftUI 모달이 나타날때 뒤에 뷰에 깔리는 색상 변경하기 ~ ]]></title>
            <link>https://velog.io/@ljh3904_a/SwiftUI-%EB%AA%A8%EB%8B%AC%EC%9D%B4-%EB%82%98%ED%83%80%EB%82%A0%EB%95%8C-%EB%92%A4%EC%97%90-%EB%B7%B0%EC%97%90-%EA%B9%94%EB%A6%AC%EB%8A%94-%EC%83%89%EC%83%81-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/SwiftUI-%EB%AA%A8%EB%8B%AC%EC%9D%B4-%EB%82%98%ED%83%80%EB%82%A0%EB%95%8C-%EB%92%A4%EC%97%90-%EB%B7%B0%EC%97%90-%EA%B9%94%EB%A6%AC%EB%8A%94-%EC%83%89%EC%83%81-%EB%B3%80%EA%B2%BD%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 29 Jul 2024 07:25:01 GMT</pubDate>
            <description><![CDATA[<p>오늘 개발하다가 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/5f18aff5-ee34-4864-9044-0fa850ca3075/image.png" alt=""></p>
<p>디자이너에게 이러한 피드백을 받고 색상을 변경하려고 했습니다. </p>
<p>하지만 제공해주는 모디파이어가 없더라구요 .. 쥐피티씨는 </p>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/fe1412a6-8235-4242-acaf-d28a432dba5e/image.png" alt=""></p>
<p>저게 맞는지 아닌지 확실하게는 모르지만 어려운 방법으로는 처리하지 않았습니다.</p>
<p>저는 어떻게 해결했는냐! </p>
<p>매우 간단해서 3시간동안의 뻘짓이 허무해지더라구요 </p>
<pre><code class="language-swift">ZStack {
 CustomColor.GrayScaleColor.gs2.ignoresSafeArea()
 Text(categoryName)
   .font(.createFont(weight: .bold, size: 26))
   .padding(.trailing, 7)
   Button {
     isSheet = true
      } label: {
      Image(&quot;CategoryIcon&quot;)
      .font(.createFont(weight: .medium, size: 14))              
      .foregroundStyle(CustomColor.GrayScaleColor.gs6)
      }
      .sheet(isPresented: $isSheet) {
        CategoryModal(categoryStore: categoryStore)
        .cornerRadius(12)          
        .presentationDetents([.height(688)])}

if isSheet {
   ----------
   CustomColor.GrayScaleColor.black.ignoresSafeArea()
                    .opacity(0.7)
            }
   ----------
</code></pre>
<p>이런식으로 zstack의 맨앞에 디자이너가 지정해준 컬러를 맨 위로 올리면 되더라구요.. 만약에 앞에다가 조건문안에 코드를 사용하면 버튼의 색상들은 따로 올라가기에 무조건 뒤에다가 써야합니다 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[삽질금지! SwiftUI 폰트 Extension하기]]></title>
            <link>https://velog.io/@ljh3904_a/%EC%82%BD%EC%A7%88%EA%B8%88%EC%A7%80-SwiftUI-%ED%8F%B0%ED%8A%B8-Extension%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/%EC%82%BD%EC%A7%88%EA%B8%88%EC%A7%80-SwiftUI-%ED%8F%B0%ED%8A%B8-Extension%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 18 Jun 2024 01:05:04 GMT</pubDate>
            <description><![CDATA[<p>Pretendard 폰트를 저의 프로젝트에 적용시켜볼겁니다 ! 
Swift내 커스텀 폰트를 적용시키는 방법은 아래와 같습니다. 
일단은 사이트에 들어가서 원하는 폰트를 다운 받은 후에 프로젝트내에 넣으면 됩니다.
<strong>폰트 다운 받는곳</strong>: <a href="https://noonnu.cc/">https://noonnu.cc/</a></p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/f3ca0f5c-7533-4649-a537-75db24b86bcf/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/3223bcf7-74f8-4add-8a5c-0328c320c3a3/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>그리고 <strong>Project -&gt; Targets -&gt; Info</strong> 들어가서 밑에 사진처럼 추가하면 됩니다. 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/3cbbb79e-77c0-4e31-bf53-e75b0f9f0e95/image.png" alt=""></p>
<h1 id="문제">문제</h1>
<p>Pretendard 폰트 유명하죠! 폰트들을 Extension으로 추가해서 앱 내에 사용하려고 했는데 말이죠,
폰트들을 추가하다 보니 코드가 더러워지고 있다는 걸 느꼈어요. 아래는 고치기 전에 제 코드입니다.</p>
<pre><code class="language-swift">extension Font {
    // Black
    static let pretendardBlack28: Font = .custom(&quot;Pretendard-Black&quot;, size: 28)
    static let pretendardBlack26: Font = .custom(&quot;Pretendard-Black&quot;, size: 26)
    static let pretendardBlack24: Font = .custom(&quot;Pretendard-Black&quot;, size: 24)
    static let pretendardBlack22: Font = .custom(&quot;Pretendard-Black&quot;, size: 22)
    static let pretendardBlack20: Font = .custom(&quot;Pretendard-Black&quot;, size: 20)
    static let pretendardBlack18: Font = .custom(&quot;Pretendard-Black&quot;, size: 18)
    static let pretendardBlack16: Font = .custom(&quot;Pretendard-Black&quot;, size: 16)
    static let pretendardBlack14: Font = .custom(&quot;Pretendard-Black&quot;, size: 14)
    static let pretendardBlack12: Font = .custom(&quot;Pretendard-Black&quot;, size: 12)
    static let pretendardBlack10: Font = .custom(&quot;Pretendard-Black&quot;, size: 10)

    // Bold
    static let pretendardBold28: Font = .custom(&quot;Pretendard-Bold&quot;, size: 28)
    static let pretendardBold26: Font = .custom(&quot;Pretendard-Bold&quot;, size: 26)
    static let pretendardBold24: Font = .custom(&quot;Pretendard-Bold&quot;, size: 24)
    static let pretendardBold22: Font = .custom(&quot;Pretendard-Bold&quot;, size: 22)
    static let pretendardBold20: Font = .custom(&quot;Pretendard-Bold&quot;, size: 20)
    static let pretendardBold18: Font = .custom(&quot;Pretendard-Bold&quot;, size: 18)
    static let pretendardBold16: Font = .custom(&quot;Pretendard-Bold&quot;, size: 16)
    static let pretendardBold14: Font = .custom(&quot;Pretendard-Bold&quot;, size: 14)
    static let pretendardBold12: Font = .custom(&quot;Pretendard-Bold&quot;, size: 12)
    static let pretendardBold10: Font = .custom(&quot;Pretendard-Bold&quot;, size: 10)

    // ExtraBold
    static let pretendardExtraBold28: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 28)
    static let pretendardExtraBold26: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 26)
    static let pretendardExtraBold24: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 24)
    static let pretendardExtraBold22: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 22)
    static let pretendardExtraBold20: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 20)
    static let pretendardExtraBold18: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 18)
    static let pretendardExtraBold16: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 16)
    static let pretendardExtraBold14: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 14)
    static let pretendardExtraBold12: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 12)
    static let pretendardExtraBold10: Font = .custom(&quot;Pretendard-ExtraBold&quot;, size: 10)

    // ExtraLight
    static let pretendardExtraLight28: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 28)
    static let pretendardExtraLight26: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 26)
    static let pretendardExtraLight24: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 24)
    static let pretendardExtraLight22: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 22)
    static let pretendardExtraLight20: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 20)
    static let pretendardExtraLight18: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 18)
    static let pretendardExtraLight16: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 16)
    static let pretendardExtraLight14: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 14)
    static let pretendardExtraLight12: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 12)
    static let pretendardExtraLight10: Font = .custom(&quot;Pretendard-ExtraLight&quot;, size: 10)

    // Light
    static let pretendardLight28: Font = .custom(&quot;Pretendard-Light&quot;, size: 28)
    static let pretendardLight26: Font = .custom(&quot;Pretendard-Light&quot;, size: 26)
    static let pretendardLight24: Font = .custom(&quot;Pretendard-Light&quot;, size: 24)
    static let pretendardLight22: Font = .custom(&quot;Pretendard-Light&quot;, size: 22)
    static let pretendardLight20: Font = .custom(&quot;Pretendard-Light&quot;, size: 20)
    static let pretendardLight18: Font = .custom(&quot;Pretendard-Light&quot;, size: 18)
    static let pretendardLight16: Font = .custom(&quot;Pretendard-Light&quot;, size: 16)
    static let pretendardLight14: Font = .custom(&quot;Pretendard-Light&quot;, size: 14)
    static let pretendardLight12: Font = .custom(&quot;Pretendard-Light&quot;, size: 12)
    static let pretendardLight10: Font = .custom(&quot;Pretendard-Light&quot;, size: 10)
}</code></pre>
<p>딱 봐도 엄청나게 길어 보이죠..!
그래서 생각해 봤습니다. 홀수 값이 오면 어떻게 할 것이고, 또 다른 추가되는 폰트가 필요하다면 또 코드를 늘릴 것인가? 이대로 가면 Extension 코드가 끊임없이 늘어날 것 같았습니다.</p>
<h1 id="해결">해결</h1>
<p>열거형으로 파라미터에 자동으로 뜨게 하여 타이핑하는 번거로움을 해결했고, 어떤 사이즈의 폰트가 와도 메서드 하나로 처리할 수 있게 했습니다.</p>
<pre><code class="language-swift">extension Font {
    enum FontWeight: String {
        case black = &quot;Pretendard-Black&quot;
        case bold = &quot;Pretendard-Bold&quot;
        case extraBold = &quot;Pretendard-ExtraBold&quot;
        case extraLight = &quot;Pretendard-ExtraLight&quot;
        case light = &quot;Pretendard-Light&quot;
        case medium = &quot;Pretendard-Medium&quot;
        case regular = &quot;Pretendard-Regular&quot;
        case semiBold = &quot;Pretendard-SemiBold&quot;
        case thin = &quot;Pretendard-Thin&quot;
    }

    static func createFont(weight: FontWeight, size: CGFloat) -&gt; Font {
        return .custom(weight.rawValue, size: size)
    }
}</code></pre>
<h4 id="사용방법">사용방법</h4>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/7a0b1a87-1c7b-4c83-b149-de789db57790/image.png" alt=""></p>
<p>참고
<a href="https://velog.io/@tkddn0518/SwiftUI-Font-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%8F%B0%ED%8A%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0">https://velog.io/@tkddn0518/SwiftUI-Font-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%8F%B0%ED%8A%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[애플로그인 커스텀 하기 ! ]]></title>
            <link>https://velog.io/@ljh3904_a/%EC%95%A0%ED%94%8C%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/%EC%95%A0%ED%94%8C%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 Jun 2024 07:23:10 GMT</pubDate>
            <description><![CDATA[<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/5834c86e-9d23-42eb-bee2-d914d30d759d/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/bc6d5c44-8121-4043-a34c-068bd7216eac/image.png" alt=""></th>
</tr>
</thead>
</table>
<p>애플로그인버튼을 커스텀 해봤습니다
방법은 zstack 으로 먼저 커스텀한 애플로그인뷰를 만들어놓고 </p>
<pre><code class="language-swift">Image(&quot;appleLogin&quot;)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: SignSpace.screenWidth - 40, height: 55)</code></pre>
<p>그 위에다가 진짜 애플로그인 버튼을 쌓습니다</p>
<pre><code class="language-swift">ZStack {

                Image(&quot;appleLogin&quot;)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: SignSpace.screenWidth - 40, height: 55)
                AppleSignInButton()
                    .frame(width: SignSpace.screenWidth - 40, height: 55)
            }</code></pre>
<p>그리고 제일중요한 수정자를 넣어야하는데요 </p>
<p><a href="https://developer.apple.com/documentation/swiftui/view/blendmode(_:)">https://developer.apple.com/documentation/swiftui/view/blendmode(_:)</a></p>
<p>blendMode(_:)이 수정자를 사용해서 zstack안에 있는 뷰들을 혼합시킬겁니다.</p>
<pre><code class="language-swift">.blendMode(.overlay)</code></pre>
<p>그리고 기존 애플로그인의 투명도를 0.02로 두고 안보이도록 합니다</p>
<pre><code class="language-swift">.opacity(0.02)</code></pre>
<p><em>*<em>만약에 0.01로 두고 작업하시면 버튼이 안눌리실겁니다!! 이유는 몰루 ..? *</em></em></p>
<p>이렇게만 하면 버튼이 안눌릴텐데요 ! 제일중요한 수정자를 넣으면 해결이 됩니다!
바로 뭐냐면 </p>
<p>allowsHitTesting(_:) 이 수정자를 넣어야 해요 ~ !</p>
<p>이것이 무엇이냐 </p>
<p><a href="https://developer.apple.com/documentation/assignables/assignabledocumentview/allowshittesting(_:)/">https://developer.apple.com/documentation/assignables/assignabledocumentview/allowshittesting(_:)/</a></p>
<blockquote>
<p>allowsHitTesting(_:)는 SwiftUI에서 뷰가 터치 이벤트나 다른 형태의 사용자 인터랙션을 받을 수 있는지 여부를 제어하는 수정자입니다. 이 수정자는 뷰가 사용자 인터랙션을 받을 수 있도록 할지 또는 무시할지를 결정합니다. 기본값은 true이며, 이는 뷰가 터치 이벤트를 받을 수 있음을 의미합니다.</p>
</blockquote>
<p>라고 하는데요. 
즉 이는 버튼이 시각적으로는 여전히 투명하지만, SwiftUI에서는 여전히 터치 이벤트를 받을 수 있게 합니다. 그리고 allowsHitTesting(true)를 사용하여 투명한 뷰가 터치 이벤트를 받을 수 있게 했습니다.</p>
<pre><code class="language-swift">ZStack {   
                Image(&quot;appleLogin&quot;)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: SignSpace.screenWidth - 40, height: 55)
                AppleSignInButton()
                    .frame(width: SignSpace.screenWidth - 40, height: 55)
                    .blendMode(.overlay)
                    .opacity(0.02)
                    .allowsHitTesting(true)
            }</code></pre>
<ul>
<li>참고
<a href="https://sy-catbutler.tistory.com/56">https://sy-catbutler.tistory.com/56</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[런치스크린 사진에 공백해결하기]]></title>
            <link>https://velog.io/@ljh3904_a/%EB%9F%B0%EC%B9%98%EC%8A%A4%ED%81%AC%EB%A6%B0-%EC%82%AC%EC%A7%84%EC%97%90-%EA%B3%B5%EB%B0%B1%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/%EB%9F%B0%EC%B9%98%EC%8A%A4%ED%81%AC%EB%A6%B0-%EC%82%AC%EC%A7%84%EC%97%90-%EA%B3%B5%EB%B0%B1%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 Jun 2024 05:19:06 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<ul>
<li>런치스크린에 사진이 safearea가 적용이 되는듯한 문제를 겪었습니다.</li>
</ul>
<p>왼쪽은 문제의 런치스크린이고, 오른쪽은 런치스크린에 들어갈 이미지에용</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/e694eeef-0441-4344-b6ec-06b460133bed/image.png" width="50%" height="50%"></th>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/5c06c652-67fc-4c04-b08f-341088e59232/image.png" width="n%" height="n%"></th>
</tr>
</thead>
</table>
<p>비율에 맞게 
<img src="https://velog.velcdn.com/images/ljh3904_a/post/06f90db4-e21b-4b3a-83e5-2b179bf9bf98/image.png" alt=""></p>
<p>비율에 따라서 사진도 넣었습니다. </p>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/a939c719-d3ff-4331-afa7-0c2cca56699b/image.png" alt=""></p>
<p>safe area를 준수하지도 않았습니다만 해결이 안되고 있었어요 </p>
<h1 id="해결">해결</h1>
<ul>
<li>스토리보드로 런치스크린을 제작하는 방법으로 바꿔서 만들었어요! </li>
<li>너무 손쉽게 해결.. </li>
</ul>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/7916d750-ae89-4766-9937-762d36935a13/image.png" width="n%" height="n%"></th>
<th><img src="https://velog.velcdn.com/images/ljh3904_a/post/0d991c01-3eb2-45a2-be1b-88c1305f9a29/image.png" width="n%" height="n%"></th>
</tr>
</thead>
</table>
<img src="https://velog.velcdn.com/images/ljh3904_a/post/cd30a9dd-267e-46d0-a573-07f06fdedb18/image.png" width="30%" height="30%">]]></description>
        </item>
        <item>
            <title><![CDATA[swift 5.9의 속성래퍼들과 매크로 ]]></title>
            <link>https://velog.io/@ljh3904_a/swift-5.9%EC%9D%98-%EC%86%8D%EC%84%B1%EB%9E%98%ED%8D%BC%EB%93%A4%EA%B3%BC-%EB%A7%A4%ED%81%AC%EB%A1%9C</link>
            <guid>https://velog.io/@ljh3904_a/swift-5.9%EC%9D%98-%EC%86%8D%EC%84%B1%EB%9E%98%ED%8D%BC%EB%93%A4%EA%B3%BC-%EB%A7%A4%ED%81%AC%EB%A1%9C</guid>
            <pubDate>Mon, 20 May 2024 08:50:40 GMT</pubDate>
            <description><![CDATA[<h1 id="swift-59와-ios-17-새로운-observable-매크로로-더-쉬워진-swiftui">Swift 5.9와 iOS 17: 새로운 Observable 매크로로 더 쉬워진 SwiftUI</h1>
<p>Swift 5.9와 iOS 17부터 도입된 <a href="https://developer.apple.com/documentation/observation">Observation</a>의 Observable 매크로 덕분에 SwiftUI를 더 쉽게 다룰 수 있게 되었습니다. 이 매크로는 기존의 번거로운 바인딩 방식에서 벗어나 코드의 가독성을 높여줍니다.</p>
<h2 id="observable-매크로란">Observable 매크로란?</h2>
<p>Observable 매크로는 Swift 5.9와 iOS 17 이상 버전에서 지원하는 새로운 기능입니다. 이 매크로를 사용하면, 기존에 <code>@Published</code>와 같은 속성 래퍼를 사용해 프로토콜을 채택하던 방식을 대체할 수 있습니다.</p>
<h3 id="기존-방식">기존 방식</h3>
<p>기존에는 <code>ObservableObject</code> 프로토콜을 채택하고, <code>@Published</code> 속성 래퍼를 사용하여 데이터 변화를 감지하고 UI에 반영했습니다.</p>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/54d11034-c4d2-48ec-b11a-b34a48a68080/image.png" alt=""></p>
<h3 id="새로운-observable-매크로">새로운 Observable 매크로</h3>
<p>이제 <code>@Observable</code>을 클래스에 추가하는 것만으로 데이터 모델의 변경사항에 UI가 응답하도록 만들 수 있습니다.</p>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-swift">import Foundation

@Observable class Market {
    var food: [Food] = []
}

struct Food {
    var name: String
    var price: Int
}

struct ContentView: View {
    var market: Market = Market()

    var body: some View {
        VStack {
            Button(action: {
                market.food.append(Food(name: &quot;추가된거&quot;, price: 200))
            }, label: {
                Text(&quot;추가&quot;)
            })

            List {
                ForEach(market.food) { food in
                    Text(food.name)
                }
            }
        }
    }
}
</code></pre>
<p>이 코드는 <code>@Observable</code> 매크로를 사용하여 Swift 컴파일러에게 해당 클래스의 타입을 관찰하도록 지시합니다. 이제 더 이상 속성 래퍼를 사용할 필요가 없습니다. SwiftUI는 <code>Observable</code> 타입의 프로퍼티를 자동으로 감시하여 변경사항을 UI에 반영합니다.</p>
<h3 id="연산-프로퍼티를-통한-실시간-추적-예시">연산 프로퍼티를 통한 실시간 추적 예시</h3>
<pre><code class="language-swift">struct ContentView: View {
    @Environment(Market.self) var market: Market
    @ObservedObject var oldmarket: OldMarket = OldMarket()

    var body: some View {
        VStack {
            Button(action: {
                market.food.append(Food(name: &quot;추가된거&quot;, price: 200))
            }, label: {
                Text(&quot;추가&quot;)
            })
            Text(&quot;\\(market.foodCount)&quot;)
            List {
                ForEach(market.food) { food in
                    Text(food.name)
                }
            }
        }
    }
}
</code></pre>
<p>이 코드에서는 실시간으로 데이터를 추적하여 UI에 반영합니다. <code>Observable</code> 매크로를 사용하면 SwiftUI는 해당 프로퍼티에 대한 액세스를 추적하고, 변경사항을 자동으로 UI에 적용합니다.</p>
<p>그리고 이제는 stateObject observableobject를 구분하지 않고 앞에 state 속성래퍼를 사용합니다. </p>
<pre><code class="language-swift">@State var likePubishedMarket: Market = Market()</code></pre>
<h1 id="environment">“@”Environment</h1>
<p>이 매크로를 사용해서 environment를 하고싶으면 </p>
<pre><code class="language-swift">@Environment(Market.self) var market: Market  </code></pre>
<h1 id="bindable">Bindable</h1>
<ul>
<li>새로나온 프로퍼티 래퍼 입니다.</li>
<li>옵저버 매크로를 사용하는 타입에 대한 특정 속성을 바인딩하는 프로퍼티 래퍼입니다.</li>
</ul>
<pre><code class="language-swift">@Bindable var foodCount: Market = .init()</code></pre>
<h1 id="언제-사용하나">언제 사용하나</h1>
<p>해당 모델이 뷰의 현재 상태여야한다면 state </p>
<p>앱의 전역적으로 사용되어야 하는거라면 Environment </p>
<p>바인딩이 되어야한다면 ? Bindable </p>
<h1 id="기존-vs-새로운">기존 vs 새로운</h1>
<pre><code class="language-swift">struct ContentView: View {
    // 기존방식
    @ObservedObject var oldMarket: OldMarket = OldMarket()
    // 새로운 방식
    var likePubishedMarket: Market = Market()

    // 기존방식
    @StateObject var oldMarket2: OldMarket = OldMarket()
    // 새로운 방식
    @State var newMarket2: Market = Market()

    // 기존방식
    @EnvironmentObject var oldmarket2: OldMarket
    // 새로운 방식
    @Environment(Market.self) var market3: Market

    // 새로운 속성래퍼
    @Bindable var foodCount: Market = .init()

    var body: some View {
        VStack {
            Button(action: {
                market3.food.append(Food(name: &quot;추가된거&quot;, price: 200))
            }, label: {
                Text(&quot;추가&quot;)
            })
            Text(&quot;\(market3.foodCount)&quot;)
            List {
                ForEach(market3.food) { food in
                    Text(food.name)
                }
            }
        }
    }
}</code></pre>
<h1 id="결론">결론</h1>
<p>Observable 매크로를 통해 SwiftUI에서 데이터 모델을 보다 간편하게 관리할 수 있게 되었습니다. 이는 코드의 가독성을 높이고, 개발자가 데이터 변경사항을 손쉽게 UI에 반영할 수 있도록 도와줍니다. Swift 5.9와 iOS 17 이상 버전에서 이 새로운 기능을 적극 활용해보세요!</p>
<hr>
<p><a href="https://green1229.tistory.com/373">Discover Observation in SwiftUI (feat. WWDC 2023)</a></p>
<p><a href="https://www.hohyeonmoon.com/blog/swiftui-data-flow">SwiftUI의 데이터 흐름 | Hohyeon Moon</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RxSwift 면접질문 모음 ]]></title>
            <link>https://velog.io/@ljh3904_a/RxSwift-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-ay8pqcq6</link>
            <guid>https://velog.io/@ljh3904_a/RxSwift-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-ay8pqcq6</guid>
            <pubDate>Thu, 09 May 2024 17:41:31 GMT</pubDate>
            <description><![CDATA[<h1 id="reactive-programming">Reactive Programming</h1>
<ul>
<li>데이터 스트림과 변화에 반응하는 앱을 만들기 위한 프로그래밍의 패러다임입니다.</li>
<li>데이터의 흐름을 비동기적으로 처리하면서 앱내 이벤트를 실시간으로 데이터 변화에 맞게 반응할 수 있도록 하는 프로그래밍 패러다임입니다.</li>
</ul>
<h2 id="rxswift의-장단점을-설명하시오">RxSwift의 장/단점을 설명하시오.</h2>
<p>장점 </p>
<ul>
<li>반응형 패러다임이 제공하는 명확함과 비동기처리를 동기화 코드처럼 작성이 가능하다는점입니다.</li>
<li>RxSwift를 사용하지 않을 경우, 콜백 지옥, 즉 escaping closure를 통한 completion 핸들러를 사용하여 비동기 작업을 수행해야 했으며, 이로 인해 쓰레드 관리가 복잡해졌습니다.</li>
</ul>
<p>단점</p>
<ul>
<li>클로저의 캡쳐리스트를 신경을 써야합니다.</li>
<li>RxSwift는 클로저의 사용이 많습니다 그렇기에 메모리 누수를 일으키는 부분과 순환참조를 피할 수 있게 신경을 써야합니다. 그렇지 않으면 Race Condition 같은 것이 발생할 수 있습니다.</li>
</ul>
<h2 id="subject의-종류와-차이점에-대해-설명하시오">Subject의 종류와 차이점에 대해 설명하시오.</h2>
<p>Subject에는 PublishSubject, BehaviorSubject, ReplaySubject, AsyncSubject가 있습니다.</p>
<p>이 4개의 서브젝트의 가장 큰 차이점은 구독한 시점에서 데이터를 어떤것을 받냐에 따라서 갈린다고 생각합니다.</p>
<ul>
<li>PublishSubject는 구독을 하면 그시점의 데이터를 바로 보내준는 기능이입니다.</li>
<li>BehaviorSubject은 PublishSubject와 기능은같지만 초기값을 가집니다. 그렇기에 초반에 값이 없을 때 값을 내려보내지 못하는 PublishSubject와는 다르게 BehaviorSubject은 초기값을 설정하기에 구독자가 값을 가진상태로 시작합니다.</li>
<li>초기값은 항상 갱신이 됩니다. 예를 들어 초기값이 1이지만 Subject특성상 값을 받는 것도 가능하기에 그 받은 값이 초기값이 됩니다.</li>
<li>ReplaySubject의 처음 기능은 PublishedSubject와 기능이 비슷하나 누군가가 구독을 하면 구독시점 전의 데이터도 내려보내줍니다.</li>
<li>AsyncSubject은 누군가가 구독을해도 마지막 OnComplete가 되기전까지는 보내지 않고 OnComplete가 되면 그때 마지막 데이터를 보내줍니다.</li>
</ul>
<h2 id="bind-subscribe-driver-차이">bind, subscribe, driver 차이</h2>
<ul>
<li>subscribe와 bind + driver의 차이는 subscribe와 다르게 나머지 두개는 UI작업에 특화되어있습니다.</li>
<li>bind는 UI 작업에 특화되어있고, .observe(on: MainScheduler.instance)를 통하여 항상 UI쓰레드인 메인에서 작동이 되어야합니다. 그리고 순환참조를 막기위한 작업을 하지 않아도 됩니다.</li>
<li>bind와는 다르게 observe(on: MainScheduler.instance)을 통하여 메인쓰레드에서 돌아가도록 안해줘도 되는 슈거입니다. 드라이버의 에러처리는 .asDriver(onErrorJustReturn: &quot;&quot;)이것을 통하여 합니다.</li>
</ul>
<h2 id="throttle-debounce-차이">throttle, debounce 차이</h2>
<ul>
<li>이 두개는 프로그래밍에서 요청이나 처리의 빈도를 제한하거나 지연시키고자 사용하는 오퍼레이터입니다.</li>
<li>두개의 차이점은 debounce는 일정 시간을 기다리고 요청을 수행하고, 일정 시간 안에 같은 요청이 들어오면 이전 요청은 취소됩니다.</li>
<li>throttle은 시간을 정해두고 요청이 한 번만 수행되도록 합니다.</li>
</ul>
<h2 id="hot-observable-vs-cold-observable"><strong>Hot Observable VS Cold Observable</strong></h2>
<ul>
<li>일반적인 Cold Observable은 일반적인 Observable입니다.</li>
<li>구독한 시점부터 생성된 비동기적인 이벤트를 준다는 특징이있습니다.</li>
<li>Hot은 구독하기 전부터 생성된 비동기적인 이벤트 즉 Emit을 전달해주는 특징이 있습니다.</li>
<li>Hot은 하나의 API를 가져온다 생각하면 공유를 하여 메모리적으로 여유를 가지게 하는 특성이고, Cold는 Observer마다 별도의 인스턴스를 가지게 됩니다.</li>
</ul>
<h2 id="disposable-disposebag은-무엇이며-사용하는이유">Disposable, DisposeBag은 무엇이며 사용하는이유?</h2>
<ul>
<li>Disposable은 Observer가 Observable에 대한 구독을 취소할 수 있게 하는것입니다.</li>
<li>Disposable을 하는 시점이후는 Observable이 내려주는 데이터를 받을 수 없게 됩니다.</li>
<li>이것을 사용하는 이유는 메모리 관리를 위해서 사용합니다.</li>
<li>Observable은 기본적으로 .complete 또는 .error가 발생하기 전까지 이벤트를 계속 방출시켜줘서 직접 deinit을 해줘야합니다. 소멸자를 하지 않으면 필요할 때마다 이벤트를 방출시키는 메모리 릭으로 이어집니다. dispose가 Observable에 대한 리소스를 deinit을 해준다는것입니다.</li>
<li>DisposeBag은 여러개의 구독을 관리 <strong>Disposable를 할 수 있는 담는 배열입니다.</strong></li>
<li>disposed(by:)로 DisposeBag에 등록가능합니다.</li>
<li>메모리에서 해제될 경우 모든 Disposable들을 dispose 시킵니다.</li>
</ul>
<h2 id="observable--subject--relay-차이">Observable / Subject / Relay 차이</h2>
<ul>
<li>Observable 은 비동기 프로그래밍을 관찰 가능한 순서로 비동기 이벤트의 시퀀스를 생성할 수 있는 대상입니다.</li>
<li>Subject는 Observable 처럼 새로운 아이템들을 emit할 수 있고 Observer처럼 하나의 Observable을 구독할 수 있습니다.</li>
<li>Relay는 Subject와 다르게 next이벤트만 받습니다. 그렇기에 주로 종료없이 지속되는 UI 이벤트를 처리하는데 사용합니다.</li>
</ul>
<h2 id="relay--driver--signal의-예시">Relay / Driver / Signal의 예시</h2>
<h3 id="relay">Relay?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>relay는 Subject와 달리 Next 이벤트만 받고 completed와 error는 주고 받지 않는다.</li>
<li>Subject는 completed와 error는 주고 받지 않습니다. 그렇기에 주로 UI이벤트를 처리하는데 사용합니다.</li>
<li>Relay 이벤트를 전달하기 위해서는 .accept를 사용해야한다.</li>
</ul>
<h3 id="driver">Driver?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>rxCocoa에서 Observable을 바인딩을 하려면 .bind를 사용해야하고, 추가로 어느 쓰레드에서 돌아갈지 정하는 observeOn를 같이 작성해줘야합니다. 그리고 error 가 발생하면 Stream이 끊어져버리고 한번 끊어지면 재사용이 불가능하기에 Error가 발생하면 그대로 종료되는것을 방지하기 위한 catchErrorJustReturn을 사용해야합니다. 이렇게 많은 메서드들을 사용하기 귀찮아서 나온 슈거가 Driver입니다. 사용방법은 .asDriver를 사용하고 bind대신 .driver를 사용하면 됩니다.</li>
</ul>
<h3 id="signal">Signal?</h3>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
<li>시그널이란 rx코코아에서 ui작업 을 위해 사용되고 기본적으로 ui작업이기에 메인 스레드</li>
<li>드라이버와 달리 초기값이나 최신값을 주지않음서브젝트에서 퍼블리쉬 서브젝트마냥</li>
</ul>
<h2 id="rx는-무엇일까요-">.rx는 무엇일까요 ?</h2>
<ul>
<li>뷰에 상태를 Observable로 래핑하는데 사용하는 표기입니다.</li>
</ul>
<h3 id="2회차">2회차</h3>
<hr>
<h2 id="observable의-생명주기에-대해-설명해주세요">Observable의 생명주기에 대해 설명해주세요.</h2>
<p>observable은 생성되면 Next 이벤트를 방출하고 .completed 또는 .error를 방출하여 처리하고 나서 dispose로 메모리에서 해제시킵니다. </p>
<h2 id="rxswift에서의-error-handling-방법은-어떻게-되나요">RxSwift에서의 Error Handling 방법은 어떻게 되나요?</h2>
<ul>
<li>Catch: 기본값 defaultValue로 error 복구할 수 있습니다<ul>
<li>catchAndReturn를 토앟여 어떤 에러가 있을 경우 리턴값을 정해서 리턴 가능합니다.</li>
</ul>
</li>
<li>retry: 작업이 성공할 때 까지 계속 재시도를 합니다.</li>
<li><code>retry(_ maxAttemptCount: Int)</code> maxCount를 지정하여 최대 재시도 횟수를 지정할 수 있습니다.<ul>
<li><strong>RetryWhen: 에러가 나타나면 error를 가공하여 observerbleType을 반환합니다.</strong></li>
<li>.retryWhen { err → obserevable<Int> in return 이런식으로 사용</li>
</ul>
</li>
</ul>
<h2 id="rxcocoa의-ui요소와의-상호작용-방법에-대해-설명해주세요">RxCocoa의 UI요소와의 상호작용 방법에 대해 설명해주세요.</h2>
<ul>
<li>?</li>
</ul>
<h2 id="rxswift에서의-testing-방법에-대해-설명해주세요">RxSwift에서의 Testing 방법에 대해 설명해주세요.</h2>
<ul>
<li>RxSwift는 TestScheduler 정확한 시간 간격으로 이벤트를 추가할 수 있는 메서드 기능을 제공합니다.</li>
<li>RxTset를 사용해야하고, RxSwift 저장소의 한 부분을 담당합니다.</li>
<li>RxTest는 테스팅 목적으로 두가지 타입의 Observables를 드러낼 수 있습니다.<ul>
<li>Cold와 Hot이 있습니다.</li>
<li>Cold는 일반적인 Observable와 같이 동작하며 구독 시 ㅅ개로운 구독자들에게 그들의 요소를 재생해줍니다.</li>
<li>Hot은 어떤 가입자가 있는지 여부에 상관없이 테스트 스케쥴러를 사용하여 지정된 시간에 이벤트를 재생합니다.</li>
</ul>
</li>
<li>RxBlocking이 비동기적 작업을 테스트할 수 있는 또 다른 하나의 방법이고, 비동기 작업의 테스팅 간 해당 라이브러리를 사용하는 방법입니다.</li>
</ul>
<hr>
<h2 id="observeon-과-subscribeon의-차이는-무엇인가요">observeOn 과 subscribeOn의 차이는 무엇인가요</h2>
<ul>
<li>observeOn은 이벤트를 받을 때 처리하는  스케줄러를 변경하고 subscribeOn은 Observable이 생성 또는 처음 이벤트가 발생되었을 때 스케줄러를 변경합니다.</li>
</ul>
<h2 id="flatmap-flatmapfirst-flatmaplast의-차이점은-무엇인가요">flatMap, flatMapFirst, flatMapLast의 차이점은 무엇인가요</h2>
<ul>
<li>flatMap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>flatMapFirst: 첫 번째 observable의 이벤트만 받고, 다음 생성된것은 처리하지 않습니다.</li>
<li>flatMapLast: 가장 마지막의 이벤트만 처리하고, 전에 생성된 이벤트들은 처리하지 않습니다.</li>
</ul>
<h2 id="merge--concat--zip--withlastestfrom--combinelastest-차이점">merge / concat / zip , withLastestFrom / CombineLastest 차이점</h2>
<ul>
<li>merge: 같은 타입의 이벤트를 발생하는 Observable을 합성하는 함수이며, 각각의 이벤트르 모두 수신할 수 있습니다. 이벤트가 같지 않으면 merge를 할 수 없다.</li>
<li>concat: 순차적으로 여러 Observable을 합칠 때 사용합니다.</li>
<li>zip: 두 Observable의 발생 순서가 같은 이벤트를 조합해서 이벤트를 발생합니다. 그리고 이벤트가 조합되지 않으면 이벤트가 발생하지 않습니다.</li>
<li>CombineLastest: 두 Observable의 각각의 이벤트가 발생할 때 두 개의 Observable의 마지막 이벤트들을 묶어서 전달합니다.</li>
<li>withLastestFrom: 두 개의 Observable을 합성하지만 한쪽 Observable의 이벤트가 발생할 때에 합성해주는 메서드입니다. 다른쪽 이벤트가 없으면 합성하지 않습니다.</li>
</ul>
<h2 id="구독-연산자-share의-간단한-설명">구독 연산자 share의 간단한 설명</h2>
<ul>
<li>여러 subscribe에서 Observable의 구독이 일어나면 구독할 때 마다 새로운 시퀸스가 생성되어 리소스 낭비가 일어나는것을 방지하기 위한 연산자입니다.</li>
</ul>
<hr>
<h2 id="rxswift에서의-back-pressure는-무엇이며-어떻게-처리할-수-있나요">RxSwift에서의 Back Pressure는 무엇이며 어떻게 처리할 수 있나요?</h2>
<ul>
<li>Back Pressure: Observable은 이벤트의 방출 속도와 구독자의 처리 속도가 맞지 않을 때 입니다. buffer throttle debouncing 연산자를 사용하여 분기를 맞춰 처리합니다.</li>
</ul>
<h2 id="signal의-기능을-말해주시고-예시가-있다면-예시도-함께-말해주세욘">Signal의 기능을 말해주시고 예시가 있다면 예시도 함께 말해주세욘</h2>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
</ul>
<h2 id="observeon-scheduler에서-api를-받아올-때-어느-scheduler를-실행시켜야-할까요">Observe(on: Scheduler)에서 api를 받아올 때 어느 Scheduler를 실행시켜야 할까요?</h2>
<ul>
<li>ConcurrentDispatchQueueScheduler에서 처리합니다.</li>
</ul>
<hr>
<h2 id="single-completable-maybe">Single, Completable, Maybe</h2>
<ul>
<li>single: Observable의 변형으로 일련의 요소를 방출하는 대신 항상 단일 요소 또는 오류를 방출하도록 보장하는것입니다.<ul>
<li>정확히 하나의 요소 또는 error를 방출합니다.</li>
<li>single의 사용 예는 응답, 오류만 반환할 수 있는 HTTP 요청을 수행하는데 사용됩니다.</li>
</ul>
</li>
<li>Completetable: complete 하거나 error를 방출할 수 있고 아무 요소도 방출하지 않는것을 보장합니다.<ul>
<li>complete 또는 error만 방출합니다.</li>
<li>completable은 완료에 따른 요소에 신경쓰지 않은 경우 사용합니다.</li>
</ul>
</li>
<li>Maybe: Single과 Completable의 중간인 Observable의 변형입니다.<ul>
<li>완료된 이벤트, 싱글 이벤트 또는 오류를 방출합니다.</li>
<li>Maybe는 Single과 비슷하지만 아무런 값을 방출하지 않고 coimplete가 되어도 오류가 발생하지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="operator-just-of-from의-용도">operator just, of, from의 용도</h2>
<ul>
<li>just: 단 하나의 요소만 방출</li>
<li>of: 여러 요소를 방출합니다.</li>
<li>from: 배열의 형태로 오는 요소들을 한 개씩 순차적으로 내놓습니다.</li>
</ul>
<h2 id="flatmap-compactmap-map의-용도">flatmap, compactmap, map의 용도</h2>
<ul>
<li>map: 옵저버블의 가진 요소들에 특정 연산을 수행해서 값을 방출합니다</li>
<li>flatmap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>compactmap: Observable에서 방출되는 이벤트를 꺼낸 다음 옵셔널 형태로 바꾸고 원하는 변환을 실행합니다. 그리고 최종 변환된 값이 nil이면 방출하지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RxSwift 면접질문 모음 ]]></title>
            <link>https://velog.io/@ljh3904_a/RxSwift-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@ljh3904_a/RxSwift-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Thu, 09 May 2024 17:41:14 GMT</pubDate>
            <description><![CDATA[<h1 id="reactive-programming">Reactive Programming</h1>
<ul>
<li>데이터 스트림과 변화에 반응하는 앱을 만들기 위한 프로그래밍의 패러다임입니다.</li>
<li>데이터의 흐름을 비동기적으로 처리하면서 앱내 이벤트를 실시간으로 데이터 변화에 맞게 반응할 수 있도록 하는 프로그래밍 패러다임입니다.</li>
</ul>
<h2 id="rxswift의-장단점을-설명하시오">RxSwift의 장/단점을 설명하시오.</h2>
<p>장점 </p>
<ul>
<li>반응형 패러다임이 제공하는 명확함과 비동기처리를 동기화 코드처럼 작성이 가능하다는점입니다.</li>
<li>RxSwift를 사용하지 않을 경우, 콜백 지옥, 즉 escaping closure를 통한 completion 핸들러를 사용하여 비동기 작업을 수행해야 했으며, 이로 인해 쓰레드 관리가 복잡해졌습니다.</li>
</ul>
<p>단점</p>
<ul>
<li>클로저의 캡쳐리스트를 신경을 써야합니다.</li>
<li>RxSwift는 클로저의 사용이 많습니다 그렇기에 메모리 누수를 일으키는 부분과 순환참조를 피할 수 있게 신경을 써야합니다. 그렇지 않으면 Race Condition 같은 것이 발생할 수 있습니다.</li>
</ul>
<h2 id="subject의-종류와-차이점에-대해-설명하시오">Subject의 종류와 차이점에 대해 설명하시오.</h2>
<p>Subject에는 PublishSubject, BehaviorSubject, ReplaySubject, AsyncSubject가 있습니다.</p>
<p>이 4개의 서브젝트의 가장 큰 차이점은 구독한 시점에서 데이터를 어떤것을 받냐에 따라서 갈린다고 생각합니다.</p>
<ul>
<li>PublishSubject는 구독을 하면 그시점의 데이터를 바로 보내준는 기능이입니다.</li>
<li>BehaviorSubject은 PublishSubject와 기능은같지만 초기값을 가집니다. 그렇기에 초반에 값이 없을 때 값을 내려보내지 못하는 PublishSubject와는 다르게 BehaviorSubject은 초기값을 설정하기에 구독자가 값을 가진상태로 시작합니다.</li>
<li>초기값은 항상 갱신이 됩니다. 예를 들어 초기값이 1이지만 Subject특성상 값을 받는 것도 가능하기에 그 받은 값이 초기값이 됩니다.</li>
<li>ReplaySubject의 처음 기능은 PublishedSubject와 기능이 비슷하나 누군가가 구독을 하면 구독시점 전의 데이터도 내려보내줍니다.</li>
<li>AsyncSubject은 누군가가 구독을해도 마지막 OnComplete가 되기전까지는 보내지 않고 OnComplete가 되면 그때 마지막 데이터를 보내줍니다.</li>
</ul>
<h2 id="bind-subscribe-driver-차이">bind, subscribe, driver 차이</h2>
<ul>
<li>subscribe와 bind + driver의 차이는 subscribe와 다르게 나머지 두개는 UI작업에 특화되어있습니다.</li>
<li>bind는 UI 작업에 특화되어있고, .observe(on: MainScheduler.instance)를 통하여 항상 UI쓰레드인 메인에서 작동이 되어야합니다. 그리고 순환참조를 막기위한 작업을 하지 않아도 됩니다.</li>
<li>bind와는 다르게 observe(on: MainScheduler.instance)을 통하여 메인쓰레드에서 돌아가도록 안해줘도 되는 슈거입니다. 드라이버의 에러처리는 .asDriver(onErrorJustReturn: &quot;&quot;)이것을 통하여 합니다.</li>
</ul>
<h2 id="throttle-debounce-차이">throttle, debounce 차이</h2>
<ul>
<li>이 두개는 프로그래밍에서 요청이나 처리의 빈도를 제한하거나 지연시키고자 사용하는 오퍼레이터입니다.</li>
<li>두개의 차이점은 debounce는 일정 시간을 기다리고 요청을 수행하고, 일정 시간 안에 같은 요청이 들어오면 이전 요청은 취소됩니다.</li>
<li>throttle은 시간을 정해두고 요청이 한 번만 수행되도록 합니다.</li>
</ul>
<h2 id="hot-observable-vs-cold-observable"><strong>Hot Observable VS Cold Observable</strong></h2>
<ul>
<li>일반적인 Cold Observable은 일반적인 Observable입니다.</li>
<li>구독한 시점부터 생성된 비동기적인 이벤트를 준다는 특징이있습니다.</li>
<li>Hot은 구독하기 전부터 생성된 비동기적인 이벤트 즉 Emit을 전달해주는 특징이 있습니다.</li>
<li>Hot은 하나의 API를 가져온다 생각하면 공유를 하여 메모리적으로 여유를 가지게 하는 특성이고, Cold는 Observer마다 별도의 인스턴스를 가지게 됩니다.</li>
</ul>
<h2 id="disposable-disposebag은-무엇이며-사용하는이유">Disposable, DisposeBag은 무엇이며 사용하는이유?</h2>
<ul>
<li>Disposable은 Observer가 Observable에 대한 구독을 취소할 수 있게 하는것입니다.</li>
<li>Disposable을 하는 시점이후는 Observable이 내려주는 데이터를 받을 수 없게 됩니다.</li>
<li>이것을 사용하는 이유는 메모리 관리를 위해서 사용합니다.</li>
<li>Observable은 기본적으로 .complete 또는 .error가 발생하기 전까지 이벤트를 계속 방출시켜줘서 직접 deinit을 해줘야합니다. 소멸자를 하지 않으면 필요할 때마다 이벤트를 방출시키는 메모리 릭으로 이어집니다. dispose가 Observable에 대한 리소스를 deinit을 해준다는것입니다.</li>
<li>DisposeBag은 여러개의 구독을 관리 <strong>Disposable를 할 수 있는 담는 배열입니다.</strong></li>
<li>disposed(by:)로 DisposeBag에 등록가능합니다.</li>
<li>메모리에서 해제될 경우 모든 Disposable들을 dispose 시킵니다.</li>
</ul>
<h2 id="observable--subject--relay-차이">Observable / Subject / Relay 차이</h2>
<ul>
<li>Observable 은 비동기 프로그래밍을 관찰 가능한 순서로 비동기 이벤트의 시퀀스를 생성할 수 있는 대상입니다.</li>
<li>Subject는 Observable 처럼 새로운 아이템들을 emit할 수 있고 Observer처럼 하나의 Observable을 구독할 수 있습니다.</li>
<li>Relay는 Subject와 다르게 next이벤트만 받습니다. 그렇기에 주로 종료없이 지속되는 UI 이벤트를 처리하는데 사용합니다.</li>
</ul>
<h2 id="relay--driver--signal의-예시">Relay / Driver / Signal의 예시</h2>
<h3 id="relay">Relay?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>relay는 Subject와 달리 Next 이벤트만 받고 completed와 error는 주고 받지 않는다.</li>
<li>Subject는 completed와 error는 주고 받지 않습니다. 그렇기에 주로 UI이벤트를 처리하는데 사용합니다.</li>
<li>Relay 이벤트를 전달하기 위해서는 .accept를 사용해야한다.</li>
</ul>
<h3 id="driver">Driver?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>rxCocoa에서 Observable을 바인딩을 하려면 .bind를 사용해야하고, 추가로 어느 쓰레드에서 돌아갈지 정하는 observeOn를 같이 작성해줘야합니다. 그리고 error 가 발생하면 Stream이 끊어져버리고 한번 끊어지면 재사용이 불가능하기에 Error가 발생하면 그대로 종료되는것을 방지하기 위한 catchErrorJustReturn을 사용해야합니다. 이렇게 많은 메서드들을 사용하기 귀찮아서 나온 슈거가 Driver입니다. 사용방법은 .asDriver를 사용하고 bind대신 .driver를 사용하면 됩니다.</li>
</ul>
<h3 id="signal">Signal?</h3>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
<li>시그널이란 rx코코아에서 ui작업 을 위해 사용되고 기본적으로 ui작업이기에 메인 스레드</li>
<li>드라이버와 달리 초기값이나 최신값을 주지않음서브젝트에서 퍼블리쉬 서브젝트마냥</li>
</ul>
<h2 id="rx는-무엇일까요-">.rx는 무엇일까요 ?</h2>
<ul>
<li>뷰에 상태를 Observable로 래핑하는데 사용하는 표기입니다.</li>
</ul>
<h3 id="2회차">2회차</h3>
<hr>
<h2 id="observable의-생명주기에-대해-설명해주세요">Observable의 생명주기에 대해 설명해주세요.</h2>
<p>observable은 생성되면 Next 이벤트를 방출하고 .completed 또는 .error를 방출하여 처리하고 나서 dispose로 메모리에서 해제시킵니다. </p>
<h2 id="rxswift에서의-error-handling-방법은-어떻게-되나요">RxSwift에서의 Error Handling 방법은 어떻게 되나요?</h2>
<ul>
<li>Catch: 기본값 defaultValue로 error 복구할 수 있습니다<ul>
<li>catchAndReturn를 토앟여 어떤 에러가 있을 경우 리턴값을 정해서 리턴 가능합니다.</li>
</ul>
</li>
<li>retry: 작업이 성공할 때 까지 계속 재시도를 합니다.</li>
<li><code>retry(_ maxAttemptCount: Int)</code> maxCount를 지정하여 최대 재시도 횟수를 지정할 수 있습니다.<ul>
<li><strong>RetryWhen: 에러가 나타나면 error를 가공하여 observerbleType을 반환합니다.</strong></li>
<li>.retryWhen { err → obserevable<Int> in return 이런식으로 사용</li>
</ul>
</li>
</ul>
<h2 id="rxcocoa의-ui요소와의-상호작용-방법에-대해-설명해주세요">RxCocoa의 UI요소와의 상호작용 방법에 대해 설명해주세요.</h2>
<ul>
<li>?</li>
</ul>
<h2 id="rxswift에서의-testing-방법에-대해-설명해주세요">RxSwift에서의 Testing 방법에 대해 설명해주세요.</h2>
<ul>
<li>RxSwift는 TestScheduler 정확한 시간 간격으로 이벤트를 추가할 수 있는 메서드 기능을 제공합니다.</li>
<li>RxTset를 사용해야하고, RxSwift 저장소의 한 부분을 담당합니다.</li>
<li>RxTest는 테스팅 목적으로 두가지 타입의 Observables를 드러낼 수 있습니다.<ul>
<li>Cold와 Hot이 있습니다.</li>
<li>Cold는 일반적인 Observable와 같이 동작하며 구독 시 ㅅ개로운 구독자들에게 그들의 요소를 재생해줍니다.</li>
<li>Hot은 어떤 가입자가 있는지 여부에 상관없이 테스트 스케쥴러를 사용하여 지정된 시간에 이벤트를 재생합니다.</li>
</ul>
</li>
<li>RxBlocking이 비동기적 작업을 테스트할 수 있는 또 다른 하나의 방법이고, 비동기 작업의 테스팅 간 해당 라이브러리를 사용하는 방법입니다.</li>
</ul>
<hr>
<h2 id="observeon-과-subscribeon의-차이는-무엇인가요">observeOn 과 subscribeOn의 차이는 무엇인가요</h2>
<ul>
<li>observeOn은 이벤트를 받을 때 처리하는  스케줄러를 변경하고 subscribeOn은 Observable이 생성 또는 처음 이벤트가 발생되었을 때 스케줄러를 변경합니다.</li>
</ul>
<h2 id="flatmap-flatmapfirst-flatmaplast의-차이점은-무엇인가요">flatMap, flatMapFirst, flatMapLast의 차이점은 무엇인가요</h2>
<ul>
<li>flatMap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>flatMapFirst: 첫 번째 observable의 이벤트만 받고, 다음 생성된것은 처리하지 않습니다.</li>
<li>flatMapLast: 가장 마지막의 이벤트만 처리하고, 전에 생성된 이벤트들은 처리하지 않습니다.</li>
</ul>
<h2 id="merge--concat--zip--withlastestfrom--combinelastest-차이점">merge / concat / zip , withLastestFrom / CombineLastest 차이점</h2>
<ul>
<li>merge: 같은 타입의 이벤트를 발생하는 Observable을 합성하는 함수이며, 각각의 이벤트르 모두 수신할 수 있습니다. 이벤트가 같지 않으면 merge를 할 수 없다.</li>
<li>concat: 순차적으로 여러 Observable을 합칠 때 사용합니다.</li>
<li>zip: 두 Observable의 발생 순서가 같은 이벤트를 조합해서 이벤트를 발생합니다. 그리고 이벤트가 조합되지 않으면 이벤트가 발생하지 않습니다.</li>
<li>CombineLastest: 두 Observable의 각각의 이벤트가 발생할 때 두 개의 Observable의 마지막 이벤트들을 묶어서 전달합니다.</li>
<li>withLastestFrom: 두 개의 Observable을 합성하지만 한쪽 Observable의 이벤트가 발생할 때에 합성해주는 메서드입니다. 다른쪽 이벤트가 없으면 합성하지 않습니다.</li>
</ul>
<h2 id="구독-연산자-share의-간단한-설명">구독 연산자 share의 간단한 설명</h2>
<ul>
<li>여러 subscribe에서 Observable의 구독이 일어나면 구독할 때 마다 새로운 시퀸스가 생성되어 리소스 낭비가 일어나는것을 방지하기 위한 연산자입니다.</li>
</ul>
<hr>
<h2 id="rxswift에서의-back-pressure는-무엇이며-어떻게-처리할-수-있나요">RxSwift에서의 Back Pressure는 무엇이며 어떻게 처리할 수 있나요?</h2>
<ul>
<li>Back Pressure: Observable은 이벤트의 방출 속도와 구독자의 처리 속도가 맞지 않을 때 입니다. buffer throttle debouncing 연산자를 사용하여 분기를 맞춰 처리합니다.</li>
</ul>
<h2 id="signal의-기능을-말해주시고-예시가-있다면-예시도-함께-말해주세욘">Signal의 기능을 말해주시고 예시가 있다면 예시도 함께 말해주세욘</h2>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
</ul>
<h2 id="observeon-scheduler에서-api를-받아올-때-어느-scheduler를-실행시켜야-할까요">Observe(on: Scheduler)에서 api를 받아올 때 어느 Scheduler를 실행시켜야 할까요?</h2>
<ul>
<li>ConcurrentDispatchQueueScheduler에서 처리합니다.</li>
</ul>
<hr>
<h2 id="single-completable-maybe">Single, Completable, Maybe</h2>
<ul>
<li>single: Observable의 변형으로 일련의 요소를 방출하는 대신 항상 단일 요소 또는 오류를 방출하도록 보장하는것입니다.<ul>
<li>정확히 하나의 요소 또는 error를 방출합니다.</li>
<li>single의 사용 예는 응답, 오류만 반환할 수 있는 HTTP 요청을 수행하는데 사용됩니다.</li>
</ul>
</li>
<li>Completetable: complete 하거나 error를 방출할 수 있고 아무 요소도 방출하지 않는것을 보장합니다.<ul>
<li>complete 또는 error만 방출합니다.</li>
<li>completable은 완료에 따른 요소에 신경쓰지 않은 경우 사용합니다.</li>
</ul>
</li>
<li>Maybe: Single과 Completable의 중간인 Observable의 변형입니다.<ul>
<li>완료된 이벤트, 싱글 이벤트 또는 오류를 방출합니다.</li>
<li>Maybe는 Single과 비슷하지만 아무런 값을 방출하지 않고 coimplete가 되어도 오류가 발생하지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="operator-just-of-from의-용도">operator just, of, from의 용도</h2>
<ul>
<li>just: 단 하나의 요소만 방출</li>
<li>of: 여러 요소를 방출합니다.</li>
<li>from: 배열의 형태로 오는 요소들을 한 개씩 순차적으로 내놓습니다.</li>
</ul>
<h2 id="flatmap-compactmap-map의-용도">flatmap, compactmap, map의 용도</h2>
<ul>
<li>map: 옵저버블의 가진 요소들에 특정 연산을 수행해서 값을 방출합니다</li>
<li>flatmap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>compactmap: Observable에서 방출되는 이벤트를 꺼낸 다음 옵셔널 형태로 바꾸고 원하는 변환을 실행합니다. 그리고 최종 변환된 값이 nil이면 방출하지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rx 면접질문 모음 ]]></title>
            <link>https://velog.io/@ljh3904_a/Rx-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-cog4la9x</link>
            <guid>https://velog.io/@ljh3904_a/Rx-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C-cog4la9x</guid>
            <pubDate>Thu, 09 May 2024 17:40:56 GMT</pubDate>
            <description><![CDATA[<h1 id="reactive-programming">Reactive Programming</h1>
<ul>
<li>데이터 스트림과 변화에 반응하는 앱을 만들기 위한 프로그래밍의 패러다임입니다.</li>
<li>데이터의 흐름을 비동기적으로 처리하면서 앱내 이벤트를 실시간으로 데이터 변화에 맞게 반응할 수 있도록 하는 프로그래밍 패러다임입니다.</li>
</ul>
<h2 id="rxswift의-장단점을-설명하시오">RxSwift의 장/단점을 설명하시오.</h2>
<p>장점 </p>
<ul>
<li>반응형 패러다임이 제공하는 명확함과 비동기처리를 동기화 코드처럼 작성이 가능하다는점입니다.</li>
<li>RxSwift를 사용하지 않을 경우, 콜백 지옥, 즉 escaping closure를 통한 completion 핸들러를 사용하여 비동기 작업을 수행해야 했으며, 이로 인해 쓰레드 관리가 복잡해졌습니다.</li>
</ul>
<p>단점</p>
<ul>
<li>클로저의 캡쳐리스트를 신경을 써야합니다.</li>
<li>RxSwift는 클로저의 사용이 많습니다 그렇기에 메모리 누수를 일으키는 부분과 순환참조를 피할 수 있게 신경을 써야합니다. 그렇지 않으면 Race Condition 같은 것이 발생할 수 있습니다.</li>
</ul>
<h2 id="subject의-종류와-차이점에-대해-설명하시오">Subject의 종류와 차이점에 대해 설명하시오.</h2>
<p>Subject에는 PublishSubject, BehaviorSubject, ReplaySubject, AsyncSubject가 있습니다.</p>
<p>이 4개의 서브젝트의 가장 큰 차이점은 구독한 시점에서 데이터를 어떤것을 받냐에 따라서 갈린다고 생각합니다.</p>
<ul>
<li>PublishSubject는 구독을 하면 그시점의 데이터를 바로 보내준는 기능이입니다.</li>
<li>BehaviorSubject은 PublishSubject와 기능은같지만 초기값을 가집니다. 그렇기에 초반에 값이 없을 때 값을 내려보내지 못하는 PublishSubject와는 다르게 BehaviorSubject은 초기값을 설정하기에 구독자가 값을 가진상태로 시작합니다.</li>
<li>초기값은 항상 갱신이 됩니다. 예를 들어 초기값이 1이지만 Subject특성상 값을 받는 것도 가능하기에 그 받은 값이 초기값이 됩니다.</li>
<li>ReplaySubject의 처음 기능은 PublishedSubject와 기능이 비슷하나 누군가가 구독을 하면 구독시점 전의 데이터도 내려보내줍니다.</li>
<li>AsyncSubject은 누군가가 구독을해도 마지막 OnComplete가 되기전까지는 보내지 않고 OnComplete가 되면 그때 마지막 데이터를 보내줍니다.</li>
</ul>
<h2 id="bind-subscribe-driver-차이">bind, subscribe, driver 차이</h2>
<ul>
<li>subscribe와 bind + driver의 차이는 subscribe와 다르게 나머지 두개는 UI작업에 특화되어있습니다.</li>
<li>bind는 UI 작업에 특화되어있고, .observe(on: MainScheduler.instance)를 통하여 항상 UI쓰레드인 메인에서 작동이 되어야합니다. 그리고 순환참조를 막기위한 작업을 하지 않아도 됩니다.</li>
<li>bind와는 다르게 observe(on: MainScheduler.instance)을 통하여 메인쓰레드에서 돌아가도록 안해줘도 되는 슈거입니다. 드라이버의 에러처리는 .asDriver(onErrorJustReturn: &quot;&quot;)이것을 통하여 합니다.</li>
</ul>
<h2 id="throttle-debounce-차이">throttle, debounce 차이</h2>
<ul>
<li>이 두개는 프로그래밍에서 요청이나 처리의 빈도를 제한하거나 지연시키고자 사용하는 오퍼레이터입니다.</li>
<li>두개의 차이점은 debounce는 일정 시간을 기다리고 요청을 수행하고, 일정 시간 안에 같은 요청이 들어오면 이전 요청은 취소됩니다.</li>
<li>throttle은 시간을 정해두고 요청이 한 번만 수행되도록 합니다.</li>
</ul>
<h2 id="hot-observable-vs-cold-observable"><strong>Hot Observable VS Cold Observable</strong></h2>
<ul>
<li>일반적인 Cold Observable은 일반적인 Observable입니다.</li>
<li>구독한 시점부터 생성된 비동기적인 이벤트를 준다는 특징이있습니다.</li>
<li>Hot은 구독하기 전부터 생성된 비동기적인 이벤트 즉 Emit을 전달해주는 특징이 있습니다.</li>
<li>Hot은 하나의 API를 가져온다 생각하면 공유를 하여 메모리적으로 여유를 가지게 하는 특성이고, Cold는 Observer마다 별도의 인스턴스를 가지게 됩니다.</li>
</ul>
<h2 id="disposable-disposebag은-무엇이며-사용하는이유">Disposable, DisposeBag은 무엇이며 사용하는이유?</h2>
<ul>
<li>Disposable은 Observer가 Observable에 대한 구독을 취소할 수 있게 하는것입니다.</li>
<li>Disposable을 하는 시점이후는 Observable이 내려주는 데이터를 받을 수 없게 됩니다.</li>
<li>이것을 사용하는 이유는 메모리 관리를 위해서 사용합니다.</li>
<li>Observable은 기본적으로 .complete 또는 .error가 발생하기 전까지 이벤트를 계속 방출시켜줘서 직접 deinit을 해줘야합니다. 소멸자를 하지 않으면 필요할 때마다 이벤트를 방출시키는 메모리 릭으로 이어집니다. dispose가 Observable에 대한 리소스를 deinit을 해준다는것입니다.</li>
<li>DisposeBag은 여러개의 구독을 관리 <strong>Disposable를 할 수 있는 담는 배열입니다.</strong></li>
<li>disposed(by:)로 DisposeBag에 등록가능합니다.</li>
<li>메모리에서 해제될 경우 모든 Disposable들을 dispose 시킵니다.</li>
</ul>
<h2 id="observable--subject--relay-차이">Observable / Subject / Relay 차이</h2>
<ul>
<li>Observable 은 비동기 프로그래밍을 관찰 가능한 순서로 비동기 이벤트의 시퀀스를 생성할 수 있는 대상입니다.</li>
<li>Subject는 Observable 처럼 새로운 아이템들을 emit할 수 있고 Observer처럼 하나의 Observable을 구독할 수 있습니다.</li>
<li>Relay는 Subject와 다르게 next이벤트만 받습니다. 그렇기에 주로 종료없이 지속되는 UI 이벤트를 처리하는데 사용합니다.</li>
</ul>
<h2 id="relay--driver--signal의-예시">Relay / Driver / Signal의 예시</h2>
<h3 id="relay">Relay?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>relay는 Subject와 달리 Next 이벤트만 받고 completed와 error는 주고 받지 않는다.</li>
<li>Subject는 completed와 error는 주고 받지 않습니다. 그렇기에 주로 UI이벤트를 처리하는데 사용합니다.</li>
<li>Relay 이벤트를 전달하기 위해서는 .accept를 사용해야한다.</li>
</ul>
<h3 id="driver">Driver?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>rxCocoa에서 Observable을 바인딩을 하려면 .bind를 사용해야하고, 추가로 어느 쓰레드에서 돌아갈지 정하는 observeOn를 같이 작성해줘야합니다. 그리고 error 가 발생하면 Stream이 끊어져버리고 한번 끊어지면 재사용이 불가능하기에 Error가 발생하면 그대로 종료되는것을 방지하기 위한 catchErrorJustReturn을 사용해야합니다. 이렇게 많은 메서드들을 사용하기 귀찮아서 나온 슈거가 Driver입니다. 사용방법은 .asDriver를 사용하고 bind대신 .driver를 사용하면 됩니다.</li>
</ul>
<h3 id="signal">Signal?</h3>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
<li>시그널이란 rx코코아에서 ui작업 을 위해 사용되고 기본적으로 ui작업이기에 메인 스레드</li>
<li>드라이버와 달리 초기값이나 최신값을 주지않음서브젝트에서 퍼블리쉬 서브젝트마냥</li>
</ul>
<h2 id="rx는-무엇일까요-">.rx는 무엇일까요 ?</h2>
<ul>
<li>뷰에 상태를 Observable로 래핑하는데 사용하는 표기입니다.</li>
</ul>
<h3 id="2회차">2회차</h3>
<hr>
<h2 id="observable의-생명주기에-대해-설명해주세요">Observable의 생명주기에 대해 설명해주세요.</h2>
<p>observable은 생성되면 Next 이벤트를 방출하고 .completed 또는 .error를 방출하여 처리하고 나서 dispose로 메모리에서 해제시킵니다. </p>
<h2 id="rxswift에서의-error-handling-방법은-어떻게-되나요">RxSwift에서의 Error Handling 방법은 어떻게 되나요?</h2>
<ul>
<li>Catch: 기본값 defaultValue로 error 복구할 수 있습니다<ul>
<li>catchAndReturn를 토앟여 어떤 에러가 있을 경우 리턴값을 정해서 리턴 가능합니다.</li>
</ul>
</li>
<li>retry: 작업이 성공할 때 까지 계속 재시도를 합니다.</li>
<li><code>retry(_ maxAttemptCount: Int)</code> maxCount를 지정하여 최대 재시도 횟수를 지정할 수 있습니다.<ul>
<li><strong>RetryWhen: 에러가 나타나면 error를 가공하여 observerbleType을 반환합니다.</strong></li>
<li>.retryWhen { err → obserevable<Int> in return 이런식으로 사용</li>
</ul>
</li>
</ul>
<h2 id="rxcocoa의-ui요소와의-상호작용-방법에-대해-설명해주세요">RxCocoa의 UI요소와의 상호작용 방법에 대해 설명해주세요.</h2>
<ul>
<li>?</li>
</ul>
<h2 id="rxswift에서의-testing-방법에-대해-설명해주세요">RxSwift에서의 Testing 방법에 대해 설명해주세요.</h2>
<ul>
<li>RxSwift는 TestScheduler 정확한 시간 간격으로 이벤트를 추가할 수 있는 메서드 기능을 제공합니다.</li>
<li>RxTset를 사용해야하고, RxSwift 저장소의 한 부분을 담당합니다.</li>
<li>RxTest는 테스팅 목적으로 두가지 타입의 Observables를 드러낼 수 있습니다.<ul>
<li>Cold와 Hot이 있습니다.</li>
<li>Cold는 일반적인 Observable와 같이 동작하며 구독 시 ㅅ개로운 구독자들에게 그들의 요소를 재생해줍니다.</li>
<li>Hot은 어떤 가입자가 있는지 여부에 상관없이 테스트 스케쥴러를 사용하여 지정된 시간에 이벤트를 재생합니다.</li>
</ul>
</li>
<li>RxBlocking이 비동기적 작업을 테스트할 수 있는 또 다른 하나의 방법이고, 비동기 작업의 테스팅 간 해당 라이브러리를 사용하는 방법입니다.</li>
</ul>
<hr>
<h2 id="observeon-과-subscribeon의-차이는-무엇인가요">observeOn 과 subscribeOn의 차이는 무엇인가요</h2>
<ul>
<li>observeOn은 이벤트를 받을 때 처리하는  스케줄러를 변경하고 subscribeOn은 Observable이 생성 또는 처음 이벤트가 발생되었을 때 스케줄러를 변경합니다.</li>
</ul>
<h2 id="flatmap-flatmapfirst-flatmaplast의-차이점은-무엇인가요">flatMap, flatMapFirst, flatMapLast의 차이점은 무엇인가요</h2>
<ul>
<li>flatMap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>flatMapFirst: 첫 번째 observable의 이벤트만 받고, 다음 생성된것은 처리하지 않습니다.</li>
<li>flatMapLast: 가장 마지막의 이벤트만 처리하고, 전에 생성된 이벤트들은 처리하지 않습니다.</li>
</ul>
<h2 id="merge--concat--zip--withlastestfrom--combinelastest-차이점">merge / concat / zip , withLastestFrom / CombineLastest 차이점</h2>
<ul>
<li>merge: 같은 타입의 이벤트를 발생하는 Observable을 합성하는 함수이며, 각각의 이벤트르 모두 수신할 수 있습니다. 이벤트가 같지 않으면 merge를 할 수 없다.</li>
<li>concat: 순차적으로 여러 Observable을 합칠 때 사용합니다.</li>
<li>zip: 두 Observable의 발생 순서가 같은 이벤트를 조합해서 이벤트를 발생합니다. 그리고 이벤트가 조합되지 않으면 이벤트가 발생하지 않습니다.</li>
<li>CombineLastest: 두 Observable의 각각의 이벤트가 발생할 때 두 개의 Observable의 마지막 이벤트들을 묶어서 전달합니다.</li>
<li>withLastestFrom: 두 개의 Observable을 합성하지만 한쪽 Observable의 이벤트가 발생할 때에 합성해주는 메서드입니다. 다른쪽 이벤트가 없으면 합성하지 않습니다.</li>
</ul>
<h2 id="구독-연산자-share의-간단한-설명">구독 연산자 share의 간단한 설명</h2>
<ul>
<li>여러 subscribe에서 Observable의 구독이 일어나면 구독할 때 마다 새로운 시퀸스가 생성되어 리소스 낭비가 일어나는것을 방지하기 위한 연산자입니다.</li>
</ul>
<hr>
<h2 id="rxswift에서의-back-pressure는-무엇이며-어떻게-처리할-수-있나요">RxSwift에서의 Back Pressure는 무엇이며 어떻게 처리할 수 있나요?</h2>
<ul>
<li>Back Pressure: Observable은 이벤트의 방출 속도와 구독자의 처리 속도가 맞지 않을 때 입니다. buffer throttle debouncing 연산자를 사용하여 분기를 맞춰 처리합니다.</li>
</ul>
<h2 id="signal의-기능을-말해주시고-예시가-있다면-예시도-함께-말해주세욘">Signal의 기능을 말해주시고 예시가 있다면 예시도 함께 말해주세욘</h2>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
</ul>
<h2 id="observeon-scheduler에서-api를-받아올-때-어느-scheduler를-실행시켜야-할까요">Observe(on: Scheduler)에서 api를 받아올 때 어느 Scheduler를 실행시켜야 할까요?</h2>
<ul>
<li>ConcurrentDispatchQueueScheduler에서 처리합니다.</li>
</ul>
<hr>
<h2 id="single-completable-maybe">Single, Completable, Maybe</h2>
<ul>
<li>single: Observable의 변형으로 일련의 요소를 방출하는 대신 항상 단일 요소 또는 오류를 방출하도록 보장하는것입니다.<ul>
<li>정확히 하나의 요소 또는 error를 방출합니다.</li>
<li>single의 사용 예는 응답, 오류만 반환할 수 있는 HTTP 요청을 수행하는데 사용됩니다.</li>
</ul>
</li>
<li>Completetable: complete 하거나 error를 방출할 수 있고 아무 요소도 방출하지 않는것을 보장합니다.<ul>
<li>complete 또는 error만 방출합니다.</li>
<li>completable은 완료에 따른 요소에 신경쓰지 않은 경우 사용합니다.</li>
</ul>
</li>
<li>Maybe: Single과 Completable의 중간인 Observable의 변형입니다.<ul>
<li>완료된 이벤트, 싱글 이벤트 또는 오류를 방출합니다.</li>
<li>Maybe는 Single과 비슷하지만 아무런 값을 방출하지 않고 coimplete가 되어도 오류가 발생하지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="operator-just-of-from의-용도">operator just, of, from의 용도</h2>
<ul>
<li>just: 단 하나의 요소만 방출</li>
<li>of: 여러 요소를 방출합니다.</li>
<li>from: 배열의 형태로 오는 요소들을 한 개씩 순차적으로 내놓습니다.</li>
</ul>
<h2 id="flatmap-compactmap-map의-용도">flatmap, compactmap, map의 용도</h2>
<ul>
<li>map: 옵저버블의 가진 요소들에 특정 연산을 수행해서 값을 방출합니다</li>
<li>flatmap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>compactmap: Observable에서 방출되는 이벤트를 꺼낸 다음 옵셔널 형태로 바꾸고 원하는 변환을 실행합니다. 그리고 최종 변환된 값이 nil이면 방출하지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rx 면접질문 모음 ]]></title>
            <link>https://velog.io/@ljh3904_a/Rx-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@ljh3904_a/Rx-%EB%A9%B4%EC%A0%91%EC%A7%88%EB%AC%B8-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Thu, 09 May 2024 17:40:53 GMT</pubDate>
            <description><![CDATA[<h1 id="reactive-programming">Reactive Programming</h1>
<ul>
<li>데이터 스트림과 변화에 반응하는 앱을 만들기 위한 프로그래밍의 패러다임입니다.</li>
<li>데이터의 흐름을 비동기적으로 처리하면서 앱내 이벤트를 실시간으로 데이터 변화에 맞게 반응할 수 있도록 하는 프로그래밍 패러다임입니다.</li>
</ul>
<h2 id="rxswift의-장단점을-설명하시오">RxSwift의 장/단점을 설명하시오.</h2>
<p>장점 </p>
<ul>
<li>반응형 패러다임이 제공하는 명확함과 비동기처리를 동기화 코드처럼 작성이 가능하다는점입니다.</li>
<li>RxSwift를 사용하지 않을 경우, 콜백 지옥, 즉 escaping closure를 통한 completion 핸들러를 사용하여 비동기 작업을 수행해야 했으며, 이로 인해 쓰레드 관리가 복잡해졌습니다.</li>
</ul>
<p>단점</p>
<ul>
<li>클로저의 캡쳐리스트를 신경을 써야합니다.</li>
<li>RxSwift는 클로저의 사용이 많습니다 그렇기에 메모리 누수를 일으키는 부분과 순환참조를 피할 수 있게 신경을 써야합니다. 그렇지 않으면 Race Condition 같은 것이 발생할 수 있습니다.</li>
</ul>
<h2 id="subject의-종류와-차이점에-대해-설명하시오">Subject의 종류와 차이점에 대해 설명하시오.</h2>
<p>Subject에는 PublishSubject, BehaviorSubject, ReplaySubject, AsyncSubject가 있습니다.</p>
<p>이 4개의 서브젝트의 가장 큰 차이점은 구독한 시점에서 데이터를 어떤것을 받냐에 따라서 갈린다고 생각합니다.</p>
<ul>
<li>PublishSubject는 구독을 하면 그시점의 데이터를 바로 보내준는 기능이입니다.</li>
<li>BehaviorSubject은 PublishSubject와 기능은같지만 초기값을 가집니다. 그렇기에 초반에 값이 없을 때 값을 내려보내지 못하는 PublishSubject와는 다르게 BehaviorSubject은 초기값을 설정하기에 구독자가 값을 가진상태로 시작합니다.</li>
<li>초기값은 항상 갱신이 됩니다. 예를 들어 초기값이 1이지만 Subject특성상 값을 받는 것도 가능하기에 그 받은 값이 초기값이 됩니다.</li>
<li>ReplaySubject의 처음 기능은 PublishedSubject와 기능이 비슷하나 누군가가 구독을 하면 구독시점 전의 데이터도 내려보내줍니다.</li>
<li>AsyncSubject은 누군가가 구독을해도 마지막 OnComplete가 되기전까지는 보내지 않고 OnComplete가 되면 그때 마지막 데이터를 보내줍니다.</li>
</ul>
<h2 id="bind-subscribe-driver-차이">bind, subscribe, driver 차이</h2>
<ul>
<li>subscribe와 bind + driver의 차이는 subscribe와 다르게 나머지 두개는 UI작업에 특화되어있습니다.</li>
<li>bind는 UI 작업에 특화되어있고, .observe(on: MainScheduler.instance)를 통하여 항상 UI쓰레드인 메인에서 작동이 되어야합니다. 그리고 순환참조를 막기위한 작업을 하지 않아도 됩니다.</li>
<li>bind와는 다르게 observe(on: MainScheduler.instance)을 통하여 메인쓰레드에서 돌아가도록 안해줘도 되는 슈거입니다. 드라이버의 에러처리는 .asDriver(onErrorJustReturn: &quot;&quot;)이것을 통하여 합니다.</li>
</ul>
<h2 id="throttle-debounce-차이">throttle, debounce 차이</h2>
<ul>
<li>이 두개는 프로그래밍에서 요청이나 처리의 빈도를 제한하거나 지연시키고자 사용하는 오퍼레이터입니다.</li>
<li>두개의 차이점은 debounce는 일정 시간을 기다리고 요청을 수행하고, 일정 시간 안에 같은 요청이 들어오면 이전 요청은 취소됩니다.</li>
<li>throttle은 시간을 정해두고 요청이 한 번만 수행되도록 합니다.</li>
</ul>
<h2 id="hot-observable-vs-cold-observable"><strong>Hot Observable VS Cold Observable</strong></h2>
<ul>
<li>일반적인 Cold Observable은 일반적인 Observable입니다.</li>
<li>구독한 시점부터 생성된 비동기적인 이벤트를 준다는 특징이있습니다.</li>
<li>Hot은 구독하기 전부터 생성된 비동기적인 이벤트 즉 Emit을 전달해주는 특징이 있습니다.</li>
<li>Hot은 하나의 API를 가져온다 생각하면 공유를 하여 메모리적으로 여유를 가지게 하는 특성이고, Cold는 Observer마다 별도의 인스턴스를 가지게 됩니다.</li>
</ul>
<h2 id="disposable-disposebag은-무엇이며-사용하는이유">Disposable, DisposeBag은 무엇이며 사용하는이유?</h2>
<ul>
<li>Disposable은 Observer가 Observable에 대한 구독을 취소할 수 있게 하는것입니다.</li>
<li>Disposable을 하는 시점이후는 Observable이 내려주는 데이터를 받을 수 없게 됩니다.</li>
<li>이것을 사용하는 이유는 메모리 관리를 위해서 사용합니다.</li>
<li>Observable은 기본적으로 .complete 또는 .error가 발생하기 전까지 이벤트를 계속 방출시켜줘서 직접 deinit을 해줘야합니다. 소멸자를 하지 않으면 필요할 때마다 이벤트를 방출시키는 메모리 릭으로 이어집니다. dispose가 Observable에 대한 리소스를 deinit을 해준다는것입니다.</li>
<li>DisposeBag은 여러개의 구독을 관리 <strong>Disposable를 할 수 있는 담는 배열입니다.</strong></li>
<li>disposed(by:)로 DisposeBag에 등록가능합니다.</li>
<li>메모리에서 해제될 경우 모든 Disposable들을 dispose 시킵니다.</li>
</ul>
<h2 id="observable--subject--relay-차이">Observable / Subject / Relay 차이</h2>
<ul>
<li>Observable 은 비동기 프로그래밍을 관찰 가능한 순서로 비동기 이벤트의 시퀀스를 생성할 수 있는 대상입니다.</li>
<li>Subject는 Observable 처럼 새로운 아이템들을 emit할 수 있고 Observer처럼 하나의 Observable을 구독할 수 있습니다.</li>
<li>Relay는 Subject와 다르게 next이벤트만 받습니다. 그렇기에 주로 종료없이 지속되는 UI 이벤트를 처리하는데 사용합니다.</li>
</ul>
<h2 id="relay--driver--signal의-예시">Relay / Driver / Signal의 예시</h2>
<h3 id="relay">Relay?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>relay는 Subject와 달리 Next 이벤트만 받고 completed와 error는 주고 받지 않는다.</li>
<li>Subject는 completed와 error는 주고 받지 않습니다. 그렇기에 주로 UI이벤트를 처리하는데 사용합니다.</li>
<li>Relay 이벤트를 전달하기 위해서는 .accept를 사용해야한다.</li>
</ul>
<h3 id="driver">Driver?</h3>
<ul>
<li>RxCocoa를 import해야 사용이 가능합니다.</li>
<li>rxCocoa에서 Observable을 바인딩을 하려면 .bind를 사용해야하고, 추가로 어느 쓰레드에서 돌아갈지 정하는 observeOn를 같이 작성해줘야합니다. 그리고 error 가 발생하면 Stream이 끊어져버리고 한번 끊어지면 재사용이 불가능하기에 Error가 발생하면 그대로 종료되는것을 방지하기 위한 catchErrorJustReturn을 사용해야합니다. 이렇게 많은 메서드들을 사용하기 귀찮아서 나온 슈거가 Driver입니다. 사용방법은 .asDriver를 사용하고 bind대신 .driver를 사용하면 됩니다.</li>
</ul>
<h3 id="signal">Signal?</h3>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
<li>시그널이란 rx코코아에서 ui작업 을 위해 사용되고 기본적으로 ui작업이기에 메인 스레드</li>
<li>드라이버와 달리 초기값이나 최신값을 주지않음서브젝트에서 퍼블리쉬 서브젝트마냥</li>
</ul>
<h2 id="rx는-무엇일까요-">.rx는 무엇일까요 ?</h2>
<ul>
<li>뷰에 상태를 Observable로 래핑하는데 사용하는 표기입니다.</li>
</ul>
<h3 id="2회차">2회차</h3>
<hr>
<h2 id="observable의-생명주기에-대해-설명해주세요">Observable의 생명주기에 대해 설명해주세요.</h2>
<p>observable은 생성되면 Next 이벤트를 방출하고 .completed 또는 .error를 방출하여 처리하고 나서 dispose로 메모리에서 해제시킵니다. </p>
<h2 id="rxswift에서의-error-handling-방법은-어떻게-되나요">RxSwift에서의 Error Handling 방법은 어떻게 되나요?</h2>
<ul>
<li>Catch: 기본값 defaultValue로 error 복구할 수 있습니다<ul>
<li>catchAndReturn를 토앟여 어떤 에러가 있을 경우 리턴값을 정해서 리턴 가능합니다.</li>
</ul>
</li>
<li>retry: 작업이 성공할 때 까지 계속 재시도를 합니다.</li>
<li><code>retry(_ maxAttemptCount: Int)</code> maxCount를 지정하여 최대 재시도 횟수를 지정할 수 있습니다.<ul>
<li><strong>RetryWhen: 에러가 나타나면 error를 가공하여 observerbleType을 반환합니다.</strong></li>
<li>.retryWhen { err → obserevable<Int> in return 이런식으로 사용</li>
</ul>
</li>
</ul>
<h2 id="rxcocoa의-ui요소와의-상호작용-방법에-대해-설명해주세요">RxCocoa의 UI요소와의 상호작용 방법에 대해 설명해주세요.</h2>
<ul>
<li>?</li>
</ul>
<h2 id="rxswift에서의-testing-방법에-대해-설명해주세요">RxSwift에서의 Testing 방법에 대해 설명해주세요.</h2>
<ul>
<li>RxSwift는 TestScheduler 정확한 시간 간격으로 이벤트를 추가할 수 있는 메서드 기능을 제공합니다.</li>
<li>RxTset를 사용해야하고, RxSwift 저장소의 한 부분을 담당합니다.</li>
<li>RxTest는 테스팅 목적으로 두가지 타입의 Observables를 드러낼 수 있습니다.<ul>
<li>Cold와 Hot이 있습니다.</li>
<li>Cold는 일반적인 Observable와 같이 동작하며 구독 시 ㅅ개로운 구독자들에게 그들의 요소를 재생해줍니다.</li>
<li>Hot은 어떤 가입자가 있는지 여부에 상관없이 테스트 스케쥴러를 사용하여 지정된 시간에 이벤트를 재생합니다.</li>
</ul>
</li>
<li>RxBlocking이 비동기적 작업을 테스트할 수 있는 또 다른 하나의 방법이고, 비동기 작업의 테스팅 간 해당 라이브러리를 사용하는 방법입니다.</li>
</ul>
<hr>
<h2 id="observeon-과-subscribeon의-차이는-무엇인가요">observeOn 과 subscribeOn의 차이는 무엇인가요</h2>
<ul>
<li>observeOn은 이벤트를 받을 때 처리하는  스케줄러를 변경하고 subscribeOn은 Observable이 생성 또는 처음 이벤트가 발생되었을 때 스케줄러를 변경합니다.</li>
</ul>
<h2 id="flatmap-flatmapfirst-flatmaplast의-차이점은-무엇인가요">flatMap, flatMapFirst, flatMapLast의 차이점은 무엇인가요</h2>
<ul>
<li>flatMap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>flatMapFirst: 첫 번째 observable의 이벤트만 받고, 다음 생성된것은 처리하지 않습니다.</li>
<li>flatMapLast: 가장 마지막의 이벤트만 처리하고, 전에 생성된 이벤트들은 처리하지 않습니다.</li>
</ul>
<h2 id="merge--concat--zip--withlastestfrom--combinelastest-차이점">merge / concat / zip , withLastestFrom / CombineLastest 차이점</h2>
<ul>
<li>merge: 같은 타입의 이벤트를 발생하는 Observable을 합성하는 함수이며, 각각의 이벤트르 모두 수신할 수 있습니다. 이벤트가 같지 않으면 merge를 할 수 없다.</li>
<li>concat: 순차적으로 여러 Observable을 합칠 때 사용합니다.</li>
<li>zip: 두 Observable의 발생 순서가 같은 이벤트를 조합해서 이벤트를 발생합니다. 그리고 이벤트가 조합되지 않으면 이벤트가 발생하지 않습니다.</li>
<li>CombineLastest: 두 Observable의 각각의 이벤트가 발생할 때 두 개의 Observable의 마지막 이벤트들을 묶어서 전달합니다.</li>
<li>withLastestFrom: 두 개의 Observable을 합성하지만 한쪽 Observable의 이벤트가 발생할 때에 합성해주는 메서드입니다. 다른쪽 이벤트가 없으면 합성하지 않습니다.</li>
</ul>
<h2 id="구독-연산자-share의-간단한-설명">구독 연산자 share의 간단한 설명</h2>
<ul>
<li>여러 subscribe에서 Observable의 구독이 일어나면 구독할 때 마다 새로운 시퀸스가 생성되어 리소스 낭비가 일어나는것을 방지하기 위한 연산자입니다.</li>
</ul>
<hr>
<h2 id="rxswift에서의-back-pressure는-무엇이며-어떻게-처리할-수-있나요">RxSwift에서의 Back Pressure는 무엇이며 어떻게 처리할 수 있나요?</h2>
<ul>
<li>Back Pressure: Observable은 이벤트의 방출 속도와 구독자의 처리 속도가 맞지 않을 때 입니다. buffer throttle debouncing 연산자를 사용하여 분기를 맞춰 처리합니다.</li>
</ul>
<h2 id="signal의-기능을-말해주시고-예시가-있다면-예시도-함께-말해주세욘">Signal의 기능을 말해주시고 예시가 있다면 예시도 함께 말해주세욘</h2>
<ul>
<li>signal은 driver처럼 새로운 구독자에게 replay를 해주지 않습니다.</li>
<li>driver처럼 구독하는 순간 초기값이나 최신값을 주지 않습니다. 구독한 이후에 발행되는 값을 받습니다.</li>
<li>signal은 emit함수로 이벤트를 처리하고 driver은 drive함수로 이벤트를 처리합니다.</li>
</ul>
<h2 id="observeon-scheduler에서-api를-받아올-때-어느-scheduler를-실행시켜야-할까요">Observe(on: Scheduler)에서 api를 받아올 때 어느 Scheduler를 실행시켜야 할까요?</h2>
<ul>
<li>ConcurrentDispatchQueueScheduler에서 처리합니다.</li>
</ul>
<hr>
<h2 id="single-completable-maybe">Single, Completable, Maybe</h2>
<ul>
<li>single: Observable의 변형으로 일련의 요소를 방출하는 대신 항상 단일 요소 또는 오류를 방출하도록 보장하는것입니다.<ul>
<li>정확히 하나의 요소 또는 error를 방출합니다.</li>
<li>single의 사용 예는 응답, 오류만 반환할 수 있는 HTTP 요청을 수행하는데 사용됩니다.</li>
</ul>
</li>
<li>Completetable: complete 하거나 error를 방출할 수 있고 아무 요소도 방출하지 않는것을 보장합니다.<ul>
<li>complete 또는 error만 방출합니다.</li>
<li>completable은 완료에 따른 요소에 신경쓰지 않은 경우 사용합니다.</li>
</ul>
</li>
<li>Maybe: Single과 Completable의 중간인 Observable의 변형입니다.<ul>
<li>완료된 이벤트, 싱글 이벤트 또는 오류를 방출합니다.</li>
<li>Maybe는 Single과 비슷하지만 아무런 값을 방출하지 않고 coimplete가 되어도 오류가 발생하지 않습니다.</li>
</ul>
</li>
</ul>
<h2 id="operator-just-of-from의-용도">operator just, of, from의 용도</h2>
<ul>
<li>just: 단 하나의 요소만 방출</li>
<li>of: 여러 요소를 방출합니다.</li>
<li>from: 배열의 형태로 오는 요소들을 한 개씩 순차적으로 내놓습니다.</li>
</ul>
<h2 id="flatmap-compactmap-map의-용도">flatmap, compactmap, map의 용도</h2>
<ul>
<li>map: 옵저버블의 가진 요소들에 특정 연산을 수행해서 값을 방출합니다</li>
<li>flatmap: map과 비슷한 기능을 하지만 한번에 여러 스트림을 사용할 수 있고, 여러 스트림에서 방출된 아이템에 대해 누락 없이 구독이 가능합니다.</li>
<li>compactmap: Observable에서 방출되는 이벤트를 꺼낸 다음 옵셔널 형태로 바꾸고 원하는 변환을 실행합니다. 그리고 최종 변환된 값이 nil이면 방출하지 않습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Rx subject]]></title>
            <link>https://velog.io/@ljh3904_a/Rx-subject</link>
            <guid>https://velog.io/@ljh3904_a/Rx-subject</guid>
            <pubDate>Thu, 09 May 2024 17:39:50 GMT</pubDate>
            <description><![CDATA[<h2 id="subject">Subject</h2>
<ul>
<li>Subject는 옵저버블처럼 값을 받을 수 있고 외부에서 값을 통제할 수 있는 특별한 형태의 스트림입니다.</li>
<li>옵저버블은 이미 정해진 데이터를 내보내는 스트림이지만, Subject는 외부에서 데이터를 주입하고 구독도 가능한 특징을 가지고 있습니다.</li>
<li><strong>값 주입 및 통제</strong>: Subject는 외부에서 <strong><code>.onNext(값)</code></strong>과 같은 메서드를 사용하여 값을 주입할 수 있습니다. 이로써 Subject에게 새로운 데이터를 전달할 수 있습니다.</li>
<li><strong>옵저버블처럼 동작</strong>: Subject는 옵저버블처럼 동작하여 값을 구독할 수 있습니다. 따라서 Subject에 데이터가 흘러들어올 때마다 등록된 옵저버(Subscriber)들에게 해당 데이터가 전달됩니다.</li>
<li><strong>데이터의 내보내기를 통제</strong>: Subject를 생성할 때 어떤 데이터를 내보낼지 미리 정할 수 있습니다. 이는 Subject가 생성되면서 이미 스트림에 데이터가 정해져 있다는 점을 의미합니다.</li>
<li>옵저버블은 데이터가 이미 정해진 스트림으로, create할 때 어떤 데이터를 내보낼지 미리 결정됩니다. 그러나 외부에서 옵저버블에 데이터를 주입하려면 추가적인 메커니즘이 필요합니다. 이 때 등장하는 것이 Subject입니다.</li>
<li>Subject는 외부에서 값을 주입할 수 있는 옵저버블의 확장이며, 생성 시점에는 어떤 데이터를 내보낼지 미리 정할 수 있습니다. 이로써 Subject는 옵저버블을 동적으로 제어하면서 데이터를 주고받을 수 있는 중간자 역할을 수행합니다.</li>
</ul>
<hr>
<h3 id="publishsubject">PublishSubject</h3>
<ul>
<li>서브젝트의 어떤것이 Subscribe하면 데이터를 그시점부터 내려보내줌</li>
<li>다른애가 구독해도 그 구독 시점 이후에 데이터를 보내줌</li>
<li>자체가 서브젝트의 누군가가 구독을 할 수 있음 내부에서 데이터가 생성되면 데이터를 내려준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/a8057a6d-9bfa-4d24-a225-9a3c4b0e300e/image.png" alt=""></p>
<h3 id="behaviorsubject">BehaviorSubject</h3>
<ul>
<li>PublishSubject과 다르게 기본값을 가지고 시작한다 .</li>
<li>누군가가 구독하면 기본값을 내려보내줌</li>
<li>새로운놈이 중간에 구독하면 최근의 발생한 값을 내려보내줌</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/99696c99-a32e-4865-a389-0d30e9f6f7ce/image.png" alt=""></p>
<pre><code class="language-swift">let menus: [Menu] = [
        Menu(title: &quot;이제현&quot;, price: 10000, count: 1),
        Menu(title: &quot;이제서&quot;, price: 2000, count: 3),
        Menu(title: &quot;이제사&quot;, price: 30000, count: 2),
        Menu(title: &quot;이제수&quot;, price: 40000, count: 4),
 ]

lazy var menuObservable = 
BehaviorSubject&lt;[Menu]&gt;(value: menus)</code></pre>
<h3 id="replaysubject">ReplaySubject</h3>
<ul>
<li>처음 기능은 PublishedSubject와 기능이 비슷하나 누군가가 구독을 하면 구독시점 전의 데이터도 내려보내줍니다.
<img src="https://velog.velcdn.com/images/ljh3904_a/post/74964ef1-7963-4a81-ab84-854a00581510/image.png" alt=""></li>
</ul>
<h3 id="asyncsubject">AsyncSubject</h3>
<ul>
<li>누군가가 구독을해도 마지막 OnComplete가 되기전까지는 보내지 않고 OnComplete가 되면 그때 마지막 데이터를 보내줍니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/67f57eb6-161d-4869-ac9a-889b7e725f6f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[iOS Mapkit]]></title>
            <link>https://velog.io/@ljh3904_a/iOS-Mapkit</link>
            <guid>https://velog.io/@ljh3904_a/iOS-Mapkit</guid>
            <pubDate>Fri, 12 Apr 2024 13:20:44 GMT</pubDate>
            <description><![CDATA[<p>오늘은 간단히 iOS에 MapKit프레임워크를 사용해보도록 하겠습니다.</p>
<p>Map()을 사용하기 위해서는 </p>
<pre><code class="language-swift">Map(coordinateRegion: , annotationItems: , annotationContent: </code></pre>
<p>region, item, content가 필요합니다. 
region은 맵이 켜질때 어느 위치에 있을지 정하는것입니다.
item은 맵에 특정부분을 가르키는 마커들의 집합입니다.
Content는 핀들의 뷰를 클로저로 받아 뷰로 뱉습니다. (사용자정의하는곳)</p>
<h4 id="map에-기본적인-pin-방식으로-사용하기">map에 기본적인 PIN 방식으로 사용하기</h4>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/813424f1-bdc7-44d7-87c2-01a54d635423/image.png" alt=""></p>
<pre><code class="language-swift">
 MapPin(coordinate: item.location, tint: .accent)
</code></pre>
<h4 id="mapmarker를-사용하기">MapMarker를 사용하기</h4>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/f426e5b6-7fc7-4a10-aac6-7556ae4d382b/image.png" alt=""></p>
<pre><code class="language-swift">
MapMarker(coordinate: item.location, tint: .accentColor)
</code></pre>
<h4 id="사용자정의로-핀을-만들기">사용자정의로 핀을 만들기</h4>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/3cec6e3b-c69f-4765-aec1-eebe34d8cd40/image.png" alt=""></p>
<pre><code class="language-swift">
 MapAnnotation(coordinate: item.location) {
                Image(&quot;logo&quot;)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 32,height: 32, alignment: .center)
            }
</code></pre>
<p>예시 코드 </p>
<pre><code class="language-swift">Map(coordinateRegion: $region, annotationItems: locations) { item in
//            MapPin(coordinate: item.location, tint: .accent)

//            MapMarker(coordinate: item.location, tint: .accentColor)
            MapAnnotation(coordinate: item.location) {
                Image(&quot;logo&quot;)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 32,height: 32, alignment: .center)
            }

        }
</code></pre>
<p>참고 </p>
<p><a href="https://www.udemy.com/course/swiftui-masterclass-course-ios-development-with-swift/learn/lecture/22673459#overview">https://www.udemy.com/course/swiftui-masterclass-course-ios-development-with-swift/learn/lecture/22673459#overview</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ios AVkit ]]></title>
            <link>https://velog.io/@ljh3904_a/ios-AVkit</link>
            <guid>https://velog.io/@ljh3904_a/ios-AVkit</guid>
            <pubDate>Thu, 11 Apr 2024 16:14:13 GMT</pubDate>
            <description><![CDATA[<p>SwiftUI에서 AVkit을 사용해서 동영상을 재생시키는방법을 적어보려고 합니다.</p>
<p>일단 프레임워크중 AVkit 프레임워크를 import 해줘야 사용이 가능합니다.</p>
<h4 id="기본적인-사용방법">기본적인 사용방법</h4>
<pre><code class="language-swift">VideoPlayer(player: AVPlayer(url: &quot;url&quot;)) </code></pre>
<p>위 방식대로 사용하면 iOS 에서 제공하는 동영상 재생도구를 지원해줍니다. </p>
<h4 id="자동재생-하는방법">자동재생 하는방법</h4>
<pre><code class="language-swift">func playVideo(fileName: String, fileFormat: String) -&gt; AVPlayer {
    if Bundle.main.url(forResource: fileName, withExtension: fileFormat) != nil {
        videoPlayer = AVPlayer(url: Bundle.main.url(forResource: fileName, withExtension: fileFormat)!)
        //***********************
        videoPlayer?.play()
        //***********************
    }
    return videoPlayer!
}</code></pre>
<p>나머지 코드는 전부 무시해도 됩니다. AVPlayer 타입의 동영상에 .play()를 사용해주면 동영상이 있는뷰에 들어가면 동영상을 누르지 않아도 자동으로 재생시켜줍니다. </p>
<h4 id="동영상에-타이틀을-입히는-방법">동영상에 타이틀을 입히는 방법</h4>
<p><img src="https://velog.velcdn.com/images/ljh3904_a/post/ee49be64-3093-4bea-a2d0-edc88e5574a5/image.png" alt=""></p>
<pre><code class="language-swift">VideoPlayer(player: playVideo(fileName: videoSelected, fileFormat: &quot;mp4&quot;)) 
// *************************
{ Text(videoTitle) }</code></pre>
<p>이런식으로 후행클로저에 뷰를 넣으면 Zstack을 입힌것 처럼 영상위에 올라갑니다. </p>
<ul>
<li>다른방법 (Overlay 사용)</li>
</ul>
<pre><code class="language-swift">VideoPlayer(player: playVideo(fileName: videoSelected, fileFormat: &quot;mp4&quot;)) {
//                Text(videoTitle)
            }
            .overlay(
                Image(&quot;logo&quot;)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 32, height: 32)
                    , alignment: .topLeading
            )</code></pre>
<p>Overlay수정자 안에 넣어도 위에 올라갑니다. 회사의 로고같은걸 영상 상단위에 놓기 좋겠네요 </p>
<p>참고
<a href="https://www.udemy.com/course/swiftui-masterclass-course-ios-development-with-swift/learn/lecture/22561262#overview">https://www.udemy.com/course/swiftui-masterclass-course-ios-development-with-swift/learn/lecture/22561262#overview</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[iOS 햅틱 기능넣기]]></title>
            <link>https://velog.io/@ljh3904_a/iOS-%ED%96%85%ED%8B%B1-%EA%B8%B0%EB%8A%A5%EB%84%A3%EA%B8%B0</link>
            <guid>https://velog.io/@ljh3904_a/iOS-%ED%96%85%ED%8B%B1-%EA%B8%B0%EB%8A%A5%EB%84%A3%EA%B8%B0</guid>
            <pubDate>Thu, 11 Apr 2024 15:37:16 GMT</pubDate>
            <description><![CDATA[<p>사용자가 터치했음을 감지시키고 싶을때 햅틱 기능을 넣으면 좋긴하죠 
그렇기에 햅틱 기능을 사용하는 방법에 대해서 적어보도록 하겠습니다.</p>
<pre><code class="language-swift">let hapticImpact = UIImpactFeedbackGenerator(style: .medium)
</code></pre>
<p>UIImpactFeedbackGenerator 클래스를 생성하고 init에 5개중 하나의 선택지를 넣으면 됩니다. 
// light, medium, soft 등 </p>
<p>그 후 햅틱을 적용시키고 싶은 부분에 넣어주면 되는데요.</p>
<pre><code class="language-swift">let hapticImpact = UIImpactFeedbackGenerator(style: .medium)

    var body: some View {
        NavigationView {
            List {
                ForEach(videos) { video in
                    VideoListItemView(video: video)
                        .padding(.vertical, 8)
                }
            }
            .listStyle(InsetGroupedListStyle())
            .navigationTitle(&quot;Videos&quot;)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        videos.shuffle()
                        // *********************
                        hapticImpact.impactOccurred()
                        // *********************
                    } label: {
                        Image(systemName: &quot;arrow.2.squarepath&quot;)
                    }

                }
            }
        }
    }</code></pre>
<p>.impactOccurred() 이 코드를 넣으면 됩니다! 
저는 사용자가 비디오들을 섞으면 섞인다는 촉감? 을 주기위해 햅틱을 shuffle버튼에 넣고 사용했습니다. </p>
<p><strong><em>시뮬레이터에선 당연히 작동이되는지 모르겠죠? 실기기에서 활용하세요!</em></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스켈레톤뷰를 구현해 봅시다. ]]></title>
            <link>https://velog.io/@ljh3904_a/%EC%8A%A4%EC%BC%88%EB%A0%88%ED%86%A4%EB%B7%B0%EB%A5%BC-%EA%B5%AC%ED%98%84%ED%95%B4-%EB%B4%85%EC%8B%9C%EB%8B%A4</link>
            <guid>https://velog.io/@ljh3904_a/%EC%8A%A4%EC%BC%88%EB%A0%88%ED%86%A4%EB%B7%B0%EB%A5%BC-%EA%B5%AC%ED%98%84%ED%95%B4-%EB%B4%85%EC%8B%9C%EB%8B%A4</guid>
            <pubDate>Mon, 26 Feb 2024 10:59:52 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요!
이번에는 SwiftUI를 사용하여 
사용자가 기다리고 있다는것을 
인지할 수 있도록 직접 스켈레톤을 만들어 보려고 합니다. 
대부분 <strong>개발하는 정대리</strong>님의 코드를 보고 작성하였습니다.</p>
<ul>
<li><a href="https://github.com/TuenTuenna/SwiftUi_Skeleton_modifier">https://github.com/TuenTuenna/SwiftUi_Skeleton_modifier</a></li>
</ul>
<p>저는 스켈레톤뷰를 검정 -&gt; 회색으로 배경화면이 바뀌도록 설정했습니다.
<img src="https://velog.velcdn.com/images/ljh3904_a/post/be7be134-c10a-45ac-8cad-085eae966cf3/image.png" alt="">
<img src="https://velog.velcdn.com/images/ljh3904_a/post/998ffbb0-5a20-45ef-8b95-ad5161a9a247/image.png" alt=""></p>
<pre><code class="language-swift">import SwiftUI

struct SkeletonView: View {
    private let size: CGSize
    @State private var isBlackBackground = true

    var backgroundColor: Color { isBlackBackground ? .black : .gray
    }

    init(size: CGSize) {
        self.size = size
    }

    var body: some View {
        RoundedRectangle(cornerRadius: 8)
            .fill(backgroundColor)
            .opacity(0.5)
            .frame(width: size.width, height: size.height)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.white, lineWidth: 2)
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(Color.white, lineWidth: 1)
                            .padding(1)
                    )
            )
            .overlay(content: {
                    ProgressView()
            })
            .redacted(reason: .placeholder)
            .onAppear {
                withAnimation(Animation.easeInOut(duration: 1.0).repeatForever(autoreverses: true)) {
                    isBlackBackground.toggle()
                }
            }
    }
}</code></pre>
<h4 id="코드설명">코드설명</h4>
<p>하나의 구조체로 생성을해 각 이미지마다 크기별로 스켈레톤을 입히기 위해서 생성자로 size를 받아왔습니당
그 후 RoundedRectangle을 사용하여 사이즈에 맞게 뷰의 크기를 적용하였고, 
.onappear됐을 때 배경화면을 1초마다 변경하도록 하였습니다. </p>
<p>코드를 적용시킨 곳 </p>
<pre><code class="language-swift">var body: some View {
        Button(action: {
            isSheet = true
        }, label: {
            AsyncImage(url: URL(string: unsplash.urls.regular)) { image in
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(minWidth: 100, maxWidth: 200, maxHeight: 150)
                    .cornerRadius(10)
            } placeholder: {
                SkeletonView(size: CGSize(width: 200, height: 150))
            } // 여깁니다 
        })
        .fullScreenCover(isPresented: $isSheet, content: {
            DetailView(bookmarkStore: bookmarkStore, unsplash: unsplash, id: &quot;&quot;, isSheet: $isSheet)
        })
    }</code></pre>
<p>주석을 달아놓은곳에 적용을 해보았는데요. 사이즈는 알아서 조절하시면 됩니다! </p>
]]></description>
        </item>
    </channel>
</rss>