<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>FramiOS  </title>
    <link>https://framios.tistory.com/</link>
    <description>UIKit으로 개발하다가 현재는 SwiftUI로 서비스 개발하고 있는 Fram입니다
SwiftUI와 Combine에 대해 알게 된 것과 기술을 공유하는 블로그 입니다.
주의 : 적었던거 또 적을 수 있음</description>
    <language>ko</language>
    <pubDate>Thu, 21 May 2026 23:48:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>fram</managingEditor>
    <image>
      <title>FramiOS  </title>
      <url>https://tistory1.daumcdn.net/tistory/5938436/attach/8fd750f5271240a19f332c02bbde581b</url>
      <link>https://framios.tistory.com</link>
    </image>
    <item>
      <title>StringTokenizer 사용해서 문자열의 단어 갯수 구하기</title>
      <link>https://framios.tistory.com/120</link>
      <description>&lt;pre id=&quot;code_1709887614658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(),&quot; &quot;);
        System.out.print(st.countTokens());
    }
}&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;StringTokenizer에는 countTokens()라는 메소드가 있다. 이를 사용해서 문자열에서의 단어 갯수를 카운트 해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;words-3583294_640.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNQsqI/btsFFRIxMWY/Cjt8AV25omS5Kh0Rq0kwyk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNQsqI/btsFFRIxMWY/Cjt8AV25omS5Kh0Rq0kwyk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNQsqI/btsFFRIxMWY/Cjt8AV25omS5Kh0Rq0kwyk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNQsqI%2FbtsFFRIxMWY%2FCjt8AV25omS5Kh0Rq0kwyk%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;559&quot; height=&quot;373&quot; data-filename=&quot;words-3583294_640.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Coding Test</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/120</guid>
      <comments>https://framios.tistory.com/120#entry120comment</comments>
      <pubDate>Fri, 8 Mar 2024 17:48:16 +0900</pubDate>
    </item>
    <item>
      <title>이진 탐색 (Binary Search) 시간 복잡도가 왜 logn일까?</title>
      <link>https://framios.tistory.com/119</link>
      <description>&lt;pre id=&quot;code_1709825204189&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public static int search(int[] nums, int target) {
        int start = 0, end = nums.length - 1;

        while(start &amp;lt;= end) {
            int mid = (start + end) / 2;

            if(nums[mid] == target) {
                return mid;
            }

            if(nums[mid] &amp;lt; target) {
                start = mid + 1;
            } else {
                end = end - 1;
            }
        }

        return -1; // target 없음
    }&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;이진 탐색은 divide and conquer의 개념을 가지고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도는 O(logn)이 되는데, 처음에는 탐색해야 할 데이터가 N개라면 N개 모두가 탐색 범위가 된다. 그 이후&amp;nbsp;&lt;br /&gt;다음 탐색에서 탐색 범위는 절반으로 줄어 N/2 가 되고 그 다음 탐색에서 탐색 범위는 또 절반으로 줄어 N/4가 되고 그 다음은 N/8, N/16 이 되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 탐색해야 하는 횟수가 k 라면 N개의 데이터를 탐색하는 데 N = 2^k가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k의 값을 구하려면 k = log2(N)이라 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그가 약한 사람들을 위한 팁&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;log2(8) 은 2를 몇 제곱 해야 8이라는 값을 얻을 수 있느냐는 뜻이다. log2(2^3) 이며 이는 3log2(2)로 표현 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;log2(2), log3(3) ... 등은 값이 일이므로 3log2(2) = 3 이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 2^3 = 8 이고 3 = log2(8)이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 탐색 범위는 2^k = N&amp;nbsp; 이므로 이를 로그로 표현하면 k = log2(N)이라고 할 수 있다!&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-filename=&quot;nature-4829886_640.jpg&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxFVrv/btsFE3vAoJs/MKbh6KkKCKvCvdAKJNrnF1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxFVrv/btsFE3vAoJs/MKbh6KkKCKvCvdAKJNrnF1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxFVrv/btsFE3vAoJs/MKbh6KkKCKvCvdAKJNrnF1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxFVrv%2FbtsFE3vAoJs%2FMKbh6KkKCKvCvdAKJNrnF1%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;250&quot; height=&quot;375&quot; data-filename=&quot;nature-4829886_640.jpg&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Coding Test</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/119</guid>
      <comments>https://framios.tistory.com/119#entry119comment</comments>
      <pubDate>Fri, 8 Mar 2024 00:34:01 +0900</pubDate>
    </item>
    <item>
      <title>VIA로 키크론 키 맵핑 하기</title>
      <link>https://framios.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://usevia.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://usevia.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706155693568&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;VIA&quot; data-og-description=&quot;Your keyboard's best friend&quot; data-og-host=&quot;usevia.app&quot; data-og-source-url=&quot;https://usevia.app/&quot; data-og-url=&quot;https://usevia.app/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LEHsf/hyU8ZZBj4N/PYgeBeIm50d0R3rwjvTtM1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/ubuA9/hyVcfNeOvj/cDBLcM9vcEWW6iPf9hoDs1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024&quot;&gt;&lt;a href=&quot;https://usevia.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://usevia.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LEHsf/hyU8ZZBj4N/PYgeBeIm50d0R3rwjvTtM1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/ubuA9/hyVcfNeOvj/cDBLcM9vcEWW6iPf9hoDs1/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024');&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;VIA&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Your keyboard's best friend&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;usevia.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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-filename=&quot;스크린샷 2024-01-25 오후 1.09.11.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvnnZb/btsDUVfzCcU/PQwvoeC55Kjb8SPxHEkAOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvnnZb/btsDUVfzCcU/PQwvoeC55Kjb8SPxHEkAOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvnnZb/btsDUVfzCcU/PQwvoeC55Kjb8SPxHEkAOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvnnZb%2FbtsDUVfzCcU%2FPQwvoeC55Kjb8SPxHEkAOK%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;315&quot; height=&quot;190&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.09.11.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;190&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.09.50.png&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4p8pT/btsDXBf41VL/TwtwW6JF0Rn6VSiPDjrlD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4p8pT/btsDXBf41VL/TwtwW6JF0Rn6VSiPDjrlD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4p8pT/btsDXBf41VL/TwtwW6JF0Rn6VSiPDjrlD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4p8pT%2FbtsDXBf41VL%2FTwtwW6JF0Rn6VSiPDjrlD1%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;1804&quot; height=&quot;245&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.09.50.png&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;245&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; 맵핑할 키를 누른 다음, 설정하길 원하는 키를 눌러주면 됩니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.13.10.png&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hcx9I/btsDT4RDtEm/hhLHi7cwV1bBsIx8rCHQNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hcx9I/btsDT4RDtEm/hhLHi7cwV1bBsIx8rCHQNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hcx9I/btsDT4RDtEm/hhLHi7cwV1bBsIx8rCHQNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHcx9I%2FbtsDT4RDtEm%2FhhLHi7cwV1bBsIx8rCHQNK%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;190&quot; height=&quot;165&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.13.10.png&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매크로를 사용하려면 우선 매크로를 작성해 주어야 합니다. 가장 왼쪽에 MACROS를 눌러주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.13.55.png&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9yoaq/btsDTwHlZXU/6KaYYdvXVzD9LVGjQkMiyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9yoaq/btsDTwHlZXU/6KaYYdvXVzD9LVGjQkMiyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9yoaq/btsDTwHlZXU/6KaYYdvXVzD9LVGjQkMiyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9yoaq%2FbtsDTwHlZXU%2F6KaYYdvXVzD9LVGjQkMiyK%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;1295&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.13.55.png&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;302&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;lt;/&amp;gt; 버튼을 눌러 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.17.35.png&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEN0C/btsDWSvTDyK/cxqJV9fC6sD2XJnjv5uuLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEN0C/btsDWSvTDyK/cxqJV9fC6sD2XJnjv5uuLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEN0C/btsDWSvTDyK/cxqJV9fC6sD2XJnjv5uuLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEN0C%2FbtsDWSvTDyK%2FcxqJV9fC6sD2XJnjv5uuLK%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;1049&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.17.35.png&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;387&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;중괄호를 열어주시고 지정할 키 이름을 입력하면 VIA에서 사용하는 단축어로 변경됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.17.18.png&quot; data-origin-width=&quot;1039&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I17vy/btsDRLkv7Nu/TFpxwWTSjcQ9zRJMlyxes1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I17vy/btsDRLkv7Nu/TFpxwWTSjcQ9zRJMlyxes1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I17vy/btsDRLkv7Nu/TFpxwWTSjcQ9zRJMlyxes1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI17vy%2FbtsDRLkv7Nu%2FTFpxwWTSjcQ9zRJMlyxes1%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;1039&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.17.18.png&quot; data-origin-width=&quot;1039&quot; data-origin-height=&quot;368&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;맥에서 커멘드는 Left Winow 키와 동일하기 때문에 Ctl + C를 지정하려면 KC_LCUI, KC_C 두 개를 지정해 주면 되요. Save 버튼을 반드시 눌러 저장해 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.19.08.png&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MBcgo/btsDWHnAZPy/fK7kKvsDGq374gG6lEeFk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MBcgo/btsDWHnAZPy/fK7kKvsDGq374gG6lEeFk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MBcgo/btsDWHnAZPy/fK7kKvsDGq374gG6lEeFk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMBcgo%2FbtsDWHnAZPy%2FfK7kKvsDGq374gG6lEeFk0%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;1015&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.19.08.png&quot; data-origin-width=&quot;1015&quot; data-origin-height=&quot;302&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwB2r5/btsDUV0Zz79/VgtyP5hKhylidYSDSk7nz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwB2r5/btsDUV0Zz79/VgtyP5hKhylidYSDSk7nz0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;222&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.19.19.png&quot; data-widthpercent=&quot;36.86&quot; style=&quot;width: 36.4304%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwB2r5/btsDUV0Zz79/VgtyP5hKhylidYSDSk7nz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwB2r5%2FbtsDUV0Zz79%2FVgtyP5hKhylidYSDSk7nz0%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;230&quot; height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MFEW9/btsDY5OO25P/VkrZKYomSMCua9zmxh68LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MFEW9/btsDY5OO25P/VkrZKYomSMCua9zmxh68LK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;222&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.20.10.png&quot; style=&quot;width: 62.4068%;&quot; data-widthpercent=&quot;63.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MFEW9/btsDY5OO25P/VkrZKYomSMCua9zmxh68LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMFEW9%2FbtsDY5OO25P%2FVkrZKYomSMCua9zmxh68LK%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;394&quot; height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/div&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;다시 KEYMAP 메뉴로 넘어와서 변경할 키를 눌러주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.20.46.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/26eWt/btsDUe0OmEa/bxzdTbmw6dPc9tbblAegZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/26eWt/btsDUe0OmEa/bxzdTbmw6dPc9tbblAegZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/26eWt/btsDUe0OmEa/bxzdTbmw6dPc9tbblAegZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F26eWt%2FbtsDUe0OmEa%2FbxzdTbmw6dPc9tbblAegZk%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;362&quot; height=&quot;207&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.20.46.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KEYMAP &amp;gt; MACRO에서 M0를 눌러 키를 맵핑해 줍니다. 키가 맵핑되면 바로 다음 키로 넘어가기 때문에 연속해서 누르지 말고 한 번만 눌러주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bibzAs/btsDUefomKp/eoLTK8TauAOTGvMEcDHA2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bibzAs/btsDUefomKp/eoLTK8TauAOTGvMEcDHA2K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;203&quot; data-origin-height=&quot;93&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.22.09.png&quot; style=&quot;width: 37.4777%; margin-right: 10px;&quot; data-widthpercent=&quot;38.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bibzAs/btsDUefomKp/eoLTK8TauAOTGvMEcDHA2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbibzAs%2FbtsDUefomKp%2FeoLTK8TauAOTGvMEcDHA2K%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;203&quot; height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu9zoC/btsDUa5avNC/Bh4CIjn1RGyN5sgdQXLEHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu9zoC/btsDUa5avNC/Bh4CIjn1RGyN5sgdQXLEHk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;108&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.22.04.png&quot; style=&quot;width: 33.8623%; margin-right: 10px;&quot; data-widthpercent=&quot;34.67&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu9zoC/btsDUa5avNC/Bh4CIjn1RGyN5sgdQXLEHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu9zoC%2FbtsDUa5avNC%2FBh4CIjn1RGyN5sgdQXLEHk%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;213&quot; height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPqSBA/btsDUbiDS2W/IxOoPmf1UsjUhh2WEhmo90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPqSBA/btsDUbiDS2W/IxOoPmf1UsjUhh2WEhmo90/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;227&quot; data-origin-height=&quot;148&quot; data-filename=&quot;스크린샷 2024-01-25 오후 1.22.12.png&quot; style=&quot;width: 26.3344%;&quot; data-widthpercent=&quot;26.96&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPqSBA/btsDUbiDS2W/IxOoPmf1UsjUhh2WEhmo90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPqSBA%2FbtsDUbiDS2W%2FIxOoPmf1UsjUhh2WEhmo90%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;227&quot; height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/div&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;복붙용 키로 만들었는데 생각보다 불편한거 같아요 ㅎㅎㅎㅎ&lt;/p&gt;</description>
      <category>iOS  /Log  </category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/110</guid>
      <comments>https://framios.tistory.com/110#entry110comment</comments>
      <pubDate>Thu, 25 Jan 2024 13:23:07 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI custom TabBar에 More 제거하는 방법</title>
      <link>https://framios.tistory.com/103</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yk21c/btsCZ8T7dic/odLXRmgrZuraoBWigAKFT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yk21c/btsCZ8T7dic/odLXRmgrZuraoBWigAKFT0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;Simulator Screenshot - iPhone 15 Pro - 2024-01-03 at 19.49.27.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yk21c/btsCZ8T7dic/odLXRmgrZuraoBWigAKFT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYk21c%2FbtsCZ8T7dic%2FodLXRmgrZuraoBWigAKFT0%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;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AaX6W/btsC362jsi4/JBs7RY6b4kk5Tr4ydI8PxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AaX6W/btsC362jsi4/JBs7RY6b4kk5Tr4ydI8PxK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;Simulator Screenshot - iPhone 15 Pro - 2024-01-03 at 19.49.09.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AaX6W/btsC362jsi4/JBs7RY6b4kk5Tr4ydI8PxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAaX6W%2FbtsC362jsi4%2FJBs7RY6b4kk5Tr4ydI8PxK%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;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/div&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;SwiftUI에서 하단 탭바를 구현하고 싶을 때 TabView를 사용할 수 있다. 하단 탭바 뿐만 아니라 캐러셀 스크롤 뷰 (예를 들어 배너)도 TabView를 사용할 수 있다. 탭 아이템이 없는 커스텀 TabView를 구현했을 때 하단에 자동으로 생기는 More을 제거 하고 싶으면 tabViewStyle modifier를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704279182161&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TabView(selection: viewStore.$selectedCategoryID) {
	// 생략
}
.tabViewStyle(.page(indexDisplayMode: .never))&lt;/code&gt;&lt;/pre&gt;</description>
      <category>iOS  /Issue Fixing</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/103</guid>
      <comments>https://framios.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 3 Jan 2024 19:53:07 +0900</pubDate>
    </item>
    <item>
      <title>[SwiftUI] NavigationLink</title>
      <link>https://framios.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NavigationStack과 NavigationSplitView에서 뷰 이동을 위해 NavigationLink를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703936148134&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct Fruit: Identifiable, Hashable {
    var id = UUID()
    let name: String
    let emoji: String
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
}&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;pre id=&quot;code_1703936183602&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FruitDetailView: View {
    let fruit: Fruit
    
    var body: some View {
        VStack {
            Text(fruit.emoji)
                .font(.largeTitle)
            Text(fruit.name)
        }
    }
}&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;데이터 모델을 보여주기 위한 View로서 navigation link에 의해 이동되는 화면&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NavigationLink(_ title:destination:)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703936709941&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public init&amp;lt;S&amp;gt;(_ title: S, @ViewBuilder destination: () -&amp;gt; Destination) where S : StringProtocol&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;List의 목록에서 보여줄 title, 이동할 화면은 destination으로 정의해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703936718402&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FruitView: View {
    private var fruits = [Fruit(name: &quot;사과&quot;, emoji: &quot; &quot;), Fruit(name: &quot;오렌지&quot;, emoji: &quot; &quot;), Fruit(name: &quot;바나나&quot;, emoji: &quot; &quot;)]
    
    var body: some View {
        NavigationStack {
            List(fruits) { fruit in
                NavigationLink(fruit.name) {
                    FruitDetailView(fruit: fruit)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NavigationLink(destination:label:)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703937110235&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                NavigationLink {
                    FruitDetailView(fruit: fruit)
                } label: {
                    VStack {
                        Text(fruit.name)
                        Text(fruit.emoji)
                    }
                }&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;만약 단순 라벨이 아닌 커스텀 뷰를 리스트 목록으로 지정하고 싶은 경우 label 파라미터를 사용해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;navigationDestination(for:destination:)&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703935952335&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func navigationDestination&amp;lt;D, C&amp;gt;(
    for data: D.Type,
    @ViewBuilder destination: @escaping (D) -&amp;gt; C
) -&amp;gt; some View where D : Hashable, C : View&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;데이터 타입과 데이터 값을 기반으로 navigation link를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703936256272&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FruitView: View {
    
    private var fruits = [Fruit(name: &quot;사과&quot;, emoji: &quot; &quot;), Fruit(name: &quot;오렌지&quot;, emoji: &quot; &quot;), Fruit(name: &quot;바나나&quot;, emoji: &quot; &quot;)]
    
    var body: some View {
        NavigationStack {
            List(fruits) { fruit in
                NavigationLink(fruit.name, value: fruit)
            }
            .navigationDestination(for: Fruit.self) { fruit in
                FruitDetailView(fruit: fruit)
            }
        }
    }
}&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;NavigationLink에서 지정한 value에서의 값을 navigationDestination(for:destination:)으로 연결해 준다. LazyVStack, LazyHStack, List 등은  child view가 화면에 보여져야 할 때만 view를 렌더링 하므로 navigationDestination을 lazy 내부가 아닌 바깥에 정의해 줘야 한다.&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;사용자가 NavigationLink를 탭하면 navigationDestination에 의해 상세 화면으로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 사이트 및 도서&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1703935680238&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Routing &amp;amp; Navigation in SwiftUI &amp;mdash; the right way&quot; data-og-description=&quot;Learn how to create a light &amp;amp; simple routing layer to handle your application&amp;rsquo;s navigation&quot; data-og-host=&quot;blorenzop.medium.com&quot; data-og-source-url=&quot;https://blorenzop.medium.com/routing-navigation-in-swiftui-f1f8ff818937&quot; data-og-url=&quot;https://blorenzop.medium.com/routing-navigation-in-swiftui-f1f8ff818937&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cOhrFb/hyUTKPcdnd/KJmAyPF60HsHTX4e05DvE0/img.jpg?width=1200&amp;amp;height=808&amp;amp;face=0_0_1200_808,https://scrap.kakaocdn.net/dn/TnVX2/hyUXNpV8Hz/hiHKDsBsK9vQvIo98LmLhk/img.png?width=1358&amp;amp;height=622&amp;amp;face=0_0_1358_622,https://scrap.kakaocdn.net/dn/bYKKev/hyUXMR6jNj/5hlBiV10iXUzZMNXO6y9D1/img.png?width=1358&amp;amp;height=622&amp;amp;face=0_0_1358_622&quot;&gt;&lt;a href=&quot;https://blorenzop.medium.com/routing-navigation-in-swiftui-f1f8ff818937&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blorenzop.medium.com/routing-navigation-in-swiftui-f1f8ff818937&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cOhrFb/hyUTKPcdnd/KJmAyPF60HsHTX4e05DvE0/img.jpg?width=1200&amp;amp;height=808&amp;amp;face=0_0_1200_808,https://scrap.kakaocdn.net/dn/TnVX2/hyUXNpV8Hz/hiHKDsBsK9vQvIo98LmLhk/img.png?width=1358&amp;amp;height=622&amp;amp;face=0_0_1358_622,https://scrap.kakaocdn.net/dn/bYKKev/hyUXMR6jNj/5hlBiV10iXUzZMNXO6y9D1/img.png?width=1358&amp;amp;height=622&amp;amp;face=0_0_1358_622');&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;Routing &amp;amp; Navigation in SwiftUI &amp;mdash; the right way&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to create a light &amp;amp; simple routing layer to handle your application&amp;rsquo;s navigation&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blorenzop.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703936130329&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;NavigationLink | Apple Developer Documentation&quot; data-og-description=&quot;A view that controls a navigation presentation.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swiftui/navigationlink#Create-a-presentation-link&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swiftui/navigationlink&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bN7zwV/hyUXNXMBrR/0X0IlK96aFa2OP3OQNfKkk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/blNwCg/hyUTFNPJ8s/u9hyJSVZvfbSZLBid6kXwK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/navigationlink#Create-a-presentation-link&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swiftui/navigationlink#Create-a-presentation-link&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bN7zwV/hyUXNXMBrR/0X0IlK96aFa2OP3OQNfKkk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/blNwCg/hyUTFNPJ8s/u9hyJSVZvfbSZLBid6kXwK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&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;NavigationLink | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A view that controls a navigation presentation.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;hummingbird-2139279_640.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AHyIz/btsCLpJHyRU/nfYjwo0RCdYOf8VodYgIDk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AHyIz/btsCLpJHyRU/nfYjwo0RCdYOf8VodYgIDk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AHyIz/btsCLpJHyRU/nfYjwo0RCdYOf8VodYgIDk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAHyIz%2FbtsCLpJHyRU%2FnfYjwo0RCdYOf8VodYgIDk%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;472&quot; height=&quot;311&quot; data-filename=&quot;hummingbird-2139279_640.jpg&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>iOS  /SwiftUI</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/101</guid>
      <comments>https://framios.tistory.com/101#entry101comment</comments>
      <pubDate>Sat, 30 Dec 2023 20:52:36 +0900</pubDate>
    </item>
    <item>
      <title>[TCA] TCA에서의 비동기 처리</title>
      <link>https://framios.tistory.com/98</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Reduce&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703118782968&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// Reduce&amp;lt;State, Action&amp;gt; initializer
  @inlinable
  public init(_ reduce: @escaping (_ state: inout State, _ action: Action) -&amp;gt; Effect&amp;lt;Action&amp;gt;) {
    self.init(internal: reduce)
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reduce는 Effect&amp;lt;Action&amp;gt;을 반환하는 클로져를 가지고 있음&lt;/li&gt;
&lt;li&gt;Effect&amp;lt;Action&amp;gt;을 사용해서 상태를 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Reducer 내부에서 State를 변형하고 관리&lt;/li&gt;
&lt;li&gt;Effect를 Application에 피드백 (Side Effect)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Reducer 내부에서 Reduce를 가지고 상태를 관리한다. 이때 effect를 일으켜 피드백을 할 수 있는데 이를 side effect라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;.run&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reduce는 Effect&amp;lt;Action&amp;gt;을 반환해야 한다&lt;/p&gt;
&lt;pre id=&quot;code_1703119323845&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static func run(
    priority: TaskPriority? = nil,
    operation: @escaping @Sendable (_ send: Send&amp;lt;Action&amp;gt;) async throws -&amp;gt; Void,
    catch handler: (@Sendable (_ error: Error, _ send: Send&amp;lt;Action&amp;gt;) async -&amp;gt; Void)? = nil,
    fileID: StaticString = #fileID,
    line: UInt = #line
  ) -&amp;gt; Self&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Effect의 익스텐션에 정의된 static 함수인 run의 반환타입은 Self 즉 Effect&lt;/li&gt;
&lt;li&gt;operation으로 비동기 코드를 전달하고 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703119860384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;operation: @escaping @Sendable (_ send: Send&amp;lt;Action&amp;gt;) async throws -&amp;gt; Void,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Send&amp;lt;Action&amp;gt;을 사용해서 결과를 피드백 하며 Action 케이스의 연관 값으로 결과를 전달&lt;/li&gt;
&lt;li&gt;operation { } 클로저 내부의 작업은 작업을 위한 새로운 스레드에서 처리 (Task), 비동기 작업의 결과는 main 스레드에서 처리 되어야 함(State 반영)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Send&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703120463485&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;operation: @escaping @Sendable (_ send: Send&amp;lt;Action&amp;gt;) async throws -&amp;gt; Void,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;operation { } 클로저 내부에 send는 Send&amp;lt;Action&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703120530620&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public struct Send&amp;lt;Action&amp;gt;: Sendable {
  let send: @MainActor @Sendable (Action) -&amp;gt; Void

  public init(send: @escaping @MainActor @Sendable (Action) -&amp;gt; Void) {
    self.send = send
  }
  // 생략&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Send&amp;lt;Action&amp;gt;에는 send 인스턴스를 내부적으로 가지고 있음&lt;/li&gt;
&lt;li&gt;send는 MainActor로서 동작
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Effect의 결과를 State에 반영하면 이 state는 UI를 업데이트 하므로&amp;nbsp; main 스레드에서 일어나야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sendable을 준수하는 타입은 해당 타입의 인스턴스를 스레드 간 안전하게 전달 할 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Send 구조체를 사용해서 Action을 호출할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703120890786&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      case .alert(.presented(.confirmSave)):
        return .run { [transcript = state.transcript] send in
          await send(.delegate(.saveMeeting(transcript: transcript)))
          await self.dismiss()
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(코드 출처 : TCA / episode-code-samples-main / 0249-tca-tour-pt7 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 코드에서 await send(.delegate(.saveMeeting(transcript: transcript))는 MainActor 인스턴스인 send가 state의 변형을 메인스레드에서 할 수 있게 해줌&lt;/li&gt;
&lt;li&gt;비동기 처리로 얻은 결과 값을 send를 사용해서 액션으로 피드백 하고 main 스레드의 흐름으로 다시 편입시키는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCA에서 state 값을 직접적으로 변경하지 않고 send를 활용하고 있는데 이는 Swift의 동시성 프로그래밍과 관련이 있다. TCA비동기 처리의 매커니즘 이해가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://framios.tistory.com/97&quot;&gt;2023.12.20 - [Swift] - [Swift] 동시성 프로그래밍(async/await, actor, sendable)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainActor, Sendable에 대한 예시 코드와 동작은 이 글 참고&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;s&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;아 task 안에서 값을 변경을 못하니까 effect의 run 함수에 send를 사용해서 action으로 피드백 하는 구나&lt;/span&gt;&lt;/s&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비동기 처리 매커니즘 이해하기&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.23.49.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0NJGO/btsClIbeRof/2ozm5Whz55XKNSdoMxzcv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0NJGO/btsClIbeRof/2ozm5Whz55XKNSdoMxzcv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0NJGO/btsClIbeRof/2ozm5Whz55XKNSdoMxzcv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0NJGO%2FbtsClIbeRof%2F2ozm5Whz55XKNSdoMxzcv1%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;662&quot; height=&quot;197&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.23.49.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;197&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 비동기 처리에서 Task가 생성할 새로운 스레드에서 새로운 값을 할당하려고 할 때 이 할당 되는 시점은 어느 시점에 이루어질지 확정할 수 없음&lt;/li&gt;
&lt;li&gt;외부 변수를 비동기 맥락에서 변형할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.34.56.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMEtr/btsCkoj5dpv/HjEawVbBKBKSDsnRGtc481/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMEtr/btsCkoj5dpv/HjEawVbBKBKSDsnRGtc481/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMEtr/btsCkoj5dpv/HjEawVbBKBKSDsnRGtc481/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMEtr%2FbtsCkoj5dpv%2FHjEawVbBKBKSDsnRGtc481%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;634&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.34.56.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;226&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;inout은 파라미터로 전달된 값의 원본 값을 변경할 수 있음&lt;/li&gt;
&lt;li&gt;변형 가능성을 가지는 inout 파라미터를 비동기 맥락에서 사용할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 값의 변형은 main에서 수행되어야 함 -&amp;gt; TCA가 MainActor, Send를 도입한 이유&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.37.59.png&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5AEqR/btsColzF17B/Gv9SSXHP9sTHhFz10xv3bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5AEqR/btsColzF17B/Gv9SSXHP9sTHhFz10xv3bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5AEqR/btsColzF17B/Gv9SSXHP9sTHhFz10xv3bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5AEqR%2FbtsColzF17B%2FGv9SSXHP9sTHhFz10xv3bK%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;305&quot; height=&quot;117&quot; data-filename=&quot;스크린샷 2023-12-21 오전 10.37.59.png&quot; data-origin-width=&quot;305&quot; data-origin-height=&quot;117&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Task에서 안전하게 사용하려면 변형이 불가능한 형태로 전달되어야 함.&lt;/li&gt;
&lt;li&gt;값의 캡처 방식으로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703122974451&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AsyncTest {
    var foo = &quot;&quot;
    
    func doAsyncBar(
        _ foo: inout String,
        mainCompletion: @escaping @Sendable @MainActor (String) -&amp;gt; Void) async {
            Task { [foo = foo] in
                let taskResult = &quot;Task&quot; + foo
                await mainCompletion(taskResult)
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값을 캡쳐해서 전달 받고 main으로 피드백 하기 위해 mainCompletion 파라미터 추가&lt;/li&gt;
&lt;li&gt;mainCompletion
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mainCompletion은 @MainActor을 사용해서 main 스레드에서의 작업을 보장&lt;/li&gt;
&lt;li&gt;@Sendable로 스레드 안전하게 값을 전달&lt;/li&gt;
&lt;li&gt;Task의 결과를 클로저를 통해 전달 할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703123595954&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class AsyncTest: @unchecked Sendable {
    private var foo: String
    
    init(foo: String = &quot;&quot;) {
        self.foo = foo
    }
    
    func doAsyncBar(
        _ foo: inout String,
        mainCompletion: @escaping @Sendable @MainActor (String) -&amp;gt; Void) async {
            Task { [foo = foo] in
                let taskResult = &quot;Task&quot; + foo
                await mainCompletion(taskResult)
            }
    }
    
    func updateUI() async {
        await doAsyncBar(&amp;amp;foo) { result in
            self.foo = result
        }
    }
}&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;최종형태를 확인해 보면 TCA의 Effect run과 유사한걸 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703123713121&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;operation: @escaping @Sendable (_ send: Send&amp;lt;Action&amp;gt;) async throws -&amp;gt; Void,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703123721974&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  let send: @MainActor @Sendable (Action) -&amp;gt; Void
  
  public init(send: @escaping @MainActor @Sendable (Action) -&amp;gt; Void) {
    self.send = send
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 및 출처&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1703123839158&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Chapter 6. Swift의 비동기 처리와 TCA에서의 응용 | Built with Notion&quot; data-og-description=&quot;이번 장에서는 TCA에서의 비동기 처리를 어떻게 관리하는지 알아보기 전에 Swift Concurrency, 즉 동시성 프로그래밍에 대해 알아보겠습니다. Combine 프레임워크와 달리 동시성 프로그래밍은 Task의 병&quot; data-og-host=&quot;axiomatic-fuschia-666.notion.site&quot; data-og-source-url=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-6-Swift-TCA-af211cdc6e54486d815e5ef1b4f2599d&quot; data-og-url=&quot;https://axiomatic-fuschia-666.notion.site/af211cdc6e54486d815e5ef1b4f2599d&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hnKxr/hyUPALQD1v/gH0HqGEDGKxeuCOr29XMqk/img.png?width=2000&amp;amp;height=340&amp;amp;face=0_0_2000_340,https://scrap.kakaocdn.net/dn/Fcdsi/hyUPGeehcw/Wou96e8ZWJJnbJY3VVZ6J0/img.png?width=2000&amp;amp;height=340&amp;amp;face=0_0_2000_340&quot;&gt;&lt;a href=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-6-Swift-TCA-af211cdc6e54486d815e5ef1b4f2599d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-6-Swift-TCA-af211cdc6e54486d815e5ef1b4f2599d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hnKxr/hyUPALQD1v/gH0HqGEDGKxeuCOr29XMqk/img.png?width=2000&amp;amp;height=340&amp;amp;face=0_0_2000_340,https://scrap.kakaocdn.net/dn/Fcdsi/hyUPGeehcw/Wou96e8ZWJJnbJY3VVZ6J0/img.png?width=2000&amp;amp;height=340&amp;amp;face=0_0_2000_340');&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;Chapter 6. Swift의 비동기 처리와 TCA에서의 응용 | Built with Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 장에서는 TCA에서의 비동기 처리를 어떻게 관리하는지 알아보기 전에 Swift Concurrency, 즉 동시성 프로그래밍에 대해 알아보겠습니다. Combine 프레임워크와 달리 동시성 프로그래밍은 Task의 병&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;axiomatic-fuschia-666.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5fDQV/btsCpPz3nWB/06B93t7M3EuYhfyGRjqeKK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5fDQV/btsCpPz3nWB/06B93t7M3EuYhfyGRjqeKK/img.webp&quot; data-alt=&quot;https://pixabay.com/ko/photos/%EB%B9%84%EB%88%84-%EC%A7%91%EC%97%90%EC%84%9C-%EB%A7%8C%EB%93%A0-%EB%B9%84%EB%88%84-8429699/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5fDQV/btsCpPz3nWB/06B93t7M3EuYhfyGRjqeKK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5fDQV%2FbtsCpPz3nWB%2F06B93t7M3EuYhfyGRjqeKK%2Fimg.webp&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;338&quot; height=&quot;507&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://pixabay.com/ko/photos/%EB%B9%84%EB%88%84-%EC%A7%91%EC%97%90%EC%84%9C-%EB%A7%8C%EB%93%A0-%EB%B9%84%EB%88%84-8429699/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS  /Architecture</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/98</guid>
      <comments>https://framios.tistory.com/98#entry98comment</comments>
      <pubDate>Thu, 21 Dec 2023 10:47:37 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 동시성 프로그래밍(async/await, actor, sendable)</title>
      <link>https://framios.tistory.com/97</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;동기 &amp;amp; 비동기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronous는 작업을 동기적으로 실행. 실행 중인 작업이 끝날 때 까지 다른 작업을 기다림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asynchronous는 비동기로 현재 실행 중인 작업이 있어도 다른 작업을 실행 할 수 있다. 오래 걸리는 작업을 비동기로 처리해서 메인 스레드에서 필요로 하는 작업이 멈추지 않게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MainActor&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 관련 작업은 메인 스레드에서 실행되어야 하는데, MainActor는 이를 쉽게 관리할 수 있게 해준다. UI 업데이트와 관련된 작업을 메인 스레드에서 실행하도록 보장한다. 비동기 작업에서 메인 스레드로의 접근을 안전하게 관리하기 때문에 스레드 안정적이며 DispatchQueue.main.async와 같은 코드를 작성할 필요가 없어 간결한 코드를 작성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703051986326&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct DefaultProductDetailUseCase: ProductDetailUseCase {
    func fetchProductDetail(idx: Int) async throws -&amp;gt; Product {
        let (data, response) = try await URLSession.shared.data(from: URL(string: &quot;https://dummyjson.com/products/\(idx)&quot;)!)
        guard let response = response as? HTTPURLResponse,
              (200..&amp;lt;300).contains(response.statusCode) else {
            throw CustomError.statusCodeError
        }
        
        guard let result = try? JSONDecoder().decode(Product.self, from: data) else {
            throw CustomError.invalidResponse
        }
        
        return result
    }
}&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;비동기 작업을 수행하는 함수가 있다고 할 때 ViewModel에서 아래와 같이 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703052035767&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ProductDetailViewModel: ObservableObject {
    var useCase: ProductDetailUseCase?
    
    @Published var product: Product?
    
    init(useCase: ProductDetailUseCase) {
        self.useCase = useCase
    }
    
    func getchProductDetail(idx: Int) {
        Task {
            let product = try await self.useCase?.fetchProductDetail(idx: idx)
            await updateUI(with: product)
        }
    }
    
    @MainActor
    private func updateUI(with product: Product?) async {
        self.product = product
    }
}&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;MainActor을 사용해서 메인 스레드에서 동작해야 하는 코드를 실행한다. 비동기 작업 후 UI 업데이트 코드를 메인 스레드에서 안전하게 실행할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Async&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 처리를 수행하는 함수를 정의하려면 async 키워드를 사용해 준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703052554264&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func fetchProductDetail(idx: Int) async -&amp;gt; Product {
		// 생략
    }&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;async 코드는 concurrent 컨텍스트에서만 실행 가능하다. Task 혹은 다른 async 함수에서 async 함수를 호출할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러를 반환할 수 있는 경우 async throws로 정의해 준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703052635709&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func fetchProductDetail(idx: Int) async throws -&amp;gt; Product {
		// 생략
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Await&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1703053605330&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        Task {
            let product = try await self.useCase?.fetchProductDetail(idx: idx)
            await updateUI(with: product)
        }&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;async 함수를 호출하기 위해서 await 키워드를 사용한다. await으로 마킹된 곳은 potential suspension point(잠재적인 일시 중단 지점)으로 지정된다. 즉 이 함수를 호출한 부분도 대기 상태가 될 수 있다는 것을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 suspension point 의 suspend란 스레드를 block 시키는 것이 아니라 다른 동작을 수행할 수 있도록 컨트롤을 포기하는 것을 의미한다. &lt;b&gt;스레드에 대한 제어권을 system에게 전달&lt;/b&gt;하게 되고, 시스템은 해당 스레드를 사용해 다른 작업을 수행할 수 있다. 이후 시스템은 다른 특정 스레드 제어권을 전달해서 suspension point 이후 코드를 수행할 수 있게 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;task&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 Task와 함께 동시에 실행 할 수 있는 비동기 컨텍스트를 제공한다. SwiftUI에서는 task view modifier를 제공하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703056704647&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @inlinable public func task(priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -&amp;gt; Void) -&amp;gt; some View&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;task modifier는 아래와 같이 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703056785186&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        VStack { 생략 }
        .task {
            do {
                try await vm.getProductDetail(idx: 1)
            } catch {
                print(&quot;error!&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;getProductDetail(idx:)는 error를 반환 할 수 있는 async throw 이기 때문에 task modifier 내부에서 try catch를 사용한다. 만약 task modifier가 아닌 Button을 눌렀을 때와 같은 동작 이후에 수행되야 한다면 Task.init() 를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703056987057&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@frozen public struct Task&amp;lt;Success, Failure&amp;gt; : Sendable where Success : Sendable, Failure : Error {
}&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;initializer는 task modifier와 동일하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703057015098&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@discardableResult
public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async -&amp;gt; Success)&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;pre id=&quot;code_1703057066716&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            Button {
                Task {
                    do {
                        try await vm.getProductDetail(idx: 1)
                    } catch {
                        
                    }
                }
            } label: {
                Text(&quot;버튼으로 실행 할 경우&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;Task로 생성된 작업은 백그라운드 스레드에서 즉시 실행되며 await 키워드로 해당 지점에서 완료된 값이 돌아올 때까지 기다릴 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;inout paramters&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;concurrency랑 관련된건 아니지만 자주 나와서 정리하자면, inout은 Swift 내에 정의된 키워드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에 값을 전달할 때 매개변수를 사용하게 되는데 이때 매개 변수의 값을 변경해도 원래 변수 값에 영향을 미치지 않는다. inout을 사용하면 함수의 파라미터 값 변경사항을 원본 변수에 적용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703060896607&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ProductDetailViewModel: ObservableObject {
    
    @Published var product: Product?
    
    func removeCurrentProduct() {
        self.removeProductRequest(product: &amp;amp;product)
    }
    
    private func removeProductRequest(product: inout Product?) {
        product = nil
    }
    
    // product를 fetch하는 코드는 제외됨
}&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;SwiftUI의 View에서 removeCurenProduct를 호출하면 viewModel 내부에 정의된 private 함수인 removeProductRequest를 호출한다. 이때 파라미터로 inout 매개변수를 전달한다. 전달받은 매개변수의 값을 변경하면 viewModel의 원본 변수인 @Published로 정의된 product의 값이 nil로 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703060992717&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            Button {
                vm.removeCurrentProduct()
            } label: {
                Text(&quot;상품 정보 제거하기&quot;)
            }
            .buttonStyle(.borderedProminent)
            
            Text(vm.product?.title ?? &quot;&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;View 코드로 원본 product 값의 변경을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703061075743&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;removeProductRequest(product: &amp;amp;product)&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;amp;(ampersand)을 사용한다. 이는 매개변수가 inout으로 사용되고 있음을 명시적으로 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Isolated Actor&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 data-ke-style=&quot;style1&quot;&gt; 동시성을 안전하게 관리하기 위한 타입&lt;/li&gt;
&lt;li data-ke-style=&quot;style1&quot;&gt;Actor 내의 데이터는 다른 스레드나 컨텍스트에서 접근 할 수 없지만 특정 메서드나 프로퍼티를 isolated로 표시하면 해당 actor의 상태에 접근하고 수정할 수 있음&lt;/li&gt;
&lt;li data-ke-style=&quot;style1&quot;&gt;모든 Actor 타입은 암묵적으로 Sendable을 따른다. actor는 mutable state에 대한 isolation을 보장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Sendable&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;Sendable 타입을 준수하는 타입은 해당 타입의 인스턴스를 스레드 간 안전하게 전달 할 수 있음&lt;/li&gt;
&lt;li&gt;Value Type은  내부적으로 Sendable 프로토콜을 따르므로 스레드의 안전을 보장한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Reference Type은 final 클래스 혹은 클래스 내 상수 타입으로 선언하여 Sendable 프로토콜을 사용할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(어디에 사용될 수 있을지는 잘 모르겠다 )&lt;/p&gt;
&lt;pre id=&quot;code_1703069577813&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public static func run(
    priority: TaskPriority? = nil,
    operation: @escaping @Sendable (_ send: Send&amp;lt;Action&amp;gt;) async throws -&amp;gt; Void,
    catch handler: (@Sendable (_ error: Error, _ send: Send&amp;lt;Action&amp;gt;) async -&amp;gt; Void)? = nil,
    fileID: StaticString = #fileID,
    line: UInt = #line
  ) -&amp;gt; Self&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCA에서 비동기 처리 이후 Effect를 반환하기 위해 .run을 사용하는데 이때 operation에 @Sendable이 들어가 있다(TCA하다가 여기까지 왔당 ㅋㅋㅋ ㅠㅠ)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703083690055&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct SendableLibraryStruct: Sendable {
    var name: String
    var bookCount: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값 타입인 구조체는 Sendable을 준수해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703083718826&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class SendableLibraryClass: Sendable {
    let name: String
    let bookCount: Int
    
    init(name: String, bookCount: Int) {
        self.name = name
        self.bookCount = bookCount
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조 타입인 class는 final class여야 하고 Sendable을 준수해 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703083765522&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actor SendableTestActor {
    private var libraryStruct = SendableLibraryStruct(name: &quot;&quot;, bookCount: 0)
    private var libraryClass = SendableLibraryClass(name: &quot;&quot;, bookCount: 0)
    
    func updateDataWithStruct(libraryInfo: SendableLibraryStruct) {
        self.libraryStruct = libraryInfo
    }
    
    func updateDataWithClass(libraryInfo: SendableLibraryClass) {
        self.libraryClass = libraryInfo
    }
    
    func getNameFromClass() async throws -&amp;gt; String {
        try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
        return libraryClass.name
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;actor는 동시성 접근을 안전하게 관리한다.&lt;/li&gt;
&lt;li&gt;내부의 데이터는 다른 스레드나 실행 컨텍스트에서 직접 접근할 수 없다.&lt;/li&gt;
&lt;li&gt;특정 메서드나 프로퍼티를 isolated로 표시하면 해당 actor 상태에 안전하게 접근하고 수정할 수 있다.&lt;/li&gt;
&lt;li&gt;데이터의 무결성을 유지와 동시성 문제를 해결할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703083858013&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SendableTestViewModel: ObservableObject {
    @Published var userName: String = &quot;&quot;
    let testActor = SendableTestActor()
    
    func updateStruct() async {
        let libraryInfo = SendableLibraryStruct(name: &quot;kim&quot;, bookCount: 19)
        await testActor.updateDataWithStruct(libraryInfo: libraryInfo)
    }
    
    func updateClass() async {
        let libraryInfo = SendableLibraryClass(name: &quot;lee&quot;, bookCount: 20)
        await testActor.updateDataWithClass(libraryInfo: libraryInfo)
    }
    
    @MainActor
    func getName() async {
        do {
            self.userName = try await testActor.getNameFromClass()
        } catch {
            
        }
        
    }
}&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;ViewModel에 actor인 SendableTestActor를 사용해서 스레드 간에 안전하게 값을 전달 할 수 있다. updateStruct()와 updateClass는 값을 전달 받아 내부 데이터를 안전하게 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703083959503&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func getNameFromClass() async throws -&amp;gt; String {
        try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
        return libraryClass.name
    }&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;actor인 SendableTestActor을 확인해 보면 내부 데이터 중 이름을 반환하는 비동기 함수가 있다. 이를 viewModel에서 전달받아 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1703084015122&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @MainActor
    func getName() async {
        do {
            self.userName = try await testActor.getNameFromClass()
        } catch {
            
        }
    }&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;actor을 사용하면 actor의 state에 안전하게 접근하고 수정하며 값을 가져올 수 있기 때문에 데이터의 무결성을 유지하면서 동시성 문제를 해결 할 수 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;References&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1703051788254&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[WWDC21] Use async/await with URLSession을 적용해보자..!&quot; data-og-description=&quot;해당 글은 [WWDC21] Use async/await with URLSession 보고 작성했습니다. Swift Concurrency 에 대한 얘기가 몇몇 나왔었다. Swift Concurrency에 대해 간략하게 설명해보자면, 코드를 선형적이고 간결하게 만들고, Nat&quot; data-og-host=&quot;dev-mandos.tistory.com&quot; data-og-source-url=&quot;https://dev-mandos.tistory.com/28&quot; data-og-url=&quot;https://dev-mandos.tistory.com/28&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DBDh6/hyUPLM4VCI/P1AkHOY3OuZQBxJZP6uY01/img.png?width=800&amp;amp;height=1028&amp;amp;face=0_0_800_1028,https://scrap.kakaocdn.net/dn/Tsr1I/hyUPEmS79E/JPVsYKhQE1PC5vfibj6141/img.png?width=800&amp;amp;height=1028&amp;amp;face=0_0_800_1028,https://scrap.kakaocdn.net/dn/ctdRix/hyUPL0DCEX/z8o60MA9yck5oPqrsk76xk/img.png?width=1120&amp;amp;height=595&amp;amp;face=0_0_1120_595&quot;&gt;&lt;a href=&quot;https://dev-mandos.tistory.com/28&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dev-mandos.tistory.com/28&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DBDh6/hyUPLM4VCI/P1AkHOY3OuZQBxJZP6uY01/img.png?width=800&amp;amp;height=1028&amp;amp;face=0_0_800_1028,https://scrap.kakaocdn.net/dn/Tsr1I/hyUPEmS79E/JPVsYKhQE1PC5vfibj6141/img.png?width=800&amp;amp;height=1028&amp;amp;face=0_0_800_1028,https://scrap.kakaocdn.net/dn/ctdRix/hyUPL0DCEX/z8o60MA9yck5oPqrsk76xk/img.png?width=1120&amp;amp;height=595&amp;amp;face=0_0_1120_595');&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;[WWDC21] Use async/await with URLSession을 적용해보자..!&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;해당 글은 [WWDC21] Use async/await with URLSession 보고 작성했습니다. Swift Concurrency 에 대한 얘기가 몇몇 나왔었다. Swift Concurrency에 대해 간략하게 설명해보자면, 코드를 선형적이고 간결하게 만들고, Nat&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dev-mandos.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703052686268&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift] async / await &amp;amp; concurrency&quot; data-og-description=&quot;Swift 5.5 에서 등장한 비동기와 동시성을 위한 방법을 알아봅시다&quot; data-og-host=&quot;sujinnaljin.medium.com&quot; data-og-source-url=&quot;https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f&quot; data-og-url=&quot;https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vWaWD/hyUPGZixHJ/iiSVUxwEWClK1s4jQkxoGk/img.png?width=1200&amp;amp;height=520&amp;amp;face=0_0_1200_520&quot;&gt;&lt;a href=&quot;https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sujinnaljin.medium.com/swift-async-await-concurrency-bd7bcf34e26f&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vWaWD/hyUPGZixHJ/iiSVUxwEWClK1s4jQkxoGk/img.png?width=1200&amp;amp;height=520&amp;amp;face=0_0_1200_520');&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;[Swift] async / await &amp;amp; concurrency&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Swift 5.5 에서 등장한 비동기와 동시성을 위한 방법을 알아봅시다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sujinnaljin.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.hackingwithswift.com/sixty/5/10/inout-parameters&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.hackingwithswift.com/sixty/5/10/inout-parameters&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703061193692&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[SwiftUI] Sendable&quot; data-og-description=&quot;What is the Sendable protocol in Swift? | Swift Concurrency 비동기 환경(async)에서 특정 데이터를 보내도 안전한지 점검하는 프로토콜스레드 안전을 보장하는 액터, 특정 액터를 사용하는 여러 개의 스레드 안&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@j_aion/SwiftUI-Sendable&quot; data-og-url=&quot;https://velog.io/@j_aion/SwiftUI-Sendable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bE1Ipb/hyUPMZAfJN/Y4S9nEGiOFxzKQSpNltC2K/img.jpg?width=2048&amp;amp;height=1152&amp;amp;face=0_0_2048_1152,https://scrap.kakaocdn.net/dn/Ivk0q/hyUPNKWzIw/k2kWkHsaLPIoT8SedW2Xnk/img.jpg?width=2048&amp;amp;height=1152&amp;amp;face=0_0_2048_1152&quot;&gt;&lt;a href=&quot;https://velog.io/@j_aion/SwiftUI-Sendable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@j_aion/SwiftUI-Sendable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bE1Ipb/hyUPMZAfJN/Y4S9nEGiOFxzKQSpNltC2K/img.jpg?width=2048&amp;amp;height=1152&amp;amp;face=0_0_2048_1152,https://scrap.kakaocdn.net/dn/Ivk0q/hyUPNKWzIw/k2kWkHsaLPIoT8SedW2Xnk/img.jpg?width=2048&amp;amp;height=1152&amp;amp;face=0_0_2048_1152');&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;[SwiftUI] Sendable&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What is the Sendable protocol in Swift? | Swift Concurrency 비동기 환경(async)에서 특정 데이터를 보내도 안전한지 점검하는 프로토콜스레드 안전을 보장하는 액터, 특정 액터를 사용하는 여러 개의 스레드 안&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1703062166676&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Swift] Sendable&quot; data-og-description=&quot;Sendable 문서와 WWDC 22 &amp;gt; Eliminate data races using Swift Concurrency 를 조합해서 재구성한 내용입니다. [1] Sendable = safe to share Sendable 프로토콜은 concurrencey 상황에서 안전하게 공유될 수 있는 타입을 나타냅&quot; data-og-host=&quot;eunjin3786.tistory.com&quot; data-og-source-url=&quot;https://eunjin3786.tistory.com/572&quot; data-og-url=&quot;https://eunjin3786.tistory.com/572&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hGxDL/hyUPG59ttZ/bKKHWHdhxbWZouknDtL3FK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bKCOpa/hyUPHxcXgq/kS80w3fiFs4eMU8WklAnE0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bNnwP8/hyUPLTUbNq/vjXaT3ZBk8DKhk7u1Er4k1/img.png?width=1349&amp;amp;height=490&amp;amp;face=0_0_1349_490&quot;&gt;&lt;a href=&quot;https://eunjin3786.tistory.com/572&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://eunjin3786.tistory.com/572&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hGxDL/hyUPG59ttZ/bKKHWHdhxbWZouknDtL3FK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bKCOpa/hyUPHxcXgq/kS80w3fiFs4eMU8WklAnE0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bNnwP8/hyUPLTUbNq/vjXaT3ZBk8DKhk7u1Er4k1/img.png?width=1349&amp;amp;height=490&amp;amp;face=0_0_1349_490');&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;[Swift] Sendable&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Sendable 문서와 WWDC 22 &amp;gt; Eliminate data races using Swift Concurrency 를 조합해서 재구성한 내용입니다. [1] Sendable = safe to share Sendable 프로토콜은 concurrencey 상황에서 안전하게 공유될 수 있는 타입을 나타냅&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;eunjin3786.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) ChatGPT&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이후 공부할 것&lt;/b&gt;&lt;/h3&gt;
&lt;figure id=&quot;og_1703057199487&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;Explore structured concurrency in Swift - WWDC21 - Videos - Apple Developer&quot; data-og-description=&quot;When you have code that needs to run at the same time as other code, it's important to choose the right tool for the job. We'll take you...&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/videos/play/wwdc2021/10134/&quot; data-og-url=&quot;https://developer.apple.com/videos/play/wwdc2021/10134/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Atssl/hyUPFMS0fr/4ZN8cS1L136K7YnMVj3Z2K/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282&quot;&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10134/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/videos/play/wwdc2021/10134/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Atssl/hyUPFMS0fr/4ZN8cS1L136K7YnMVj3Z2K/img.jpg?width=500&amp;amp;height=282&amp;amp;face=0_0_500_282');&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;Explore structured concurrency in Swift - WWDC21 - Videos - Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;When you have code that needs to run at the same time as other code, it's important to choose the right tool for the job. We'll take you...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.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;figure id=&quot;og_1703123928934&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Sendable and @Sendable closures explained with code examples&quot; data-og-description=&quot;The Sendable protocol and @Sendable attribute help to eliminate data races and create thread-safety in Swift Concurrency.&quot; data-og-host=&quot;www.avanderlee.com&quot; data-og-source-url=&quot;https://www.avanderlee.com/swift/sendable-protocol-closures/&quot; data-og-url=&quot;https://www.avanderlee.com/swift/sendable-protocol-closures/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ystAK/hyUPyN1YUg/bNRuz3C2qV1x4uJkkY7LZ0/img.jpg?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840,https://scrap.kakaocdn.net/dn/bdmG1j/hyUPEAH9q0/vazbEHvNEZNzyZqlIDcoIK/img.jpg?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840&quot;&gt;&lt;a href=&quot;https://www.avanderlee.com/swift/sendable-protocol-closures/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.avanderlee.com/swift/sendable-protocol-closures/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ystAK/hyUPyN1YUg/bNRuz3C2qV1x4uJkkY7LZ0/img.jpg?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840,https://scrap.kakaocdn.net/dn/bdmG1j/hyUPEAH9q0/vazbEHvNEZNzyZqlIDcoIK/img.jpg?width=1600&amp;amp;height=840&amp;amp;face=0_0_1600_840');&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;Sendable and @Sendable closures explained with code examples&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Sendable protocol and @Sendable attribute help to eliminate data races and create thread-safety in Swift Concurrency.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.avanderlee.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8MMEs/btsClJN5Nbj/Ki4iO6NemL8L3qeV7JvlE1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8MMEs/btsClJN5Nbj/Ki4iO6NemL8L3qeV7JvlE1/img.webp&quot; data-alt=&quot;https://pixabay.com/ko/photos/%EC%B9%98%EC%A6%88-%EB%84%A4%EB%8D%9C%EB%9E%80%EB%93%9C-%EC%B9%98%EC%A6%88-%EC%8B%9C%EC%9E%A5-8437668/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8MMEs/btsClJN5Nbj/Ki4iO6NemL8L3qeV7JvlE1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8MMEs%2FbtsClJN5Nbj%2FKi4iO6NemL8L3qeV7JvlE1%2Fimg.webp&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;357&quot; height=&quot;238&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://pixabay.com/ko/photos/%EC%B9%98%EC%A6%88-%EB%84%A4%EB%8D%9C%EB%9E%80%EB%93%9C-%EC%B9%98%EC%A6%88-%EC%8B%9C%EC%9E%A5-8437668/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS  /Swift</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/97</guid>
      <comments>https://framios.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 20 Dec 2023 17:54:26 +0900</pubDate>
    </item>
    <item>
      <title>iOS 15 fullScreenCover(isPresented:) not working</title>
      <link>https://framios.tistory.com/95</link>
      <description>&lt;pre id=&quot;code_1702867561764&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        .onAppear {
            if [조건식] {
                DispatchQueue.main.async {
                    isShowView = true
                }
            }
        }
        .fullScreenCover(isPresented: $isShowView) {
            ChildView()
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 16, iOS 17에서는 잘 동작하는데 iOS 15에서는 DispatchQueue.main 으로 해줘야 fullScreenCover가 정상 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 이유는 아직 잘 모르겠답... ㅠㅠ&lt;/p&gt;</description>
      <category>iOS  /Issue Fixing</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/95</guid>
      <comments>https://framios.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 18 Dec 2023 11:47:17 +0900</pubDate>
    </item>
    <item>
      <title>[TCA] Overriding Dependencies (feat. Dependency Injection)</title>
      <link>https://framios.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;TCA를 공부하다 보니 SwiftUI 뿐만 아니라 UIKit에서도 사용할 수 있는 Dependency를 제공하길래 따로 글을 적어야 겠더라구요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성을 관리하기 위한 Swinject, Niddle, Factory 등의 라이브러리가 있는데 TCA의 Depdency도 훌륭하고 사용하기 편리한 의존성 관리를 제공합니다.&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;figure id=&quot;og_1702632734933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Chapter 5. Dependency | Built with Notion&quot; data-og-description=&quot;5.1 TCA와 Dependency&quot; data-og-host=&quot;axiomatic-fuschia-666.notion.site&quot; data-og-source-url=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-5-Dependency-de90da4e19554625af3ffc005ab13ed9&quot; data-og-url=&quot;https://axiomatic-fuschia-666.notion.site/de90da4e19554625af3ffc005ab13ed9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0HTFH/hyULSe5x1g/5YEEv6fH2vKdjHSKPiNTak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cMCvuA/hyULViARD6/YHSzDYdNIZtu01nbvYDelk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-5-Dependency-de90da4e19554625af3ffc005ab13ed9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-5-Dependency-de90da4e19554625af3ffc005ab13ed9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0HTFH/hyULSe5x1g/5YEEv6fH2vKdjHSKPiNTak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cMCvuA/hyULViARD6/YHSzDYdNIZtu01nbvYDelk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Chapter 5. Dependency | Built with Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;5.1 TCA와 Dependency&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;axiomatic-fuschia-666.notion.site&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서를 보시려면 이 사이트로 이동하시면 됩니다.&lt;/p&gt;
&lt;figure id=&quot;og_1702632756892&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;Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;pointfreeco.github.io&quot; data-og-source-url=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&quot; data-og-url=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pointfreeco.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCA에서는 DI(Dependency Injection)를 위한 @Dependency 프로퍼티 래퍼와 DependencyKey, DependencyValues를 제공합니다. Environment에서 사용한 EnvironmentKey와 EnvironmentValues와 동일한 형태를 갖고 있는 걸 알 수 있는데, Dependency 또한 같은 구현 방식으로 dependency를 전역에서 사용할 수 있도록 만들어 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 전역으로 사용할 의존성을 만들어 주어야 해요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Dependency 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702632799270&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ProductClient {
    typealias Products = [Product]
    var fetch: @Sendable (_ offset: Int, _ limit: Int) async throws -&amp;gt; [Product]
}&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;그 후 Environment와 동일하게 DepdencyKey를 등록하고 DependencyValues에 등록해 의존성을 접근하고 사용할 수 있도록 해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ProductClient는 remote 서버로 부터 product 목록을 가져옵니다. 실제 api를 request하는 코드는 DepdencyKey를 등록할 때 작성해 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DependencyKeh 등록&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702633002579&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension ProductClient: DependencyKey {
    static let liveValue = Self { offset, limit in
        let (data, _) = try await URLSession.shared.data(from: URL(string: &quot;[api path 생략]?offset=\(offset)&amp;amp;limit=\(limit)&quot;)!)
        return try JSONDecoder().decode(Products.self, from: data)
    }
    
    static var previewValue = ProductClient { offset, limit in
        let products = [
            Product(id: 0, title: &quot;맥북 에어 2022&quot;, price: 1000, description: &quot;&quot;, images: [&quot;&quot;], creationAt: &quot;&quot;, updatedAt: &quot;&quot;, category: Category(id: 0, name: &quot;&quot;, image: &quot;&quot;, creationAt: &quot;&quot;, updatedAt: &quot;&quot;)),
            // 생략..
        ]
        
        return products
    }
}&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;TCA에서는 DependencyKey를 제공해서 전역으로 사용하기 위한 Depdency를 관리할 수 있어요. 여기서 dependency overriding을 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702633217215&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static var liveValue: Value { get }
static var previewValue: Value { get }
static var testValue: Value { get }&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;TCA의 DependencyKey 내부를 확인해 보면 liveValue, previewValue, testValue를 제공하고 있어요. 각각 상황에 따라 적절한 dependency를 지정한 후 주입해 줄 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시뮬레이터나 실제 기기에서는 liveValue를 사용해 dependency를 주입하고, preview에서는 preview에서 UI를 확인 하기 위한 용도로&amp;nbsp; 여러 mock 데이터나 실제 api request 코드 등을 넣을 수도 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PointFree 강의를 보니 실제 기기에서 권한 받아오는 것은 문제가 없지만 프리뷰에서는 정상 동작하지 않아 이를 Dependency를 사용해서 각 상황에 맞게 대응해 주고 있었습니다.&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;의존성을 약하게 만드는 것이 왜 중요한지는 SwiftUI의 Preview를 생각해보면 쉽게 이해할 수 있습니다. SwiftUI는 Canvas에 SwiftUI의 View를 빌드하기 전 미리 보여줍니다. 시뮬레이터와 앱에서 실행하는 코드들과 달리 Preview는 현재 View에서 시작합니다. View가 외부로 부터 필요로 하는 값이 있다면 Preview에서 initializer나 dependency를 주입하는 방식(for example: Environment)을 사용해 주면 됩니다.&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;이때 View가 필요로 하는 값 중 UserDefault의 값이 있다면 어떻게 해야 할까요? Preview에서 기기에 저장된 값이 있을까요? Preview 뿐만 아니라 Unit Test시 필요로 하는 값은 어떻게 설정해 주어야 할까요?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 작동하는 앱 뿐만 아니라 Preview, Test 코드 등에서 이 dependency를 주입해야 하고 이 의존성을 약하고 유현하게 만드는 것이 중요합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DepdencyValues&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702633430476&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension DependencyValues {
    var productClient: ProductClient {
        get { self[ProductClient.self] }
        set { self[ProductClient.self] = newValue }
    }
}&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;@Depdency 프로퍼티 래퍼를 사용해서 의존성에 접근하고 사용하기 위해 get, set을 통해 코드를 작성해 주면 됩니다. 이로서 모든 준비가 끝났으니 실제 사용하는 방법을 확인해 봅시다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MainViewModel&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702997901062&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class MainViewModel: ObservableObject {
    var offset = 0
    var limit = 10
    @Published var products: [Product] = []
    
    @Dependency(\.productClient) var productClient
    
    @MainActor
    func fetchProduct() async {
        self.products = try! await productClient.fetch(offset, limit)
    }
}&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;MainViewModel은 productClient라는 depdency를 가지고 있고 fetchProduct가 실행되면 이 depdency로 부터 데이터를 fetch 해 옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainViewModel이 생성될 때 이 dependency 를 주입해 줄 수 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OnboardingView&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702998033151&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct OnboardingView: View {
    @ObservedObject var viewModel: OnboardingViewModel
    
    var body: some View {
        ZStack {
            Button {
                viewModel.startButtonTapped()
            } label: {
                Text(&quot;시작하기&quot;)
            }
            .buttonStyle(.borderedProminent)
            if viewModel.isShowMain {
                AMainView(viewModel: withDependencies {
                    $0.productClient = .liveValue
                  } operation: {
                    MainViewModel()
                  })
            }
        }
    }
}&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;AMainView는 MainViewModel을 필요로 합니다. viewModel을 생성할 때 dependency였던 productClient를 주입해 줘야 합니다. 코드를 자세히 살펴 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702998140907&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;AMainView(viewModel: withDependencies {
    $0.productClient = .liveValue
  } operation: {
    MainViewModel()
  })&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;withDependecies를 사용해서 depdency를 지정해 주고 있어요.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702998181439&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static var liveValue: Value { get }
static var previewValue: Value { get }
static var testValue: Value { get }&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;이때 liveValue는 바로 위에서 DepdencyKey를 구현해 줄 때 적어 주었던 값입니다. liveValue는 무조건 적어 주어야 하는 값이고 실제 동작시 사용할 dependency를 정의해 놓기 때문에 Dependency 주입시 liveValue를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 preview는 어떻게 해야 할까요? 앱을 실제 실행할 때는 liveValue가 필요하지만 preview에서는 실제 데이터가 필요하지 않을 수 있어요. 예를 들어 로그인 뒤에 조회 할 수 있는 api라면 현재 화면 부터 보여주는 preview의 경우 실제 api로 부터 값을 받아 오기 힘들 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702998337410&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#Preview {
    AMainView(viewModel: withDependencies {
        $0.productClient = .previewValue
      } operation: {
        MainViewModel()
      })
}&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;preview도 withDependencies를 사용해서 Dependency의 값을 주입해 주면 됩니다. 이때는 .previewValue를 사용해서 mock 데이터를 보여주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmDIrG/btsCiJm9GLa/ekFGN0kLaAhepyPlN6bjeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmDIrG/btsCiJm9GLa/ekFGN0kLaAhepyPlN6bjeK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;662&quot; data-filename=&quot;스크린샷 2023-12-20 오전 12.08.20.png&quot; style=&quot;width: 51.3352%; margin-right: 10px;&quot; data-widthpercent=&quot;51.94&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmDIrG/btsCiJm9GLa/ekFGN0kLaAhepyPlN6bjeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmDIrG%2FbtsCiJm9GLa%2FekFGN0kLaAhepyPlN6bjeK%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;330&quot; height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7av0y/btsCk62ZSLv/wfHC33xAdFPO3lISBToZRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7av0y/btsCk62ZSLv/wfHC33xAdFPO3lISBToZRk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;simulator_screenshot_B02641CA-0932-4681-BC3D-8D601B9418F9.png&quot; style=&quot;width: 47.502%;&quot; data-widthpercent=&quot;48.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7av0y/btsCk62ZSLv/wfHC33xAdFPO3lISBToZRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7av0y%2FbtsCk62ZSLv%2FwfHC33xAdFPO3lISBToZRk%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;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/div&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;왼쪽은 Preview 이고, 오른쪽 사진은 실제 시뮬레이터에서 돌렸을 때 나오는 화면입니다. (아니 누가 api 에 있는 값 리셋해놨나봐... 원래 10개 받아와야 하는데 데이터가 진짜 한개밖에 없다.. 누가 건드려쒀... ㅠㅠㅠ)&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;TCA는 SwiftUI 아키텍처로 사용되는데, DI를 위한 Dependency는 UIKit에서도 적용 가능합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고 사이트 및 도서&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/designingdependencies/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/designingdependencies/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/overridingdependencies/#Scoping-dependencies&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.pointfree.co/collections/tours/composable-architecture-1-0/ep248-tour-of-the-composable-architecture-1-0-dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.pointfree.co/collections/tours/composable-architecture-1-0/ep248-tour-of-the-composable-architecture-1-0-dependencies&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pointfreeco/isowords/blob/isowords-deploy-v117/Sources/SettingsFeature/Settings.swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/pointfreeco/isowords/blob/isowords-deploy-v117/Sources/SettingsFeature/Settings.swift&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pointfreeco/isowords/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/pointfreeco/isowords/tree/main&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://axiomatic-fuschia-666.notion.site/Chapter-5-Dependency-de90da4e19554625af3ffc005ab13ed9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://axiomatic-fuschia-666.notion.site/Chapter-5-Dependency-de90da4e19554625af3ffc005ab13ed9&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;855&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/binWDX/btsBHSzoyLX/QZt1S7OXxLIcBZdn5dkp21/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/binWDX/btsBHSzoyLX/QZt1S7OXxLIcBZdn5dkp21/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/binWDX/btsBHSzoyLX/QZt1S7OXxLIcBZdn5dkp21/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbinWDX%2FbtsBHSzoyLX%2FQZt1S7OXxLIcBZdn5dkp21%2Fimg.webp&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;439&quot; height=&quot;293&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;855&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;</description>
      <category>iOS  /Architecture</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/93</guid>
      <comments>https://framios.tistory.com/93#entry93comment</comments>
      <pubDate>Wed, 13 Dec 2023 01:04:51 +0900</pubDate>
    </item>
    <item>
      <title>[SwiftUI/TCA] Binding</title>
      <link>https://framios.tistory.com/91</link>
      <description>&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;SwiftUI&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;TCA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- State 의 변경 -&amp;gt; UI에 즉시 반영&lt;br /&gt;- 코드 작성이 간단함&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- State 변경 로직 관리 용이&lt;br /&gt;- 복잡한 State와 이에 따른 Side Effect 처리 용이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- State 관리가 복잡해 질수록 State 변화에 따른 side effect를 관리하기 어려움&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;- 어려운 구현 난이도    &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TCA Binding&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1701870978372&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public func binding&amp;lt;Value&amp;gt;(
    get: @escaping (_ state: ViewState) -&amp;gt; Value,
    send valueToAction: @escaping (_ value: Value) -&amp;gt; ViewAction
  ) -&amp;gt; Binding&amp;lt;Value&amp;gt; {
    ObservedObject(wrappedValue: self)
      .projectedValue[get: .init(rawValue: get), send: .init(rawValue: valueToAction)]
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;get : &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;State&lt;/span&gt;를 바인딩의 값으로 변환하도록 하는 클로저&lt;/li&gt;
&lt;li&gt;send : 바인딩의 값을 다시 Store에 피드백 하는 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;Action&lt;/span&gt;으로 변환하는 클로저&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 ChildView에게 Binding&amp;lt;Value&amp;gt;를 전달할 때가 있는데 위 binding&amp;lt;Value&amp;gt;는 그 때 사용하는 것&lt;/p&gt;
&lt;pre id=&quot;code_1701880077413&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text(&quot;쿠폰&quot;)
.padding(.leading, 10)
VStack {
    TextField(&quot;프로모션 코드를 입력해 주세요.&quot;, text: .constant(&quot;&quot;))
}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(text에 Binding&amp;lt;String&amp;gt;으로 상위 State를 전달하게 되는데 이때의 State는 어떻게 관리할 것인가에 관한것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;binding(get:send:) 사용 하는 법&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1701880214867&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    struct State: Equatable {
        var couponCode = &quot;&quot;
        // 생략
    }&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1701880302078&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    enum Action: Equatable {
        case couponCodeInserted(String)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View에서 텍스트 필드를 통해 텍스트를 입력하면 Store가 아닌 Store 외부에 있는 TextField 내부에서 State를 조정하게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때의 Action을 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701880444984&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        Reduce { state, action in
            switch action {
            case .couponCodeInserted(let code):
                state.couponCode = code
                return .none
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reducer에서 Action에 대한 결과로 State의 값을 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701880551786&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VStack {
    TextField(&quot;프로모션 코드를 입력해 주세요.&quot;, 
    text: viewStore.binding(
        get: \.couponCode,
        send: { .couponCodeInserted($0) }
    ))}
.padding()
.border(.black, width: 1)
.padding(.horizontal, 10)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TextField의 Binding&amp;lt;String&amp;gt;으로 넘겨준다. 이때 Store의 binding(get:send:)를 사용&lt;/li&gt;
&lt;li&gt;viewStore.binding(get:send:)에서 get에 전달된 객체 바인딩 하여 text에 전달, send에 전달된 Action을 Store에 다시 피드백, 이후 해당 Action에 해당하는 reducer 로직이 실행&lt;/li&gt;
&lt;li&gt;get으로 State, send로 Action&lt;/li&gt;
&lt;li&gt;이벤트를 단방향 통신으로 바꾸고 State와 사이드 이펙트를 관리할 수 있게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;binding(get:send)의 단점&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;각 서브 뷰에서 State를 관리하는 방식이 아니기 때문에 비즈니스 로직이 더 크고 복잡해 질 수록 많은 바인딩 코드가 작성되며 reducer의 관리가 복잡해 짐.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;가독성이 나빠지고 반복되는 코드 작업이 필요하게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@BindingState&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;@BindingState를 사용하면 SwiftUI의 UI 컨트롤에 바인딩 가능&lt;/li&gt;
&lt;li&gt;View에서 해당 필드 값 조정 가능&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;View에서 해당 필드 값을 변경할 수 없도록 하려면&amp;nbsp;&lt;/span&gt;@BindingState를 붙이지 않아야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI에서 Binding은 State를 다른 뷰와 공유하는 개념이기 때문에 Source of Truth 관리가 어려워진다. 따라서 모든 필드에 @BindingState를 사용하는건 권장되지 않음 (캡슐화 손상)&lt;/li&gt;
&lt;li&gt;컴포넌트에 전달하기 위한 필드에만 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702031964543&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    struct State: Equatable {
        @BindingState var isToggled = false
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BindableAction protocol&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702032177202&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    enum Action: BindableAction, Equatable {
        case binding(BindingAction&amp;lt;State&amp;gt;)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Action enum에 BindableAction 프로토콜 채택&lt;/li&gt;
&lt;li&gt;BindingState를 사용한 필드는 이 하나의 Action에 연결됨&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702032295891&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct BindingAction&amp;lt;Root&amp;gt;: CasePathable, Equatable, @unchecked Sendable&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Action case가 여러 BindingState에 대응할 수 있게 하기 위해 제네릭 타입을 가짐&lt;/li&gt;
&lt;li&gt;제네릭 타입은 reducer의 State&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702092431501&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;associatedtype State&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bindable 내에는 State 타입을 제네릭으로 받기 위해 associatedtype으로 State가 정의되어 있음(바인딩을 위한 상태 값의 타입은 여러가지가 올 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BindingReducer&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702032443181&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    var body: some ReducerOf&amp;lt;Self&amp;gt; {
        BindingReducer()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;reducer에는 BindingReducer를 사용해서 State 변경을 간단하게 할 수 있음&lt;/li&gt;
&lt;li&gt;Binding action이 수신되면 State를 업데이트 해주는 reducer&lt;/li&gt;
&lt;li&gt;Store 내부 바인딩 가능한 State(예시 코드에서는isToggled) 필드가 업데이트 되면 BindingReducer()가 업데이트 된 필드 값과 함께 Action 수신 후 Reducer 클로저 내에 도메인 로직 처리하여 State에 결과 반영&lt;/li&gt;
&lt;li&gt;State와 Action 사이를 바인딩 하는 역활&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702093767442&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Reduce { state, action in&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;action이 BindableAction을 따르게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702093825546&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static func binding(_ action: BindingAction&amp;lt;State&amp;gt;) -&amp;gt; Self&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BindableAction의 위 메서드는 BindingAction을 반환&amp;nbsp;&lt;/li&gt;
&lt;li&gt;BindingState로 정의한 State를 지정해주면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702033488870&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            case .binding(\.$isToggled):
                return .none
            case .binding(_):
                return .none&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Effect를 반환하는 Reduce 클로저에는 case .bindg(_) 에서 정의해 주며 키패스를 사용해서 접근&lt;/li&gt;
&lt;li&gt;.binding(_) 도 적어주어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BindingState 프로퍼티 래퍼의 사용&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702033719568&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Toggle(&quot;test 입니다.&quot;, isOn: viewStore.$isToggled)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;.&lt;/span&gt;_printChanges&lt;span&gt;()&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1702091985340&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;received action:
  ProductDetailFeature.Action.amountControl(.plusButtonTapped)
  ProductDetailFeature.State(
-   productAmount: 1,
+   productAmount: 2,
-   amountControl: AmountControl.State(amount: 1)
+   amountControl: AmountControl.State(amount: 2)
    _isToggled: true
  )&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;View State Binding&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Store 외부에 있는 View State를 바인딩하기 위해서 BindingViewState 프로퍼티 래퍼를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702300190272&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct CartButton: View { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Child View 혹은 Sub View를 만들면 Binding을 통해서 값을 공유해야 하는 상황이 발생하게 되는데 이때 BindingViewState를 사용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702300246410&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let store: StoreOf&amp;lt;ProductDetailFeature&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위뷰에 상위 스토어를 정의해 주고 init 시점에 전달 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702300274667&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    struct ViewState: Equatable {
        @BindingViewState var productAmount: Int
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ViewState를 구조체를 만들고 바인딩이 필요한 값을 정의 해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702300314638&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    var body: some View {
        WithViewStore(store, observe: { bindingViewStore in
            ViewState(productAmount: bindingViewStore.$productAmount)
        }) { viewStore in
            ZStack(alignment: .bottomLeading) {
                Image(systemName: &quot;cart&quot;)
                    .foregroundStyle(.green)
                    .font(.system(size: 30))
                
                if viewStore.productAmount &amp;gt; 0 {
                    Circle()
                        .frame(width: 20, height: 20)
                        .foregroundStyle(.red)
                        .overlay(
                            Text(&quot;\(viewStore.productAmount)&quot;)
                                .foregroundStyle(.white)
                                .fontWeight(.heavy)
                        )
                        .offset(x: -5, y: 8)
                }
            }
            .frame(width: 50, height: 50)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 State에서 전달 받은 값 중에 하위 뷰에서만 사용할 값을 지정하기 위해서 WithViewStore의 observe 파라미터를 지정해 줌&lt;/li&gt;
&lt;li&gt;BindingViewStore을 사용해서 상위 뷰의 모든 값에 접근하는 것이 아닌 필요한 값만 공유할 수 있음&lt;/li&gt;
&lt;li&gt;ViewState에서 BindingViewState로 지정한 값과 상위 ViewStore로 부터 전달 받은 State를 바인딩 해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1702300431622&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CartButton(store: self.store)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위뷰에서 하위뷰를 사용할 때는 store를 전달해 주면 된다.&lt;/li&gt;
&lt;/ul&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-filename=&quot;oranges-8032713_640.jpg&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7WRlM/btsBtGTqJWa/nFoekRtsrd45WbZ3k3hFXk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7WRlM/btsBtGTqJWa/nFoekRtsrd45WbZ3k3hFXk/img.jpg&quot; data-alt=&quot;Pixabay 로부터 입수된&amp;amp;amp;nbsp; Tim Mossholder 님의 이미지 입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7WRlM/btsBtGTqJWa/nFoekRtsrd45WbZ3k3hFXk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7WRlM%2FbtsBtGTqJWa%2FnFoekRtsrd45WbZ3k3hFXk%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;242&quot; height=&quot;363&quot; data-filename=&quot;oranges-8032713_640.jpg&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pixabay 로부터 입수된&amp;amp;nbsp; Tim Mossholder 님의 이미지 입니다.&lt;/figcaption&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;</description>
      <category>iOS  /Architecture</category>
      <author>fram</author>
      <guid isPermaLink="true">https://framios.tistory.com/91</guid>
      <comments>https://framios.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 7 Dec 2023 01:59:14 +0900</pubDate>
    </item>
  </channel>
</rss>