<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>iOS 개발 공부</title>
    <link>https://junbok97.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 20 Jun 2026 18:29:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>물복딱복준복</managingEditor>
    <item>
      <title>[Swift] RxSwift의 subscribe(on:)와 observe(on:), Combine의 subscribe(on:)와 receive(on:)</title>
      <link>https://junbok97.tistory.com/378</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;rxSwift와 combine의 비동기 흐름은 아래와 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;이벤트 생성&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;전달&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;Observer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RxSwift의 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;이벤트 생성&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;전달&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;Observer&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; subscribe(on:)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; observe(on:)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트가 생성되는 쪽의 실행 스레드를 지정해주는 것이 subscribe(on:)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 이벤트를 전달받아 처리하는 쪽의 실행 스레드를 지정해주는 것이 observe(on:)이다.&lt;/p&gt;
&lt;pre id=&quot;code_1750422758995&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable&amp;lt;Int&amp;gt;
     .create { observer in
         // Subscription 영역
     }
     .subscribe(on:) // Subscription 영역의 실행 스레드 지정 (업스트림)
     .observe(on:) // Observing 영역을 지정 (다운 스트림)
     .subscribe { _ in
         // Observing 영역
     }
     .disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribe(on:)은 어느곳에서 호출되더라도 이벤트를 방출하는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Subscription영역의 실행 스레드를 변경시키고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observe(on:)은 Observing 영역의 실행스레드를 변경시키기 때문에 호출된 곳의 아래 있는 실행스레드들이 바뀌게 된다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwA0q/btsOLjwPo5K/J6U08Jcl5FoSLWiqMmArzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwA0q/btsOLjwPo5K/J6U08Jcl5FoSLWiqMmArzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwA0q/btsOLjwPo5K/J6U08Jcl5FoSLWiqMmArzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwA0q%2FbtsOLjwPo5K%2FJ6U08Jcl5FoSLWiqMmArzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;1600&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;subscribe(on:)은 여러번 호출되더라도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Subscription영역은 한군데이기 때문에 위치와 관계 없이 가장 처음 호출된것이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observe(on:)은 다른 observe(on:)을 만날때 까지 적용된다&lt;/p&gt;
&lt;pre id=&quot;code_1750423398415&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Observable&amp;lt;Int&amp;gt;
    .create { observer in
        // Subscription 영역
        print(&quot;create 스레드: \(Thread.current)&quot;)
        observer.onNext(1)
        observer.onCompleted()
        return Disposables.create()
    }
    .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background))  // 이것만 적용    
    .observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) // 아래 전부 적용
    .map {
        // Observing 영역
        print(&quot;Map 스레드: \(Thread.current)&quot;)
        return $0
    }
    .subscribe(on: MainScheduler.instance)
    .observe(on: MainScheduler.instance) // 아래 전부 적용
    .subscribe { _ in
        // Observing 영역
        print(&quot;subscribe 스레드: \(Thread.current)&quot;)
    }
    .disposed(by: disposeBag)
    
    
Observable&amp;lt;Int&amp;gt;
    .create { observer in
        // Subscription 영역
        print(&quot;create 스레드: \(Thread.current)&quot;)
        observer.onNext(1)
        observer.onCompleted()
        return Disposables.create()
    }
    .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background))  // 이것만 적용
    .subscribe(on: MainScheduler.instance)
    .observe(on: ConcurrentDispatchQueueScheduler(qos: .background)) // 아래 전부 적용
    .map {
        // Observing 영역
        print(&quot;Map 스레드: \(Thread.current)&quot;)
        return $0
    }
    .observe(on: MainScheduler.instance) // 아래 전부 적용
    .subscribe { _ in
        // Observing 영역
        print(&quot;subscribe 스레드: \(Thread.current)&quot;)
    }
    .disposed(by: disposeBag)
    

// 결과
create 스레드: &amp;lt;NSThread: 0x600001750c00&amp;gt;{number = 5, name = (null)}
Map 스레드: &amp;lt;NSThread: 0x600001750c00&amp;gt;{number = 5, name = (null)}
subscribe 스레드: &amp;lt;_NSMainThread: 0x60000170c000&amp;gt;{number = 1, name = main}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Combine의 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;이벤트 생성&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;전달&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr;&amp;nbsp; &amp;nbsp; &amp;nbsp;Observer&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; subscribe(on:)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;receive(on:)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;combine도 rxSwift와 동일하다 다만 이름이 observe(on:) -&amp;gt; receive(on:)로 변경된것 뿐이다.&lt;/p&gt;
&lt;pre id=&quot;code_1750424278030&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Just(1)
    .subscribe(on: DispatchQueue.global()) // 이것만 적용
    .subscribe(on: RunLoop.main)
    .handleEvents(receiveSubscription: { _ in
        print(&quot;subscription 스레드 \(Thread.current)&quot;)
    })
    .receive(on: RunLoop.main)
    .map { (value) -&amp;gt; Int in
        print(&quot;map 스레드 \(Thread.current)&quot;)
        return value
    }
    .receive(on: DispatchQueue.global())
    .sink { value in
        print(&quot;received 스레드 \(Thread.current)&quot;)
    }
    
Just(1)
    .handleEvents(receiveSubscription: { _ in
        print(&quot;subscription 스레드 \(Thread.current)&quot;)
    })
    .receive(on: RunLoop.main)
    .map { (value) -&amp;gt; Int in
        print(&quot;map 스레드 \(Thread.current)&quot;)
        return value
    }
	.subscribe(on: DispatchQueue.global()) // 이것만 적용
    .subscribe(on: RunLoop.main)
    .receive(on: DispatchQueue.global())
    .sink { value in
        print(&quot;received 스레드 \(Thread.current)&quot;)
    }
    
// 결과
subscription 스레드 &amp;lt;NSThread: 0x6000017bf700&amp;gt;{number = 9, name = (null)}
map 스레드 &amp;lt;_NSMainThread: 0x600001710000&amp;gt;{number = 1, name = main}
received 스레드 &amp;lt;NSThread: 0x600001718300&amp;gt;{number = 7, name = (null)}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트 생성 시점 스레드 지정 (가장 처음 호출된 것만 적용 / 업스트림)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2517&quot; data-start=&quot;2475&quot;&gt;subscribe(on:)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이벤트 처리 시점 스레드 지정 (호출된곳에서부터 아래로 전부 영향을 받음 / 다운스트림)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;2520&quot; data-end=&quot;2594&quot;&gt;RxSwift - observe(on:)&lt;/li&gt;
&lt;li data-start=&quot;2520&quot; data-end=&quot;2594&quot;&gt;Combine - receive(on:)&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>iOS/Swift</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/378</guid>
      <comments>https://junbok97.tistory.com/378#entry378comment</comments>
      <pubDate>Fri, 20 Jun 2025 21:59:24 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] @Published는 값을 언제 방출할까 ?</title>
      <link>https://junbok97.tistory.com/377</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;combine에서 @Published는 값이 바뀔때마다 방출을 하는데 이때 방출되는 시점은 언제일까 ??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 didSet이 호출되어 값이 바뀌고 난 뒤라고 생각했는데 실제 값과 receive된 값이 다르다는 걸 알게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1750420740871&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ContentView.swift
//  Test
//
//  Created by 이준복 on 6/20/25.
//

import SwiftUI
import Combine

struct ContentView: View {
    
    @StateObject
    private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text(&quot;Count: \(viewModel.count)&quot;)
                .font(.largeTitle)
            
            Button(&quot;Increment&quot;) {
                viewModel.increment()
            }
        }
        .padding()
    }
    
}

#Preview {
    ContentView()
}


class ViewModel: ObservableObject {
    
    @Published
    var count: Int = 0 {
        willSet {
            print(&quot;willSet: \(count)&quot;)
        }
        
        didSet {
            print(&quot;didSet: \(count)&quot;)
        }
    }
    
    private var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()
    
    init() {
        $count
            .sink { [weak self] value in
                print(&quot;receive: \(value)&quot;, &quot;real: \(self?.count ?? -1)&quot;)
            }
            .store(in: &amp;amp;cancellables)
    }

    func increment() {
        print(&quot;변경 전: \(count)&quot;)
        count += 1
        print(&quot;변경 후: \(count)&quot;)
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 실행하기 전 결과를 예상 했을땐 아래라고 생각했는데 실제 결과 값은 그렇지 않았다.&lt;/p&gt;
&lt;pre id=&quot;code_1750421204592&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;변경 전: 0
willSet: 0
didSet: 1
receive: 1 real: 0
변경 후: 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 결과 값&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfjpY9/btsOLPPvhnT/kHPaekK0FsrIKpfn2GZ6nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfjpY9/btsOLPPvhnT/kHPaekK0FsrIKpfn2GZ6nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfjpY9/btsOLPPvhnT/kHPaekK0FsrIKpfn2GZ6nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfjpY9%2FbtsOLPPvhnT%2FkHPaekK0FsrIKpfn2GZ6nK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3248&quot; height=&quot;1988&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 찾아보니 willSet에서 .send(newValue)를 호출하여 값을 방출하는 것 같았다. 실제 메모리 반영되고 값이 바뀐 시점은 didSet 시점이니 이 부분을 알고 있으면 좋을 것 같다.&lt;/p&gt;</description>
      <category>iOS/Swift</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/377</guid>
      <comments>https://junbok97.tistory.com/377#entry377comment</comments>
      <pubDate>Fri, 20 Jun 2025 21:08:58 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 일급객체, 일급함수, 고차함수, 순수함수</title>
      <link>https://junbok97.tistory.com/376</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;일급 객체&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일급객체, 일급함수란 객체나 함수가 &lt;b&gt;값으로 취급될 수 있다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값으로 취급 할 수 있다&lt;/b&gt;는 것의 의미는 변수를 할당받거나, 파라미터로 전달되거나 리턴 값으로 취급 될 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명으로만 보면 이해하기 힘든데 코드를 보면 쉽게 이해가 가능하다&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// 반환 값
func getName() -&amp;gt; String {
    &quot;Name&quot;
}

// 파라미터로 전달
func add(_ a: Int, _ b: Int) -&amp;gt; Int {
    a + b
}

// 변수에 저장
struct Person {
    let name: String
    let age: Int
}

let person = Person(name: &quot;준&quot;, age: 20)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 처럼 정수형, 배열, 객체, 클래스 등 변수에 할당받을 수 있는 타입을 일급객체라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일급함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일급함수란 일급객체로 취급되는 함수를 일급 함수라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 함수를 값처럼 다를 수 있다는 이야기. Swift에선 함수(클로저)를 일급객체로 취급한다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// 매개변수
func run(_ completion: ((String) -&amp;gt; Void)?) {
    completion?(&quot;Finish&quot;)
}

// 변수에 저장
func helloWorld() {
    print(&quot;Hello World!&quot;)
}

let task1 = helloWorld
let task2 = {
    print(&quot;Good Bye World!&quot;)
}

var tasks: [() -&amp;gt; Void] = [task1, task2]

// 리턴 값
func plus(_ x: Int) -&amp;gt; (Int) -&amp;gt; Int {
    { y in x + y }
}

let plus5 = plus(5)
print(plus5(10)) // 15&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고차함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차함수는 다른 함수를 인자를 받거나 함수를 결과값으로 반환하는 함수를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 모든 고차함수는 일급함수(일급객체)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 모든 일급함수가 고차함수인것은 아니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 일급함수가 다른 함수를 인자로 받거나 함수를 결과값으로 반환하는게 아니기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에선 클로저가 일급객체 취급이므로 고차 함수는 클로저를 인자로 받거나 반환할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고차함수의 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 가독성 향상&lt;/li&gt;
&lt;li&gt;재사용성 증가&lt;/li&gt;
&lt;li&gt;유연성 및 확장성 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 코드 가독성 향상
let numbers = [1, 2, 3, 4, 5]

var evens: [Int] = []
for number in numbers {
    if number % 2 == 0 {
        evens.append(number)
    }
}

let evens2 = numbers.filter { $0 % 2 == 0 }


// 재사용성 증가
func performCalculation(_ a: Int, _ b: Int, using operation: (Int, Int) -&amp;gt; Int) -&amp;gt; Int {
    return operation(a, b)
}

let sum = performCalculation(3, 5, using: +) // 8
let product = performCalculation(3, 5, using: *) // 15


// 유연성 및 확장성 증가
// completion 구현에 따른 확장성
func reqeust(completion: (() -&amp;gt; Void)?) {
    DispatchQueue.global().async {
        completion?()
    }
}


reqeust {
    print(&quot;do SomeThing&quot;)
}


reqeust {
    print(&quot;do SomeThing 2&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단점도 존재하는데 대표적인것이 콜백 지옥이다&lt;/p&gt;
&lt;pre id=&quot;code_1750080340091&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func getUserID(_ completion: @escaping (Result&amp;lt;String, Error&amp;gt;) -&amp;gt; Void) {
    ... 비동기처리 ...
    isSuccess ? completion(.success(&quot;ID&quot;)) : completion(.failure(SomeError))
}

func getUserName(id: String, _ completion: @escaping (Result&amp;lt;String, Error&amp;gt;) -&amp;gt; Void) {
    ... 비동기처리 ...
    isSuccess ? completion(.success(&quot;Name&quot;)) : completion(.failure(SomeError))
}


getUserID { result in
    switch result {
    case .success(let id):
        getUserName(id: id) { result in
            switch result {
            case .success(let name):
                print(name)
            case .failure(let error):
                print(error)
            }
        }
    case .failure(let error):
        print(error)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;순수함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수함수는 두가지 조건을 만족해야 한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 input엔 항상 동일한 output&lt;/li&gt;
&lt;li&gt;Side Effect가 없다 (함수 외부에서 변경이 일어나지 않고 함수 실행하는 것 외에 다른 외부 상호작용이 없다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수함수는 수학의 함수와 동일하다고 생각하면 된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 f(x) = x + 5 에서 f(5)는 항상 10 이다. f(6)은 항상 11이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1750080791126&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func add(_ a: Int, _ b: Int) -&amp;gt; Int {
    a + b
}
add(10, 5) // 항상 10


func minus(_ a: Int, _ b: Int) -&amp;gt; Int {
    a - b
}
minus(10, 5) // 항상 5


// Side Effect가 존재
var multiple = 2
func increment(_ a: Int) -&amp;gt; Int {
    a * multiple
}

increment(5) // 10
multiple = 10
increment(5) // 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Side Effect가 존재하지 않으면 동일한 input에 동일한 output이 나온다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹가다 아래 처럼 Date(), random() 처럼 input이 없고 동일한 함수를 호출하는데 다른 output이 나오니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 말한 조건에 위배되는것 아닌가라고 생각할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄연히 사이드 이팩트가 존재하는 불순 함수들이다&lt;/p&gt;
&lt;pre id=&quot;code_1750080894488&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 외부 환경인 현재 시간에 의존
func getCurrentTime() -&amp;gt; Date {
    return Date() 
}

// 외부 환경인 난수 생성기에 의존
func getRandomNumber() -&amp;gt; Int {
    return Int.random(in: 1...100)
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>iOS/Swift</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/376</guid>
      <comments>https://junbok97.tistory.com/376#entry376comment</comments>
      <pubDate>Mon, 16 Jun 2025 22:18:50 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] gaurd let _ = try? await evaluateJavaScript 오류</title>
      <link>https://junbok97.tistory.com/375</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 하고 있는 App 하나의 리팩토링을 끝낸후 나머지 앱을 리팩토링 하던 중 아래와 같은 오류 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thread&amp;nbsp;1:&amp;nbsp;Swift&amp;nbsp;runtime&amp;nbsp;failure:&amp;nbsp;Unexpectedly&amp;nbsp;found&amp;nbsp;nil&amp;nbsp;while&amp;nbsp;implicitly&amp;nbsp;unwrapping&amp;nbsp;an&amp;nbsp;Optional&amp;nbsp;value&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Crash 발생시 함수 스택에도 표기되지 않고 storyboard나 xib파일이 아예없기에 아주 당황 ;;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;혹시몰라 다시 체크해보았는데 당연히 코드레벨에서 옵셔널을 강제 언래핑하는곳이 아무곳도 없었음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크래시가 발생하는 화면 처음부터 디버깅 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 함수 호출 시 크래시 발생&lt;/p&gt;
&lt;pre id=&quot;code_1746952605983&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func getSectionPositions() async&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 리팩토링이 끝난 앱에서 공통코드로 뺀 부분이고 정상적으로 동작하는 것까지 검증이 끝난 함수인데 아주 당황;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한줄한줄 디버깅해보니 아래 코드에서 Crash ...&lt;/p&gt;
&lt;pre id=&quot;code_1746952753086&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// titleScript = &quot;document.querySelector('#\(rawValue) span')?.textContent&quot;
guard let titleResult = try? await webView.evaluateJavaScript(type.titleScript),
      let title = titleResult as? String else { return }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webView에서 특정 content를 가져오는 script인데 try?를 통해 강제 언래핑해제도 아닌데 터져버림 ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 해서 do catch로 감싸 실행해보니 정상적으로 동작 ...&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746952922819&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;do {
    ...
    let titleResult = try await webView.evaluateJavaScript(type.titleScript)
    let title = titleResult as? String
    ...
} catch {
    LogManager.log(level: .error, self, #function, error, &quot;title is Invalid : \(type.titleScript)&quot;)
    continue
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 하는 마음에 몇일 후 guard let으로 재 실행&lt;/p&gt;
&lt;pre id=&quot;code_1748349237685&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// titleScript = &quot;document.querySelector('#\(rawValue) span')?.textContent&quot;
guard let titleResult = try? await webView.evaluateJavaScript(type.titleScript),
      let title = titleResult as? String else { return }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 동작 ;;;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐가 문제인지 결국 찾지 못했다 ....&lt;/p&gt;</description>
      <category>iOS/Trouble Shooting</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/375</guid>
      <comments>https://junbok97.tistory.com/375#entry375comment</comments>
      <pubDate>Sun, 11 May 2025 17:43:15 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] KakaoOpenSDK에서 Concurrency 사용시 App Crash</title>
      <link>https://junbok97.tistory.com/374</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;App Crash Report에서 Concurrency 오류 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KakaoLogin시 간혈적으로 App Crash가 발생을 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daVsXt/btsNTnMDJvl/5hudXddPNJkchDNqKJ7n6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daVsXt/btsNTnMDJvl/5hudXddPNJkchDNqKJ7n6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daVsXt/btsNTnMDJvl/5hudXddPNJkchDNqKJ7n6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaVsXt%2FbtsNTnMDJvl%2F5hudXddPNJkchDNqKJ7n6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;1088&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 ThirdPartyLogin을 위해 async/await을 사용하고 있었기때문에 withCheckedThrowingContinuation로 감싼 형태였고&lt;br /&gt;특정 기기나 OS, 혹은 특정 유저마다 발생하는게 아니였고 같은 조건이여도 정상적으로 동작하는 경우가 있었고&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;CheckedContinuation.resume(returning:)
CheckedContinuation.resume(throwing:)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 모두에서 Crash가 발생하여 SDK에서 발생하는 문제임이라 생각하여 개발자 포럼에 확인 요청&lt;br /&gt;- &lt;a href=&quot;https://devtalk.kakao.com/t/ios-sdk-concurrency-app-crash/143672&quot;&gt;iOS SDK에서 Concurrency 사용시 app crash&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746952235995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;iOS SDK에서 Concurrency 사용시 app crash&quot; data-og-description=&quot;KakaoOpenSDK - 2.24.0 App ID - 573044 문의 시, 사용하시는 SDK 버전 정보와 디벨로퍼스 앱ID를 알려주세요. 앱에서 Concurrency 사용을 위해서 UserApi.shared.loginWithKakaoTalk 를 withCheckedThrowingContinuation로 감싸서 &quot; data-og-host=&quot;devtalk.kakao.com&quot; data-og-source-url=&quot;https://devtalk.kakao.com/t/ios-sdk-concurrency-app-crash/143672&quot; data-og-url=&quot;https://devtalk.kakao.com/t/ios-sdk-concurrency-app-crash/143672&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cQ6uww/hyYRnorzLI/x8MWU9kDyUKhnaHWYEM8H0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/Y2GS2/hyYRvGN0Mm/3gy3GUlwICIaO5kD342KG0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/cli6zJ/hyYPpNWEed/TpNzOHG3Uhv5thcdlgK4k0/img.jpg?width=690&amp;amp;height=369&amp;amp;face=0_0_690_369&quot;&gt;&lt;a href=&quot;https://devtalk.kakao.com/t/ios-sdk-concurrency-app-crash/143672&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devtalk.kakao.com/t/ios-sdk-concurrency-app-crash/143672&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cQ6uww/hyYRnorzLI/x8MWU9kDyUKhnaHWYEM8H0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/Y2GS2/hyYRvGN0Mm/3gy3GUlwICIaO5kD342KG0/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/cli6zJ/hyYPpNWEed/TpNzOHG3Uhv5thcdlgK4k0/img.jpg?width=690&amp;amp;height=369&amp;amp;face=0_0_690_369');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;iOS SDK에서 Concurrency 사용시 app crash&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;KakaoOpenSDK - 2.24.0 App ID - 573044 문의 시, 사용하시는 SDK 버전 정보와 디벨로퍼스 앱ID를 알려주세요. 앱에서 Concurrency 사용을 위해서 UserApi.shared.loginWithKakaoTalk 를 withCheckedThrowingContinuation로 감싸서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devtalk.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@MainActor
    func loginWithKakao() async throws -&amp;gt; OAuthToken {
        try await UserApi.isKakaoTalkLoginAvailable() ? loginWithKakaoTalk() : loginWithKakaoAccount()
    }

    @MainActor
    func loginWithKakaoTalk() async throws -&amp;gt; OAuthToken {
        try await withCheckedThrowingContinuation { continuation in
            UserApi.shared.loginWithKakaoTalk { oAuthToken, error in
                if let error {
                    continuation.resume(throwing: error)
                } else if let oAuthToken {
                    continuation.resume(returning: oAuthToken)
                } else {
                    continuation.resume(throwing: KakaoLoginError.emptyData)
                }
            }
        }
    }

    @MainActor
    func loginWithKakaoAccount() async throws -&amp;gt; OAuthToken {
        try await withCheckedThrowingContinuation { continuation in
            UserApi.shared.loginWithKakaoAccount { oAuthToken, error in
                if let error {
                    continuation.resume(throwing: error)
                } else if let oAuthToken {
                    continuation.resume(returning: oAuthToken)
                } else {
                    continuation.resume(throwing: KakaoLoginError.emptyData)
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 측에서 오류 확인, 수정 후 배포&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BCC7Z/btsNStUc0Bt/5Mc5xTkEbv1tMFh8pn5qAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BCC7Z/btsNStUc0Bt/5Mc5xTkEbv1tMFh8pn5qAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BCC7Z/btsNStUc0Bt/5Mc5xTkEbv1tMFh8pn5qAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBCC7Z%2FbtsNStUc0Bt%2F5Mc5xTkEbv1tMFh8pn5qAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1560&quot; height=&quot;486&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 된 SDK로 버전 업을 하였는데 같은 크래시 발생 .... 그 전 보단 확실히 줄었지만 내쪽 코드가 문제인것 같은데 혹시나 해서 재 문의&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VLIoI/btsOfxhmeZy/oBG2tSsULVygPvS5fdnCQK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VLIoI/btsOfxhmeZy/oBG2tSsULVygPvS5fdnCQK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VLIoI/btsOfxhmeZy/oBG2tSsULVygPvS5fdnCQK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVLIoI%2FbtsOfxhmeZy%2FoBG2tSsULVygPvS5fdnCQK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;421&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SDK 담당자분과 소통하면서 원인을 찾아가던 중 하기와 같은 답변을 받자마자 원인 파악이 가능했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-05-27 오후 9.36.06.png&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCvoIp/btsOdZzsdMV/LtfpsjxLONhOPUbEJLNCnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCvoIp/btsOdZzsdMV/LtfpsjxLONhOPUbEJLNCnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCvoIp/btsOdZzsdMV/LtfpsjxLONhOPUbEJLNCnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCvoIp%2FbtsOdZzsdMV%2FLtfpsjxLONhOPUbEJLNCnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1638&quot; height=&quot;622&quot; data-filename=&quot;스크린샷 2025-05-27 오후 9.36.06.png&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리나 SDK는 문제가 없을거라는 안일한 생각이 문제였던것 .....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보자마자 nslock을 사용하여 단일 실행을 보장하였고 그 뒤엔 관련 크래시가 발생하지 않았다 ...ㅎㅎㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS/Trouble Shooting</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/374</guid>
      <comments>https://junbok97.tistory.com/374#entry374comment</comments>
      <pubDate>Sun, 11 May 2025 17:29:33 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] 앱 아이콘 앱 스토어 심사 없이 동적으로 바꾸기</title>
      <link>https://junbok97.tistory.com/373</link>
      <description>&lt;h1&gt;Dynamic App Icon&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정기간이나 특정 조건에서 앱 아이콘을 바꾸고 싶을 때 앱 배포를 하지 않고 앱 아이콘을 바꾸는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Assets 에 원하는 AppIcon(BIcon, CIcon, DIcon) 들을 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/cd46bdd6-2142-42ff-88c7-8616f4375838&quot; alt=&quot;스크린샷 2025-04-19 오후 11 47 01&quot; width=&quot;2032&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Targets &amp;rarr; Build Setting(All) &amp;rarr; Alternate App Icon Sets 에서 Assets에 추가한 AppIcon(BIcon, CIcon, DIcon) 들을 입력&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/2fdf3797-7be2-4150-a761-44812742e465&quot; alt=&quot;스크린샷 2025-04-19 오후 11 47 22&quot; width=&quot;2032&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UIApplication.shared.setAlternateIconName(String?:)&lt;/code&gt; 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/user-attachments/assets/345e9eda-495d-4ce7-a5f1-4d2f496ffb33&quot;&gt;https://github.com/user-attachments/assets/345e9eda-495d-4ce7-a5f1-4d2f496ffb33&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }


    @IBAction func buttonTapped(_ sender: Any) {
        setAppIcon()
    }

    func setAppIcon() {

        let count = (UserDefaults.standard.integer(forKey: &quot;AppIcon&quot;) + 1) % 4
        UserDefaults.standard.setValue(count, forKey: &quot;AppIcon&quot;)

        var iconName: String? = nil
        switch count {
        case 1:
            iconName = &quot;BIcon&quot;
        case 2:
            iconName = &quot;CIcon&quot;
        case 3:
            iconName = &quot;DIcon&quot;
        default: break
        }

        UIApplication.shared.setAlternateIconName(iconName) { error in
            if let error = error {
                print(&quot;아이콘 변경 실패: \(error)&quot;)
            } else {
                print(&quot;아이콘 변경 성공!&quot;)
            }
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alert이 뜨는게 싫다면 아래 함수 호출&lt;br /&gt;주의해야할 점은 appStore 심사가 거절될 수 있고&lt;br /&gt;&lt;code&gt;UIApplication.shared.setAlternateIconName(String?:)&lt;/code&gt;에선 nil로 값을 주면 기본으로 설정된 appIcon으로 돌아가는데&lt;br /&gt;setIconWithoutAlert을 사용할 경우엔 기본으로 돌아갈 appIcon을 따로 추가해주어야 함&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt; func setIconWithoutAlert(_ appIconName: String) {
        if let alternateIconName = UIApplication.shared.alternateIconName,
           alternateIconName == appIconName {
            return
        }

        if UIApplication.shared.responds(to: #selector(getter: UIApplication.supportsAlternateIcons)) &amp;amp;&amp;amp; UIApplication.shared.supportsAlternateIcons {
            typealias setAlternateIconName = @convention(c) (NSObject, Selector, NSString, @escaping (NSError) -&amp;gt; ()) -&amp;gt; ()
            let selectorString = &quot;_setAlternateIconName:completionHandler:&quot;
            let selector = NSSelectorFromString(selectorString)
            let imp = UIApplication.shared.method(for: selector)
            let method = unsafeBitCast(imp, to: setAlternateIconName.self)
            method(UIApplication.shared, selector, appIconName as NSString, { _ in })
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹가다 icon 변경이 안될때는 assets 이미지 이름에 icon(ex: special -&amp;gt; specialIcon)을 붙혀 적용해보면 변경됨&lt;/p&gt;</description>
      <category>iOS/iOS</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/373</guid>
      <comments>https://junbok97.tistory.com/373#entry373comment</comments>
      <pubDate>Sun, 20 Apr 2025 21:32:09 +0900</pubDate>
    </item>
    <item>
      <title>Configurations 사용시 주의점</title>
      <link>https://junbok97.tistory.com/372</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모듈화시 각 모듈에서 사용하는 ThirdParty Library들의 효율적인 관리를 위해 ThirdPartyKit이라는 모듈로 분리하여 사용하였다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/75895744-e221-4ae3-956c-9daf03760dc8&quot; alt=&quot;1&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/df1dd9b6-379f-4848-9bc7-92bbc7b9d30c&quot; alt=&quot;2&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/9f2e5dd5-d132-4203-8632-fc392ffe34ae&quot; alt=&quot;3&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App 테스트를 위해 Configurations를 각각 다르게 줘고 Scheme를 각각 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dev, QA, Release Scheme이다&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/c7372638-ca04-4855-84d8-9747eb10b1cd&quot; alt=&quot;4&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/27bbdbe4-b175-419c-8e9f-9f15a7609881&quot; alt=&quot;5&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/da572b80-a138-470f-8747-c3ce90844d53&quot; alt=&quot;6&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/866ae549-0cc9-4dfe-9414-1665f3f1a90f&quot; alt=&quot;7&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App-Release 스키마와 App-Dev 스키마는 정상적으로 실행되는데 App-QA 스키마는 아래와 같은 에러가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;Framework 'ThirdPartyKit' not found
Linker command failed with exit code 1 (use -v to see invocation)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;~/DerivedData/프로젝트이름/Build/Products의 폴더를 들어가 각각의 폴더를 확인해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QA에만 ThirdPartyKit.framework만 포함되어 있지 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 ThirdPartyKit에 추가한 라이브러리들(SnapKit)은 다 포함되어있다&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/b597c319-4091-441d-99cb-217684f7ce8b&quot; alt=&quot;8&quot; width=&quot;1032&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/e063d2c0-baa3-4483-a51a-9347507441b6&quot; alt=&quot;9&quot; width=&quot;1032&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/e8f04af4-4d3b-4e7b-b3e5-c33d5147de43&quot; alt=&quot;10&quot; width=&quot;1032&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/cb36e17e-a6af-4f77-bbdc-f60f1c823553&quot; alt=&quot;11&quot; width=&quot;1032&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThirdPartyKit.framework가 포함되지 않는 이유는 ThirdPartyKit의 Configurations와 App의 Configurations가 일치하지 않기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/5d351f8f-8e99-4d83-b663-67aedc58bf54&quot; alt=&quot;12&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/fb7bbab2-55a0-4bc6-a1c1-3fa6c5e299cd&quot; alt=&quot;13&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/f48dccd7-8213-421d-b537-8a9c1668ace7&quot; alt=&quot;14&quot; width=&quot;1624&quot; /&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/5c2b2d82-7b62-43be-b821-b91c062e1903&quot; alt=&quot;15&quot; width=&quot;1032&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 Configurations의 Name은 일치해야 하고&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/31c8830f-7153-4f35-ad7d-7e4c6764d766&quot; alt=&quot;16&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App에 Configurations을 추가할 경우 참조하고 있는 모든 framework의 Configurations를 App과 맞춰줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App에 AnotherFramework 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnotherFramework에 QA Configurations를 추가 하기 전 App Build Fail&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/6f3c43c2-d0df-4406-a4f1-98a17c01a6be&quot; alt=&quot;17&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/aafa4508-e944-4da5-b14b-6f93a7b2b12b&quot; alt=&quot;18&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AnotherFramework에 QA Configurations를 추가한 후 App Build Success&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/a9c1d680-ad6c-4c5e-bcf9-a1019c735d73&quot; alt=&quot;19&quot; width=&quot;1624&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Example&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731826919230&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;TIL/Swift/블로그/Framework-Configurations at main &amp;middot; junbok97/TIL&quot; data-og-description=&quot;Today I Learn. Contribute to junbok97/TIL development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&quot; data-og-url=&quot;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bfmL7R/hyXzPTrAcv/MdlG8qIDjFpv79pYcS4Me1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b7VyE6/hyXzPeQZbP/za5ruC3X1SDaO9kKKNPKJK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ABV8B/hyXzVzmXmD/Peq8q1FoMxA29Q2Mjr8suk/img.png?width=3248&amp;amp;height=1978&amp;amp;face=0_0_3248_1978&quot;&gt;&lt;a href=&quot;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/junbok97/TIL/tree/main/Swift/%EB%B8%94%EB%A1%9C%EA%B7%B8/Framework-Configurations&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bfmL7R/hyXzPTrAcv/MdlG8qIDjFpv79pYcS4Me1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b7VyE6/hyXzPeQZbP/za5ruC3X1SDaO9kKKNPKJK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/ABV8B/hyXzVzmXmD/Peq8q1FoMxA29Q2Mjr8suk/img.png?width=3248&amp;amp;height=1978&amp;amp;face=0_0_3248_1978');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;TIL/Swift/블로그/Framework-Configurations at main &amp;middot; junbok97/TIL&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Today I Learn. Contribute to junbok97/TIL development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS/Xcode</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/372</guid>
      <comments>https://junbok97.tistory.com/372#entry372comment</comments>
      <pubDate>Sun, 17 Nov 2024 15:43:03 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC - 2023] Discover Observation in SwiftUI</title>
      <link>https://junbok97.tistory.com/371</link>
      <description>&lt;h1&gt;What is Observation ?&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/user-attachments/assets/b344c0cf-494c-42e9-a6eb-5b97c8bf439a&quot; alt=&quot;1&quot; width=&quot;794&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기본 데이터가 변경될 때 프레젠테이션을 업데이트하는 반응형 앱을 만듭니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Obserable &lt;/span&gt;은 normal type의 property를 observable type으로 마킹하여 UI가 data의 변화에 대응할 수 있게 해주는 매크로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Obserable&lt;/span&gt;&amp;nbsp;은 Swift 컴파일러에게 명령을 내려서 코드를 확장형 Observable type으로 바꿔 SwiftUI가 뷰를 동작하게 만듬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reference type, value type을 모두 지원&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Published &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@State &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Binding &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@ObservedObject &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;등 다른 프로퍼티 래퍼가 없어도 작동함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 property마다 &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Published&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@State&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Binding&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@ObservedObject &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;등을 붙인것을 data model에 붙인다고 이해하면 됨&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// Before
class FoodTruckModel {    
    var orders: [Order] = []
    var donuts = Donut.all
}

// After
@Observable class FoodTruckModel {    
    var orders: [Order] = []
    var donuts = Donut.all
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Examples&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 body가 실행될 때 Observable 타입에서 사용된 property의 모든 접근을 추적함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 추적 정보를 이용해 특정 인스턴스에서 property의 다음변화가 언제 일어날지를 예측&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PHC1O/btsOHYeVIQI/XbH2EjMkheuRkwlFNFaQH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PHC1O/btsOHYeVIQI/XbH2EjMkheuRkwlFNFaQH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PHC1O/btsOHYeVIQI/XbH2EjMkheuRkwlFNFaQH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPHC1O%2FbtsOHYeVIQI%2FXbH2EjMkheuRkwlFNFaQH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DonutMenu의 body에서 model.donuts에 접근이 이루어져 SwiftUI가 donuts를 추적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도넛 추가 버튼을 클릭해 donuts가 변경되면 DonutMenu View가 무효화 되고 변경사항에 맞춰 UI가 업데이트 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orders가 변경되었을 때는 DonutMenu View가 무효화 되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view의 body에서 접근하지 않고 있어 SwiftUI가 추적하지 않는 property이기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산 프로퍼티에도 같은 규칙이 적용 됨 &lt;b&gt;프로퍼티가 변하면 UI는 업데이트&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Observable class FoodTruckModel {    
  var orders: [Order] = []
  var donuts = Donut.all
  var orderCount: Int { orders.count }
}

struct DonutMenu: View {
  let model: FoodTruckModel

  var body: some View {
    List {
      Section(&quot;Donuts&quot;) {
        ForEach(model.donuts) { donut in
          Text(donut.name)
        }
        Button(&quot;Add new donut&quot;) {
          model.addDonut()
        }
      }
      Section(&quot;Orders&quot;) {
        LabeledContent(&quot;Count&quot;, value: &quot;\(model.orderCount)&quot;)
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 FoodTruckModel에 orderCount라는 연산 프로퍼티를 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view의 body에서 orderCount에 접근하여 orderCount를 추적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orderCount는 orders에 접근했으므로 orders가 변경되면 view가 업데이트&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Observable&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Observable 매크로를 사용하면 타입이 확장되어 Observation을 지원&lt;/li&gt;
&lt;li&gt;SwiftUI는 프로퍼티의 접근을 추적, Observation에서 해당 프로퍼티가 언제 변할지 관찰 가능&lt;/li&gt;
&lt;li&gt;추적이 가능해지면 프로퍼티가 변할때 UI는 view의 body를 재계산 하면 끝&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLlFwT/btsOJtLJSDp/hq5Vza4eCAu4Pdc19xjOB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLlFwT/btsOJtLJSDp/hq5Vza4eCAu4Pdc19xjOB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLlFwT/btsOJtLJSDp/hq5Vza4eCAu4Pdc19xjOB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLlFwT%2FbtsOJtLJSDp%2Fhq5Vza4eCAu4Pdc19xjOB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;SwiftUI property wrappers&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 핵심 프로퍼티 래퍼 3가지는 State, Environment, Bindable&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Observable에서 SwiftUI의 프로퍼티 래퍼가 필요한 경우&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@State&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;model안에 view 전용 상태를 저장해야할 때 사용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BD7p9/btsOH6DQKsV/PN3OlgXSGhI7o0kLKqbKK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BD7p9/btsOH6DQKsV/PN3OlgXSGhI7o0kLKqbKK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BD7p9/btsOH6DQKsV/PN3OlgXSGhI7o0kLKqbKK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBD7p9%2FbtsOH6DQKsV%2FPN3OlgXSGhI7o0kLKqbKK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;donutToAdd는 소속된 donutListView의 view의 수명 동안만 관리&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Environment&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Environment는 각 값을 어디에서든 접근 가능하게 만들어 줌 &amp;rarr; Global로 만들어 준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observable 타입이 Environment와 잘 맞는데 observable은 접근을 추적하여 업데이트 하기 때문에 여러곳에서 공유하는 Environment와 잘 맞음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDjc4s/btsOG5yYJde/jnjPefkm1PmmpcrPN6KSi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDjc4s/btsOG5yYJde/jnjPefkm1PmmpcrPN6KSi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDjc4s/btsOG5yYJde/jnjPefkm1PmmpcrPN6KSi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDjc4s%2FbtsOG5yYJde%2FjnjPefkm1PmmpcrPN6KSi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FoodTruckMenuView의 body를 불러올 때 Observable 타입의 Account의 userName에 접근하게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userName이 변경되면 FoodTruckMenuView의 body도 업데이트&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@Bindable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 최근에 생긴 프로퍼티 래퍼 굉장히 가볍다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 타입으로부터 바인딩이 생성되게 하는 기능만 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;래핑된 Bindable 프로퍼티에서 바인딩을 만드는 방법은 굉장히 쉬운데 $구문을 사용해 프로퍼티에 바인딩을 만들면 됨&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/myIgk/btsOHGFpJSs/2mKeWse0wPFsXyKEmO9KK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/myIgk/btsOHGFpJSs/2mKeWse0wPFsXyKEmO9KK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/myIgk/btsOHGFpJSs/2mKeWse0wPFsXyKEmO9KK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmyIgk%2FbtsOHGFpJSs%2F2mKeWse0wPFsXyKEmO9KK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 view로 있어야 한다면 &amp;rarr; &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@State&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 전역(Global)으로 있어야 한다면 &amp;rarr; &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Environment&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델에 바인딩만 필요하다면 &amp;rarr; &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Bindable&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋 다 아니라면 &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;var&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sOaMc/btsOGOR0QLV/kYl8IkQ3zkndORf1FQYaI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sOaMc/btsOGOR0QLV/kYl8IkQ3zkndORf1FQYaI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sOaMc/btsOGOR0QLV/kYl8IkQ3zkndORf1FQYaI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsOaMc%2FbtsOGOR0QLV%2FkYl8IkQ3zkndORf1FQYaI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Advanced uses&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 프로퍼티 접근을 인스턴스마다 추적하기 때문에 Observable 모델이 포함된 어떤 타입도 사용될 수 있음&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Example&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA0Rgk/btsOHBYAJPy/UCbPThNLX2u7tKzbnSQiwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA0Rgk/btsOHBYAJPy/UCbPThNLX2u7tKzbnSQiwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA0Rgk/btsOHBYAJPy/UCbPThNLX2u7tKzbnSQiwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA0Rgk%2FbtsOHBYAJPy%2FUCbPThNLX2u7tKzbnSQiwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;DonutList는 donuts 배열을 가지고 있는데 이 배열은 모두 &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Observable&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Donut의 name이 하나라도 변경되면 SwiftUI는 언제 뷰를 무효화할지를 파악하기 위해 해당 프로퍼티에 일어난 접근을 포착하여 추적함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Observable의 기본 규칙은 사용중인 프로퍼티가 변경되면 뷰도 업데이트 된다는 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 예외도 존재하는데 연산 프로퍼티에 사용된 저장 프로퍼티가 없다면 Observation에서 사용되기 전에 2단계를 더 거쳐가게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 관찰 대상인 프로퍼티가 observable 타입에 저장된 구성에 의해 변경되지 않았을 때만 필요한 조치임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우엔 프로퍼티에 접근과 변경이 언제 이루어졌는지 Observation에 전달해주기만 하면 됨&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Manual Observation&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CHxTO/btsOGzN7jyp/rPguy3kBxsptcFNqitklE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CHxTO/btsOGzN7jyp/rPguy3kBxsptcFNqitklE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CHxTO/btsOGzN7jyp/rPguy3kBxsptcFNqitklE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCHxTO%2FbtsOGzN7jyp%2FrPguy3kBxsptcFNqitklE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access point를 수동으로 고쳐서 someNonObservableLocation의 name을 저장하게 해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 직접 구현이 필요한 경우는 드문데 왜냐하면 보통 이런 모델의 프로퍼티는 다른 저장 프로퍼티처럼 구성되기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 고급 기능이 필요한 경우엔 직접 구현하면 됨&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Computed properties (연산 프로퍼티)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI는 구성의 변화를 바로 인식하는데 프로퍼티 접근을 바탕으로 Observable 타입을 추적하기 때문&lt;/li&gt;
&lt;li&gt;연산 프로퍼티에 다른 저장 프로퍼티를 사용하여도 Observation이 잘 작동하게 됨&lt;/li&gt;
&lt;li&gt;하지만 드물게 Observation이 작동하지 않으면 직접 구현하여 프로퍼티의 접근과 변경이 언제 일어나는지만 알려주면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQtcOD/btsOHCQIBxF/QQGGrJujzd6QzPypi4Utsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQtcOD/btsOHCQIBxF/QQGGrJujzd6QzPypi4Utsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQtcOD/btsOHCQIBxF/QQGGrJujzd6QzPypi4Utsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQtcOD%2FbtsOHCQIBxF%2FQQGGrJujzd6QzPypi4Utsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;938&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;@ObservableObject&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@ObservableObject &amp;rarr; @Observable&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@ObservableObject&lt;/span&gt; 에서 &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Observable&lt;/span&gt;&amp;nbsp;로 넘어가려면 대부분의 어노테이션을 삭제하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@State &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Environment&lt;/span&gt;&lt;/span&gt; &lt;span style=&quot;background-color: #e6f5ff; color: #333333; text-align: start;&quot;&gt;@Bindable&lt;/span&gt;&amp;nbsp;이 세가지로 단순화하면 됨&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// Before
class FoodTruckModel: ObservableObject {
    @Published var truck = Truck()

    @Published var orders: [Order] = []
    @Published var donuts = Donut.all

    var dailyOrderSummaries: [City.ID: [OrderSummary]] = [:]
    var monthlyOrderSummaries: [City.ID: [OrderSummary]] = [:]
    ...
}

struct AccountView: View {
    @ObservableObject var model: FoodTruckModel

    @EnvironmentObject private var accountStore: AccountStore
    @Environment(\.authorizationController) private var authorizationController

    @State private var isSingUpSheetPresented = false
    @State private var isSingOutSheetPresented = false
    ...
}

// After
@Observable
class FoodTruckModel {
    var truck = Truck()

    var orders: [Order] = []
    var donuts = Donut.all

    var dailyOrderSummaries: [City.ID: [OrderSummary]] = [:]
    var monthlyOrderSummaries: [City.ID: [OrderSummary]] = [:]
    ...
}

struct AccountView: View {
    var model: FoodTruckModel

    @Environment(AccountStore.self) private var accountStore: AccountStore
    @Environment(AuthorizationController.self) private var authorizationController

    @State private var isSingUpSheetPresented = false
    @State private var isSingOutSheetPresented = false
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@ObservableObject와 @Observable 차이점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드가 간결해짐&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observable에서는 대부분의 어노테이션이 필요가 없어지기 때문&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;옵셔널 가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observable은 옵셔널 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observableObject 은 옵셔널 불가능&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;// ObservableObject
class FoodTruckModel: ObservableObject {
    ...
}

struct AccountView: View {
    @ObservableObject var model: FoodTruckModel? // 컴파일 에러
}

// Observable
@Observable
class FoodTruckModel {
    ...
}

struct AccountView: View {
    var model: FoodTruckModel? // 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 향상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObservableObject 에서는 published property 가 변하면 뷰의 body에서 해당 프로퍼티를 읽고 있지 않아도 다시 그려짐&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;final class Food: ObservableObject {

    let name = &quot;Food Name&quot;
    @Published var isAvailable = true

    var cancellable: Cancellable?

    func connectTimer() {
        cancellable = Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
                self?.isAvailable.toggle()
            }
    }
}

struct FoodMenuView: View {
    @ObservedObject var food: Food

    var body: some View {
        Text(food.name)
            .background(.random)
            .onAppear {
                food.connectTimer()
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FoodMenuView의 body에서 isAvailable를 접근하고 있지 않음에도 isAvailable가 변경되면 뷰가 계속 업데이트 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable은 뷰의 body에서 프로퍼티를 읽어야 업데이트 되기 때문에 불필요한 업데이트가 일어나지 않아 성능이 향상&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;@Observable
final class Food: ObservableObject {

    let name = &quot;Food Name&quot;
    var isAvailable = true

    var cancellable: Cancellable?

    func connectTimer() {
        cancellable = Timer.publish(every: 0.1, on: .main, in: .default)
            .autoconnect()
            .sink { [weak self] _ in
                self?.isAvailable.toggle()
            }
    }
}

struct FoodMenuView: View {
    var food: Food

    var body: some View {
        Text(food.name)
            .background(.random)
            .onAppear {
                food.connectTimer()
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FoodMenuView의 body에서 isAvailable를 접근하고 있지 않기 때문에 isAvailable가 변경되어도 뷰가 업데이트 되지 않음&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10149/&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10149/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10166&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10166&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10167&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10167&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/observation&quot;&gt;https://developer.apple.com/documentation/observation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app&quot;&gt;https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro&quot;&gt;https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eunjin3786.tistory.com/580&quot;&gt;https://eunjin3786.tistory.com/580&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eunjin3786.tistory.com/582&quot;&gt;https://eunjin3786.tistory.com/582&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/WWDC</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/371</guid>
      <comments>https://junbok97.tistory.com/371#entry371comment</comments>
      <pubDate>Sat, 10 Aug 2024 20:26:49 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC - 2021] Demystify SwiftUI ‐ Dependency (3/3)</title>
      <link>https://junbok97.tistory.com/370</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/368&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Identity (1/3)&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/369&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Lifetime (2/3)&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/370&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Dependency (3/3)&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;  SwiftUI는 우리의 코드에서 어떤걸 볼까 ?&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Identity&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 여러번의 업데이트 동안 &lt;b&gt;요소(element)&lt;/b&gt; 가 동일한것인지 다른것인지 &lt;b&gt;인식(recognize)&lt;/b&gt; 하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Lifetime&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 뷰와 데이터의 &lt;b&gt;존재(existence)&lt;/b&gt; 를 시간에 따라 추적하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Dependency&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 인터페이스를 업데이트해야하는 &lt;b&gt;시기(when)&lt;/b&gt; 와 이유 &lt;b&gt;(why)&lt;/b&gt; 를 이해하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 개념을 통해 SwiftUI는 &lt;b&gt;변경해야할 사항(what needs to change)&lt;/b&gt;, &lt;b&gt;시기(when)&lt;/b&gt;, &lt;b&gt;방법(how)&lt;/b&gt; 을 &lt;b&gt;결정&lt;/b&gt;할 수 있어 결과적으로 동적인 사용자 인터페이스가 화면에 표시&lt;/p&gt;
&lt;h1&gt;How SwiftUI updates the UI ?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The goal is to give you a better mental model for how to structure SwiftUI code.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Dependency&lt;/b&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dependency는 단순히 view에 대한 input일 뿐임&lt;/li&gt;
&lt;li&gt;dependency가 변경되면 view는 새로운 body를 생성해야 함&lt;/li&gt;
&lt;li&gt;body는 뷰 계층 구조를 생성하는 곳&lt;/li&gt;
&lt;li&gt;action은 뷰의 dependencies에 대한 변경의 trigger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qbRxA/btsOHgmDteW/XnHmzUm2dMhXZ42qCgvbo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qbRxA/btsOHgmDteW/XnHmzUm2dMhXZ42qCgvbo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qbRxA/btsOHgmDteW/XnHmzUm2dMhXZ42qCgvbo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqbRxA%2FbtsOHgmDteW%2FXnHmzUm2dMhXZ42qCgvbo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 도식화하면 아래와 같음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGBtC9/btsOIAdBFfA/IHbtPsJ0wEC2Km06IPdnJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGBtC9/btsOIAdBFfA/IHbtPsJ0wEC2Km06IPdnJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGBtC9/btsOIAdBFfA/IHbtPsJ0wEC2Km06IPdnJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGBtC9%2FbtsOIAdBFfA%2FIHbtPsJ0wEC2Km06IPdnJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 탭하면 강아지에게 보상을 주는 액션이 전달됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 강아지는 변화가 생김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dependecy가 변화되었기 때문에 DogView는 새로운 body를 그려야 함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5OJ7K/btsOG5yV9dN/8eEKIOMAOoWXaOnT3Jj9JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5OJ7K/btsOG5yV9dN/8eEKIOMAOoWXaOnT3Jj9JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5OJ7K/btsOG5yV9dN/8eEKIOMAOoWXaOnT3Jj9JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5OJ7K%2FbtsOG5yV9dN%2F8eEKIOMAOoWXaOnT3Jj9JK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 계층 구조에 초점을 맞춰 그래프를 조금 더 단순화 하여 확인해보면 DogView에 dependecy를 추가해도 여전히 tree로 보임&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FecYB/btsOGVi4tCZ/uBVSE0wb98nN7nWUbxxtK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FecYB/btsOGVi4tCZ/uBVSE0wb98nN7nWUbxxtK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FecYB/btsOGVi4tCZ/uBVSE0wb98nN7nWUbxxtK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFecYB%2FbtsOGVi4tCZ%2FuBVSE0wb98nN7nWUbxxtK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DogView뿐 아니라 다른곳에서도 dependecy가 추가될 수 있음 SwiftUI는 자체 dependecy를 가질 수도 있음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfmmc6/btsOHmUHwVc/dVhkfwCqKEXS31TY7ZlTt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfmmc6/btsOHmUHwVc/dVhkfwCqKEXS31TY7ZlTt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfmmc6/btsOHmUHwVc/dVhkfwCqKEXS31TY7ZlTt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfmmc6%2FbtsOHmUHwVc%2FdVhkfwCqKEXS31TY7ZlTt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 아래처럼 동일한 state나 data에 다수의 view가 의존할 수 도 있음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDLeEY/btsOHkihVid/o5idJ6Mw0NvDxNmDqeBkOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDLeEY/btsOHkihVid/o5idJ6Mw0NvDxNmDqeBkOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDLeEY/btsOHkihVid/o5idJ6Mw0NvDxNmDqeBkOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDLeEY%2FbtsOHkihVid%2Fo5idJ6Mw0NvDxNmDqeBkOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까진 tree처럼 보이지만 선이 겹치는 것을 피하기 위해 재배치 해보면 실제론 tree가 아니라 greaph 임을 알 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 &lt;b&gt;dependecy graph&lt;/b&gt; 라고 부름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조는 SwiftUI가 새로운 body가 필요한 view만 효율적으로 업데이트 할 수 있도록 해주기 때문에 중요함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxzphj/btsOJocEKAB/UrMavK0y02CKKPfEn4YrR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxzphj/btsOJocEKAB/UrMavK0y02CKKPfEn4YrR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxzphj/btsOJocEKAB/UrMavK0y02CKKPfEn4YrR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxzphj%2FbtsOJocEKAB%2FUrMavK0y02CKKPfEn4YrR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 하단의 dependency를 예로 들면 이 dependency에 영향을 받는 view는 2개가 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dependency graph의 비밀은 dependency가 변경되면 dependency에 영향을 받는 뷰만 무효화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 무효화된 각 view의 body를 호출하여 각 view에 대한 새로운 body value를 인스턴스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해 무효화된 view와 연관된 더 많은 dependency가 변경될 수 있지만 &lt;b&gt;항상 그런 것은 아님&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 view는 value type이기 때문에 이를 효율 적으로 비교하여 view의 올바른 하위 집합만 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view의 value(struct)는 수명이 매우 짧아 값 비교에만 사용되지만 view 자체 수명은 이보다 더 김&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrdzn/btsOGTyNvzX/bZ1xs9kbNiQlAXNuvnVNVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrdzn/btsOGTyNvzX/bZ1xs9kbNiQlAXNuvnVNVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrdzn/btsOGTyNvzX/bZ1xs9kbNiQlAXNuvnVNVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqrdzn%2FbtsOGTyNvzX%2FbZ1xs9kbNiQlAXNuvnVNVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaDeOh/btsOIyGUHlT/zGiBOCm3TKKTK8ZEy3bDKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaDeOh/btsOIyGUHlT/zGiBOCm3TKKTK8ZEy3bDKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaDeOh/btsOIyGUHlT/zGiBOCm3TKKTK8ZEy3bDKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaDeOh%2FbtsOIyGUHlT%2FzGiBOCm3TKKTK8ZEy3bDKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KakWd/btsOHGFnzCF/WQIb5yNeWG6x0EpDGHkEOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KakWd/btsOHGFnzCF/WQIb5yNeWG6x0EpDGHkEOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KakWd/btsOHGFnzCF/WQIb5yNeWG6x0EpDGHkEOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKakWd%2FbtsOHGFnzCF%2FWQIb5yNeWG6x0EpDGHkEOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;Identity is the backbone of the graph&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;identity는 dependecy graph의 핵심(backbone)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;모든 view는 &lt;b&gt;명시적(Explicit)&lt;/b&gt; 또는 &lt;b&gt;구조적(Structural)&lt;/b&gt; identity를 가지고 있음&lt;/li&gt;
&lt;li&gt;이 identity를 통해 SwiftUI는 변경사항을 올바른 View에 전달하고 UI를 효율적으로 업데이트 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btXZtq/btsOJseXHGx/2QnHMBHxaw9EPYpl79ED3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btXZtq/btsOJseXHGx/2QnHMBHxaw9EPYpl79ED3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btXZtq/btsOJseXHGx/2QnHMBHxaw9EPYpl79ED3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtXZtq%2FbtsOJseXHGx%2F2QnHMBHxaw9EPYpl79ED3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1204&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tJpze/btsOG2WxOpa/crVi6HsbDdZTqKd2tYIOr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tJpze/btsOG2WxOpa/crVi6HsbDdZTqKd2tYIOr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tJpze/btsOG2WxOpa/crVi6HsbDdZTqKd2tYIOr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtJpze%2FbtsOG2WxOpa%2FcrVi6HsbDdZTqKd2tYIOr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1312&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;identity의 활용을 향상시키는 방법&lt;/h1&gt;
&lt;h1&gt;Explicit Identity 사용 시 고려사항&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Identifier의 안정성(stablilty)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰의 수명은 id의 지속시간이므로 identity의 안정성(stablilty)이 중요함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정성이 강한 identity를 갖게 되면 SwiftUI는 view에 대한 storage를 지속적으로 생성하거나&lt;br /&gt;dependency graph를 업데이트 하지 않아도 되기 때문에 성능(performance) 향상에 도움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 SwiftUI는 identity를 사용하여 persisted storage를 관리하기 때문에 state 손실 방지에도 도움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만 안정성이 좋다고 해서 유일한 것은 아님&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpvGb2/btsOHGMapPi/fs32h26H6jDvbfBV0ODRtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpvGb2/btsOHGMapPi/fs32h26H6jDvbfBV0ODRtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpvGb2/btsOHGMapPi/fs32h26H6jDvbfBV0ODRtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpvGb2%2FbtsOHGMapPi%2Ffs32h26H6jDvbfBV0ODRtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1137&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Identifier의 고유성(uniqueness)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 identity의 또 다른 조건은 &lt;b&gt;고유성(uniqueness)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 각각의 identity는 단 하나의 뷰(single view)에만 매핑 되어야 함 (1:1 매치)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;933&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyBr3/btsOGW3lwSp/me4YBnXyBCkqmizKFiX8m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyBr3/btsOGW3lwSp/me4YBnXyBCkqmizKFiX8m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyBr3/btsOGW3lwSp/me4YBnXyBCkqmizKFiX8m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkyBr3%2FbtsOGW3lwSp%2Fme4YBnXyBCkqmizKFiX8m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;933&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;933&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;Example of Explicit identity&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0Gjs/btsOGRugdzk/3pdGtXdKXPKPqNDiiz9Oc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0Gjs/btsOGRugdzk/3pdGtXdKXPKPqNDiiz9Oc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0Gjs/btsOGRugdzk/3pdGtXdKXPKPqNDiiz9Oc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0Gjs%2FbtsOGRugdzk%2F3pdGtXdKXPKPqNDiiz9Oc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에는 버그가 있는데 바로 id를 UUID()로 초기화 해주는 것임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경될때마다 새로운 id를 얻기 때문에 안정적이지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWjScQ/btsOJnLBu8O/EIQGJIZFntzIVdulO1PLD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWjScQ/btsOJnLBu8O/EIQGJIZFntzIVdulO1PLD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWjScQ/btsOJnLBu8O/EIQGJIZFntzIVdulO1PLD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWjScQ%2FbtsOJnLBu8O%2FEIQGJIZFntzIVdulO1PLD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index로 바꾸어도 안정적이지 않음 왜냐하면 index는 데이터의 추가나 삭제에 의해 변경될 수 있기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEHse9/btsOJpCEs9t/IDF5AHlwjVLksRIUroXFzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEHse9/btsOJpCEs9t/IDF5AHlwjVLksRIUroXFzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEHse9/btsOJpCEs9t/IDF5AHlwjVLksRIUroXFzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEHse9%2FbtsOJpCEs9t%2FIDF5AHlwjVLksRIUroXFzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 database에서 가져온 것처럼 안정적인 속성에서 파생된 안정적인 id를 사용해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biOxHg/btsOIZj6jto/RljasjK2lo9K7YCexQL9z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biOxHg/btsOIZj6jto/RljasjK2lo9K7YCexQL9z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biOxHg/btsOIZj6jto/RljasjK2lo9K7YCexQL9z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiOxHg%2FbtsOIZj6jto%2FRljasjK2lo9K7YCexQL9z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name은 unique하지 않기 때문에 id로는 적합하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Tkes/btsOJpo7Sq6/TLMvl3MU272rlxaDzwU7zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Tkes/btsOJpo7Sq6/TLMvl3MU272rlxaDzwU7zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Tkes/btsOJpo7Sq6/TLMvl3MU272rlxaDzwU7zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Tkes%2FbtsOJpo7Sq6%2FTLMvl3MU272rlxaDzwU7zK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;serialNumber 같은 unique한 id를 사용하는 것이 더 나은 애니메이션과 퍼포먼스를 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Explicit identity의 주의점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계산 속성에서 random identifier(ex: UUID())를 사용할 때는 주의&lt;/li&gt;
&lt;li&gt;일반적으로 모든 identity는 안정적이기를 원함&lt;/li&gt;
&lt;li&gt;identity는 시간이 지나도 변경되어선 안됨&lt;/li&gt;
&lt;li&gt;새로운 identity는 새로운 lifetime을 가지는 새로운 item을 보여줌&lt;/li&gt;
&lt;li&gt;identity는 고유해야함 identity를 공유하면 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 이러한 속성을 사용하여 버그없이 원할하게 실행되도록 함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb58t1/btsOH64W2NL/CWNh0Js5NPDooyZwHwa6O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb58t1/btsOH64W2NL/CWNh0Js5NPDooyZwHwa6O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb58t1/btsOH64W2NL/CWNh0Js5NPDooyZwHwa6O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb58t1%2FbtsOH64W2NL%2FCWNh0Js5NPDooyZwHwa6O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1165&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;Structural&lt;/b&gt; Identity 사용 시 고려사항&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;Example of Structural&lt;/b&gt; Identity&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPuuU7/btsOHDITVzc/RZshid5A73sGgv6T3clkq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPuuU7/btsOHDITVzc/RZshid5A73sGgv6T3clkq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPuuU7/btsOHDITVzc/RZshid5A73sGgv6T3clkq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPuuU7%2FbtsOHDITVzc%2FRZshid5A73sGgv6T3clkq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if 구문을 통해 분기 처리되었기에 content는 구조적으로 각각 다른 id를 가진 2개의 view로 취급함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 단일 id를 가지는 하나의 content로 처리하고 싶다면 ??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MiD8t/btsOHl2AdJs/Kaex2BWhhNuGXOE77BwAq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MiD8t/btsOHl2AdJs/Kaex2BWhhNuGXOE77BwAq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MiD8t/btsOHl2AdJs/Kaex2BWhhNuGXOE77BwAq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMiD8t%2FbtsOHl2AdJs%2FKaex2BWhhNuGXOE77BwAq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modifier 내에서 컨디션에 따라 값을 변경하여 branch를 제거 해줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zMB1U/btsOGYfIVjk/i8oViLd337jXnPmZeDkJJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zMB1U/btsOGYfIVjk/i8oViLd337jXnPmZeDkJJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zMB1U/btsOGYfIVjk/i8oViLd337jXnPmZeDkJJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzMB1U%2FbtsOGYfIVjk%2Fi8oViLd337jXnPmZeDkJJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;704&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 결과값이 false면 불투명도가 1이기 때문에 아무 변화도 일어나지 않는데 이러한 modifier을 비활성화 수정자(Insert modifier)라고 부름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 modifier은 저렴하기 때문에 Inser modifier의 비용은 거의 없다고 봐도 무방함&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;Structural&lt;/b&gt; Identity의 주의점&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;branch를 불필요하게 사용하면 성능 저하, 애니메이션 오류, state 손실 등이 발생&lt;/li&gt;
&lt;li&gt;branch 도입 시에는 여러 view를 나타내는지 아니면 단일 view의 다른 state를 나타내는지 것인지 고려&lt;/li&gt;
&lt;li&gt;단일 view를 나타내는 것이라면 보통은 Insert modifier을 사용하는 것이 더 나음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq674x/btsOGMmicl9/PL5RU8D7dKBQyOegSMbjXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq674x/btsOGMmicl9/PL5RU8D7dKBQyOegSMbjXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq674x/btsOGMmicl9/PL5RU8D7dKBQyOegSMbjXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq674x%2FbtsOGMmicl9%2FPL5RU8D7dKBQyOegSMbjXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1231&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10022&quot;&gt;https://developer.apple.com/videos/play/wwdc2021/10022&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/WWDC</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/370</guid>
      <comments>https://junbok97.tistory.com/370#entry370comment</comments>
      <pubDate>Wed, 31 Jul 2024 01:25:55 +0900</pubDate>
    </item>
    <item>
      <title>[WWDC - 2021] Demystify SwiftUI ‐ Lifetime (2/3)</title>
      <link>https://junbok97.tistory.com/369</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/368&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Identity (1/3)&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/369&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Lifetime (2/3)&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://junbok97.tistory.com/370&quot;&gt;[WWDC - 2021] Demystify SwiftUI ‐ Dependency (3/3)&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;  SwiftUI는 우리의 코드에서 어떤걸 볼까 ?&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Identity&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 여러번의 업데이트 동안 &lt;b&gt;요소(element)&lt;/b&gt;가 동일한것인지 다른것인지 &lt;b&gt;인식(recognize)&lt;/b&gt;하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Lifetime&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 뷰와 데이터의 &lt;b&gt;존재(existence)&lt;/b&gt;를 시간에 따라 추적하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;Dependency&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI가 인터페이스를 업데이트해야하는 &lt;b&gt;시기(when)&lt;/b&gt;와 이유&lt;b&gt;(why)&lt;/b&gt;를 이해하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 개념을 통해 SwiftUI는 &lt;b&gt;변경해야할 사항(what needs to change)&lt;/b&gt;, &lt;b&gt;시기(when)&lt;/b&gt;, &lt;b&gt;방법(how)&lt;/b&gt; 을 &lt;b&gt;결정&lt;/b&gt;할 수 있어 결과적으로 동적인 사용자 인터페이스가 화면에 표시&lt;/p&gt;
&lt;h1&gt;Lifetime&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지난번 학습을 통해 SwiftUI가 identity를 사용하여 뷰를 식별하는 방법을 이해&lt;/li&gt;
&lt;li&gt;이번엔 identity가 뷰와 데이터의 lifetime과 어떻게 연결되는지를 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;State(Value)는 identity가 아니다&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wip3w/btsOHhFTEkg/h7y2k5qpYUXodTtYxdSBw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wip3w/btsOHhFTEkg/h7y2k5qpYUXodTtYxdSBw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wip3w/btsOHhFTEkg/h7y2k5qpYUXodTtYxdSBw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwip3w%2FbtsOHhFTEkg%2Fh7y2k5qpYUXodTtYxdSBw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테세우스라는 이름(identity)을 가진 고양이가 있을 때 상태(State)가 변할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간에 따라 테세우스는 졸고 있을 수도, 깨어 있을 수도 있고, 짜증을 낼 수도있음 (State가 변화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 변화해도 테세우스는 테세우스임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Why? State(Value)가 변화하는거지 identity가 변하는 것이 아니기 때문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이것이 view의 identity와 lifetime을 연결하는 본질(essence)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vuSI2/btsOGPwnnfE/1fbM1ldGare9asKkzr5YhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vuSI2/btsOGPwnnfE/1fbM1ldGare9asKkzr5YhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vuSI2/btsOGPwnnfE/1fbM1ldGare9asKkzr5YhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvuSI2%2FbtsOGPwnnfE%2F1fbM1ldGare9asKkzr5YhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테세우스 처럼 view는 &lt;b&gt;identity가 동일하다면 state(value)가 달라도 동일한 view로 인식&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;identity를 사용하면 시간에 따라 변화하는 다양한 Value를 안정적으로 정의 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연속성을 가진다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;View value &amp;ne; View Identity&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ2CkT/btsOHZLzJZK/Y1H2wqfZtuiGQPnZsqq3R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ2CkT/btsOHZLzJZK/Y1H2wqfZtuiGQPnZsqq3R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ2CkT/btsOHZLzJZK/Y1H2wqfZtuiGQPnZsqq3R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ2CkT%2FbtsOHZLzJZK%2FY1H2wqfZtuiGQPnZsqq3R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xIbll/btsOJjCzryL/Y9IAmCuxAiLkVJFlAzQ1Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xIbll/btsOJjCzryL/Y9IAmCuxAiLkVJFlAzQ1Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xIbll/btsOJjCzryL/Y9IAmCuxAiLkVJFlAzQ1Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxIbll%2FbtsOJjCzryL%2FY9IAmCuxAiLkVJFlAzQ1Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 body가 실행되어 &lt;code&gt;PurrDecibelView(intensity: 25)&lt;/code&gt; 가 생성되고 나중에 &lt;code&gt;PurrDecibelView(intensity: 50)&lt;/code&gt; 이 생성되면 SwiftUI는 뷰가 변경되었는지 비교하기 위해 값의 복사본을 유지한 뒤 비교한 후에 소멸 시킴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xuSCz/btsOI7IZ1Bd/sl19kF72Bmnog2OX0MKoY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xuSCz/btsOI7IZ1Bd/sl19kF72Bmnog2OX0MKoY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xuSCz/btsOI7IZ1Bd/sl19kF72Bmnog2OX0MKoY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxuSCz%2FbtsOI7IZ1Bd%2Fsl19kF72Bmnog2OX0MKoY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주목해야할 점은 view value와 view identity는 다르다는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view value는 일시적이기 때문에 view lifetime에 의존해선 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 identity는 컨트롤 가능함 How?&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;A view&amp;rsquo;s lifetime is the duration of the identity&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEtqrz/btsOHeP3h31/3SkXokhwbil3R0ZgGQaPVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEtqrz/btsOHeP3h31/3SkXokhwbil3R0ZgGQaPVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEtqrz/btsOHeP3h31/3SkXokhwbil3R0ZgGQaPVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEtqrz%2FbtsOHeP3h31%2F3SkXokhwbil3R0ZgGQaPVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view가 처음 생성되고 onAppear될때 SwiftUI는 identity를 할당 (Explicit or Structural)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간에 따라 view에 새로운 value들이 생성될 수 있지만(state의 변경) identity는 동일하기 때문에 SwiftUI는 동일한 view로 인식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view의 lifetime이 종료될때는 identity가 바뀌거나 view가 제거될 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 view의 lifetime은 view의 identity 지속시간이 됨&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVKHkb/btsOHhlxh2U/ZBYCApWfRfWhFaXjYLgbnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVKHkb/btsOHhlxh2U/ZBYCApWfRfWhFaXjYLgbnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVKHkb/btsOHhlxh2U/ZBYCApWfRfWhFaXjYLgbnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVKHkb%2FbtsOHhlxh2U%2FZBYCApWfRfWhFaXjYLgbnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;State lifetime = View lifetime&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJInpY/btsOGNeoP6q/xwDLhFetkCXBze9Ow5EI71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJInpY/btsOGNeoP6q/xwDLhFetkCXBze9Ow5EI71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJInpY/btsOGNeoP6q/xwDLhFetkCXBze9Ow5EI71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJInpY%2FbtsOGNeoP6q%2FxwDLhFetkCXBze9Ow5EI71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WTuFy/btsOJucKp9v/qUsG3UED05VqkbjfKMybEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WTuFy/btsOJucKp9v/qUsG3UED05VqkbjfKMybEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WTuFy/btsOJucKp9v/qUsG3UED05VqkbjfKMybEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWTuFy%2FbtsOJucKp9v%2FqUsG3UED05VqkbjfKMybEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI가 view에서 &lt;code&gt;@state&lt;/code&gt;, &lt;code&gt;@stateObject&lt;/code&gt; 를 보면 해당 데이터를 view의 lifetime동안 유지해야한다는것을 인식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;code&gt;@state&lt;/code&gt;, &lt;code&gt;@stateObject&lt;/code&gt; 는 view의 identity와 연결된 영구적인 저장소(persistent storage)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view의 ID가 처음 생성될때 SwiftUI는 &lt;code&gt;@state&lt;/code&gt;, &lt;code&gt;@stateObject&lt;/code&gt; 의 초기값을 사용하여 메모리에 저장소(storage)를 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ucobM/btsOIoEmS0n/Uqkzm6OkPAFMqbaHzNWlC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ucobM/btsOIoEmS0n/Uqkzm6OkPAFMqbaHzNWlC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ucobM/btsOIoEmS0n/Uqkzm6OkPAFMqbaHzNWlC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FucobM%2FbtsOIoEmS0n%2FUqkzm6OkPAFMqbaHzNWlC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;title state&lt;/code&gt; 를 예시로 보면 SwiftUI는 storage가 변경되어 view의 body가 재호출(re-evaluated)되어도 view의 lifetime동안 storage를 유지(persist)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/21FfS/btsOG3HX2ai/7eI5JkMVkr2nUQ4NhiGfDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/21FfS/btsOG3HX2ai/7eI5JkMVkr2nUQ4NhiGfDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/21FfS/btsOG3HX2ai/7eI5JkMVkr2nUQ4NhiGfDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F21FfS%2FbtsOG3HX2ai%2F7eI5JkMVkr2nUQ4NhiGfDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 시간 학습을 통해 위 예제 코드에는 두개의 뷰에 다른 Structural Identity가 할당됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번에는 이것이 애니메이션에 어떤 영향을 미치는지에 대해서만 학습했지만 이것은 state 지속성에도 영향을 미침&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5j2S/btsOHCwnt50/GvB1cnK5UeZItxbhFKJno1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5j2S/btsOHCwnt50/GvB1cnK5UeZItxbhFKJno1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5j2S/btsOHCwnt50/GvB1cnK5UeZItxbhFKJno1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5j2S%2FbtsOHCwnt50%2FGvB1cnK5UeZItxbhFKJno1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 body가 처음 동작하게 되면 실제 분기에서 &lt;code&gt;dayTime&lt;/code&gt; 이 &lt;code&gt;True&lt;/code&gt; 일때 SwiftUI는 초기값을 사용해 영구 저장소를 할당함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 view의 lifetime동안 다양한 작업에 의해 state가 바뀌어도 이 저장소를 유지함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJrRvi/btsOHnlKAqg/08HEtY7iM0KVrJNjKBzXz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJrRvi/btsOHnlKAqg/08HEtY7iM0KVrJNjKBzXz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJrRvi/btsOHnlKAqg/08HEtY7iM0KVrJNjKBzXz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJrRvi%2FbtsOHnlKAqg%2F08HEtY7iM0KVrJNjKBzXz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;code&gt;dayTime&lt;/code&gt; 이 &lt;code&gt;False&lt;/code&gt; 로 변경되어 분기가 바뀌게 되면 SwiftUI는 다른 identity를 가진 view라고 인식하게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 다른 view라고 인식했기 때문에 초기값을 사용하여 새로운 영구저장소를 할당하고 기존에 있던 영구 저장소는 할당 취소 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjlV3m/btsOGWhUkxl/6jK0KxIkqkK9nLuYHuqhF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjlV3m/btsOGWhUkxl/6jK0KxIkqkK9nLuYHuqhF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjlV3m/btsOGWhUkxl/6jK0KxIkqkK9nLuYHuqhF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjlV3m%2FbtsOGWhUkxl%2F6jK0KxIkqkK9nLuYHuqhF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다시 원래 분기로 돌아간다면 ?? 아까와 같은 상황이 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 identity이므로 초기값을 사용하여 새로운 저장소를 할당하고 기존의 저장소를 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 identity가 바뀔때마다 state는 대체(replace)된다는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmd7AK/btsOI794ZpU/STcZN6MkPaCAbGhsVIhjC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmd7AK/btsOI794ZpU/STcZN6MkPaCAbGhsVIhjC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmd7AK/btsOI794ZpU/STcZN6MkPaCAbGhsVIhjC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmd7AK%2FbtsOI794ZpU%2FSTcZN6MkPaCAbGhsVIhjC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9KxCI/btsOH8aA5T0/eOKTN9GhoTFyWRzKTdh8fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9KxCI/btsOH8aA5T0/eOKTN9GhoTFyWRzKTdh8fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9KxCI/btsOH8aA5T0/eOKTN9GhoTFyWRzKTdh8fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9KxCI%2FbtsOH8aA5T0%2FeOKTN9GhoTFyWRzKTdh8fK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identity가 변경되면 state(storage)가 교체(replaced)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identity는 view의 lifetime에 연관되어 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state의 persistence는 view의 lifetime와 연관이 있다는 것&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTqklP/btsOI8upZ4F/0fj50vXqUFwUTvxRgkVUC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTqklP/btsOI8upZ4F/0fj50vXqUFwUTvxRgkVUC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTqklP/btsOI8upZ4F/0fj50vXqUFwUTvxRgkVUC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTqklP%2FbtsOI8upZ4F%2F0fj50vXqUFwUTvxRgkVUC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;Lifetime&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;view value는 일시적이기 때문에 view value의 lifetime에 의존하면 안됨&lt;/li&gt;
&lt;li&gt;view의 lifetime이 view의 identity의 지속시간임&lt;/li&gt;
&lt;li&gt;view의 identity를 제어할 수 있기 때문에 identity를 사용하여 state lifetime의 범위를 명확히 지정 가능&lt;/li&gt;
&lt;li&gt;SwiftUI는 데이터 기반 components에 대해 &lt;code&gt;Identifiable&lt;/code&gt; 프로토콜을 최대한 활용하기 때문에 안정적인 identifier를 선택하는것이 매우 중요&lt;/li&gt;
&lt;li&gt;A view's value is short-lived&lt;/li&gt;
&lt;li&gt;A view's lifetime is the duration of its identity&lt;/li&gt;
&lt;li&gt;Persistence of state is tied to lifetime&lt;/li&gt;
&lt;li&gt;Provide a stable identity for your data&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;참고&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10022&quot;&gt;https://developer.apple.com/videos/play/wwdc2021/10022&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS/WWDC</category>
      <author>물복딱복준복</author>
      <guid isPermaLink="true">https://junbok97.tistory.com/369</guid>
      <comments>https://junbok97.tistory.com/369#entry369comment</comments>
      <pubDate>Mon, 29 Jul 2024 17:16:54 +0900</pubDate>
    </item>
  </channel>
</rss>