<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>NamS의 iOS일기</title>
    <link>https://nsios.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 23:33:42 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>냄수</managingEditor>
    <image>
      <title>NamS의 iOS일기</title>
      <url>https://tistory1.daumcdn.net/tistory/3197485/attach/8628db088d9448cd8a4e15cd5e46e408</url>
      <link>https://nsios.tistory.com</link>
    </image>
    <item>
      <title>[Swift] WWDC 25 App Intent 알아보기 (feat. 단축어)</title>
      <link>https://nsios.tistory.com/260</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;애플 인텔리전스를 활용하기위해 먼저 앱인텐트에 대해서 알아보려합니다&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;App Intents&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;App Intents는 시스템 전체 및 모든 Apple 플랫폼에서 앱의 발견 가능성, 가시성, 기능을 확장해 주는 프레임워크입니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;앱을 열지 않은 사용자에게도 풍부한 경험을 제공할 수 있도록 합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;앱 인텐트로 두 가지 유형의 값을 만들 수 있습니다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;상수 값 집합을 가진 타입에는 &lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;AppEnum&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;동적 타입에는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;AppEntity&lt;/b&gt;&lt;/span&gt;를 사용합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앱 단축어&lt;/b&gt;는 앱 인텐트를 강조해 더 쉽게 접근하고 발견할 수 있도록 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spotlight 검색이나 Siri 사용, 동작 버튼 구성과 같은 상황에서 표시됩니다&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/bJSyIz/dJMcacRs9CE/tmbELMryxxkFtbKgvWTmb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJSyIz/dJMcacRs9CE/tmbELMryxxkFtbKgvWTmb1/img.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;542&quot; data-is-animation=&quot;false&quot; style=&quot;width: 62.9669%; margin-right: 10px;&quot; data-widthpercent=&quot;63.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJSyIz/dJMcacRs9CE/tmbELMryxxkFtbKgvWTmb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJSyIz%2FdJMcacRs9CE%2FtmbELMryxxkFtbKgvWTmb1%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;1332&quot; height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oi53j/dJMcaazsAPP/ye28Z1ZTbVmDHohgmlhxqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oi53j/dJMcaazsAPP/ye28Z1ZTbVmDHohgmlhxqK/img.png&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.8703%;&quot; data-widthpercent=&quot;36.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oi53j/dJMcaazsAPP/ye28Z1ZTbVmDHohgmlhxqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOi53j%2FdJMcaazsAPP%2Fye28Z1ZTbVmDHohgmlhxqK%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;504&quot; height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;supportedModes&lt;/b&gt;&lt;/span&gt; 속성을 foreground로 설정하여 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;인텐트가 실행되기 전에 앱을 열도록 할수&lt;/b&gt;&lt;/span&gt;있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 설치한 후 인텐트는 단축어에서 찾을 수 있습니다 &lt;br /&gt;새 단축어를 만들고 탐색 인텐트를 추가할 수 있음 &lt;br /&gt;단축어를 실행하면 앱이 포그라운드로 이동하고 Landmarks 뷰가 바로 표시됨&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;h4 data-ke-size=&quot;size20&quot;&gt;AppEnum사용&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzNBtD/dJMcaazsAP7/YKif2sryDALKKjgfBb59HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzNBtD/dJMcaazsAP7/YKif2sryDALKKjgfBb59HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzNBtD/dJMcaazsAP7/YKif2sryDALKKjgfBb59HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzNBtD%2FdJMcaazsAP7%2FYKif2sryDALKKjgfBb59HK%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;1648&quot; height=&quot;636&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AppEnum&lt;/b&gt;을 사용하려면 몇 가지 요건만 충족하면 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열로부터 인스턴스화가 가능해야 하므로 String 원본 값을 추가&lt;br /&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;typeDisplayRepresentation은 타입을 전체적으로 설명&lt;br /&gt;caseDisplayRepresentation은 enum의 각 case를 설명&lt;/span&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byisN0/dJMcadCLIA6/5nHGG8xQmTFO2q2rDeQk31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byisN0/dJMcadCLIA6/5nHGG8xQmTFO2q2rDeQk31/img.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;356&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.5329%; margin-right: 10px;&quot; data-widthpercent=&quot;46.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byisN0/dJMcadCLIA6/5nHGG8xQmTFO2q2rDeQk31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyisN0%2FdJMcadCLIA6%2F5nHGG8xQmTFO2q2rDeQk31%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;978&quot; height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eco9aB/dJMcaiYqrtp/VuDB9m35AnXpVFRI6u28F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eco9aB/dJMcaiYqrtp/VuDB9m35AnXpVFRI6u28F1/img.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;358&quot; data-is-animation=&quot;false&quot; style=&quot;width: 24.4449%; margin-right: 10px;&quot; data-widthpercent=&quot;25.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eco9aB/dJMcaiYqrtp/VuDB9m35AnXpVFRI6u28F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feco9aB%2FdJMcaiYqrtp%2FVuDB9m35AnXpVFRI6u28F1%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;528&quot; height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4BssV/dJMcadCLIBb/IAkFsVPn5EiOeb93pJ4Rc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4BssV/dJMcadCLIBb/IAkFsVPn5EiOeb93pJ4Rc0/img.png&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;304&quot; data-is-animation=&quot;false&quot; style=&quot;width: 27.6966%;&quot; data-widthpercent=&quot;28.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4BssV/dJMcadCLIBb/IAkFsVPn5EiOeb93pJ4Rc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4BssV%2FdJMcadCLIBb%2FIAkFsVPn5EiOeb93pJ4Rc0%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;508&quot; height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;@Parameter&lt;/b&gt;&lt;/span&gt; 속성을 추가하여 인텐트 매개변수로 만들 수 있음&lt;br /&gt;인텐트 매개변수는 인텐트의 입력으로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단축어로 돌아가면 NavigationOption이 편집 가능한 매개변수로 표시되고 &lt;br /&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-origin-width=&quot;1638&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/urqZ9/dJMcadQnhNI/EdjENwtUU0bR9NjrNhm7M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/urqZ9/dJMcadQnhNI/EdjENwtUU0bR9NjrNhm7M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/urqZ9/dJMcadQnhNI/EdjENwtUU0bR9NjrNhm7M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FurqZ9%2FdJMcadQnhNI%2FEdjENwtUU0bR9NjrNhm7M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1638&quot; height=&quot;674&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘을 추가하려면 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;DisplayRepresentation&lt;/b&gt;&lt;/span&gt; 이니셜라이저가 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 case에 대해 기호를 추가가능&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;1648&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gcewt/dJMcagl37LJ/ifLxYo3YQ1kAy7YuRDggB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gcewt/dJMcagl37LJ/ifLxYo3YQ1kAy7YuRDggB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gcewt/dJMcagl37LJ/ifLxYo3YQ1kAy7YuRDggB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGcewt%2FdJMcagl37LJ%2FifLxYo3YQ1kAy7YuRDggB0%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;1648&quot; height=&quot;468&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;468&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트는 ParameterSummary라는 유동적인 문장 형식으로 구성가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ParameterSummary는 동작과 관련 매개변수를 사용자가 읽기 편한 방식으로 설명합니다&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;1676&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u9nfz/dJMb99UM87w/Ylv39m2ILYtxGDJXJqib8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u9nfz/dJMb99UM87w/Ylv39m2ILYtxGDJXJqib8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u9nfz/dJMb99UM87w/Ylv39m2ILYtxGDJXJqib8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu9nfz%2FdJMb99UM87w%2FYlv39m2ILYtxGDJXJqib8K%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;1676&quot; height=&quot;634&quot; data-origin-width=&quot;1676&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수에 맞춤화된 title을 추가하여 이해하기 힘든 문장도 해결할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 동작을 인텐트로 모델링하면 사용자가 강력한 단축어와 자동화를 만들 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서 매우 중요한 인텐트는 앱이 설치된 순간부터 제공되어야 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 단축어를 채택하여 제공할 수 있습니다 &lt;br /&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;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;AppShortcutsProvider&lt;/b&gt;&lt;/span&gt;를 통해 앱 단축어를 제공합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서는 모든 앱 단축어를 포함하는 단일 Provider를 정의해야 합니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZINn/dJMcad3UY1F/i9hOvfHm7n9HL5As7ETkf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZINn/dJMcad3UY1F/i9hOvfHm7n9HL5As7ETkf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZINn/dJMcad3UY1F/i9hOvfHm7n9HL5As7ETkf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZINn%2FdJMcad3UY1F%2Fi9hOvfHm7n9HL5As7ETkf1%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;1642&quot; height=&quot;644&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수 없이 phrase를 제공하면 shortTitle, systemImageName 기반으로 앱 단축어가 생성 &amp;rarr; Navigate 버튼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppEnum을 포함한 phrase를 제공하면 case마다 앱 단축어가 생성 &amp;rarr; Landmarks,Map,Collections 버튼&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;Siri나 Spotlight를 통해 인텐트를 실행할 수 있습니다&lt;br /&gt;앱 단축어를 빌드하면 사용자가 인텐트를 쉽게 찾을 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785378548127&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct LandmarksShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: NavigateIntent(),
            phrases: [
                &quot;Navigate in \(.applicationName)&quot;,
                &quot;\(.applicationName)에서 이동&quot;,
            ],
            shortTitle: &quot;Navigate&quot;,
            systemImageName: &quot;arrow.turn.down.right&quot;
        )

        // navigationOption의 case 수만큼 앱 단축어가 만들어진다 (Landmarks / Map / Collections)
        AppShortcut(
            intent: NavigateIntent(),
            phrases: [
                &quot;Open \(\.$navigationOption) in \(.applicationName)&quot;
            ],
            shortTitle: &quot;Open Section&quot;,
            systemImageName: &quot;square.grid.2x2&quot;
        )

        AppShortcut(
            intent: FindNextLandmarkIntent(),
            phrases: [
                &quot;Find my next landmark in \(.applicationName)&quot;,
                &quot;\(.applicationName) 다음 여행지 추천&quot;,
            ],
            shortTitle: &quot;Next Landmark&quot;,
            systemImageName: &quot;sparkles&quot;
        )
    }
}&lt;/code&gt;&lt;/pre&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;1283&quot; data-origin-height=&quot;1117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7pHQg/dJMcahZzEKo/Bx98wmIZfjtllkdCOaLTi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7pHQg/dJMcahZzEKo/Bx98wmIZfjtllkdCOaLTi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7pHQg/dJMcahZzEKo/Bx98wmIZfjtllkdCOaLTi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7pHQg%2FdJMcahZzEKo%2FBx98wmIZfjtllkdCOaLTi0%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;355&quot; height=&quot;309&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;1117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;섹션이 구분돼서 표시됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;AppEntity사용&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OLpHC/dJMcafHyF5v/14jyFRM3tKprWDLjrd52MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OLpHC/dJMcafHyF5v/14jyFRM3tKprWDLjrd52MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OLpHC/dJMcafHyF5v/14jyFRM3tKprWDLjrd52MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOLpHC%2FdJMcafHyF5v%2F14jyFRM3tKprWDLjrd52MK%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;1590&quot; height=&quot;788&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppEntity는 식별 가능해야 하므로 ID를 추가합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 식별자는 영구적이어야 하며 이 ID를 통해 엔티티의 인스턴스를 조회할 수 있어야 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;@ComputedProperty&lt;/b&gt; &lt;/span&gt;속성을 사용해서 엔티티 속성에 Getter를 추가할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 간에 값을 복사하는 대신 데이터 모델을 따를 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ComputedProperty매크로는 &lt;span style=&quot;color: #8a3db6;&quot;&gt;$로 시작하는 prefix 변수를 생성&lt;/span&gt;합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name같은경우 $name 때문에 나중에 \LandmarkEntity.$name으로 키패스를 사용할 수 있습니다.&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;AppEnum처럼 AppEntity도 타입 및 타입 인스턴스에 대한 Representation이 필요합니다&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;1374&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJULcA/dJMcaiRLSJ2/4hFBav36BgXH3sHcZnpj5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJULcA/dJMcaiRLSJ2/4hFBav36BgXH3sHcZnpj5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJULcA/dJMcaiRLSJ2/4hFBav36BgXH3sHcZnpj5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJULcA%2FdJMcaiRLSJ2%2F4hFBav36BgXH3sHcZnpj5K%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;1374&quot; height=&quot;602&quot; data-origin-width=&quot;1374&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppEnum이 알려진 값을 갖는 반면 AppEntity는 동적입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에 있는 랜드마크의 수는 달라질 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리는 시스템이 엔티티에 대해 추론하는 방법입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 다양한 질문을 답하며 진행됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 유형의 질문은 &amp;ldquo;어떤 엔티티가 존재하나요?&amp;rdquo;입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리는 이 질문에 답변하고 일치하는 엔티티의 컬렉션을 반환합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtyhJ/dJMcafHyGa1/gA8C57uA3NJZYE6xefTpX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtyhJ/dJMcafHyGa1/gA8C57uA3NJZYE6xefTpX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtyhJ/dJMcafHyGa1/gA8C57uA3NJZYE6xefTpX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrtyhJ%2FdJMcafHyGa1%2FgA8C57uA3NJZYE6xefTpX0%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;1724&quot; height=&quot;924&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;@Dependency&lt;/b&gt; &lt;/span&gt;속성을 사용하여 쿼리에 종속성을 삽입할 수 있습니다 공유된 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;AppDependencyManager&lt;/b&gt;&lt;/span&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-origin-width=&quot;1442&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cksM3O/dJMcaf1KfyT/IleAZV8wMeB5peVooPDUk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cksM3O/dJMcaf1KfyT/IleAZV8wMeB5peVooPDUk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cksM3O/dJMcaf1KfyT/IleAZV8wMeB5peVooPDUk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcksM3O%2FdJMcaf1KfyT%2FIleAZV8wMeB5peVooPDUk0%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;1442&quot; height=&quot;544&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1785377442145&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FindNextLandmarkIntent: AppIntent {
    static let title: LocalizedStringResource = &quot;Find Next Landmark&quot;
    static let description = IntentDescription(&quot;아직 방문하지 않은 랜드마크 하나를 추천합니다.&quot;)

    @Dependency var store: LandmarkStore

    func perform() async throws -&amp;gt; some IntentResult
        &amp;amp; ReturnsValue&amp;lt;LandmarkEntity&amp;gt;
        &amp;amp; ProvidesDialog
        &amp;amp; ShowsSnippetView
    {
        ...
        return .result(
            value: entity,
            dialog: &quot;다음은 \(pick.name)이 어떨까요?&quot;,
            view: LandmarkSnippet(entity: entity)
        )
    }
}


struct LandmarkSnippet: View {
    let entity: LandmarkEntity

    var body: some View {
        HStack(spacing: 16) {
            Image(systemName: entity.landmark.symbol)
                .font(.system(size: 44))
                .foregroundStyle(.tint)
            VStack(alignment: .leading, spacing: 4) {
                Text(entity.landmark.name).font(.headline)
                Text(entity.landmark.continent).font(.subheadline).foregroundStyle(.secondary)
                Text(entity.landmark.summary).font(.caption)
            }
            Spacer()
        }
        .padding()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perform 메서드에 LandmarkEntity의 &lt;b&gt;ReturnsValue&lt;/b&gt;를 추가합니다&lt;br /&gt;인텐트 매개변수로 사용된 타입은 인텐트에서 반환될 수도 있습니다&lt;br /&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;반환된 값은 여러 단계의 단축어에서처럼 다른 인텐트의 입력으로 사용할 수&lt;/b&gt;&lt;/span&gt; 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화상자와 뷰 스니펫도 반환되도록 설정하겠습니다 &amp;rarr;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt; ProvidesDialog &amp;amp; ShowsSnippetView&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&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/NQ7M0/dJMcahyyRFg/Q8ygsGxkYWHk4a4RzfXkXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NQ7M0/dJMcahyyRFg/Q8ygsGxkYWHk4a4RzfXkXk/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&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/NQ7M0/dJMcahyyRFg/Q8ygsGxkYWHk4a4RzfXkXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNQ7M0%2FdJMcahyyRFg%2FQ8ygsGxkYWHk4a4RzfXkXk%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CXIjy/dJMcagl4uvN/J8aKkxQfXMJndzrQvmr6h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CXIjy/dJMcagl4uvN/J8aKkxQfXMJndzrQvmr6h1/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CXIjy/dJMcagl4uvN/J8aKkxQfXMJndzrQvmr6h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCXIjy%2FdJMcagl4uvN%2FJ8aKkxQfXMJndzrQvmr6h1%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽이미지 - 실행시킨 뷰는 LandMarkSnippet뷰를 제공한것이고&lt;/p&gt;
&lt;pre id=&quot;code_1785378731225&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct LandmarkEntity: IndexedEntity {
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: &quot;\(landmark.name) - IndexEntity&quot;,
            subtitle: &quot;\(landmark.continent)&quot;,
            image: .init(systemName: landmark.symbol)
        )
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽이미지 - 단축어에서 미리보기는 LandmarkEntity의 displayRepresentation를 보여줍니다&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;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Transferable&lt;/b&gt;&lt;/span&gt; 프로토콜을 사용하여 엔티티의 imageRepresentation을 선언할 수 있습니다&lt;br /&gt;Transferable은 &lt;span style=&quot;color: #8a3db6;&quot;&gt;타입의 다양한 데이터 표현을 선언적으로 설명하는 방법&lt;/span&gt;입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 representation은 앱 간에 데이터를 공유하는 데 사용될 수 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HeTy3/dJMcab6aEcN/CO8LBv0lSpEGFWwCmJOtj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HeTy3/dJMcab6aEcN/CO8LBv0lSpEGFWwCmJOtj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HeTy3/dJMcab6aEcN/CO8LBv0lSpEGFWwCmJOtj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHeTy3%2FdJMcab6aEcN%2FCO8LBv0lSpEGFWwCmJOtj0%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;1266&quot; height=&quot;338&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 타입의 TransferRepresentation 일부로 제공하면 &lt;br /&gt;단축어에서 엔티티의 imageRepresentation을 표시하도록 선택할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단축어를 실행하면 랜드마크의 이미지가 표시됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;imageRepresentation 선언 시 누릴 수 있는 이점중 하나로&lt;br /&gt;사진을 촬영하는 모든 동작의 입력으로 이 이미지 값을 사용할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 앱에 있는 사진도 활용할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;1754&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/33jIh/dJMb998j3S9/H4GRzIu3NTb6C9KVs5XQNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/33jIh/dJMb998j3S9/H4GRzIu3NTb6C9KVs5XQNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/33jIh/dJMb998j3S9/H4GRzIu3NTb6C9KVs5XQNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F33jIh%2FdJMb998j3S9%2FH4GRzIu3NTb6C9KVs5XQNK%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;1754&quot; height=&quot;608&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spotlight는 여러 앱에서 작동하는 강력한 시맨틱 검색을 제공합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티를 Spotlight에 제공하면 시스템이 콘텐츠를 이해하는 범위가 확장됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spotlight가 엔티티를 인덱싱하도록 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;IndexedEntity&lt;/b&gt;&lt;/span&gt; 프로토콜을 채택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IndexedEntity는 Core Spotlight 속성 집합을 포함하는 AppEntity임&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;1346&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/quZWD/dJMcafHyIMD/4P2i3lzIiAwQLkBhkIv6L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/quZWD/dJMcafHyIMD/4P2i3lzIiAwQLkBhkIv6L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/quZWD/dJMcafHyIMD/4P2i3lzIiAwQLkBhkIv6L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FquZWD%2FdJMcafHyIMD%2F4P2i3lzIiAwQLkBhkIv6L1%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;1346&quot; height=&quot;426&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;OpenIntent&lt;/b&gt;&lt;/span&gt; 프로토콜을 채택한 인텐트는 실행 전에 앱을 자동으로 엽니다&lt;br /&gt;그러므로 SupportedMode를 추가하지 않아도 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenIntent에는 target 매개변수가 있어야 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spotlight에서 엔티티를 누르면 해당 엔티티와 일치하는 OpenIntent가 실행됩니다&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;1322&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RwDx1/dJMcagNfILr/7UDjKVHAz73SeG9PaaBjf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RwDx1/dJMcagNfILr/7UDjKVHAz73SeG9PaaBjf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RwDx1/dJMcagNfILr/7UDjKVHAz73SeG9PaaBjf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRwDx1%2FdJMcagNfILr%2F7UDjKVHAz73SeG9PaaBjf1%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;1322&quot; height=&quot;782&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;내비게이션을&lt;/b&gt;&lt;/span&gt; 위해 특별히 설계된 새로운 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;TargetContentProvidingIntent&lt;/b&gt;&lt;/span&gt; 프로토콜을 채택할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인텐트에는 perform 메서드가 필요하지 않습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 뷰에 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;onAppIntentExecution&lt;/b&gt;&lt;/span&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-origin-width=&quot;1208&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCxMwK/dJMcafAJ4CZ/roZ5iHMdC0XefxEmKpIqNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCxMwK/dJMcafAJ4CZ/roZ5iHMdC0XefxEmKpIqNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCxMwK/dJMcafAJ4CZ/roZ5iHMdC0XefxEmKpIqNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCxMwK%2FdJMcafAJ4CZ%2FroZ5iHMdC0XefxEmKpIqNK%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;1208&quot; height=&quot;450&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 사용하여 랜드마크를 제공할 수 있습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;EntityQuery&lt;/b&gt;&lt;/span&gt;에 선택적인 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;suggestedEntities&lt;/b&gt;&lt;/span&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-origin-width=&quot;1102&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BRA6B/dJMcafAJ4HP/2JbONaMQXl2OYbKCYmksu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BRA6B/dJMcafAJ4HP/2JbONaMQXl2OYbKCYmksu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BRA6B/dJMcafAJ4HP/2JbONaMQXl2OYbKCYmksu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBRA6B%2FdJMcafAJ4HP%2F2JbONaMQXl2OYbKCYmksu0%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;1102&quot; height=&quot;254&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티가 모두 메모리에 들어갈 경우 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;모든 엔티티를 반환하기 위해 EnumerableEntityQuery&lt;/b&gt;&lt;/span&gt;를 사용할 수 있습니다&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;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TargetContentProvidingIntent예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 네비게이션을위한 앱인텐트&lt;/p&gt;
&lt;pre id=&quot;code_1785378804411&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct NavigateIntent: AppIntent, TargetContentProvidingIntent {
    static let title: LocalizedStringResource = &quot;Navigate&quot;
    static let description = IntentDescription(&quot;앱의 특정 섹션을 엽니다.&quot;)

    @Parameter(title: &quot;Section&quot;)
    var navigationOption: NavigationOption

    /// ParameterSummary &amp;mdash; 액션을 사람이 읽는 문장으로 만든다.
    static var parameterSummary: some ParameterSummary {
        Summary(&quot;Navigate to \(\.$navigationOption)&quot;)
    }

    init() {}
    init(navigationOption: NavigationOption) { self.navigationOption = navigationOption }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;title: 노출되는 타이틀&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;description: i버튼을 눌렀을때 나타나는 설명&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/ZNT7c/dJMcafOl7GI/GDcfmR5bBw81MJFFiCJOe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZNT7c/dJMcafOl7GI/GDcfmR5bBw81MJFFiCJOe1/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&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/ZNT7c/dJMcafOl7GI/GDcfmR5bBw81MJFFiCJOe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZNT7c%2FdJMcafOl7GI%2FGDcfmR5bBw81MJFFiCJOe1%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPezV0/dJMcaasHzpJ/ik7JK2wbeEAQfDpVWgNa70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPezV0/dJMcaasHzpJ/ik7JK2wbeEAQfDpVWgNa70/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPezV0/dJMcaasHzpJ/ik7JK2wbeEAQfDpVWgNa70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPezV0%2FdJMcaasHzpJ%2Fik7JK2wbeEAQfDpVWgNa70%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;1290&quot; height=&quot;2796&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;OpenIntent 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템적인 네비게이션을위한 앱인텐트&lt;/p&gt;
&lt;pre id=&quot;code_1785378948477&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct OpenLandmarkIntent: OpenIntent, TargetContentProvidingIntent {
    static let title: LocalizedStringResource = &quot;Open Landmark&quot;

    @Parameter(title: &quot;Landmark&quot;)
    var target: LandmarkEntity

    init() {}
    init(target: LandmarkEntity) { self.target = target }
}&lt;/code&gt;&lt;/pre&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/dDktSR/dJMcagfksbw/Fk3mQMAOoObpBur00cULVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDktSR/dJMcagfksbw/Fk3mQMAOoObpBur00cULVK/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&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/dDktSR/dJMcagfksbw/Fk3mQMAOoObpBur00cULVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDktSR%2FdJMcagfksbw%2FFk3mQMAOoObpBur00cULVK%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEJscc/dJMcaaMYXYX/9OKWxZ6OtVxe7xi7vQ5bh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEJscc/dJMcaaMYXYX/9OKWxZ6OtVxe7xi7vQ5bh0/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEJscc/dJMcaaMYXYX/9OKWxZ6OtVxe7xi7vQ5bh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEJscc%2FdJMcaaMYXYX%2F9OKWxZ6OtVxe7xi7vQ5bh0%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;1290&quot; height=&quot;2796&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;h4 data-ke-size=&quot;size20&quot;&gt;AppIntent로 데이터 수정 하는 예시&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1785379425762&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct MarkVisitedIntent: AppIntent {
    static let title: LocalizedStringResource = &quot;Mark Landmarks Visited&quot;

    @Parameter(title: &quot;Landmarks&quot;)
    var landmarks: [LandmarkEntity]

    @Parameter(title: &quot;Visited&quot;, default: true)
    var visited: Bool

    static var parameterSummary: some ParameterSummary {
        Summary(&quot;Set \(\.$landmarks) visited to \(\.$visited)&quot;)
    }

    @Dependency var store: LandmarkStore

    func perform() async throws -&amp;gt; some IntentResult &amp;amp; ProvidesDialog {
        let ids = landmarks.map(\.id)
        await store.setVisited(ids: ids, visited: visited)
        await store.note(&quot;\(ids.count)개 방문 상태 = \(visited)&quot;)
        return .result(dialog: &quot;\(ids.count)개 랜드마크를 업데이트했습니다.&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;821&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSJXHf/dJMcafgtB00/OAk6R4yKK2fSQcoYYYQ0R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSJXHf/dJMcafgtB00/OAk6R4yKK2fSQcoYYYQ0R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSJXHf/dJMcafgtB00/OAk6R4yKK2fSQcoYYYQ0R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSJXHf%2FdJMcafgtB00%2FOAk6R4yKK2fSQcoYYYQ0R1%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;352&quot; height=&quot;224&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;821&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;h4 data-ke-size=&quot;size20&quot;&gt;Entity 및 Query&lt;/h4&gt;
&lt;pre id=&quot;code_1785379594496&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct LandmarkEntity: IndexedEntity {
    /// 영구적이어야 하는 식별자. 이 ID로 인스턴스를 되찾을 수 있어야 한다.
    var id: Int

    /// ponytail: 값을 복사하지 않고 데이터 모델을 그대로 들고 간다.
    /// @ComputedProperty가 이 모델을 읽어 프로퍼티를 노출한다.
    var landmark: Landmark

    init(_ landmark: Landmark) {
        self.id = landmark.id
        self.landmark = landmark
    }

    @ComputedProperty(title: &quot;Name&quot;, indexingKey: \.displayName)
    var name: String { landmark.name }

    @ComputedProperty(title: &quot;Continent&quot;)
    var continent: String { landmark.continent }

    @ComputedProperty(title: &quot;Summary&quot;, indexingKey: \.contentDescription)
    var summary: String { landmark.summary }

    @ComputedProperty(title: &quot;Visited&quot;)
    var visited: Bool { landmark.visited }

    /// 타입 전체를 설명
    static let typeDisplayRepresentation = TypeDisplayRepresentation(
        name: &quot;Landmark&quot;,
        numericFormat: &quot;\(placeholder: .int) landmarks&quot;
    )

    /// 인스턴스 하나를 설명 (Shortcuts 피커, Siri, Spotlight에서 이 모습으로 보인다)
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: &quot;\(landmark.name) - IndexEntity&quot;,
            subtitle: &quot;\(landmark.continent)&quot;,
            image: .init(systemName: landmark.symbol)
        )
    }

    static var defaultQuery = LandmarkQuery()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppEntity를 생성하면 defaultQuery를 기본적으로 제공해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리를 제공하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkp4mk/dJMcacxfK6U/uG7d5WSSUT3Aegz4kuURlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkp4mk/dJMcacxfK6U/uG7d5WSSUT3Aegz4kuURlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkp4mk/dJMcacxfK6U/uG7d5WSSUT3Aegz4kuURlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkp4mk%2FdJMcacxfK6U%2FuG7d5WSSUT3Aegz4kuURlk%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;401&quot; height=&quot;869&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 설명이노출됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 컨트롤할 수 있는건 설명뿐&lt;/p&gt;
&lt;pre id=&quot;code_1785387867484&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static let findIntentDescription: IntentDescription? = IntentDescription(&quot;조건에 맞는 랜드마크를 찾습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;findIntentDescription&lt;/b&gt;&lt;/span&gt;에 설명을 커스텀할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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/bbJm0n/dJMcagfky1Y/VxjhUWDEhuUUOKMTuWoZ40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbJm0n/dJMcagfky1Y/VxjhUWDEhuUUOKMTuWoZ40/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1194&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.7052%; margin-right: 10px;&quot; data-widthpercent=&quot;57.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbJm0n/dJMcagfky1Y/VxjhUWDEhuUUOKMTuWoZ40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbJm0n%2FdJMcagfky1Y%2FVxjhUWDEhuUUOKMTuWoZ40%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;1290&quot; height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wLyRA/dJMcahSLPpa/Ch1umPA0BHZwRCL4ZDSPk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wLyRA/dJMcahSLPpa/Ch1umPA0BHZwRCL4ZDSPk0/img.png&quot; width=&quot;322&quot; height=&quot;401&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1607&quot; data-filename=&quot;blob&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.132%;&quot; data-widthpercent=&quot;42.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wLyRA/dJMcahSLPpa/Ch1umPA0BHZwRCL4ZDSPk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwLyRA%2FdJMcahSLPpa%2FCh1umPA0BHZwRCL4ZDSPk0%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;1290&quot; height=&quot;1607&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기본만 제공하면 왼쪽처럼 indexingKey를 제공한 이름만 목록에 노출됨&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;반면에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;EnumerableEntityQuery&lt;/b&gt;&lt;/span&gt;에서는 allEntities에 제공된 만큼&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785389033916&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension LandmarkQuery: EntityPropertyQuery {
    typealias ComparatorMappingType = (Landmark) -&amp;gt; Bool

    static var properties = QueryProperties {
        Property(\LandmarkEntity.$name) {
            ContainsComparator { value in { $0.name.localizedCaseInsensitiveContains(value) } }
            EqualToComparator { value in { $0.name == value } }
        }
        Property(\LandmarkEntity.$continent) {
            EqualToComparator { value in { $0.continent == value } }
        }
        Property(\LandmarkEntity.$visited) {
            EqualToComparator { value in { $0.visited == value } }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt; EntityPropertyQuery&lt;/b&gt;&lt;/span&gt;에서는 properties에 정의한 만큼&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오른쪽화면같이 목록에 노출됨&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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/XZPYc/dJMcaheil0o/EY9K5yPy38CAcXhM9Nph9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XZPYc/dJMcaheil0o/EY9K5yPy38CAcXhM9Nph9K/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;1136&quot; data-is-animation=&quot;false&quot; style=&quot;width: 41.9294%; margin-right: 10px;&quot; data-widthpercent=&quot;42.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XZPYc/dJMcaheil0o/EY9K5yPy38CAcXhM9Nph9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXZPYc%2FdJMcaheil0o%2FEY9K5yPy38CAcXhM9Nph9K%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;1290&quot; height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IspjK/dJMcaheil0F/AqVXFSbCcojaDwpsK33bL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IspjK/dJMcaheil0F/AqVXFSbCcojaDwpsK33bL0/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;837&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.9078%;&quot; data-widthpercent=&quot;57.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IspjK/dJMcaheil0F/AqVXFSbCcojaDwpsK33bL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIspjK%2FdJMcaheil0F%2FAqVXFSbCcojaDwpsK33bL0%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;1290&quot; height=&quot;837&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 아무것도 제공안하면 왼쪽같이 모든필터가 선택가능하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EntityPropertyQuery에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;EqualToComparator, ContainsComparator&lt;/span&gt;와 같이 비교 타입을 제공하면 그 목록만 노출됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 인텐트로 앱을 빌드하면 코드가 Source of Truth가 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 인텐트에는 설정이나 구성 파일이 필요하지 않습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 소스 코드는 빌드 시점에 읽혀 앱 인텐트의 representation을 생성하게 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 representation은 앱이나 프레임워크 내부에 저장됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 설치된 후 시스템은 이 데이터를 사용하여 앱을 실행하지 않고도 앱의 기능을 이해합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트의 이름은 해당 동작의 고유한 식별자가 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트의 title은 사용자가 인텐트를 구분할 수 있도록 하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perform 메서드의 return 시그니처는 인텐트의 결과를 렌더링하는 방법을 설명합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 실행 시점이 아닌 빌드 시점에 진행되므로 특정 값은 상수 값으로 제공되어야 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;App Intent는 앱의 동작 하나를 시스템에 등록하는 함수 하나&lt;/b&gt;&lt;/span&gt;로 앱이 실행중이 아니여도 시스템이 알고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 인텐트는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Siri, Spotlight 검색, 위젯, 동작 버튼, 제어 센터, 자동화, 단축어 에 사용합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단축어는 App Intent를 소비하는 기능중 하나입니다&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/260</guid>
      <comments>https://nsios.tistory.com/260#entry260comment</comments>
      <pubDate>Wed, 29 Jul 2026 17:45:56 +0900</pubDate>
    </item>
    <item>
      <title>[SwiftUI] iPad Layout 대응시 고려할점</title>
      <link>https://nsios.tistory.com/259</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아이폰앱을 아이패드로 대응할때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면크기 때문에 고려해야할 점에 대해 알아보려합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Environment(\.horizontalSizeClass)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewTahtFits&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NavigationSplitView&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inspector&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;h3 data-ke-size=&quot;size23&quot;&gt;@Environment(\.horizontalSizeClass)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 뷰가 그려지는 가로 폭의 종류를 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 개발자가 직접 분기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 화면 크기, 멀티윈도우 창에 맞는 크기로 알아서 적용&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;세로일때 = compact&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로일때 = regular&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;아이패드에서 값은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세로,가로 풀 사이즈 일때 = regular&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티윈도우로 사이즈조절시 = compact&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/cvDs4n/dJMcahyduOW/ZmLRyUZrhVM2gZhO2vwWKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvDs4n/dJMcahyduOW/ZmLRyUZrhVM2gZhO2vwWKk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2301&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50.08&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.4937%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvDs4n/dJMcahyduOW/ZmLRyUZrhVM2gZhO2vwWKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvDs4n%2FdJMcahyduOW%2FZmLRyUZrhVM2gZhO2vwWKk%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;1640&quot; height=&quot;2301&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgGE0n/dJMcadvHM7L/1PvULkKHQqxvBHdeoRNdpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgGE0n/dJMcadvHM7L/1PvULkKHQqxvBHdeoRNdpk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2308&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.92&quot; data-filename=&quot;blob&quot; style=&quot;width: 49.3435%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgGE0n/dJMcadvHM7L/1PvULkKHQqxvBHdeoRNdpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgGE0n%2FdJMcadvHM7L%2F1PvULkKHQqxvBHdeoRNdpk%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;1640&quot; height=&quot;2308&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kYMez/dJMcadP2i5N/tkraEngAh2B01YYqrBFZI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kYMez/dJMcadP2i5N/tkraEngAh2B01YYqrBFZI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kYMez/dJMcadP2i5N/tkraEngAh2B01YYqrBFZI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkYMez%2FdJMcadP2i5N%2FtkraEngAh2B01YYqrBFZI1%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;2048&quot; height=&quot;1166&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1166&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;비율대로 넓어지는 UI인경우 &lt;br /&gt;화면이 넓어지면 2열로 변경되는 대응을 할수있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782817333768&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct SizeClassDemo: View {
    @Environment(\.horizontalSizeClass) private var hSizeClass

    // 현재 상태를 화면에 보여주기 위한 보조 계산 프로퍼티들
    private var isRegular: Bool { hSizeClass == .regular }
    private var stateText: String { isRegular ? &quot;regular&quot; : &quot;compact&quot; }
    private var bannerColor: Color { isRegular ? .blue : .orange }

    var body: some View {
        VStack(spacing: 16) {
            // 현재 horizontalSizeClass 값을 알려주는 상단 배너 (상태별 배경색이 다르다)
            banner

            // 핵심 분기: 같은 콘텐츠를 폭에 따라 다르게 배치한다.
            if isRegular {
                // 넓은 화면: 목록 / 상세를 좌우로 나란히 (HStack)
                HStack(alignment: .top, spacing: 16) {
                    listPane
                    detailPane
                }
            } else {
                // 좁은 화면: 동일 콘텐츠를 위아래로 쌓는다 (VStack)
                VStack(spacing: 16) {
                    listPane
                    detailPane
                }
            }

            Spacer()
        }
        .padding()
        .navigationTitle(&quot;SizeClass 분기&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;ViewTahtFits&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간에 맞는 뷰를 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용가능한 공간을 측정해, 자식을 위-&amp;gt;아래(코드상위치) 순서로 시도하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넘치지 않고 들어 맞는 첫번째 자식만 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프레임워크가 알아서 선택(if없음)&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;gt; frame 고정시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 폭/회전/dynamic type등으로 공간이 달라질때, 코드분기없이 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;공간에 맞는 레이아웃을 자동으로 선택&lt;/b&gt;&lt;/span&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brFsXe/dJMcab5PiP0/IJnuRuxOq20Q1bbKzGakt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brFsXe/dJMcab5PiP0/IJnuRuxOq20Q1bbKzGakt0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/brFsXe/dJMcab5PiP0/IJnuRuxOq20Q1bbKzGakt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrFsXe%2FdJMcab5PiP0%2FIJnuRuxOq20Q1bbKzGakt0%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o8EQu/dJMcaiKAGvk/eZuJcRRQhoP1IWhCsCytJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o8EQu/dJMcaiKAGvk/eZuJcRRQhoP1IWhCsCytJ1/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o8EQu/dJMcaiKAGvk/eZuJcRRQhoP1IWhCsCytJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo8EQu%2FdJMcaiKAGvk%2FeZuJcRRQhoP1IWhCsCytJ1%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/bJz7yl/dJMcafmTVsu/5cKLhRSJigrNanJCax7t81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJz7yl/dJMcafmTVsu/5cKLhRSJigrNanJCax7t81/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/bJz7yl/dJMcafmTVsu/5cKLhRSJigrNanJCax7t81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJz7yl%2FdJMcafmTVsu%2F5cKLhRSJigrNanJCax7t81%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjmmnb/dJMcagzheEe/ElxDRRz9f4UaWTSs2Qk6rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjmmnb/dJMcagzheEe/ElxDRRz9f4UaWTSs2Qk6rK/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjmmnb/dJMcagzheEe/ElxDRRz9f4UaWTSs2Qk6rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjmmnb%2FdJMcagzheEe%2FElxDRRz9f4UaWTSs2Qk6rK%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;1640&quot; height=&quot;2360&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782817656414&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ViewThatFitsDemo: View {
    // 컨테이너 폭을 직접 조절해 가며 어떤 후보가 선택되는지 관찰한다.
    @State private var width: CGFloat = 400

    var body: some View {
        VStack(spacing: 24) {
            // 현재 폭을 숫자로 표시 &amp;mdash; 슬라이더를 움직이며 레이아웃 전환 지점을 확인한다.
            VStack(spacing: 12) {
                Text(&quot;컨테이너 폭: \(Int(width)) pt&quot;)
                    .font(.headline)
                    .monospacedDigit()
                Slider(value: $width, in: 0...700)
            }
            .padding(.horizontal)

            ViewThatFits(in: .horizontal) {
                // (1) 넓은 레이아웃: 아이콘 + 긴 텍스트 + 버튼 (가장 큰 후보)
                
                HStack(spacing: 12) {
                    Image(systemName: &quot;wifi&quot;)
                    Text(&quot;네트워크에 연결되었습니다 연결되었습니다 연결되었습니다&quot;)
                        .lineLimit(1)
                    Button(&quot;관리&quot;) {}
                        .buttonStyle(.borderedProminent)
                }
                .candidateStyle(tint: .blue, caption: &quot;넓은 레이아웃&quot;)

                // (2) 중간: 아이콘 + 짧은 텍스트
                HStack(spacing: 8) {
                    Image(systemName: &quot;wifi&quot;)
                    Text(&quot;연결됨&quot;)
                        .lineLimit(1)
                }
                .candidateStyle(tint: .green, caption: &quot;중간&quot;)

                // (3) 폴백: 아이콘만 (가장 작은 후보 &amp;mdash; 항상 들어맞음)
                Image(systemName: &quot;wifi&quot;)
                    .candidateStyle(tint: .orange, caption: &quot;아이콘만&quot;)
            }
            .frame(width: width)

            Spacer()
        }
        .padding(.top, 24)
        .navigationTitle(&quot;ViewThatFits&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;ViewThatFits vs HorizontalSizeClass&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 픽셀폭 -&amp;gt; ViewThatFit사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가로길이가 600이던 1300이던 regular이기 때문에 iPad 풀스크린 세로는 구분 불가능 -&amp;gt; ViewThatFit으로 구분해서 사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;화면 전체 패러다임 전환, 시스템 레벨의 큰 의미 -&amp;gt; HorizontalSizeClass 사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배타적으로 사용하지않고 큰 구조를 HorizontalSizeClass를 사용하고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 안에서 디테일은 ViewThatFit으로 처리 하는 식으로 같이사용&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;NavigationSplitView&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2단, 3단 메뉴 옵션이 존재&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- side/content/detail 3단 일땐&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메뉴가 side, content 2개로 이뤄져있고 메뉴를 뎁스로 하나 더 열 수 있는 구조&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 이때 만약 balanced스타일이라면 content, detail이 화면내에 존재하고 sidebar가 메뉴로 나옴&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- compact모드일땐 메뉴 스타일 상관없이 동작방식이 모두 같음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- NavigationSplitViewStyle.&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;prominentDetail&lt;/span&gt;&lt;/b&gt;: 컨텐츠 사이즈 그대로, &lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;딤처럼 그 위에 사이드바 나옴&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- NavigationSplitViewStyle.&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;balanced&lt;/span&gt;&lt;/b&gt;: &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;컨텐츠가 좁아지고&lt;/b&gt;&lt;/span&gt; 사이드바 나옴(컨텐츠와 메뉴가 동등한 레벨)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단 - balanced 스타일&lt;/b&gt;&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/bSvCV1/dJMcabrerrv/oesqSmY25NXT3vMRHi8GYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSvCV1/dJMcabrerrv/oesqSmY25NXT3vMRHi8GYK/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSvCV1/dJMcabrerrv/oesqSmY25NXT3vMRHi8GYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSvCV1%2FdJMcabrerrv%2FoesqSmY25NXT3vMRHi8GYK%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vnsTK/dJMcacQ9ksQ/tIXtd2e4VTOaO6jh2IGg11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vnsTK/dJMcacQ9ksQ/tIXtd2e4VTOaO6jh2IGg11/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vnsTK/dJMcacQ9ksQ/tIXtd2e4VTOaO6jh2IGg11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvnsTK%2FdJMcacQ9ksQ%2FtIXtd2e4VTOaO6jh2IGg11%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFqa3W/dJMcacQ9ks6/sZfaIKNUTdOAwgo5dGjQa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFqa3W/dJMcacQ9ks6/sZfaIKNUTdOAwgo5dGjQa1/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFqa3W/dJMcacQ9ks6/sZfaIKNUTdOAwgo5dGjQa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFqa3W%2FdJMcacQ9ks6%2FsZfaIKNUTdOAwgo5dGjQa1%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;주황색 - compact모드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단 - prominentDetail 스타일&lt;/b&gt;&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/MvgTW/dJMcafAoN6Q/zPI7Ne2kxC6lDtqJ3zaQj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MvgTW/dJMcafAoN6Q/zPI7Ne2kxC6lDtqJ3zaQj0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/MvgTW/dJMcafAoN6Q/zPI7Ne2kxC6lDtqJ3zaQj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMvgTW%2FdJMcafAoN6Q%2FzPI7Ne2kxC6lDtqJ3zaQj0%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cR027D/dJMcabLBsyR/8kZMRTuiCmMia5JYUjKwQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cR027D/dJMcabLBsyR/8kZMRTuiCmMia5JYUjKwQ0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cR027D/dJMcabLBsyR/8kZMRTuiCmMia5JYUjKwQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcR027D%2FdJMcabLBsyR%2F8kZMRTuiCmMia5JYUjKwQ0%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단 - balanced 스타일&lt;/b&gt;&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/bc5DoV/dJMcadJflRK/YJ4sgs7D8Ap77ChkQ2OkVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc5DoV/dJMcadJflRK/YJ4sgs7D8Ap77ChkQ2OkVK/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/bc5DoV/dJMcadJflRK/YJ4sgs7D8Ap77ChkQ2OkVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc5DoV%2FdJMcadJflRK%2FYJ4sgs7D8Ap77ChkQ2OkVK%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7nW7N/dJMcadbpHq5/aZG1KzB2JgqwYnt4Jir7U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7nW7N/dJMcadbpHq5/aZG1KzB2JgqwYnt4Jir7U0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7nW7N/dJMcadbpHq5/aZG1KzB2JgqwYnt4Jir7U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7nW7N%2FdJMcadbpHq5%2FaZG1KzB2JgqwYnt4Jir7U0%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단 - prominentDetail 스타일&lt;/b&gt;&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/cvK1FP/dJMcadbpHuY/WAfRGkQsbro07RxEESOB81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvK1FP/dJMcadbpHuY/WAfRGkQsbro07RxEESOB81/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvK1FP/dJMcadbpHuY/WAfRGkQsbro07RxEESOB81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvK1FP%2FdJMcadbpHuY%2FWAfRGkQsbro07RxEESOB81%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sp7cZ/dJMcaiDTbRi/PLxM4X8sSBjSs6NqLlVVOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sp7cZ/dJMcaiDTbRi/PLxM4X8sSBjSs6NqLlVVOk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sp7cZ/dJMcaiDTbRi/PLxM4X8sSBjSs6NqLlVVOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsp7cZ%2FdJMcaiDTbRi%2FPLxM4X8sSBjSs6NqLlVVOk%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ac9L9/dJMcac4HZ6q/FnqwmBVhEOyGkOlvVpKPe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ac9L9/dJMcac4HZ6q/FnqwmBVhEOyGkOlvVpKPe0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ac9L9/dJMcac4HZ6q/FnqwmBVhEOyGkOlvVpKPe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAc9L9%2FdJMcac4HZ6q%2FFnqwmBVhEOyGkOlvVpKPe0%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;1640&quot; height=&quot;2360&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;inspector(isPresented: )&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 인스펙터 패널&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;regular 폭(iPad) 에서는 본문 우측에 컬럼으로 붙고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compact 폭(iPhone)에서는 자동으로 sheet형식으로 적응해 표시됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아이폰 가로모드에서는 확인가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;sheet과 배경색도 다르고 가로모드 햇을때 대응도 달라서 같은 용도로 사용하면 안됩니다.&lt;/b&gt;&lt;/span&gt;&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/kWhq7/dJMcadWRnpq/uvqdvDSUGxPpWEA4r8DbDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWhq7/dJMcadWRnpq/uvqdvDSUGxPpWEA4r8DbDk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWhq7/dJMcadWRnpq/uvqdvDSUGxPpWEA4r8DbDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWhq7%2FdJMcadWRnpq%2FuvqdvDSUGxPpWEA4r8DbDk%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZMjhS/dJMcabY6wVG/k3SaqfPp1adXQK92FAeh70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZMjhS/dJMcabY6wVG/k3SaqfPp1adXQK92FAeh70/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZMjhS/dJMcabY6wVG/k3SaqfPp1adXQK92FAeh70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZMjhS%2FdJMcabY6wVG%2Fk3SaqfPp1adXQK92FAeh70%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvwEne/dJMcahydvoO/OVwNxe06EvutsP6OZ12zTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvwEne/dJMcahydvoO/OVwNxe06EvutsP6OZ12zTk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvwEne/dJMcahydvoO/OVwNxe06EvutsP6OZ12zTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvwEne%2FdJMcahydvoO%2FOVwNxe06EvutsP6OZ12zTk%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;1640&quot; height=&quot;2360&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;&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;.popover&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이폰&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- compact: sheet으로 노출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- regular: sheet으로 노출&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;- compact: sheet으로 변경되서 노출&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- regular: 말풍선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;presentationCompactAdaptation(.popover)&lt;/b&gt;&lt;/span&gt; 사용시 &lt;br /&gt;아이폰에서도, 아이패드에서도 compact에서도 모두 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;말풍선으로 강제적용됨&lt;/b&gt;&lt;/span&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW5EbQ/dJMcaiX6jUc/LcXkFj1OSBg2dVRDYS79X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW5EbQ/dJMcaiX6jUc/LcXkFj1OSBg2dVRDYS79X1/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/bW5EbQ/dJMcaiX6jUc/LcXkFj1OSBg2dVRDYS79X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW5EbQ%2FdJMcaiX6jUc%2FLcXkFj1OSBg2dVRDYS79X1%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l7XCJ/dJMcahdVIxf/kRfQ4dxryMnKRbBm2ieqY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l7XCJ/dJMcahdVIxf/kRfQ4dxryMnKRbBm2ieqY1/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l7XCJ/dJMcahdVIxf/kRfQ4dxryMnKRbBm2ieqY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl7XCJ%2FdJMcahdVIxf%2FkRfQ4dxryMnKRbBm2ieqY1%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vIuVf/dJMcaicSlF3/6kAkc1ejf77cOCA0l4Cch1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vIuVf/dJMcaicSlF3/6kAkc1ejf77cOCA0l4Cch1/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px; margin-top: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vIuVf/dJMcaicSlF3/6kAkc1ejf77cOCA0l4Cch1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvIuVf%2FdJMcaicSlF3%2F6kAkc1ejf77cOCA0l4Cch1%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brlQbD/dJMcahLJjVJ/ooJHJmVUm4RbkveUoeh6Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brlQbD/dJMcahLJjVJ/ooJHJmVUm4RbkveUoeh6Y0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-top: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brlQbD/dJMcahLJjVJ/ooJHJmVUm4RbkveUoeh6Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrlQbD%2FdJMcahLJjVJ%2FooJHJmVUm4RbkveUoeh6Y0%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;1640&quot; height=&quot;2360&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.presentationDetents&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- medium - 화면절반&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- large - 전체높이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fraction() - 화면 비율(0~1)로 지정한 커스텀 높이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- height() - 포인트 단위 고정 높이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;presentationDragIndicator&lt;/span&gt; - 손잡이 노출옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;presentationBackgroundInteraction&lt;/span&gt; - 배경과 상호작용 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이옵션보다 작은 크기로 펼치면 배경 상호작용은 막힘(배경 딤처리도 달라짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) option이 medium인 경우 large일 때 배경선택해서 창닫기 가능 (상호 작용되는 경우 뒷배경 회색 딤처리)&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/DxmMO/dJMcag0i8Z2/fxIJRk2YG361NgYMWtd9zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DxmMO/dJMcag0i8Z2/fxIJRk2YG361NgYMWtd9zk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&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/DxmMO/dJMcag0i8Z2/fxIJRk2YG361NgYMWtd9zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDxmMO%2FdJMcag0i8Z2%2FfxIJRk2YG361NgYMWtd9zk%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3pHNR/dJMcag0i8Z7/mDfj596ZlHzbkjqs4vI9M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3pHNR/dJMcag0i8Z7/mDfj596ZlHzbkjqs4vI9M0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3pHNR/dJMcag0i8Z7/mDfj596ZlHzbkjqs4vI9M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3pHNR%2FdJMcag0i8Z7%2FmDfj596ZlHzbkjqs4vI9M0%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;1640&quot; height=&quot;2360&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&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/biOaLA/dJMcafUI2H9/HTYOEcq71lk9J6Z0auzQ40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biOaLA/dJMcafUI2H9/HTYOEcq71lk9J6Z0auzQ40/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biOaLA/dJMcafUI2H9/HTYOEcq71lk9J6Z0auzQ40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiOaLA%2FdJMcafUI2H9%2FHTYOEcq71lk9J6Z0auzQ40%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wXywS/dJMcafAoOz5/oHbLUozcajhotkpll4xzUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wXywS/dJMcafAoOz5/oHbLUozcajhotkpll4xzUk/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wXywS/dJMcafAoOz5/oHbLUozcajhotkpll4xzUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwXywS%2FdJMcafAoOz5%2FoHbLUozcajhotkpll4xzUk%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;1640&quot; height=&quot;2360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpfxPv/dJMcafUI2It/ACgWhDvns1TpKKDPKhxou0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpfxPv/dJMcafUI2It/ACgWhDvns1TpKKDPKhxou0/img.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;2360&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpfxPv/dJMcafUI2It/ACgWhDvns1TpKKDPKhxou0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpfxPv%2FdJMcafUI2It%2FACgWhDvns1TpKKDPKhxou0%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;1640&quot; height=&quot;2360&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;&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;</description>
      <category>iyOmSd/Title: SwiftUI</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/259</guid>
      <comments>https://nsios.tistory.com/259#entry259comment</comments>
      <pubDate>Tue, 30 Jun 2026 20:33:40 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] BGContinuedProcessingTask 백그라운드 작업</title>
      <link>https://nsios.tistory.com/258</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;백그라운드에서 작업을 돌릴 수 있는 타입에대해 알아보려합니다&lt;/span&gt;&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/bTNHIb/dJMcagTeDB7/pP8wksBtCeFLpnNbNGY9Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTNHIb/dJMcagTeDB7/pP8wksBtCeFLpnNbNGY9Nk/img.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;288&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.1071%; margin-right: 10px;&quot; data-widthpercent=&quot;58.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTNHIb/dJMcagTeDB7/pP8wksBtCeFLpnNbNGY9Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTNHIb%2FdJMcagTeDB7%2FpP8wksBtCeFLpnNbNGY9Nk%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;696&quot; height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TJZNl/dJMcaaMdKtH/3Xi8sJLIeaKllJfay7VVf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TJZNl/dJMcaaMdKtH/3Xi8sJLIeaKllJfay7VVf0/img.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1209&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.7301%;&quot; data-widthpercent=&quot;41.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TJZNl/dJMcaaMdKtH/3Xi8sJLIeaKllJfay7VVf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTJZNl%2FdJMcaaMdKtH%2F3Xi8sJLIeaKllJfay7VVf0%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;2048&quot; height=&quot;1209&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;span style=&quot;color: #333333;&quot;&gt;백그라운드 전환 이후에도 작업을 계속 수행하고 시스템은 UI를 통해 진행 상황을 표시를 지원합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 버튼 클릭이나 다른 제스처처럼 항상 명확한 사용자 행동으로 시작됩니다&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 각 작업은 즉각적이고 명확한 목표를 가지고 있습니다 (파일 내보내기 SNS 콘텐츠 게시 연결된 액세서리 업데이트 등)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 이러한 작업은 측정 가능한 진행률을 가지고 있으며 작업이 완료됐을 때 무엇을 의미하는지 알 수 있습니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;시스템은 리소스 부족과 같은 런타임 상황에 따라 백그라운드 작업을 갑작스럽게 종료&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;구현 시에는 이 작업이 준수하는 ProgressReporting 프로토콜을 사용하여 진행 상황을 보고해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;리소스가 부족해지면 시스템은 진행 상황이 거의 없거나 전혀 없는 작업의 종료를 우선적으로 처리합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;BGContinuedProcessingTask&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;즉시&amp;nbsp;시작&amp;nbsp;/&amp;nbsp;진행률&amp;nbsp;UI&amp;nbsp;자동&amp;nbsp;/&amp;nbsp;사용자가&amp;nbsp;기다리는&amp;nbsp;작업이라&amp;nbsp;시스템이&amp;nbsp;우호적(런타임&amp;nbsp;넉넉)&amp;nbsp;/&amp;nbsp;GPU&amp;middot;CPU&amp;middot;네트워크&amp;nbsp;사용&amp;nbsp;가능&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-   iOS 26+ 전용 / 반드시 포그라운드 사용자 액션에서 제출(서버&amp;middot;백그라운드 트리거 ❌) / progress 필수 / 무한은 아님(자원 압박 시 expirationHandler로 회수) / 사용자에게 보이는 작업에만 적합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존에 쓰던 백그라운드 방식과 비교해볼까요?&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0rwMq/dJMcagFJsup/t0bVWCKNVg5mt6QZ2E60qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0rwMq/dJMcagFJsup/t0bVWCKNVg5mt6QZ2E60qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0rwMq/dJMcagFJsup/t0bVWCKNVg5mt6QZ2E60qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0rwMq%2FdJMcagFJsup%2Ft0bVWCKNVg5mt6QZ2E60qK%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;541&quot; height=&quot;352&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;748&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;BGProcessingTask&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;수&amp;nbsp;분&amp;nbsp;이상&amp;nbsp;긴&amp;nbsp;작업&amp;nbsp;/&amp;nbsp;&quot;충전&amp;nbsp;중&amp;middot;네트워크&amp;nbsp;필요&quot;&amp;nbsp;조건&amp;nbsp;지정&amp;nbsp;가능&amp;nbsp;/&amp;nbsp;무거운&amp;nbsp;유지보수에&amp;nbsp;최적&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;실행&amp;nbsp;시점을&amp;nbsp;시스템이&amp;nbsp;결정(즉시성&amp;nbsp;없음,&amp;nbsp;보통&amp;nbsp;밤/충전)&amp;nbsp;/&amp;nbsp;UI&amp;nbsp;없음&amp;nbsp;/&amp;nbsp;사용자&amp;nbsp;인지&amp;nbsp;못&amp;nbsp;함&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;BGAppRefreshTask&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;사용&amp;nbsp;패턴&amp;nbsp;학습해&amp;nbsp;&quot;열어볼&amp;nbsp;만한&amp;nbsp;때&quot;&amp;nbsp;갱신&amp;nbsp;/&amp;nbsp;콘텐츠&amp;nbsp;신선도&amp;nbsp;유지에&amp;nbsp;적합&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;매우&amp;nbsp;짧음(~30s)&amp;nbsp;/&amp;nbsp;시점&amp;middot;실행&amp;nbsp;보장&amp;nbsp;안&amp;nbsp;됨&amp;nbsp;/&amp;nbsp;UI&amp;nbsp;없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp; - Background&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;Push Notifications: 서버가 원할때 푸시로 백그라운드 업데이트 진행, BGAppRefreshTask의 푸시버전&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;beginBackgroundTask&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(구형)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;모든&amp;nbsp;버전&amp;nbsp;/&amp;nbsp;간단&amp;nbsp;/&amp;nbsp;즉시&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;수십&amp;nbsp;초&amp;nbsp;/&amp;nbsp;긴&amp;nbsp;작업&amp;nbsp;불가&amp;nbsp;/&amp;nbsp;UI&amp;nbsp;없음&amp;nbsp;&amp;rarr;&amp;nbsp;continued가&amp;nbsp;사실상&amp;nbsp;현대적&amp;nbsp;대체재&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Background&amp;nbsp;URLSession&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;앱이&amp;nbsp;종료돼도&amp;nbsp;전송&amp;nbsp;지속,&amp;nbsp;시스템이&amp;nbsp;재시도/관리&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;-&amp;nbsp; &amp;nbsp;순수&amp;nbsp;다운/업로드&amp;nbsp;전용&amp;nbsp;&amp;mdash;&amp;nbsp;임의&amp;nbsp;연산은&amp;nbsp;못&amp;nbsp;함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;플로우&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;info.plist정의 - UIBackgroundModes키 [processing] BGTaskSchedulerPermittedIdentifiers: [식별자 identifier]&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Permitted background task scheduler identifiers 배열에 새 값을 추가하고 앱 번들 ID로 시작되도록 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정적인 식별자 외에도 동적 접미사를 지원하는 와일드카드 형식을 지원합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;BGTaskScheduler.shared.register 로 id, 큐, 작업 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;BGContinuedProcessingTaskRequest 생성 후 BGTaskScheduler.shared.submit 제출하면 작업시작&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;진행 속도가 예상보다 느릴 경우 시스템은 사용자에게 작업을 계속할지 묻게 됩니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;필요한 작업 마지막에 setTaskCompleted로 작업완료 알림&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;span style=&quot;color: #333333;&quot;&gt;키워드&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;BGContinuedProcessingTaskRequest&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;init - 제출요청 id, 시스템 UI에 최초노출될 타이틀 지정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;request.strategy&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;실행시 전략으로 2가지가 있습니다&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;queue(기본): 본적으로 지속적 처리 작업을 즉시 실행할 수 없을 경우 시스템이 queue에 작업을 추가합니다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;즉시 실행 불가시 큐 사용해서 가능할때까지 대기합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;fail: 해당 작업이 당장 시작되어야만 유용할때.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작업을 queue에 넣는 대신 즉시 실행할 수 없으면 제출을 실패하게 할 수 있습니다 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이를 통해 앱은 즉각적인 피드백을 받고 상황을 적절히 처리할 수 있습니다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;BGContinuedProcessingTask&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;.progress.totalUnitCount&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;현재 진행 상황에 대한 추적 대상 작업 단위의 총 수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;.progress.completedUnitCount&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;시스템UI, Live Activity, Dynamic Island용 진행률 갱신&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;.updateTitle(, subtitle: )&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;시스템UI, Live Activity, Dynamic Island용 제목 갱신&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;.expirationHandler&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작업의 백그라운드 시간이 만료되기 직전에 호출되는 핸들러입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정리/중단 된경우 처리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;.setTaskCompleted(success: )&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;작업종료를 시스템에 알림(성공/실패)&lt;/span&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;span style=&quot;color: #333333;&quot;&gt;예시코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1780042067012&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 등록코드 
nonisolated func registerHandler() {
    BGTaskScheduler.shared.register(
        forTaskWithIdentifier: Self.taskIdentifier,
        using: Self.workQueue
    ) { [self] task in
        guard let task = task as? BGContinuedProcessingTask else {
            task.setTaskCompleted(success: false)
            return
        }
        // workQueue 위에서 동기적으로 작업을 수행한다.
        performWork(task)
    }
}

// 사용자가 시작버튼 (Ex: 업로드 버튼)

func start() {
    // 최초 시스템 UI 노출 타이틀지정
    let request = BGContinuedProcessingTaskRequest(
        identifier: Self.taskIdentifier,
        title: &quot;긴 작업 진행 중&quot;,
        subtitle: &quot;백그라운드로 전환해도 계속 진행됩니다&quot;
    )
    // 즉시 실행할 수 없으면 큐잉 (다른 옵션: .fail = 즉시 불가 시 제출 실패)
    request.strategy = .queue

    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        statusMessage = &quot;작업 제출 실패: \(error.localizedDescription)&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780041984365&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 작업코드
nonisolated private func performWork(_ task: BGContinuedProcessingTask) {
    let total: Int64 = 600

    // 진행률 보고는 BGContinuedProcessingTask 에서 필수.
    task.progress.totalUnitCount = total
    
    // 만료(시스템 회수/강제 종료) 시 루프를 중단시키기 위한 플래그.
    let isCancelled = Locked(false)
    task.expirationHandler = {
        isCancelled.set(true)
    }

    publishStatus(&quot;작업 시작&quot;)

    var step: Int64 = 0
    while step &amp;lt; total {
        if isCancelled.get() {
            task.setTaskCompleted(success: false)
            return
        }

        // 1초 단위의 더미 작업 시뮬레이션
        Thread.sleep(forTimeInterval: 1.0)
        step += 1

        // 시스템 UI(Live Activity / Dynamic Island)용 진행률 + 제목 갱신
        task.progress.completedUnitCount = step
        task.updateTitle(&quot;긴 작업 진행 중&quot;, subtitle: &quot;\(step)/\(total)초 완료&quot;)
        
    }
    task.setTaskCompleted(success: true)

    // 로컬 푸시 알림
    sendCompletionNotification()
}&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;/h3&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/b5iurJ/dJMcaaeqq17/y2lxwSBjYkkqXVMUaczaW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5iurJ/dJMcaaeqq17/y2lxwSBjYkkqXVMUaczaW0/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;503&quot; data-is-animation=&quot;false&quot; style=&quot;width: 42.4069%; margin-right: 10px;&quot; data-widthpercent=&quot;42.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5iurJ/dJMcaaeqq17/y2lxwSBjYkkqXVMUaczaW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5iurJ%2FdJMcaaeqq17%2Fy2lxwSBjYkkqXVMUaczaW0%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;1290&quot; height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euxY6y/dJMcabqRFqo/Ghv5xIPl6A47UMPww0f7VK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euxY6y/dJMcabqRFqo/Ghv5xIPl6A47UMPww0f7VK/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;378&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.4303%;&quot; data-widthpercent=&quot;57.09&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euxY6y/dJMcabqRFqo/Ghv5xIPl6A47UMPww0f7VK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeuxY6y%2FdJMcabqRFqo%2FGhv5xIPl6A47UMPww0f7VK%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;1290&quot; height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/basZOl/dJMcaiDveLh/b8uKsPyKZBYvb0XfR2UX0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/basZOl/dJMcaiDveLh/b8uKsPyKZBYvb0XfR2UX0k/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&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/basZOl/dJMcaiDveLh/b8uKsPyKZBYvb0XfR2UX0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbasZOl%2FdJMcaiDveLh%2Fb8uKsPyKZBYvb0XfR2UX0k%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1w5Ls/dJMcadPFtsj/KGfWGcLCHKbAwbWNR02VAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1w5Ls/dJMcadPFtsj/KGfWGcLCHKbAwbWNR02VAK/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1w5Ls/dJMcadPFtsj/KGfWGcLCHKbAwbWNR02VAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1w5Ls%2FdJMcadPFtsj%2FKGfWGcLCHKbAwbWNR02VAK%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;1290&quot; height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/258</guid>
      <comments>https://nsios.tistory.com/258#entry258comment</comments>
      <pubDate>Fri, 29 May 2026 22:11:38 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Protocol Buffer (Protobuf)</title>
      <link>https://nsios.tistory.com/257</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;protobuf를 쓸일이 생겨서 학습한 내용을 정리해보려합니다&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;Protobuf가 무엇이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 쓰며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 동작하는지 간단하게 알아보겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Protocol buffer란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Google에서 개발하고 오픈소스로 공개한 데이터 직렬화 시스템&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터를 스키마(.proto) 로 정의하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 기반으로 각 언어의 객체 형태와 작고 빠른 바이너리 형식으로 직렬화 / 역직렬화할 수 있게 해주는 프레임워크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;직렬화란&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 안의 객체나 데이터를 저장하거나 전송 할 수 있는 형태로 변환하는 걸 말해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swift에서는 아래처럼 통신할때 많이사용해본 경험이 있으시죠?&lt;/p&gt;
&lt;pre id=&quot;code_1776917600113&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let jsonData = try JSONSerialization.data(withJSONObject: ...)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKCVlw/dJMcacJECkD/xVrYClCzV6jEHLU51yxzkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKCVlw/dJMcacJECkD/xVrYClCzV6jEHLU51yxzkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKCVlw/dJMcacJECkD/xVrYClCzV6jEHLU51yxzkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKCVlw%2FdJMcacJECkD%2FxVrYClCzV6jEHLU51yxzkk%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;1598&quot; height=&quot;472&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;472&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;이런것을 protobuf는 json처럼 text형태가아니라 이진 바이너리 데이터로 주고받는 직렬화를 사용합니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K7CKr/dJMb99MRPZJ/WhokbIQFCj26nCFaUgnPkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K7CKr/dJMb99MRPZJ/WhokbIQFCj26nCFaUgnPkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K7CKr/dJMb99MRPZJ/WhokbIQFCj26nCFaUgnPkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK7CKr%2FdJMb99MRPZJ%2FWhokbIQFCj26nCFaUgnPkK%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;1584&quot; height=&quot;542&quot; data-origin-width=&quot;1584&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 왜 이렇게 쓰는걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버와 앱이 같은 데이터 구조를 서로 다르게 해석하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수동 JSON 파싱에서 생기는 타입 불일치와 런타임 에러&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JSON, XML와 같은 텍스트 기반 포맷의 크기와 파싱 비용 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구버전/신버전 호환성 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 문제를 해결하기 위해서 고안됐습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경량화된 바이너리 포맷&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트가 아닌 바이너리 형식으로 전송과 저장공간 측면에서 효율적&lt;/p&gt;
&lt;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;JSON 대비 처리속도가 빠르고 성능이 중요한 환경에서 유리&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;.proto 파일에 구조와 타입을 명시하여 서버와 클라이언트가 같은 데이터 구조를 가져갈 수 있음&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;.proto 파일하나로 swift, kotlin, go, python, 등 코드 생성 가능&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;b&gt;코드 자동생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 기반으로 각 언어 코드 자동생성으로 개발 생산성 높아짐&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;호환성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 필드 추가시 구버전에서는 해당 필드 무시하고 동작하여 구버전 클라이언트 호환이 가능함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 어떻게 동작하는지 알아볼게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러 설치 - 컴파일러인 protoc 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;⬇️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스키마 정의 - .proto 파일에 데이터 구조정의(syntax, package, message, field 정보)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;⬇️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 생성 - protoc 사용해서 각 언어 코드로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;⬇️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직렬화 - 객체를 protobuf 형식의 바이너리 데이터로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &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; &amp;nbsp; &amp;nbsp; &amp;nbsp;⬇️&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역직렬화&lt;span&gt; - 전달받은 데이터를 동일한 .proto 스키마를 기준으로 다시 객체로 변환&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;span&gt;스키마&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;어떤 종류의 타입들이 있는지 알아볼게요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;syntax&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;protobuf 문법 버전 지정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;proto2, proto3이 있고 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;최신이 3이라 보통 3을 많이사용하고있어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;package&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네임스페이스로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 커졌을때 중복을 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;message&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;protobuf에서 데이터를 표현하는 기본 단위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swift에서 구조체와 비슷한 개념&lt;/p&gt;
&lt;pre id=&quot;code_1776918510442&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;message&amp;nbsp;Person {
    int32&amp;nbsp;id&amp;nbsp;=&amp;nbsp;1;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;정수형&amp;nbsp;ID(필드&amp;nbsp;번호&amp;nbsp;1)
    string&amp;nbsp;name&amp;nbsp;=&amp;nbsp;2;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;이름(필드&amp;nbsp;번호&amp;nbsp;2)
    string&amp;nbsp;email&amp;nbsp;=&amp;nbsp;3;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;이메일(필드&amp;nbsp;번호&amp;nbsp;3)
    bool&amp;nbsp;is_active&amp;nbsp;=&amp;nbsp;4;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;활성화&amp;nbsp;여부(필드&amp;nbsp;번호&amp;nbsp;4)
    repeated&amp;nbsp;int32&amp;nbsp;scores&amp;nbsp;=&amp;nbsp;5;&amp;nbsp;//&amp;nbsp;여러&amp;nbsp;개의&amp;nbsp;정수형&amp;nbsp;점수(필드&amp;nbsp;번호&amp;nbsp;5)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 필드는 필드 번호를 갖고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직렬화 된 바이너리 데이터에서 필드를 식별하기위한 번호로 message내에서 유니크해야함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4hCHi/dJMcahD8Ros/s9dUiE6d88edsrkBxIy9Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4hCHi/dJMcahD8Ros/s9dUiE6d88edsrkBxIy9Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4hCHi/dJMcahD8Ros/s9dUiE6d88edsrkBxIy9Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4hCHi%2FdJMcahD8Ros%2Fs9dUiE6d88edsrkBxIy9Ck%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;215&quot; height=&quot;574&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대응 되는 타입은 문서를 보고 맞춰서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776918748955&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;swift-protobuf/Documentation/API.md at main &amp;middot; apple/swift-protobuf&quot; data-og-description=&quot;Plugin and runtime library for using protobuf with Swift - apple/swift-protobuf&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&quot; data-og-url=&quot;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6Jvsk/dJMb8SpKm3t/KEqA25WXSBc61TZ3HOizsk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RCMsl/dJMb8U8VZ5M/K63rgeYAqlEvAUWAHlL5Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/apple/swift-protobuf/blob/main/Documentation/API.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6Jvsk/dJMb8SpKm3t/KEqA25WXSBc61TZ3HOizsk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/RCMsl/dJMb8U8VZ5M/K63rgeYAqlEvAUWAHlL5Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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-protobuf/Documentation/API.md at main &amp;middot; apple/swift-protobuf&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Plugin and runtime library for using protobuf with Swift - apple/swift-protobuf&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&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;h3 data-ke-size=&quot;size23&quot;&gt;구성&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi8yNo/dJMcaakJEno/m5I7lebGoCdijiX7lAxKjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi8yNo/dJMcaakJEno/m5I7lebGoCdijiX7lAxKjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi8yNo/dJMcaakJEno/m5I7lebGoCdijiX7lAxKjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi8yNo%2FdJMcaakJEno%2Fm5I7lebGoCdijiX7lAxKjk%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;351&quot; height=&quot;158&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; 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;768&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwLIU8%2FdJMcaciyWiC%2F9fY3V6oUpavwRr27QaWdP1%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;300&quot; height=&quot;128&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;328&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;크게 tag와 payload 데이터로 이뤄집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 wire type이란 데이터가 어떻게 인코딩 되었는지 알려주는 태그로&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/msHJM/dJMcagrIoNO/KKCuIEgzJ9k5watDLHtls1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/msHJM/dJMcagrIoNO/KKCuIEgzJ9k5watDLHtls1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/msHJM/dJMcagrIoNO/KKCuIEgzJ9k5watDLHtls1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmsHJM%2FdJMcagrIoNO%2FKKCuIEgzJ9k5watDLHtls1%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;537&quot; height=&quot;309&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개중 2개는 디프리케이트되서 이제 4개를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parser는 wire type을 보고 데이터 유형에따라서 이 뒤를 몇 바이트로 읽는지 판단하게됩니다.&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;wire type중 제일 많이 사용하는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;VARINT&lt;/b&gt;&lt;/span&gt; 방식과 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;LEN&lt;/b&gt;&lt;/span&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;VARINT 타입 - Vairable Integer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수를 가변 길이로 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 바이트의 맨 앞 1비트(MSB, 최상위비트)는 &quot;계속/끝&quot;을 표시하는 플래그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 7비트들만 이어 붙인다는 규칙이 존재함&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rcWE2/dJMcah5fFR6/Qvx75oJhbEfSfvSsaC3NN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rcWE2/dJMcah5fFR6/Qvx75oJhbEfSfvSsaC3NN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rcWE2/dJMcah5fFR6/Qvx75oJhbEfSfvSsaC3NN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrcWE2%2FdJMcah5fFR6%2FQvx75oJhbEfSfvSsaC3NN1%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;1176&quot; height=&quot;292&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림만 봐서는 이해가안가니까 더 자세히 계산해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wqrGC/dJMcaiwhnea/du9G4vWAeukpTGZw6pLZ01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wqrGC/dJMcaiwhnea/du9G4vWAeukpTGZw6pLZ01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wqrGC/dJMcaiwhnea/du9G4vWAeukpTGZw6pLZ01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwqrGC%2FdJMcaiwhnea%2Fdu9G4vWAeukpTGZw6pLZ01%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;2082&quot; height=&quot;824&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;field_number가 1이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;field_number에 3비트를 쉬프트하는건 공식문서에서 하는 계산식이기 떄문에 그대로 따라줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;varint는 wire type 0이므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1000이랑 0과 or연산해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;00001000 -&amp;gt; tag값인 08 값을 얻었습니다.&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;768&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwLIU8/dJMcaciyWiC/9fY3V6oUpavwRr27QaWdP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwLIU8%2FdJMcaciyWiC%2F9fY3V6oUpavwRr27QaWdP1%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;300&quot; height=&quot;128&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시한번 복습하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tag + payload로 이뤄지므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;payload 즉 이제 데이터를 넣어줄차례입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a = 150이엿으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2진수로 변경하고 -&amp;gt; 7비트씩 구분합니다 -&amp;gt; 순서를 앞뒤로 바꿔주고 -&amp;gt; MSB에 알맞게 1과 0을 넣어줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게해서 다시 계산하면 십진수로 150과 1이나오는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 16진수로 변경하면 96과 01이 결과적으로 나오게됩니다.&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; 08 96 01&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;LEN타입 - Length-delimited&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태그 바로뒤에 varint로 데이터 길이 지정해서 쓰고 뒤에 실제 데이터를 저장하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;varint와 다르게 정해진 데이터 길이만큼만 뒤에 데이터가 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tag + &lt;b&gt;length&lt;/b&gt; + payload 형태입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bupcCs/dJMcacpkvgk/e2FBaUkWLxI9ILhtpDVYuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bupcCs/dJMcacpkvgk/e2FBaUkWLxI9ILhtpDVYuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bupcCs/dJMcacpkvgk/e2FBaUkWLxI9ILhtpDVYuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbupcCs%2FdJMcacpkvgk%2Fe2FBaUkWLxI9ILhtpDVYuk%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;1396&quot; height=&quot;276&quot; data-origin-width=&quot;1396&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH68nm/dJMcaf0CSy9/BkwhIONLtJO8nhesjp7v40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH68nm/dJMcaf0CSy9/BkwhIONLtJO8nhesjp7v40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH68nm/dJMcaf0CSy9/BkwhIONLtJO8nhesjp7v40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH68nm%2FdJMcaf0CSy9%2FBkwhIONLtJO8nhesjp7v40%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;2072&quot; height=&quot;800&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;field number 2에 testing값을 len으로 작성할거니까&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2 &amp;lt;&amp;lt; 3 에 len 타입은 2이므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2 &amp;lt;&amp;lt; 3 | 2 가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tag값으로는 16진수로 12를 얻을 수 있습니다.&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;그다음에는 varint와 다르게 데이터의 길이를 나타내는 값을 제공합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;testing은 UTF-8로 7바이트이므로 길이 7을 나타냅니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 07입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그뒤에는 UTF-8로 변환한 testing값을 넣어주면됩니다.&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;따라서 최종 데이터는 12 07 [74 65 73 74 69 6e 67] 형태가됩니다.&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;JSON으로 만들면 대괄호, 따옴표, 콤마, 텍스트 포맷위한 데이터를 다 포함해서 더 용량이 크고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 데이터로 만들면 이런 불필요한 데이터는 줄일 수 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON으로 만들었을때보다 데이터 용량이 작은 장점이 있기 때문에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 데이터 전송방법으로 쓰이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 스트리밍이나 고성능이 필요한곳에서 많이 적용되고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 protobuf를 프로젝트에서 사용하는 방법은 2가지가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 라이브러리 받아서 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 직접 파서만들어서 사용하기&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;804&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KwNUY/dJMcaciyXXq/3qjC9GqCDQPZdg4ZkmUr11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KwNUY/dJMcaciyXXq/3qjC9GqCDQPZdg4ZkmUr11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KwNUY/dJMcaciyXXq/3qjC9GqCDQPZdg4ZkmUr11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKwNUY%2FdJMcaciyXXq%2F3qjC9GqCDQPZdg4ZkmUr11%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;482&quot; height=&quot;181&quot; data-origin-width=&quot;804&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;&lt;a href=&quot;https://github.com/apple/swift-protobuf/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/apple/swift-protobuf/tree/main&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776920262601&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - apple/swift-protobuf: Plugin and runtime library for using protobuf with Swift&quot; data-og-description=&quot;Plugin and runtime library for using protobuf with Swift - apple/swift-protobuf&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/apple/swift-protobuf/tree/main&quot; data-og-url=&quot;https://github.com/apple/swift-protobuf&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iVBXA/dJMb8QeoACd/U8kWQTMVrzcCfZlPjxmQdK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/QOw9l/dJMb8SXAcnZ/gLIKVMwLklmdqxWalS4KKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/apple/swift-protobuf/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/apple/swift-protobuf/tree/main&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iVBXA/dJMb8QeoACd/U8kWQTMVrzcCfZlPjxmQdK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/QOw9l/dJMb8SXAcnZ/gLIKVMwLklmdqxWalS4KKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;GitHub - apple/swift-protobuf: Plugin and runtime library for using protobuf with Swift&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Plugin and runtime library for using protobuf with Swift - apple/swift-protobuf&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가이드에 맞게 쉽게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플의 공식지원이라 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 파서를 만드는 경우엔&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이너리 디코딩과 인코딩 시키는 함수작성이 필요합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776921172343&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func readVarint(_ data: Data, offset: inout Int) throws -&amp;gt; UInt64 {
    var result: UInt64 = 0
    var shift = 0
    while offset &amp;lt; data.count {
        let byte = data[offset]; offset += 1
        if shift &amp;gt; 63 { throw UpdateError.parseError(&quot;varint overflow&quot;) }
        result |= UInt64(byte &amp;amp; 0x7F) &amp;lt;&amp;lt; shift
        if byte &amp;amp; 0x80 == 0 { return result }
        shift += 7
    }
    throw UpdateError.parseError(&quot;예상치 못한 데이터 끝&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트를 다루는 연산이기때문에 함수가 좀 어렵게 느껴집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1776921335691&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;shift: 현재 조각이 최종 숫자의 몇번째 7비트 구간인지 의미

offset = 0, data[0] = 0x96
이라면 
byte = 0x96
offset은 1증가
데이터를 소비하면서 전진하는 방식

shift &amp;gt; 63: UInt64범위 체크

byte &amp;amp; 0x7F
0x7F -&amp;gt; 01111111
AND연산하면 하위 7비트만 남고 맨앞비트제거
MSB버리고 실제 payload만 꺼내는 과정임

&amp;lt;&amp;lt; shift
꺼낸 7비트를 최종 숫자 올바른 위치로 이동 (재배열 역순임)
첫번재면 shift = 0, 두번째면 shift = 7
[세번째 조각 7비트][두번째 조각 7비트][첫 조각 7비트]

result |= 
현재 값에 이번에 읽은 7비트 조각 합침&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 과정이 있습니다...  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(읽기만 하는경우엔 디코딩 함수만 있어도 무방합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 struct나 enum 모델도 직접 정의합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;proto파일이 있다면 그대로 컴파일을 돌리면 되서 편하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히스토리 부재로 proto 파일이 따로없다면 모델을 직접 정의해서 써야합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;라이브러리의 유무는 직렬화와 역직렬화를 편하게 제공해주는것에서 큰차이가 있습니다.&lt;/span&gt;&lt;/b&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 라이브러리 사용방법을 간단하게 알아볼게요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Xcode에서 SPM을 연결해줍니다&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0JbN/dJMcaad2PtN/3XWUBN9Av1bhCwmbtzyzQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0JbN/dJMcaad2PtN/3XWUBN9Av1bhCwmbtzyzQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0JbN/dJMcaad2PtN/3XWUBN9Av1bhCwmbtzyzQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0JbN%2FdJMcaad2PtN%2F3XWUBN9Av1bhCwmbtzyzQ0%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;625&quot; height=&quot;194&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행파일도 같이 딸려오는데 none으로 설정해서 무시하셔도됩니다(따로 설치합니다)&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;2. protoc를 설치해줍니다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777349548254&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install swift-protobuf&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;b&gt;3. Xcode Build Setting에서 User Script Sandboxing 값 확인 &amp;gt; no 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(설정안하면 권한때문에 스크립트 실행이 막힙니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Build Phases 작성, Compile Sources 전 단계로 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 프로젝트폴더에 testStruct.proto 파일로 구조를 정의해뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일을 스크립트에서 작성해줬기때문에 여러분이 생성한 파일이름으로 커스텀해주세요&lt;/p&gt;
&lt;pre id=&quot;code_1777349801031&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  export PATH=&quot;/opt/homebrew/bin:$PATH&quot;

  OUT_DIR=&quot;${SRCROOT}/Practice_protobuf/Generated&quot;
  mkdir -p &quot;${OUT_DIR}&quot;

  protoc \
    --proto_path=&quot;${SRCROOT}&quot; \
    --swift_out=&quot;${OUT_DIR}&quot; \
    &quot;${SRCROOT}/testStruct.proto&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;&lt;b&gt;5. 빌드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LVLI4/dJMcafzELgi/80zKlStaSvFggJ6aVfS1B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LVLI4/dJMcafzELgi/80zKlStaSvFggJ6aVfS1B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LVLI4/dJMcafzELgi/80zKlStaSvFggJ6aVfS1B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLVLI4%2FdJMcafzELgi%2F80zKlStaSvFggJ6aVfS1B1%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;314&quot; height=&quot;201&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;344&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 data-ke-size=&quot;size16&quot;&gt;왼쪽의 proto파일이 오른쪽같이 swift코드로 변환됩니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rz5CW/dJMcacpn7b2/dvOqxVPVuZTkzAJ9xlyNwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rz5CW/dJMcacpn7b2/dvOqxVPVuZTkzAJ9xlyNwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rz5CW/dJMcacpn7b2/dvOqxVPVuZTkzAJ9xlyNwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frz5CW%2FdJMcacpn7b2%2FdvOqxVPVuZTkzAJ9xlyNwk%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;1958&quot; height=&quot;548&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 어떻게사용하냐면&lt;/p&gt;
&lt;pre id=&quot;code_1777350013916&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func test() {
    let namsoo = NamSoo.with {
        $0.age = 32
        $0.job = &quot;iOS Developer&quot;
    }
    do {
        let binaryData = try namsoo.serializedData()    // 직렬화
        let decodeData = try NamSoo(serializedBytes: binaryData)    // 역직렬화
        let jsonData = try namsoo.jsonUTF8Data()    // json직렬화
        let decodeFromJson = try NamSoo(jsonUTF8Data: jsonData)     // json역직렬화
    } catch {
        print(error)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 간편하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;공식문서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://protobuf.dev/programming-guides/encoding/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://protobuf.dev/programming-guides/encoding/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776919234782&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;Encoding&quot; data-og-description=&quot;Explains how Protocol Buffers encodes data to files or to the wire.&quot; data-og-host=&quot;protobuf.dev&quot; data-og-source-url=&quot;https://protobuf.dev/programming-guides/encoding/&quot; data-og-url=&quot;https://protobuf.dev/programming-guides/encoding/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://protobuf.dev/programming-guides/encoding/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://protobuf.dev/programming-guides/encoding/&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;Encoding&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explains how Protocol Buffers encodes data to files or to the wire.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;protobuf.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/257</guid>
      <comments>https://nsios.tistory.com/257#entry257comment</comments>
      <pubDate>Tue, 28 Apr 2026 21:24:17 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] AI 기본 개념부터 온디바이스까지 정리해보기</title>
      <link>https://nsios.tistory.com/256</link>
      <description>&lt;p data-end=&quot;901&quot; data-start=&quot;867&quot; data-ke-size=&quot;size16&quot;&gt;AI를 공부하려고 하면 생각보다 용어가 한번에 많이 나옵니다.&lt;/p&gt;
&lt;p data-end=&quot;969&quot; data-start=&quot;903&quot; data-ke-size=&quot;size16&quot;&gt;머신러닝, 딥러닝, 모델, 추론, Transformer, 양자화, Core ML, Foundation Models...&lt;/p&gt;
&lt;p data-end=&quot;1033&quot; data-start=&quot;971&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1110&quot; data-start=&quot;1035&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 기본개념을 알아보고 iOS에는 어떻게 연결되고 &lt;br /&gt;앞으로는 어떻게 쓰면좋을지 고민과정을 작성해보려합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1323&quot; data-start=&quot;1217&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1227&quot; data-start=&quot;1217&quot; data-section-id=&quot;2yfql1&quot;&gt;AI 핵심 개념과 키워드&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1228&quot; data-section-id=&quot;suv3hu&quot;&gt;모델은 어떻게 사용하는가&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1228&quot; data-section-id=&quot;suv3hu&quot;&gt;iOS에서는 어떻게 연결되는가&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1228&quot; data-section-id=&quot;suv3hu&quot;&gt;Python&amp;nbsp;방식과&amp;nbsp;Create&amp;nbsp;ML&amp;nbsp;방식&lt;/li&gt;
&lt;li data-end=&quot;1284&quot; data-start=&quot;1263&quot; data-section-id=&quot;1dhif74&quot;&gt;성능최적화&lt;/li&gt;
&lt;li data-end=&quot;1307&quot; data-start=&quot;1285&quot; data-section-id=&quot;h3k0uv&quot;&gt;나는 지금 AI를 어떻게 쓰고 있는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래부터 글은 제가 정리한 자료조사를 기반으로 AI가 작성해준 글입니다.... )&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1341&quot; data-start=&quot;1330&quot; data-section-id=&quot;1na2k60&quot; data-ke-size=&quot;size26&quot;&gt;AI 핵심 개념&lt;/h2&gt;
&lt;h3 data-end=&quot;1370&quot; data-start=&quot;1343&quot; data-section-id=&quot;3ay3pb&quot; data-ke-size=&quot;size23&quot;&gt;머신러닝 (Machine Learning)&lt;/h3&gt;
&lt;p data-end=&quot;1405&quot; data-start=&quot;1371&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 보고 패턴을 학습해서 예측하거나 분류하는 방법입니다.&lt;/p&gt;
&lt;p data-end=&quot;1466&quot; data-start=&quot;1407&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;br /&gt;메일이 스팸인지 분류하거나&lt;br /&gt;사용자에게 상품을 추천하는 문제를 풀 때 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1530&quot; data-start=&quot;1468&quot; data-ke-size=&quot;size16&quot;&gt;보통 전통적인 머신러닝에서는&lt;br /&gt;사람이 어떤 특징(feature)을 넣을지 설계하는 비중이 상대적으로 컸습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1555&quot; data-start=&quot;1532&quot; data-section-id=&quot;adz8lv&quot; data-ke-size=&quot;size23&quot;&gt;딥러닝 (Deep Learning)&lt;/h3&gt;
&lt;p data-end=&quot;1575&quot; data-start=&quot;1556&quot; data-ke-size=&quot;size16&quot;&gt;딥러닝은 머신러닝의 한 종류입니다.&lt;/p&gt;
&lt;p data-end=&quot;1631&quot; data-start=&quot;1577&quot; data-ke-size=&quot;size16&quot;&gt;대량의 데이터와 많은 연산을 이용해서&lt;br /&gt;모델이 복잡한 패턴을 스스로 학습하도록 하는 방식입니다.&lt;/p&gt;
&lt;p data-end=&quot;1690&quot; data-start=&quot;1633&quot; data-ke-size=&quot;size16&quot;&gt;이미지 인식, 음성 인식, 번역, LLM 같은 생성형 AI도&lt;br /&gt;대부분 딥러닝 기반이라고 보면 됩니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1706&quot; data-start=&quot;1692&quot; data-section-id=&quot;5gbumw&quot; data-ke-size=&quot;size23&quot;&gt;모델 (Model)&lt;/h3&gt;
&lt;p data-end=&quot;1725&quot; data-start=&quot;1707&quot; data-ke-size=&quot;size16&quot;&gt;모델은 학습이 끝난 결과물입니다.&lt;/p&gt;
&lt;p data-end=&quot;1762&quot; data-start=&quot;1727&quot; data-ke-size=&quot;size16&quot;&gt;조금 단순하게 말하면&lt;br /&gt;AI의 &amp;ldquo;뇌&amp;rdquo;처럼 생각할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1821&quot; data-start=&quot;1764&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 사진을 넣었을 때&lt;br /&gt;&amp;ldquo;이건 고양이일 확률이 90%다&amp;rdquo;&lt;br /&gt;같은 결과를 내놓는게 모델입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1859&quot; data-start=&quot;1823&quot; data-section-id=&quot;1t15r0k&quot; data-ke-size=&quot;size23&quot;&gt;학습 vs 추론 (Training vs Inference)&lt;/h3&gt;
&lt;h4 data-end=&quot;1868&quot; data-start=&quot;1861&quot; data-ke-size=&quot;size20&quot;&gt;학습&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1933&quot; data-start=&quot;1869&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1897&quot; data-start=&quot;1869&quot; data-section-id=&quot;uu9ly9&quot;&gt;데이터를 이용해서 모델 파라미터를 조정하는 과정&lt;/li&gt;
&lt;li data-end=&quot;1917&quot; data-start=&quot;1898&quot; data-section-id=&quot;11b4fvx&quot;&gt;시간이 오래 걸리고 연산량이 큼&lt;/li&gt;
&lt;li data-end=&quot;1933&quot; data-start=&quot;1918&quot; data-section-id=&quot;19470xp&quot;&gt;쉽게 말하면 배우는 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1942&quot; data-start=&quot;1935&quot; data-ke-size=&quot;size20&quot;&gt;추론&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2023&quot; data-start=&quot;1943&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1977&quot; data-start=&quot;1943&quot; data-section-id=&quot;11chxuk&quot;&gt;학습이 끝난 모델로 실제 입력에 대한 결과를 반환하는 과정&lt;/li&gt;
&lt;li data-end=&quot;2000&quot; data-start=&quot;1978&quot; data-section-id=&quot;1dzo3a9&quot;&gt;서비스에서 우리가 주로 사용하는 단계&lt;/li&gt;
&lt;li data-end=&quot;2023&quot; data-start=&quot;2001&quot; data-section-id=&quot;1u0622n&quot;&gt;예: 사용자의 음성을 텍스트로 바꾸기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2069&quot; data-start=&quot;2025&quot; data-ke-size=&quot;size16&quot;&gt;iOS 개발자 입장에서는 대부분 &lt;b&gt;학습보다 추론&lt;/b&gt;을 더 많이 다루게 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;2138&quot; data-start=&quot;2071&quot; data-ke-size=&quot;size16&quot;&gt;앱에서는 이미 만들어진 모델을 넣고&lt;br /&gt;그 모델이 실제 입력에 대해 어떤 결과를 내는지 사용하는 경우가 많기 때문입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;2157&quot; data-start=&quot;2145&quot; data-section-id=&quot;yxgmfp&quot; data-ke-size=&quot;size26&quot;&gt;관련 키워드 개념&lt;/h2&gt;
&lt;h3 data-end=&quot;2183&quot; data-start=&quot;2159&quot; data-section-id=&quot;ltmzfc&quot; data-ke-size=&quot;size23&quot;&gt;Neural Network (신경망)&lt;/h3&gt;
&lt;p data-end=&quot;2210&quot; data-start=&quot;2184&quot; data-ke-size=&quot;size16&quot;&gt;사람 뇌의 뉴런 연결 방식을 흉내낸 구조입니다.&lt;/p&gt;
&lt;p data-end=&quot;2232&quot; data-start=&quot;2212&quot; data-ke-size=&quot;size16&quot;&gt;딥러닝의 기본 뼈대라고 보면 됩니다.&lt;/p&gt;
&lt;h3 data-end=&quot;2261&quot; data-start=&quot;2234&quot; data-section-id=&quot;1ddg5d8&quot; data-ke-size=&quot;size23&quot;&gt;CNN / RNN / Transformer&lt;/h3&gt;
&lt;h4 data-end=&quot;2271&quot; data-start=&quot;2263&quot; data-ke-size=&quot;size20&quot;&gt;CNN&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2330&quot; data-start=&quot;2272&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2287&quot; data-start=&quot;2272&quot; data-section-id=&quot;it14c5&quot;&gt;이미지 처리에 강한 구조&lt;/li&gt;
&lt;li data-end=&quot;2307&quot; data-start=&quot;2288&quot; data-section-id=&quot;pef6y0&quot;&gt;사진의 패턴이나 형태를 잘 잡음&lt;/li&gt;
&lt;li data-end=&quot;2330&quot; data-start=&quot;2308&quot; data-section-id=&quot;1to6t4y&quot;&gt;이미지 분류, 객체 탐지에 많이 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2340&quot; data-start=&quot;2332&quot; data-ke-size=&quot;size20&quot;&gt;RNN&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2426&quot; data-start=&quot;2341&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2365&quot; data-start=&quot;2341&quot; data-section-id=&quot;119epq9&quot;&gt;순서가 중요한 데이터를 다루기 위한 구조&lt;/li&gt;
&lt;li data-end=&quot;2397&quot; data-start=&quot;2366&quot; data-section-id=&quot;718o29&quot;&gt;이전 정보를 어느 정도 기억하면서 다음 데이터를 처리&lt;/li&gt;
&lt;li data-end=&quot;2426&quot; data-start=&quot;2398&quot; data-section-id=&quot;1w4sjmz&quot;&gt;문장, 음성 같은 순차 데이터에서 많이 다뤄졌음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2444&quot; data-start=&quot;2428&quot; data-ke-size=&quot;size20&quot;&gt;Transformer&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2529&quot; data-start=&quot;2445&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2471&quot; data-start=&quot;2445&quot; data-section-id=&quot;1yosrbg&quot;&gt;현재 자연어 처리와 생성형 AI의 핵심 구조&lt;/li&gt;
&lt;li data-end=&quot;2500&quot; data-start=&quot;2472&quot; data-section-id=&quot;14u5tpd&quot;&gt;문장 전체 관계를 잘 파악하고 병렬 처리에 유리&lt;/li&gt;
&lt;li data-end=&quot;2529&quot; data-start=&quot;2501&quot; data-section-id=&quot;pv1brd&quot;&gt;번역, 요약, 질의응답, LLM 등에 널리 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2559&quot; data-start=&quot;2531&quot; data-section-id=&quot;1vcd0gr&quot; data-ke-size=&quot;size23&quot;&gt;Input / Output / Feature&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2630&quot; data-start=&quot;2560&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2582&quot; data-start=&quot;2560&quot; data-section-id=&quot;10sk03r&quot;&gt;input: 모델에 넣는 입력 데이터&lt;/li&gt;
&lt;li data-end=&quot;2603&quot; data-start=&quot;2583&quot; data-section-id=&quot;145tbq&quot;&gt;output: 모델이 내놓는 결과&lt;/li&gt;
&lt;li data-end=&quot;2630&quot; data-start=&quot;2604&quot; data-section-id=&quot;ce5wku&quot;&gt;feature: 데이터를 설명하는 특징 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2750&quot; data-start=&quot;2632&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;br /&gt;집 사진을 넣어서 인테리어 스타일을 분류한다면&lt;br /&gt;사진은 input,&lt;br /&gt;&amp;ldquo;모던 스타일&amp;rdquo; 같은 결과는 output,&lt;br /&gt;사진 안의 패턴이나 색감, 구조 같은 정보는 feature로 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;2775&quot; data-start=&quot;2752&quot; data-section-id=&quot;u0rror&quot; data-ke-size=&quot;size23&quot;&gt;Loss / Optimization&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2853&quot; data-start=&quot;2776&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2807&quot; data-start=&quot;2776&quot; data-section-id=&quot;181pxoq&quot;&gt;loss: 모델 예측이 얼마나 틀렸는지를 나타내는 값&lt;/li&gt;
&lt;li data-end=&quot;2853&quot; data-start=&quot;2808&quot; data-section-id=&quot;10fsmbt&quot;&gt;optimization: 그 loss를 줄이도록 모델 파라미터를 조정하는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2894&quot; data-start=&quot;2855&quot; data-ke-size=&quot;size16&quot;&gt;학습은 결국&lt;br /&gt;&amp;ldquo;틀린 정도를 줄여가는 과정&amp;rdquo;이라고 이해하면 편합니다.&lt;/p&gt;
&lt;h3 data-end=&quot;2928&quot; data-start=&quot;2896&quot; data-section-id=&quot;1fvdn74&quot; data-ke-size=&quot;size23&quot;&gt;Overfitting / Generalization&lt;/h3&gt;
&lt;h4 data-end=&quot;2946&quot; data-start=&quot;2930&quot; data-ke-size=&quot;size20&quot;&gt;Overfitting&lt;/h4&gt;
&lt;p data-end=&quot;2997&quot; data-start=&quot;2947&quot; data-ke-size=&quot;size16&quot;&gt;학습 데이터는 지나치게 잘 맞추는데&lt;br /&gt;처음 보는 데이터에서는 성능이 떨어지는 상태입니다.&lt;/p&gt;
&lt;p data-end=&quot;3037&quot; data-start=&quot;2999&quot; data-ke-size=&quot;size16&quot;&gt;문제를 이해한게 아니라&lt;br /&gt;학습 데이터를 외워버린 상태에 가깝습니다.&lt;/p&gt;
&lt;h4 data-end=&quot;3058&quot; data-start=&quot;3039&quot; data-ke-size=&quot;size20&quot;&gt;Generalization&lt;/h4&gt;
&lt;p data-end=&quot;3084&quot; data-start=&quot;3059&quot; data-ke-size=&quot;size16&quot;&gt;처음 보는 데이터에도 잘 동작하는 능력입니다.&lt;/p&gt;
&lt;p data-end=&quot;3131&quot; data-start=&quot;3086&quot; data-ke-size=&quot;size16&quot;&gt;좋은 모델은 결국&lt;br /&gt;훈련셋이 아니라 실제 사용자 입력에서도 잘 동작해야 합니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3152&quot; data-start=&quot;3133&quot; data-section-id=&quot;1afmcv1&quot; data-ke-size=&quot;size23&quot;&gt;Dataset / Label&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3207&quot; data-start=&quot;3153&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3184&quot; data-start=&quot;3153&quot; data-section-id=&quot;s9609s&quot;&gt;dataset: 학습이나 평가에 사용하는 데이터 모음&lt;/li&gt;
&lt;li data-end=&quot;3207&quot; data-start=&quot;3185&quot; data-section-id=&quot;qk070c&quot;&gt;label: 데이터에 붙는 정답 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3270&quot; data-start=&quot;3209&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 고양이 사진 1만장이 dataset이고,&lt;br /&gt;각 사진에 붙은 &amp;ldquo;고양이&amp;rdquo;라는 정답이 label입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3282&quot; data-start=&quot;3272&quot; data-section-id=&quot;qx6u3b&quot; data-ke-size=&quot;size23&quot;&gt;Tensor&lt;/h3&gt;
&lt;p data-end=&quot;3321&quot; data-start=&quot;3283&quot; data-ke-size=&quot;size16&quot;&gt;머신러닝과 딥러닝에서 데이터를 담는 기본적인 다차원 배열 단위입니다.&lt;/p&gt;
&lt;p data-end=&quot;3395&quot; data-start=&quot;3323&quot; data-ke-size=&quot;size16&quot;&gt;이미지, 음성, 텍스트도&lt;br /&gt;결국은 연산 가능한 숫자 구조로 바뀌어서 다뤄지는데&lt;br /&gt;그때 자주 등장하는 표현이 tensor입니다.&lt;/p&gt;
&lt;p data-end=&quot;3395&quot; data-start=&quot;3323&quot; 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;style6&quot; /&gt;
&lt;h2 data-end=&quot;3418&quot; data-start=&quot;3402&quot; data-section-id=&quot;1i08ov3&quot; data-ke-size=&quot;size26&quot;&gt;모델은 어떻게 사용하는가&lt;/h2&gt;
&lt;p data-end=&quot;3477&quot; data-start=&quot;3420&quot; data-ke-size=&quot;size16&quot;&gt;앱 개발에서는 보통 모델을 직접 학습시키기보다&lt;br /&gt;이미 학습된 모델을 가져와 사용하는 경우가 많습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3496&quot; data-start=&quot;3479&quot; data-section-id=&quot;1tevarb&quot; data-ke-size=&quot;size23&quot;&gt;Core ML 모델 포맷&lt;/h3&gt;
&lt;p data-end=&quot;3564&quot; data-start=&quot;3497&quot; data-ke-size=&quot;size16&quot;&gt;애플 생태계에서는 .mlmodel, .mlpackage 형태의 모델을&lt;br /&gt;Xcode에 넣어서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;3624&quot; data-start=&quot;3566&quot; data-ke-size=&quot;size16&quot;&gt;이미지 분류, 텍스트 처리, 음성, 예측 등&lt;br /&gt;여러 작업을 Core ML 기반으로 연결할 수 있습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3635&quot; data-start=&quot;3626&quot; data-section-id=&quot;5mq29a&quot; data-ke-size=&quot;size23&quot;&gt;모델 변환&lt;/h3&gt;
&lt;p data-end=&quot;3723&quot; data-start=&quot;3636&quot; data-ke-size=&quot;size16&quot;&gt;실제로는 Python 환경에서 모델을 학습한 뒤&lt;br /&gt;coremltools 같은 도구를 사용해서&lt;br /&gt;Core ML 형식으로 변환하는 흐름이 자주 나옵니다.&lt;/p&gt;
&lt;p data-end=&quot;3727&quot; data-start=&quot;3725&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;p data-end=&quot;3778&quot; data-start=&quot;3729&quot; data-ke-size=&quot;size16&quot;&gt;Python에서 학습&lt;br /&gt;-&amp;gt; 모델 변환&lt;br /&gt;-&amp;gt; Xcode에 포함&lt;br /&gt;-&amp;gt; 앱에서 추론&lt;/p&gt;
&lt;p data-end=&quot;3788&quot; data-start=&quot;3780&quot; data-ke-size=&quot;size16&quot;&gt;이런 식입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3798&quot; data-start=&quot;3790&quot; data-section-id=&quot;yng435&quot; data-ke-size=&quot;size23&quot;&gt;ONNX&lt;/h3&gt;
&lt;p data-end=&quot;3840&quot; data-start=&quot;3799&quot; data-ke-size=&quot;size16&quot;&gt;ONNX는 모델을 다른 환경으로 옮기기 쉽게 하려는 중간 표준 형식입니다.&lt;/p&gt;
&lt;p data-end=&quot;3912&quot; data-start=&quot;3842&quot; data-ke-size=&quot;size16&quot;&gt;모델 학습 프레임워크와 실행 환경이 다를 수 있기 때문에&lt;br /&gt;중간 포맷을 거쳐 Core ML 쪽으로 가져오는 경우도 있습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;3933&quot; data-start=&quot;3914&quot; data-section-id=&quot;12eujap&quot; data-ke-size=&quot;size23&quot;&gt;TensorFlow Lite&lt;/h3&gt;
&lt;p data-end=&quot;4012&quot; data-start=&quot;3934&quot; data-ke-size=&quot;size16&quot;&gt;크로스플랫폼이나 이미 TFLite 모델을 쓰고 있는 환경이라면&lt;br /&gt;TensorFlow Lite 기반으로 앱에 넣어 사용하는 방법도 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;3395&quot; data-start=&quot;3323&quot; 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;style6&quot; /&gt;
&lt;h2 data-end=&quot;4038&quot; data-start=&quot;4019&quot; data-section-id=&quot;1rqr2zr&quot; data-ke-size=&quot;size26&quot;&gt;iOS에서는 어떻게 연결되는가&lt;/h2&gt;
&lt;p data-end=&quot;4144&quot; data-start=&quot;4069&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;4160&quot; data-start=&quot;4146&quot; data-section-id=&quot;1sk6zhz&quot; data-ke-size=&quot;size23&quot;&gt;Core ML이란?&lt;/h3&gt;
&lt;p data-end=&quot;4208&quot; data-start=&quot;4161&quot; data-ke-size=&quot;size16&quot;&gt;Core ML은&lt;br /&gt;앱이 모델을 사용할 수 있도록 해주는 상위 ML 프레임워크입니다.&lt;/p&gt;
&lt;p data-end=&quot;4251&quot; data-start=&quot;4210&quot; data-ke-size=&quot;size16&quot;&gt;입출력 관리, 모델 로드, 컴파일, 실행 경로 선택 같은 부분을 맡습니다.&lt;/p&gt;
&lt;p data-end=&quot;4321&quot; data-start=&quot;4253&quot; data-ke-size=&quot;size16&quot;&gt;즉&lt;br /&gt;모델 자체를 만드는 도구라기보다&lt;br /&gt;&lt;b&gt;앱에서 모델을 안정적으로 실행하게 해주는 런타임 인터페이스&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;4344&quot; data-start=&quot;4323&quot; data-section-id=&quot;1b1f8c1&quot; data-ke-size=&quot;size23&quot;&gt;Foundation Models&lt;/h3&gt;
&lt;p data-end=&quot;4417&quot; data-start=&quot;4345&quot; data-ke-size=&quot;size16&quot;&gt;최근 애플은 Foundation Models 프레임워크를 통해&lt;br /&gt;온디바이스 언어 모델에 접근하는 인터페이스를 제공하고 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4588&quot; data-start=&quot;4419&quot; data-ke-size=&quot;size16&quot;&gt;이 프레임워크는 Apple Intelligence를 지원하는 기기와 설정 여부에 따라 사용 가능 여부가 달라지고,&lt;br /&gt;세션 기반으로 모델을 호출하며, 생성 옵션이나 구조화된 응답 형태를 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;4823&quot; data-start=&quot;4590&quot; data-ke-size=&quot;size16&quot;&gt;개발자 입장에서는&lt;br /&gt;기존처럼 직접 모델 파일을 넣어 쓰는 방식과는 조금 다르게,&lt;br /&gt;&lt;b&gt;애플이 제공하는 시스템 모델 능력을 앱 기능으로 연결하는 방식&lt;/b&gt;으로 볼 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;4844&quot; data-start=&quot;4825&quot; data-section-id=&quot;q6ww3s&quot; data-ke-size=&quot;size23&quot;&gt;CPU / GPU / ANE&lt;/h3&gt;
&lt;p data-end=&quot;4925&quot; data-start=&quot;4845&quot; data-ke-size=&quot;size16&quot;&gt;CoreML은 조건에따라 적절한 장치를 사용합니다&lt;/p&gt;
&lt;p data-end=&quot;4934&quot; data-start=&quot;4927&quot; data-ke-size=&quot;size16&quot;&gt;간단히 보면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5000&quot; data-start=&quot;4935&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4959&quot; data-start=&quot;4935&quot; data-section-id=&quot;1jolxn4&quot;&gt;CPU: 제어 흐름, 범용 처리, 호환성&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;4976&quot; data-start=&quot;4960&quot; data-section-id=&quot;1l8vu4x&quot;&gt;GPU: 병렬 연산에 유리&lt;/li&gt;
&lt;li data-end=&quot;5000&quot; data-start=&quot;4977&quot; data-section-id=&quot;1o3svk8&quot;&gt;ANE: AI 추론에 특화된 연산 가속&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5044&quot; data-start=&quot;5002&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;5055&quot; data-start=&quot;5046&quot; data-section-id=&quot;ho1nsy&quot; data-ke-size=&quot;size23&quot;&gt;전체 흐름&lt;/h3&gt;
&lt;p data-end=&quot;5074&quot; data-start=&quot;5056&quot; data-ke-size=&quot;size16&quot;&gt;대략적인 구조는 이런 느낌입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;span&gt;Your App&lt;/span&gt;&lt;br /&gt;&lt;span&gt;-&amp;gt; Vision / Natural Language / Speech / SoundAnalysis&lt;/span&gt;&lt;br /&gt;&lt;span&gt;-&amp;gt; Core ML&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -&amp;gt; .mlmodel / .mlpackage&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -&amp;gt; MLModel runtime&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -&amp;gt; Input / Output abstraction&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -&amp;gt; Compute planning&lt;/span&gt;&lt;br /&gt;&lt;span&gt; -&amp;gt; CPU / GPU / Neural Engine execution&lt;/span&gt;&lt;br /&gt;&lt;span&gt;-&amp;gt; Low-level foundations&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5437&quot; data-start=&quot;5336&quot; 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;style6&quot; /&gt;
&lt;h2 data-end=&quot;5470&quot; data-start=&quot;5444&quot; data-section-id=&quot;3wigqw&quot; data-ke-size=&quot;size26&quot;&gt;Python 방식과 Create ML 방식&lt;/h2&gt;
&lt;h3 data-end=&quot;5488&quot; data-start=&quot;5472&quot; data-section-id=&quot;1tcf64b&quot; data-ke-size=&quot;size23&quot;&gt;Python 중심 방식&lt;/h3&gt;
&lt;p data-end=&quot;5530&quot; data-start=&quot;5489&quot; data-ke-size=&quot;size16&quot;&gt;모델 설계부터 학습, 평가, 변환, 배포까지 전부 직접 다루는 흐름입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5589&quot; data-start=&quot;5532&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5539&quot; data-start=&quot;5532&quot; data-section-id=&quot;nygn7l&quot;&gt;문제 정의&lt;/li&gt;
&lt;li data-end=&quot;5551&quot; data-start=&quot;5540&quot; data-section-id=&quot;1d85o0z&quot;&gt;데이터 수집/정리&lt;/li&gt;
&lt;li data-end=&quot;5559&quot; data-start=&quot;5552&quot; data-section-id=&quot;cy8ko5&quot;&gt;모델 선택&lt;/li&gt;
&lt;li data-end=&quot;5567&quot; data-start=&quot;5560&quot; data-section-id=&quot;1olu9px&quot;&gt;학습 설정&lt;/li&gt;
&lt;li data-end=&quot;5572&quot; data-start=&quot;5568&quot; data-section-id=&quot;yih75t&quot;&gt;평가&lt;/li&gt;
&lt;li data-end=&quot;5580&quot; data-start=&quot;5573&quot; data-section-id=&quot;1grqom2&quot;&gt;저장/변환&lt;/li&gt;
&lt;li data-end=&quot;5589&quot; data-start=&quot;5581&quot; data-section-id=&quot;1jaevzx&quot;&gt;배포 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5612&quot; data-start=&quot;5591&quot; data-ke-size=&quot;size16&quot;&gt;자유도는 높지만&lt;br /&gt;그만큼 복잡합니다.&lt;/p&gt;
&lt;h3 data-end=&quot;5627&quot; data-start=&quot;5614&quot; data-section-id=&quot;y46ndv&quot; data-ke-size=&quot;size23&quot;&gt;Create ML&lt;/h3&gt;
&lt;p data-end=&quot;5658&quot; data-start=&quot;5628&quot; data-ke-size=&quot;size16&quot;&gt;Create ML은 많은 부분을 자동화해주는 편입니다.&lt;/p&gt;
&lt;p data-end=&quot;5700&quot; data-start=&quot;5660&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 연구보다&lt;br /&gt;기능 구현이 목적일 때 빠르게 시도해보기에 좋습니다.&lt;/p&gt;
&lt;p data-end=&quot;5704&quot; data-start=&quot;5702&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5744&quot; data-start=&quot;5705&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5726&quot; data-start=&quot;5705&quot; data-section-id=&quot;18aczt4&quot;&gt;내가 AI 연구자가 되려는건 아니고&lt;/li&gt;
&lt;li data-end=&quot;5744&quot; data-start=&quot;5727&quot; data-section-id=&quot;1yfizp&quot;&gt;특정 기능을 앱에 넣고 싶다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5781&quot; data-start=&quot;5746&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서는 Create ML이 훨씬 실용적일 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;5805&quot; data-start=&quot;5788&quot; data-section-id=&quot;m38jr1&quot; data-ke-size=&quot;size26&quot;&gt;성능 최적화&lt;/h2&gt;
&lt;p data-end=&quot;5824&quot; data-start=&quot;5807&quot; data-ke-size=&quot;size16&quot;&gt;온디바이스 AI는 매력적입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5888&quot; data-start=&quot;5826&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5835&quot; data-start=&quot;5826&quot; data-section-id=&quot;6ibhos&quot;&gt;응답이 빠르고&lt;/li&gt;
&lt;li data-end=&quot;5856&quot; data-start=&quot;5836&quot; data-section-id=&quot;5cpm1m&quot;&gt;네트워크가 없어도 동작할 수 있고&lt;/li&gt;
&lt;li data-end=&quot;5888&quot; data-start=&quot;5857&quot; data-section-id=&quot;fzpj1l&quot;&gt;개인정보를 서버로 보내지 않아도 되는 경우가 많습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5944&quot; data-start=&quot;5890&quot; data-ke-size=&quot;size16&quot;&gt;하지만 그만큼&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;모델 크기, 메모리, 발열, 배터리, 앱 용량 같은 현실적인 문제&lt;/span&gt;가 따라옵니다.&lt;/p&gt;
&lt;p data-end=&quot;5978&quot; data-start=&quot;5946&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;온디바이스 AI는 결국 최적화가 핵심&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;125&quot; data-start=&quot;103&quot; data-section-id=&quot;192now3&quot; data-ke-size=&quot;size23&quot;&gt;Quantization (양자화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;651&quot; data-start=&quot;126&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;286&quot; data-start=&quot;126&quot; data-section-id=&quot;gum112&quot;&gt;개념
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;286&quot; data-start=&quot;133&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;161&quot; data-start=&quot;133&quot; data-section-id=&quot;y8goje&quot;&gt;모델 숫자표현을 더 작은 정밀도로 변경하는 방법&lt;/li&gt;
&lt;li data-end=&quot;203&quot; data-start=&quot;163&quot; data-section-id=&quot;1d8r6dq&quot;&gt;float32 -&amp;gt; int8, float16처럼 더 작은형식으로 변경&lt;/li&gt;
&lt;li data-end=&quot;237&quot; data-start=&quot;205&quot; data-section-id=&quot;19yiq0j&quot;&gt;목적: 모델크기감소, 메모리 사용량감소, 추론속도 향상&lt;/li&gt;
&lt;li data-end=&quot;286&quot; data-start=&quot;239&quot; data-section-id=&quot;1v2hsb9&quot;&gt;온디바이스에서는 제한된 자원 안에서 모델을 실행해야하므로 매우 중요한 최적화 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;472&quot; data-start=&quot;287&quot; data-section-id=&quot;gywgzw&quot;&gt;원리
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;472&quot; data-start=&quot;294&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;328&quot; data-start=&quot;294&quot; data-section-id=&quot;ma54ht&quot;&gt;모델 가중치와 활성값은 정밀한 실수로 저장되는 경우가 많음&lt;/li&gt;
&lt;li data-end=&quot;365&quot; data-start=&quot;330&quot; data-section-id=&quot;cu2hp0&quot;&gt;하지만 실제 추론에서는 항상 높은 정밀도가 필요한 것은 아님&lt;/li&gt;
&lt;li data-end=&quot;424&quot; data-start=&quot;367&quot; data-section-id=&quot;1st3179&quot;&gt;따라서 더 적은 비트로 표현해도 성능 손실은 작게 유지하면서 계산량과 메모리 사용량을 줄일 수 있음&lt;/li&gt;
&lt;li data-end=&quot;472&quot; data-start=&quot;426&quot; data-section-id=&quot;qxy9h4&quot;&gt;저장해야 하는 데이터 크기가 줄어들기 때문에 앱 용량과 로딩 비용에도 도움이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;651&quot; data-start=&quot;473&quot; data-section-id=&quot;pg2gto&quot;&gt;활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;651&quot; data-start=&quot;479&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;518&quot; data-start=&quot;479&quot; data-section-id=&quot;qiiw5d&quot;&gt;스마트폰, 임베디드 기기 등 자원이 제한된 환경에서 매우 자주 사용&lt;/li&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;520&quot; data-section-id=&quot;18359gl&quot;&gt;transformer, cnn처럼 큰 모델을 기기에서 실행할 때 특히 중요&lt;/li&gt;
&lt;li data-end=&quot;602&quot; data-start=&quot;565&quot; data-section-id=&quot;11x7pyx&quot;&gt;추론 속도 개선, 전력 소모 감소, 메모리 절약 측면에서 효과적&lt;/li&gt;
&lt;li data-end=&quot;651&quot; data-start=&quot;604&quot; data-section-id=&quot;12j42ek&quot;&gt;다만 양자화를 너무 강하게하면 정확도 저하가 발생할 수 있어서 적절한 균형이 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;671&quot; data-start=&quot;653&quot; data-section-id=&quot;377bjq&quot; data-ke-size=&quot;size23&quot;&gt;Pruning (가지치기)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1152&quot; data-start=&quot;672&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;805&quot; data-start=&quot;672&quot; data-section-id=&quot;15kpcsm&quot;&gt;개념
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;805&quot; data-start=&quot;679&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;720&quot; data-start=&quot;679&quot; data-section-id=&quot;ug3yum&quot;&gt;모델 안에서 중요도가 낮은 연결, 가중치, 레이어 일부를 제거하는 방법&lt;/li&gt;
&lt;li data-end=&quot;757&quot; data-start=&quot;722&quot; data-section-id=&quot;1v911pp&quot;&gt;덜 중요한 부분을 잘라내서 모델을 더 가볍고 효율적으로 만듬&lt;/li&gt;
&lt;li data-end=&quot;805&quot; data-start=&quot;759&quot; data-section-id=&quot;ks5wcn&quot;&gt;불필요한 계산을 줄여서 온디바이스 환경에 맞게 단순화하는 작업이라고 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;988&quot; data-start=&quot;806&quot; data-section-id=&quot;f40dze&quot;&gt;원리
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;988&quot; data-start=&quot;812&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;859&quot; data-start=&quot;812&quot; data-section-id=&quot;186021u&quot;&gt;실제로 학습이 끝난 모델에는 결과에 큰 영향을 주지 않는 가중치가 존재할 수 있음&lt;/li&gt;
&lt;li data-end=&quot;899&quot; data-start=&quot;861&quot; data-section-id=&quot;mplhlb&quot;&gt;이런 요소를 제거해도 전체 성능이 크게 떨어지지 않는 경우가 있음&lt;/li&gt;
&lt;li data-end=&quot;945&quot; data-start=&quot;901&quot; data-section-id=&quot;1bdiqem&quot;&gt;pruning 이후 필요하면 다시 fine-tuning을 해서 성능을 회복함&lt;/li&gt;
&lt;li data-end=&quot;988&quot; data-start=&quot;947&quot; data-section-id=&quot;1vnhdj1&quot;&gt;즉, 모델이 가진 여유분을 줄여 더 효율적인 구조로 다시 정리하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1152&quot; data-start=&quot;989&quot; data-section-id=&quot;17j444i&quot;&gt;활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1152&quot; data-start=&quot;995&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1035&quot; data-start=&quot;995&quot; data-section-id=&quot;18jetgp&quot;&gt;구조적으로 잘라낸 pruning은 실제 기기 속도개선에 더 도움되는편&lt;/li&gt;
&lt;li data-end=&quot;1064&quot; data-start=&quot;1037&quot; data-section-id=&quot;1mi6nzg&quot;&gt;모델 크기축소, 계산량감소, 메모리절약에 사용&lt;/li&gt;
&lt;li data-end=&quot;1103&quot; data-start=&quot;1066&quot; data-section-id=&quot;1we39z8&quot;&gt;온디바이스에서는 발열과 배터리 부담을 줄이는 데에도 의미가 있음&lt;/li&gt;
&lt;li data-end=&quot;1152&quot; data-start=&quot;1105&quot; data-section-id=&quot;1b09dq&quot;&gt;단순히 작게 만드는 것보다 실제 실행 효율이 좋아지는 방향으로 적용하는 것이 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1175&quot; data-start=&quot;1154&quot; data-section-id=&quot;6c2js7&quot; data-ke-size=&quot;size23&quot;&gt;Model Compression&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1557&quot; data-start=&quot;1176&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1303&quot; data-start=&quot;1176&quot; data-section-id=&quot;16mkigb&quot;&gt;개념
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1303&quot; data-start=&quot;1182&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1210&quot; data-start=&quot;1182&quot; data-section-id=&quot;e30u37&quot;&gt;모델을 더 작고 효율적으로 만드는 전체기법 묶음&lt;/li&gt;
&lt;li data-end=&quot;1256&quot; data-start=&quot;1212&quot; data-section-id=&quot;y3uv63&quot;&gt;양자화, pruning, distillation 등의 방식이 포함될 수 있음&lt;/li&gt;
&lt;li data-end=&quot;1303&quot; data-start=&quot;1258&quot; data-section-id=&quot;1ugir0x&quot;&gt;온디바이스에서 실행 가능한 수준으로 모델을 다듬는 전체 전략이라고 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1435&quot; data-start=&quot;1304&quot; data-section-id=&quot;54i6u&quot;&gt;원리
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1435&quot; data-start=&quot;1310&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1352&quot; data-start=&quot;1310&quot; data-section-id=&quot;fs2hws&quot;&gt;원본 모델 성능을 최대한 유지하면서 용량, 연산량을 줄이는 방향으로 변형&lt;/li&gt;
&lt;li data-end=&quot;1382&quot; data-start=&quot;1354&quot; data-section-id=&quot;1owxh3y&quot;&gt;같은 일을 더 적은 자원으로 수행하게 만드는 것&lt;/li&gt;
&lt;li data-end=&quot;1435&quot; data-start=&quot;1384&quot; data-section-id=&quot;jvidfa&quot;&gt;단순히 모델을 줄이는 것이 아니라, 속도&amp;middot;메모리&amp;middot;전력&amp;middot;정확도 사이에서 균형을 맞추는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1557&quot; data-start=&quot;1436&quot; data-section-id=&quot;1xuwqrz&quot;&gt;활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1557&quot; data-start=&quot;1442&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1456&quot; data-start=&quot;1442&quot; data-section-id=&quot;ehb964&quot;&gt;온디바이스에서 필수개념&lt;/li&gt;
&lt;li data-end=&quot;1486&quot; data-start=&quot;1458&quot; data-section-id=&quot;7h3ibs&quot;&gt;앱크기제한, 메모리제한, 발열문제 줄이는데 중요&lt;/li&gt;
&lt;li data-end=&quot;1509&quot; data-start=&quot;1488&quot; data-section-id=&quot;y5cubz&quot;&gt;대형모델 -&amp;gt; 소형모델 변경시 핵심&lt;/li&gt;
&lt;li data-end=&quot;1557&quot; data-start=&quot;1511&quot; data-section-id=&quot;1w7o0bd&quot;&gt;실제 서비스에서는 사용자 경험을 유지하면서 배포 가능한 형태로 만드는 데 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1592&quot; data-start=&quot;1559&quot; data-section-id=&quot;1xugqpe&quot; data-ke-size=&quot;size23&quot;&gt;Latency vs Accuracy trade-off&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1847&quot; data-start=&quot;1593&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1659&quot; data-start=&quot;1593&quot; data-section-id=&quot;ijqj4i&quot;&gt;더 빠르게 응답할수록 정확도가 떨어질수도있고, 높은 정확도를 추구할수록 모델이 무거워 느려질수 있는 트레이드오프관계&lt;/li&gt;
&lt;li data-end=&quot;1696&quot; data-start=&quot;1660&quot; data-section-id=&quot;agpemg&quot;&gt;온디바이스에서는 특히 응답속도가 사용자 경험에 직접 영향을 줌&lt;/li&gt;
&lt;li data-end=&quot;1753&quot; data-start=&quot;1697&quot; data-section-id=&quot;ib3ys6&quot;&gt;예를 들어 카메라 객체인식이나 음성인식은 정확도도 중요하지만, 늦게 반응하면 사용성이 크게 떨어짐&lt;/li&gt;
&lt;li data-end=&quot;1790&quot; data-start=&quot;1754&quot; data-section-id=&quot;15kd7it&quot;&gt;따라서 여러 모델후보를 비교해서 적정 균형점을 찾는 것이 중요&lt;/li&gt;
&lt;li data-end=&quot;1847&quot; data-start=&quot;1791&quot; data-section-id=&quot;c8jrjt&quot;&gt;서비스에서는 항상 최고 정확도보다, 충분히 좋은 정확도와 빠른 응답을 함께 만족하는 지점을 찾게됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1881&quot; data-start=&quot;1849&quot; data-section-id=&quot;edloiu&quot; data-ke-size=&quot;size23&quot;&gt;Batch vs Real-time inference&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2167&quot; data-start=&quot;1882&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1985&quot; data-start=&quot;1882&quot; data-section-id=&quot;1evr79l&quot;&gt;batch 추론 - 여러 입력을 한번에 묶어서 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1985&quot; data-start=&quot;1914&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1948&quot; data-start=&quot;1914&quot; data-section-id=&quot;wo0zgg&quot;&gt;서버로그분석, 대용량 이미지분류, 야간 일괄처리 등에 적합&lt;/li&gt;
&lt;li data-end=&quot;1985&quot; data-start=&quot;1950&quot; data-section-id=&quot;1isrc4u&quot;&gt;처리 효율은 좋지만 즉각적인 응답이 필요한 상황과는 맞지않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2089&quot; data-start=&quot;1986&quot; data-section-id=&quot;vb5tbu&quot;&gt;real time 추론 - 입력이 들어올때마다 즉시처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2089&quot; data-start=&quot;2020&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2051&quot; data-start=&quot;2020&quot; data-section-id=&quot;1d56pm1&quot;&gt;카메라 객체인식, 음성비서, 키보드 자동완성등에 사용&lt;/li&gt;
&lt;li data-end=&quot;2089&quot; data-start=&quot;2053&quot; data-section-id=&quot;v3hac&quot;&gt;사용자 입력에 바로 반응해야하므로 속도와 지연시간이 매우 중요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2120&quot; data-start=&quot;2090&quot; data-section-id=&quot;12jcryl&quot;&gt;온디바이스는 대체로 real-time 이 많이사용됨&lt;/li&gt;
&lt;li data-end=&quot;2167&quot; data-start=&quot;2121&quot; data-section-id=&quot;73q7ag&quot;&gt;그래서 모델 정확도뿐 아니라 실행속도, 메모리 사용량, 발열까지 함께 고려해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;6713&quot; data-start=&quot;6643&quot; 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;style6&quot; /&gt;
&lt;h2 data-end=&quot;6745&quot; data-start=&quot;6720&quot; data-section-id=&quot;1fqh71z&quot; data-ke-size=&quot;size26&quot;&gt;그래서 이 시점의 나는 지금 AI를 어떻게 사용하고 있는가&lt;/h2&gt;
&lt;p data-end=&quot;6811&quot; data-start=&quot;6747&quot; data-ke-size=&quot;size16&quot;&gt;크게 3가지로 나누어서 사용하고있습니다&lt;/p&gt;
&lt;p data-end=&quot;6811&quot; data-start=&quot;6747&quot; data-ke-size=&quot;size16&quot;&gt;개발영역과 생산성영역과 검색영역입니다.&lt;/p&gt;
&lt;h3 data-end=&quot;6819&quot; data-start=&quot;6813&quot; data-section-id=&quot;1hrke6e&quot; data-ke-size=&quot;size23&quot;&gt;개발&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6908&quot; data-start=&quot;6820&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6853&quot; data-start=&quot;6834&quot; data-section-id=&quot;tq4ihk&quot;&gt;에이전트를 역할별로 나눠서 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6853&quot; data-start=&quot;6834&quot; data-section-id=&quot;tq4ihk&quot;&gt;UI 구현&lt;/li&gt;
&lt;li data-end=&quot;6853&quot; data-start=&quot;6834&quot; data-section-id=&quot;tq4ihk&quot;&gt;아키텍처, 성능, 보안, 스타일 검사&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6908&quot; data-start=&quot;6894&quot; data-section-id=&quot;9yyast&quot;&gt;스킬&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6908&quot; data-start=&quot;6894&quot; data-section-id=&quot;9yyast&quot;&gt;모듈 추가&lt;/li&gt;
&lt;li data-end=&quot;6908&quot; data-start=&quot;6894&quot; data-section-id=&quot;9yyast&quot;&gt;코드 구현&lt;/li&gt;
&lt;li data-end=&quot;6908&quot; data-start=&quot;6894&quot; data-section-id=&quot;9yyast&quot;&gt;코드 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;6997&quot; data-start=&quot;6987&quot; data-section-id=&quot;rf21bu&quot; data-ke-size=&quot;size23&quot;&gt;개발 생산성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7064&quot; data-start=&quot;6998&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7009&quot; data-start=&quot;6998&quot; data-section-id=&quot;f5vo5s&quot;&gt;작업 스케줄 관리 - 옵시디언 연동&lt;/li&gt;
&lt;li data-end=&quot;7020&quot; data-start=&quot;7010&quot; data-section-id=&quot;id056w&quot;&gt;문서 작성 - 작업기반으로 작성&lt;/li&gt;
&lt;li data-end=&quot;7044&quot; data-start=&quot;7021&quot; data-section-id=&quot;1ip8fyt&quot;&gt;AI 에이전트가 수행한 작업을 md 파일로 정리&lt;/li&gt;
&lt;li data-end=&quot;7064&quot; data-start=&quot;7045&quot; data-section-id=&quot;162wxzk&quot;&gt;웹훅을 통해 메신저로 요약 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;7144&quot; data-start=&quot;7138&quot; data-section-id=&quot;1hrkftb&quot; data-ke-size=&quot;size23&quot;&gt;검색&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7191&quot; data-start=&quot;7145&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7152&quot; data-start=&quot;7145&quot; data-section-id=&quot;1tle3d9&quot;&gt;개념 검색&lt;/li&gt;
&lt;li data-end=&quot;7165&quot; data-start=&quot;7153&quot; data-section-id=&quot;1ofa72e&quot;&gt;정보 분석 및 정리&lt;/li&gt;
&lt;li data-end=&quot;7180&quot; data-start=&quot;7166&quot; data-section-id=&quot;1lz23m4&quot;&gt;필요한 코드 초안 작성&lt;/li&gt;
&lt;li data-end=&quot;7191&quot; data-start=&quot;7181&quot; data-section-id=&quot;blahgl&quot;&gt;버그 원인 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-section-id=&quot;1fqh71z&quot; data-start=&quot;6720&quot; data-end=&quot;6745&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&amp;nbsp;Next...&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;26년 최근들어 하네스 라는 방식의 에이전트 팀을 이용한 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;에이전트끼리 워크플로우를 통해서 아웃풋을 내도록하는 방식이 화두가 되고있어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하네스란&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에이전트 팀 시스템을 활용하여 복잡한 작업을 전문 에이전트 팀으로 분해&amp;middot;조율하는 아키텍처 도구&lt;br /&gt;- 궁극적으론 다른모델을써도 같은 아웃풋이나옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가드레일을 통해 제어하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기록과 피드백을통해 다듬어서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에이전트를 잘 운용할 수 있도록 관심이 몰리고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식을 이용해서&lt;/p&gt;
&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;gt;&amp;nbsp; 개발 에이전트&amp;nbsp; &amp;nbsp;-&amp;gt;&amp;nbsp; 작업&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -(명령)-&amp;gt;&amp;nbsp; 문서 에이전트&amp;nbsp; &amp;nbsp;-&amp;gt;&amp;nbsp; 작업&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -(명령)&lt;/span&gt;-&amp;gt;&amp;nbsp; 검증 에이전트&amp;nbsp; &amp;nbsp;-&amp;gt;&amp;nbsp; 작업&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이런 과정을 알아서 할 수 있는 환경을 만들어보고싶네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 과정에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;주의해야할점은 AI를 어디까지 믿어야하는가가 될거같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;7245&quot; data-start=&quot;7193&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;완전한 신뢰는 할 수 없으니 검증은 어떻게 해야할지에 대한 고민도 필요할것 같아요&lt;/span&gt;&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/256</guid>
      <comments>https://nsios.tistory.com/256#entry256comment</comments>
      <pubDate>Tue, 31 Mar 2026 21:22:27 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] FoundationModels</title>
      <link>https://nsios.tistory.com/255</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;OnDevice LLM 프레임워크인 FoundationModels에 대해서 알아보려합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 LLM은 통신을 통한다던가 혹은 기기내에서 사용하도록 OnDevice형식을 취하려면 보안과 앱용량의 trade-off를 고민했어야했는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apple의 FoundationModels는 운영체제에 이미 존재해서 앱용량이 늘어나지 않는다고하네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심타입들을 알아보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;냉장고 내에있는 재료를 통해 어떤 음식을 만들수잇는지 요청하는 데모앱을 만들어보려합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FoundationModels?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파운데이션&amp;nbsp;모델&amp;nbsp;프레임워크는&amp;nbsp;애플&amp;nbsp;인텔리전스를&amp;nbsp;구동하는&amp;nbsp;애플의&amp;nbsp;온디바이스&amp;nbsp;대규모&amp;nbsp;언어&amp;nbsp;모델에&amp;nbsp;대한&amp;nbsp;접근을&amp;nbsp;제공하여&amp;nbsp;사용자의&amp;nbsp;특정&amp;nbsp;사용&amp;nbsp;사례에&amp;nbsp;맞는&amp;nbsp;지능형&amp;nbsp;작업을&amp;nbsp;수행할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;지원합니다.&amp;nbsp;&lt;br /&gt;텍스트&amp;nbsp;기반&amp;nbsp;온디바이스&amp;nbsp;모델은&amp;nbsp;패턴을&amp;nbsp;식별하여&amp;nbsp;사용자가&amp;nbsp;요청한&amp;nbsp;내용에&amp;nbsp;적합한&amp;nbsp;새로운&amp;nbsp;텍스트를&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;특수한&amp;nbsp;작업을&amp;nbsp;수행하기&amp;nbsp;위해&amp;nbsp;사용자가&amp;nbsp;작성한&amp;nbsp;코드를&amp;nbsp;호출하는&amp;nbsp;결정을&amp;nbsp;내릴&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;파운데이션 모델 프레임워크는 두 가지 기본 안전 계층을 갖추고 있으며, 프레임워크는 다음을 활용합니다&lt;br /&gt;- 민감한 주제를 신중하게 처리하도록 훈련된 온디바이스 언어 모델.&lt;br /&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;내&amp;nbsp;언어&amp;nbsp;모델은&amp;nbsp;모든&amp;nbsp;요청을&amp;nbsp;처리하기에&amp;nbsp;적합하지&amp;nbsp;않을&amp;nbsp;수&amp;nbsp;있으며&amp;nbsp;특정&amp;nbsp;주제에&amp;nbsp;대한&amp;nbsp;요청을&amp;nbsp;거부할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;문자열&amp;nbsp;응답을&amp;nbsp;생성할&amp;nbsp;때&amp;nbsp;모델이&amp;nbsp;요청을&amp;nbsp;거부하면&amp;nbsp;&quot;죄송합니다,&amp;nbsp;도와드릴&amp;nbsp;수&amp;nbsp;없습니다&quot;와&amp;nbsp;같이&amp;nbsp;거절로&amp;nbsp;시작하는&amp;nbsp;메시지를&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;br /&gt;- 기기가 Apple Intelligence를 지원해야 합니다.&lt;br /&gt;- 기기의 설정에서 Apple Intelligence가 켜져 있어야 합니다.&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;SystemLanguageModel.default.availability 를 이용해서 사용가능/불가능 케이스 대응가능합니다&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;LanguageModelSession&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델&amp;nbsp;사용&amp;nbsp;가능&amp;nbsp;여부를&amp;nbsp;확인한&amp;nbsp;후,&amp;nbsp;모델을&amp;nbsp;호출하기&amp;nbsp;위해&amp;nbsp;LanguageModelSession&amp;nbsp;객체를&amp;nbsp;생성합니다.&amp;nbsp;&lt;br /&gt;단일&amp;nbsp;회화&amp;nbsp;상호작용의&amp;nbsp;경우&amp;nbsp;모델을&amp;nbsp;호출할&amp;nbsp;때마다&amp;nbsp;새&amp;nbsp;세션을&amp;nbsp;생성합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다중 턴 상호작용(모델이 생성한 내용에 대한 일부 지식을 유지하는 경우)에서는 모델을 호출할 때마다 동일한 세션을 재사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크를 사용하는 앱을 테스트할 때는 Xcode Instruments를 활용하여 요청 수행 시간 등 수행한 요청에 대한 정보를 더 자세히 파악할 수 있습니다.&lt;br /&gt;요청을&amp;nbsp;수행할&amp;nbsp;때&amp;nbsp;LanguageModelSession&amp;nbsp;동안&amp;nbsp;모델이&amp;nbsp;수행한&amp;nbsp;작업을&amp;nbsp;설명하는&amp;nbsp;기록&amp;nbsp;항목에&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프롬프트에 대한 최상의 결과를 얻으려면 다양한 생성 옵션을 실험해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;GenerationOptions&lt;/b&gt;는 모델의 런타임 매개변수에 영향을 미치며, 요청할 때마다 이를 맞춤 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UseCase&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;general - 기본 모델은 질문 답변 및 창의적 콘텐츠 생성 등 광범위한 텍스트 기반 작업, 일반적 상황에서 최상의 성능을 내도록 최적화됨&lt;br /&gt;contentTagging - 주제 태그 생성, 콘텐츠 분류, 텍스트에서 특정 엔티티(예: 인물, 장소) 추출 등의 텍스트의 구조를 분석하고 특정 정보를 추출하거나 분류하는데 특화된 모델&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;GenerationOptions&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sampling&lt;/b&gt; - 출력토큰 선택하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;greedy선택한 경우 같은세션상태에서 같은프롬프트 입력시 항상 같은 출력(같은 버전 온디바이스 모델이면)&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;temperature&lt;/b&gt; - 출력의 다양성 조정 (높을수록 다른 결과 생성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;maximumResponseTokens&lt;/b&gt; - 너무 긴 응답 제한&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Generable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 프롬프트에 응답할 때 사용하는 프로토콜 타입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 구조체나 열거형에 Generable 매크로를 주석 처리하여 모델이 프롬프트에 응답할 때 해당 유형의 인스턴스를 생성할 수 있도록 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과로 비구조적 자연어를 출력하는데 사람은 읽기쉽지만 뷰에 적용하기힘들어서 Json, csv 형식으로 변환후 사용해서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오류발생가능성이 있는데 이에 대한 해결책&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GenerationGuide&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Generable&amp;nbsp;유형의&amp;nbsp;속성&amp;nbsp;허용&amp;nbsp;값에&amp;nbsp;영향을&amp;nbsp;미칠&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Guide 매크로로 사용&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;p data-ke-size=&quot;size16&quot;&gt;모든 곳에 붙인다면 이 데이터도 모델이 읽어야 하는 데이터이므로 토큰 낭비와 처리속도에 영향이 있을수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헷갈릴만한 지점에 필요한곳만 붙여사용하는것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;p data-ke-size=&quot;size16&quot;&gt;간단한 예제를 만들어봅시다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;냉장고에 있는 재료들로 레시피를 추천해주는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session을 만들어주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답을 받는 방법은 2가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;response(to: )&lt;/span&gt;&lt;/b&gt; 한번에 완성된 응답받기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;streamResponse(to: )&lt;/b&gt; &lt;/span&gt;토큰단위로 스트리밍&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 @Generable을 사용하면 원하는 타입의 구조체로 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래코드는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 정의하고&lt;/p&gt;
&lt;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_1771919130226&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Generable(description: &quot;식재료&quot;)
struct Ingredient {
    let name: String
}

@Generable(description: &quot;AI가 제안할 결과 레시피&quot;)
struct Recipe: Codable {
    @Guide(description: &quot;요리이름&quot;)
    let title: String
    @Guide(description: &quot;냉장고에 없는 추가로 필요한 재료만 리스트업&quot;)
    let ingredientsNeeded: [String]
    let instructions: [String]
    @Guide(description: &quot;쉬움, 보통, 어려움 중 하나 선택&quot;)
    let difficulty: String
    let estimatedTime: Int // 분 단위
}

let instructions = &quot;&quot;&quot;
    당신은 냉장고에 있는 재료로 요리를 추천해주는 전문 셰프 어시스턴트입니다.
        
        역할:
        - 사용자가 제공한 재료만으로 만들 수 있는 현실적인 레시피를 추천합니다
        - 소금, 후추, 식용유, 간장, 된장 등 기본 양념은 항상 있다고 가정합니다
        - 가정집 주방 기준으로 실현 가능한 레시피만 제안합니다
        - 조리 단계는 초보자도 따라할 수 있도록 구체적으로 설명합니다
        - 한국어로 응답합니다
        
        레시피 선정 기준:
        - 다양한 난이도(Easy / Medium / Hard)의 레시피를 균형있게 추천합니다
        - 재료 낭비 없이 가능한 많은 재료를 활용합니다
        - 영양 균형을 고려합니다
        - 요리이름에 냉장고 단어를 제외합니다.
        - 냉장고 속에 없는건 사용하지 않습니다.
    &quot;&quot;&quot;

func makeSession() async {
    let session = LanguageModelSession(instructions: instructions)
    session.prewarm()
    self.session = session
    
    let stream = session.streamResponse(
        to: &quot;냉장고에 있는 재료로 만들 수 있는 요리는?&quot;,
        generating: Recipe.self
    )
    do {
        for try await token in stream {
            print(&quot;token.content&quot;, token.content)
            self.displayText = token.content.instructions ?? []
            self.title = token.content.title ?? &quot;&quot;
        }
    } catch {
        print(error)
    }
}&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_1771919909168&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: nil, instructions: nil, difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: nil, difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥&quot;]), difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과&quot;]), difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과 식용유를 두르고 잘 섞는다.&quot;]), difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과 식용유를 두르고 잘 섞는다.&quot;, &quot;밥을 냄비에 담고 끓인다&quot;]), difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과 식용유를 두르고 잘 섞는다.&quot;, &quot;밥을 냄비에 담고 끓인다.&quot;, &quot;밥이 익으면 소금과 후추&quot;]), difficulty: nil, estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과 식용유를 두르고 잘 섞는다.&quot;, &quot;밥을 냄비에 담고 끓인다.&quot;, &quot;밥이 익으면 소금과 후추로 간을 맞춘다.&quot;]), difficulty: Optional(&quot;&quot;), estimatedTime: nil)
(id: FoundationModels.GenerationID(value: &quot;0A90B228-5192-4EE1-B9AE-FF96715453CF&quot;), title: Optional(&quot;간장 볶음밥&quot;), ingredientsNeeded: Optional([&quot;밥&quot;]), instructions: Optional([&quot;냉장고에서 밥을 꺼내 놓는다.&quot;, &quot;밥 위에 간장과 식용유를 두르고 잘 섞는다.&quot;, &quot;밥을 냄비에 담고 끓인다.&quot;, &quot;밥이 익으면 소금과 후추로 간을 맞춘다.&quot;]), difficulty: Optional(&quot;Easy&quot;), estimatedTime: Optional(10))&lt;/code&gt;&lt;/pre&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;350&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BEvzZ/dJMcagEABwa/UzY3KonTijSmIUIG9DSFp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BEvzZ/dJMcagEABwa/UzY3KonTijSmIUIG9DSFp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BEvzZ/dJMcagEABwa/UzY3KonTijSmIUIG9DSFp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBEvzZ%2FdJMcagEABwa%2FUzY3KonTijSmIUIG9DSFp0%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;350&quot; height=&quot;352&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;냉장고에 밥이있어도 밥을 없다고 인식하고 필요한 재료라고 추가하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또다른 결과로는 냉장고에 없는 재료를 마음대로 추가해서 작성하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;기대하지않은 결과가 발생&lt;/span&gt;합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때 질문에 좀더 특수화된 도구를 추가할 수 있습니다.(혹은 프롬프트를 더 자세하고 상세하게 입력해도 가능합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tool프로토콜을 준수해서 정의해줄 수 있습니다.&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;&amp;nbsp;Tool&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI모델이 사용자 요청분석후 답변 작성대신 요청해결을 위해서 도구가 필요하다고 판단하는 과정입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAvmB5/dJMcai3pbZ9/okFDAzvvB06fGVxEaUMYUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAvmB5/dJMcai3pbZ9/okFDAzvvB06fGVxEaUMYUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAvmB5/dJMcai3pbZ9/okFDAzvvB06fGVxEaUMYUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAvmB5%2FdJMcai3pbZ9%2FokFDAzvvB06fGVxEaUMYUK%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;1726&quot; height=&quot;1032&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;1032&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 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;모델: 정의된 tool 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구: 냉장고에 있는 재료반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델: 요리 추천 및 부족한 재료 파악&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구를 만들어서 넣어주면 필요할때 가져다 쓴다라고 했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 도구가 호출이 항상되지 않더라구요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 변경하고 조건을 변경해서 적용한 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771987001720&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let instructions = &quot;&quot;&quot;
    당신은 요리 레시피를 추천해주는 전문 셰프 어시스턴트입니다.

    반드시 지켜야 할 규칙:
    1. 레시피 추천 전 반드시 get_ingredients 도구를 호출하여 사용 가능한 재료를 먼저 확인하세요.
    2. get_ingredients 도구가 반환한 재료만 사용합니다.
    3. 요리 이름(title)에 반드시 실제 요리 이름을 사용하세요 (예: 감자볶음, 순두부찌개, 햄볶음밥).
    4. 조리 단계는 초보자도 따라할 수 있도록 구체적으로 설명합니다.
    5. 한국어로 응답합니다.
    6. 만들 수 있는 요리가 없다면 현재 재료로는 만들 수 없다고 말해줍니다.
    &quot;&quot;&quot;


let prompt = &quot;사용 가능한 재료를 먼저 확인한 후, 그 재료들로만 만들 수 있는 요리를 하나 추천해줘&quot;


@Generable(description: &quot;식재료&quot;)
struct Ingredient: Hashable {
    let name: String
}

@Generable(description: &quot;AI가 제안할 결과 레시피&quot;)
struct Recipe {
    @Guide(description: &quot;실제 요리 이름을 사용. 예: 감자볶음, 순두부찌개, 햄볶음밥&quot;)
    let title: String
    @Guide(description: &quot;요리에 필요한 재료&quot;)
    let ingredients: [String]
    @Guide(description: &quot;조리 단계 목록. get_ingredients 도구로 확인한 재료만 사용&quot;)
    let instructions: [String]
    @Guide(description: &quot;쉬움, 보통, 어려움 중 하나 선택&quot;)
    let difficulty: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 정의와 프롬프트를 다시 정의했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답으로 사용할 구조체타입들도 더 상세하게 정의했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1771987061694&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FridgeIngredientsTool: Tool {
    var name: String { &quot;get_ingredients&quot; }
    var description: String {
        &quot;사용 가능한 재료 목록을 조회합니다. 레시피 추천 전 반드시 먼저 호출해야 하며, 이 도구가 반환한 재료만 레시피에 사용할 수 있습니다.&quot;
    }

    // 앱의 재료 저장소를 참조
    var ingredients: Set&amp;lt;String&amp;gt;
    
    init(ingredients: [Ingredient]) {
        self.ingredients = Set(ingredients.map { $0.name })
    }

    @Generable
    struct Arguments {
        @Guide(description: &quot;레시피로 만들 요리&quot;)
        let food: String
    }

    // 언어 모델은 이 도구를 활용하고자 할 때 이 메서드를 호출합니다.
    func call(arguments: Arguments) async throws -&amp;gt; String {
        let list = ingredients.map { &quot;\($0)&quot; }.joined(separator: &quot;,&quot;)
        print(&quot; &quot;, #function, &quot;\nlist&quot;, arguments)
        return &quot;&quot;&quot;
            사용 가능한 재료 목록 (이 재료만 레시피에 사용 가능):
            \(list)

            위 목록에 없는 재료는 레시피에 포함하지 마세요.
            &quot;&quot;&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구는 위와 같이만들었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정의를해도 없는 재료를 넣는과정이있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구가 호출되면 정상적으로 정의된 재료내에서 요리를하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구가 호출되지 않으면 없는재료를 추가해서 만드는것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;써본결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;지침과 프롬프트를 잘 다듬어서 테스트하면서 기대하는 대답이 나오는지 확인이&lt;/b&gt;&lt;/span&gt; 필요한것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;도구가 호출되지 않는다면 Arguments 가이드와 설명을 좀더 다듬어 보는걸 추천&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정시 &quot;절대 하지마세요&quot;, &quot;주의&quot;와 같이 강제로 부정적인 느낌의 명령은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple FoundationModels의 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;안전 가드레일(guardrail)이 발동되서 에러를 발생&lt;/span&gt;&lt;/b&gt;시킬 수 있으니 조심해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;Arguments&lt;/span&gt;&lt;/b&gt;의 정의에 따라서도 응답의 질이 달라지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;call 함수&lt;/span&gt;&lt;/b&gt;의 필터를 통해서도 질이 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 적용한 동작흐름은 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;모델지침으로 세션 생성&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세션 prewarm&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;세션에 프롬프트 입력&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;AI가 필요한 도구선택후 호출 (Argument로 AI가 선택한 요리 값 전달)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;call함수에서 현재 냉장고(뷰모델 배열 데이터)에 있는 재료들 반환 및 추가적인 프롬프트 적용&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;도구에서 반환받은 재료를 바탕으로 Recipe타입에 맞게 모델화&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;stream으로 Recipe모델 받아옴&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;⬇️&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;뷰로 노출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&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/4il0u/dJMcad16qhQ/OWP83w05fVE13GD8ahx7dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4il0u/dJMcad16qhQ/OWP83w05fVE13GD8ahx7dK/img.png&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;441&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.5789%; margin-right: 10px;&quot; data-widthpercent=&quot;36.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4il0u/dJMcad16qhQ/OWP83w05fVE13GD8ahx7dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4il0u%2FdJMcad16qhQ%2FOWP83w05fVE13GD8ahx7dK%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;328&quot; height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qeDYB/dJMcafZW9KI/MLrFizWBspBvMRtnK8J3H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qeDYB/dJMcafZW9KI/MLrFizWBspBvMRtnK8J3H0/img.png&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;406&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.5162%; margin-right: 10px;&quot; data-widthpercent=&quot;31.24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qeDYB/dJMcafZW9KI/MLrFizWBspBvMRtnK8J3H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqeDYB%2FdJMcafZW9KI%2FMLrFizWBspBvMRtnK8J3H0%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;259&quot; height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kDBWt/dJMcabcbvhz/nKu6PtTKOjNZhzQdoqKMf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kDBWt/dJMcabcbvhz/nKu6PtTKOjNZhzQdoqKMf0/img.png&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;512&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.5794%;&quot; data-widthpercent=&quot;32.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kDBWt/dJMcabcbvhz/nKu6PtTKOjNZhzQdoqKMf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkDBWt%2FdJMcabcbvhz%2FnKu6PtTKOjNZhzQdoqKMf0%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;338&quot; height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스없는 순두부찌개를 만들어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만능소스를 추가해줬더니 더맛잇게 만드네요  &lt;br /&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;잘다듬어서 Tool로 정의한 도구가 정상적으로 호출되고 가지고있는 재료들만으로 요리를 만들지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔씩은 구조가 정확하지않아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;decodingFailure(FoundationModels.LanguageModelSession.GenerationError.Context(debugDescription: &quot;Failed to extract content&quot;, underlyingErrors: []))&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러를 반환하기도합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 재실행하면 정상적인 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(더 다듬어서 에러도 발생하지 않도록 해야합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 실행해봅시다 ~&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;374&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dubOgo/dJMcag5E0W0/Ppmh2kKeJU4AknH4BxUGb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dubOgo/dJMcag5E0W0/Ppmh2kKeJU4AknH4BxUGb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dubOgo/dJMcag5E0W0/Ppmh2kKeJU4AknH4BxUGb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdubOgo%2FdJMcag5E0W0%2FPpmh2kKeJU4AknH4BxUGb0%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;374&quot; height=&quot;202&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FmRK9/dJMcagR7StC/qkNa9DphC1HBPM68NaQkik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FmRK9/dJMcagR7StC/qkNa9DphC1HBPM68NaQkik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FmRK9/dJMcagR7StC/qkNa9DphC1HBPM68NaQkik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFmRK9%2FdJMcagR7StC%2FqkNa9DphC1HBPM68NaQkik%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;347&quot; height=&quot;529&quot; data-origin-width=&quot;347&quot; data-origin-height=&quot;529&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 data-ke-size=&quot;size16&quot;&gt;또한 김치가 없지만 김치찌개를 추천하고 김치를 사용해버립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre id=&quot;code_1772004884261&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  call(arguments:) 
list Arguments(food: &quot;김치찌개&quot;)
  call(arguments:) 
list Arguments(food: &quot;순두부김밥&quot;)
  call(arguments:) 
list Arguments(food: &quot;김밥&quot;)
  call(arguments:) 
list Arguments(food: &quot;김밥&quot;)
content: PartiallyGenerated(id: FoundationModels.GenerationID(value: &quot;E9D6948E-1AF8-400D-88EA-D33014AB36E3&quot;), title: Optional(&quot;김밥&quot;), ingredients: Optional([&quot;&quot;]), instructions: nil, difficulty: nil)
content: PartiallyGenerated(id: FoundationModels.GenerationID(value: &quot;E9D6948E-1AF8-400D-88EA-D33014AB36E3&quot;), title: Optional(&quot;김밥&quot;), ingredients: Optional([&quot;밥&quot;, &quot;김&quot;]), instructions: Optional([&quot;&quot;]), difficulty: nil)
content: PartiallyGenerated(id: FoundationModels.GenerationID(value: &quot;E9D6948E-1AF8-400D-88EA-D33014AB36E3&quot;), title: Optional(&quot;김밥&quot;), ingredients: Optional([&quot;밥&quot;, &quot;김&quot;]), instructions: Optional([&quot;밥을 김에 말아주세요.&quot;, &quot;햄&quot;]), difficulty: nil)
content: PartiallyGenerated(id: FoundationModels.GenerationID(value: &quot;E9D6948E-1AF8-400D-88EA-D33014AB36E3&quot;), title: Optional(&quot;김밥&quot;), ingredients: Optional([&quot;밥&quot;, &quot;김&quot;]), instructions: Optional([&quot;밥을 김에 말아주세요.&quot;, &quot;햄을 넣어주세요.&quot;]), difficulty: Optional(&quot;&quot;))
content: PartiallyGenerated(id: FoundationModels.GenerationID(value: &quot;E9D6948E-1AF8-400D-88EA-D33014AB36E3&quot;), title: Optional(&quot;김밥&quot;), ingredients: Optional([&quot;밥&quot;, &quot;김&quot;]), instructions: Optional([&quot;밥을 김에 말아주세요.&quot;, &quot;햄을 넣어주세요.&quot;]), difficulty: Optional(&quot;쉬움&quot;))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 김밥인데 왜 하나는 건너뛰고 하나는 되는건지 궁금해서 디버깅해보니&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MbbNE/dJMcafMsTRv/jsFStTNSgWzA0ef7CCvvik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MbbNE/dJMcafMsTRv/jsFStTNSgWzA0ef7CCvvik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MbbNE/dJMcafMsTRv/jsFStTNSgWzA0ef7CCvvik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMbbNE%2FdJMcafMsTRv%2FjsFStTNSgWzA0ef7CCvvik%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;1174&quot; height=&quot;139&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;139&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 같은 김치찌개여도 필요하다고 생각하는 재료를 다르게 생각합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그와중에 김밥에 김과 김치를 사용하는군요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 성능이 의심스럽습니다... &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;정리하면 FoundationModel을 사용하기위해서 아래는 기본적으로 필요합니다.&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;LLM&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LanguageModelSession의 지침 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Tool 정의(Arguments, call함수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 응답 모델로 사용할 Generable과 Guide정의&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;- 프롬프트 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- response방식 선택(stream, 단일)&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;간단하게 FoundationModel을 사용해봤는데요&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;gt; 프롬프트와 지침으로 도구사용을 유도할 수 있도록 잘 작성해야할 것 같아요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다른 데이터로 명령을 수행함 -&amp;gt; ai가 상상해서 행동하지않도록 좀더 자세한 예시와 명령을 해야할 것 같아요&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 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>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/255</guid>
      <comments>https://nsios.tistory.com/255#entry255comment</comments>
      <pubDate>Thu, 26 Feb 2026 21:47:51 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Core Image Filter</title>
      <link>https://nsios.tistory.com/254</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에 필터를 적용하는 작업을 해볼겁니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core Image를 사용합니다.&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;br /&gt;(이건 UIKit에서도 제공해서 적용하긴쉽긴하지만 커스텀하기엔 더 low level인 Core Image 좋습니다.)&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;Core Image란?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs3Gbe/dJMcaaYo2qk/zd9qmWtWWRnqe1cKMVUJdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs3Gbe/dJMcaaYo2qk/zd9qmWtWWRnqe1cKMVUJdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs3Gbe/dJMcaaYo2qk/zd9qmWtWWRnqe1cKMVUJdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs3Gbe%2FdJMcaaYo2qk%2Fzd9qmWtWWRnqe1cKMVUJdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;519&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core&amp;nbsp;Image는&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;정지&amp;nbsp;영상&amp;nbsp;및&amp;nbsp;동영상에&amp;nbsp;대한&amp;nbsp;실시간에&amp;nbsp;가까운&amp;nbsp;처리를&amp;nbsp;제공하도록&amp;nbsp;설계된&amp;nbsp;이미지&amp;nbsp;처리&amp;nbsp;및&amp;nbsp;분석&amp;nbsp;기술&lt;/b&gt;&lt;/span&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core&amp;nbsp;Graphics,&amp;nbsp;Core&amp;nbsp;Video,&amp;nbsp;Image&amp;nbsp;I/O&amp;nbsp;프레임워크의&amp;nbsp;이미지&amp;nbsp;데이터&amp;nbsp;유형을&amp;nbsp;기반으로&amp;nbsp;작동하며,&amp;nbsp;GPU&amp;nbsp;또는&amp;nbsp;CPU&amp;nbsp;렌더링&amp;nbsp;경로를&amp;nbsp;사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core&amp;nbsp;Image는&amp;nbsp;사용하기&amp;nbsp;쉬운&amp;nbsp;애플리케이션&amp;nbsp;프로그래밍&amp;nbsp;인터페이스(API)를&amp;nbsp;제공함으로써&amp;nbsp;저수준&amp;nbsp;그래픽&amp;nbsp;처리의&amp;nbsp;세부&amp;nbsp;사항을&amp;nbsp;숨깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU의 성능을 활용하기 위해 OpenGL, OpenGL ES 또는 Metal의 세부 사항을 알 필요도 없으며, 멀티코어 처리의 이점을 얻기 위해 Grand Central Dispatch(GCD)에 대해 알 필요도 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Core Image가 세부 사항을 처리해 줍니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 사용하게될 CIImage는 이미지가 아니라 이미지를 생성하는데 필요한 정보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769416847088&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;About Core Image&quot; data-og-description=&quot;About Core Image Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either &quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&quot; data-og-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_intro/ci_intro.html#//apple_ref/doc/uid/TP30001185&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;About Core Image&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;About Core Image Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either&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;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769416910264&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;Core Image Filter Reference&quot; data-og-description=&quot;Core Image Filter Reference&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&quot; data-og-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBoxBlur&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;Core Image Filter Reference&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Core Image Filter Reference&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;p data-ke-size=&quot;size16&quot;&gt;필터 정보나 Core Image 가이드는 여기서 확인하세요!&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;br /&gt;CIFilter이란..?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CIImage &amp;rarr; CIFilter &amp;rarr; output CIImage &amp;rarr; CIFilter &amp;rarr; output CIImage &amp;rarr; &amp;hellip; 방식으로&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CIImage에 효과를 적용하는데 사용하는 타입입니다.&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;2번째링크를 눌러서 필터종류중에 블러쪽을 보면 이렇게생겼습니다.&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/b0jtam/dJMcaajNswI/2sPZRDSGYhpzLxEbkgRKk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0jtam/dJMcaajNswI/2sPZRDSGYhpzLxEbkgRKk0/img.png&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;606&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.1347%; margin-right: 10px;&quot; data-widthpercent=&quot;45.67&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0jtam/dJMcaajNswI/2sPZRDSGYhpzLxEbkgRKk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0jtam%2FdJMcaajNswI%2F2sPZRDSGYhpzLxEbkgRKk0%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;498&quot; height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUzPOj/dJMcafMb0V2/qXkiTErkbvEXwj9IqJEynk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUzPOj/dJMcafMb0V2/qXkiTErkbvEXwj9IqJEynk/img.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;1440&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;54.33&quot; style=&quot;width: 53.7025%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUzPOj/dJMcafMb0V2/qXkiTErkbvEXwj9IqJEynk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUzPOj%2FdJMcafMb0V2%2FqXkiTErkbvEXwj9IqJEynk%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;1408&quot; height=&quot;1440&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;CIFilter를 만드는 방법은 2가지가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1769417112830&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import CoreImage.CIFilterBuiltins

func applyBlur(originImage: CIImage) {
    let blurFilter = CIFilter.boxBlur()
    blurFilter.inputImage = originImage
    blurFilter.radius = 10
}

func applyBlur2(originImage: CIImage) {
    let filter = CIFilter(name: &quot;CIBoxBlur&quot;)
    filter?.setValue(originImage, forKey: kCIInputImageKey) // 적용할 이미지
    filter?.setValue(10, forKey: kCIInputRadiusKey) // 강도
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;import CoreImage.CIFilterBuiltins&lt;/b&gt;를 통해 CIFilter의 타입값으로 사용하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 직접 필터 문자열을 적용해서 kCI 상수값을 사용하는방법&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBUl2O/dJMcacBVpL6/NnGi5smJkk36dSTCc5rKr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBUl2O/dJMcacBVpL6/NnGi5smJkk36dSTCc5rKr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBUl2O/dJMcacBVpL6/NnGi5smJkk36dSTCc5rKr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBUl2O%2FdJMcacBVpL6%2FNnGi5smJkk36dSTCc5rKr0%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;691&quot; height=&quot;259&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 똑같으니 취향에 맞게 사용하면 좋을거 같아요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 여러 필터를 적용하고 싶다면??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체이닝을 통해서 적용 하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNBfv9/dJMcahb8PJT/wfKUjDys9J01cSP5kMYdE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNBfv9/dJMcahb8PJT/wfKUjDys9J01cSP5kMYdE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNBfv9/dJMcahb8PJT/wfKUjDys9J01cSP5kMYdE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNBfv9%2FdJMcahb8PJT%2FwfKUjDys9J01cSP5kMYdE1%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;2210&quot; height=&quot;1560&quot; data-origin-width=&quot;2210&quot; data-origin-height=&quot;1560&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;Core&amp;nbsp;Image&amp;nbsp;필터는&amp;nbsp;출력&amp;nbsp;CIImage&amp;nbsp;객체를&amp;nbsp;생성하므로,&amp;nbsp;이&amp;nbsp;객체를&amp;nbsp;다른&amp;nbsp;필터의&amp;nbsp;입력으로&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를&amp;nbsp;들어,&amp;nbsp;그림&amp;nbsp;1-1에&amp;nbsp;표시된&amp;nbsp;필터&amp;nbsp;시퀀스는&amp;nbsp;이미지에&amp;nbsp;색상&amp;nbsp;효과를&amp;nbsp;적용한&amp;nbsp;다음,&amp;nbsp;광채&amp;nbsp;효과를&amp;nbsp;추가하고,&amp;nbsp;마지막으로&amp;nbsp;결과물에서&amp;nbsp;일부&amp;nbsp;영역을&amp;nbsp;잘라냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Core&amp;nbsp;Image는&amp;nbsp;이와&amp;nbsp;같은&amp;nbsp;필터&amp;nbsp;체인의&amp;nbsp;적용을&amp;nbsp;최적화하여&amp;nbsp;결과를&amp;nbsp;빠르고&amp;nbsp;효율적으로&amp;nbsp;렌더링합니다.&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체인&amp;nbsp;내&amp;nbsp;각&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;CIImage&amp;nbsp;객체는&amp;nbsp;완전히&amp;nbsp;렌더링된&amp;nbsp;이미지가&amp;nbsp;아니라,&amp;nbsp;단순히&amp;nbsp;렌더링을&amp;nbsp;위한&amp;nbsp;&quot;레시피&quot;&lt;/b&gt;&lt;/span&gt;에&amp;nbsp;불과합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core&amp;nbsp;Image는&amp;nbsp;각&amp;nbsp;필터를&amp;nbsp;개별적으로&amp;nbsp;실행하여&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;중간&amp;nbsp;픽셀&amp;nbsp;버퍼를&amp;nbsp;렌더링하는&amp;nbsp;데&amp;nbsp;시간과&amp;nbsp;메모리를&amp;nbsp;낭비할&amp;nbsp;필요가&amp;nbsp;없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신&amp;nbsp;Core&amp;nbsp;Image는&amp;nbsp;필터를&amp;nbsp;단일&amp;nbsp;작업으로&amp;nbsp;결합하며,&amp;nbsp;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;다른&amp;nbsp;순서로&amp;nbsp;적용해도&amp;nbsp;동일한&amp;nbsp;결과를&amp;nbsp;더&amp;nbsp;효율적으로&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있다면&amp;nbsp;필터의&amp;nbsp;순서를&amp;nbsp;재구성할&amp;nbsp;수도&amp;nbsp;있습니다&lt;/b&gt;&lt;/span&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림&amp;nbsp;1-2는&amp;nbsp;그림&amp;nbsp;1-1의&amp;nbsp;예시&amp;nbsp;필터&amp;nbsp;체인을&amp;nbsp;더&amp;nbsp;정확하게&amp;nbsp;표현한&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;1-2에서&amp;nbsp;크롭&amp;nbsp;작업이&amp;nbsp;마지막에서&amp;nbsp;첫&amp;nbsp;번째로&amp;nbsp;이동했음을&amp;nbsp;확인하십시오.&amp;nbsp;이&amp;nbsp;필터는&amp;nbsp;원본&amp;nbsp;이미지의&amp;nbsp;넓은&amp;nbsp;영역이&amp;nbsp;최종&amp;nbsp;출력에서&amp;nbsp;잘려나가는&amp;nbsp;결과를&amp;nbsp;초래합니다.&amp;nbsp;따라서&amp;nbsp;해당&amp;nbsp;픽셀에는&amp;nbsp;색상&amp;nbsp;및&amp;nbsp;선명도&amp;nbsp;필터를&amp;nbsp;적용할&amp;nbsp;필요가&amp;nbsp;없습니다.&amp;nbsp;크롭&amp;nbsp;작업을&amp;nbsp;먼저&amp;nbsp;수행함으로써&amp;nbsp;Core&amp;nbsp;Image는&amp;nbsp;비용이&amp;nbsp;많이&amp;nbsp;드는&amp;nbsp;이미지&amp;nbsp;처리&amp;nbsp;작업이&amp;nbsp;최종&amp;nbsp;출력에&amp;nbsp;표시될&amp;nbsp;픽셀에만&amp;nbsp;적용되도록&amp;nbsp;보장합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&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;모든영역을 필터처리하는 연산이 비싸기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롭을 먼저 적용하고 필터처리해주는 작업을 개발자가 신경쓰지않아도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;비용이 적은방향으로 자동으로 최적화가 이뤄진다고합니다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 영역에 필터를 적용해보는 연습을위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블러필터를 구현해보고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 반만 크롭해서 적용후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비교를위해서 가운데에 선을 그려보는 구현까지&lt;/p&gt;
&lt;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_1769496942643&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    
    private func applyBlur(image: UIImage) -&amp;gt; UIImage {
        /// 기기 사이즈에 대응하는 이미지 가로길이
        let imageWidth: CGFloat = UIDevice.screenWidth
        /// 기기 사이즈에 대응하는 이미지 세로길이
        let imageHeight: CGFloat = imageWidth * image.size.height / image.size.width
        /// 이미지뷰의 사이즈
        let imageViewSize: CGSize = .init(width: imageWidth, height: imageHeight)
        /// 이미지 해상도
        let imageSize: CGSize = image.size
        let render = UIGraphicsImageRenderer(size: .init(width: imageWidth, height: imageHeight))
        
        let lineWidth: CGFloat = 3
        let lineColor = CGColor(red: 1, green: 0, blue: 0, alpha: 0.6)
        
        let newImage = render.image { context in
            image.draw(in: .init(origin: .zero, size: imageViewSize))
            /// 가로 반만 필터적용
            let halfWidthPixelRect: CGRect = .init(
                origin: .zero,
                size: .init(width: imageSize.width/2, height: imageSize.height)
            )
            let cropImage = blurWithCrop(
                image: image,
                cropPixel: halfWidthPixelRect
            )
            cropImage.draw(
                in: .init(
                    origin: .zero,
                    size: .init(width: imageWidth/2, height: imageHeight)
                )
            )
            
            // 필터적용 구분선
            context.cgContext.setStrokeColor(lineColor)
            context.cgContext.setLineWidth(lineWidth)
            context.cgContext.setLineDash(phase: 1, lengths: [10,10])
            context.cgContext.move(to: .init(x: imageWidth / 2, y: 0))
            context.cgContext.addLine(to: .init(x: imageWidth / 2, y: imageHeight))
            context.cgContext.strokePath()
        }
        
        return newImage
    }
    
    private func blurWithCrop(image: UIImage, cropPixel cropRect: CGRect) -&amp;gt; UIImage {
        if let cropImage = image.cgImage?.cropping(to: cropRect) {
            let blurImage = filterFactory.applyBlur(originImage: CIImage(cgImage: cropImage))
            return blurImage
        }
        return image
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769497017584&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ImageFilter.swift 

final class ImageFilter {
    let ciContext: CIContext = .init()
    
    func applyBlur(originImage: CIImage) -&amp;gt; UIImage {
        let blurFilter = CIFilter.boxBlur()
        blurFilter.inputImage = originImage
        blurFilter.radius = 10
        
        if let ciImage = blurFilter.outputImage,
           let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
            return UIImage(cgImage: cgImage)
        }
        
        return UIImage(ciImage: originImage)
    }
}&lt;/code&gt;&lt;/pre&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/djyhhI/dJMcabXjAYm/I3fLplwfqTYBLxZQwlxGL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djyhhI/dJMcabXjAYm/I3fLplwfqTYBLxZQwlxGL0/img.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;880&quot; data-is-animation=&quot;false&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/djyhhI/dJMcabXjAYm/I3fLplwfqTYBLxZQwlxGL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjyhhI%2FdJMcabXjAYm%2FI3fLplwfqTYBLxZQwlxGL0%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;433&quot; height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCyhyA/dJMb99LW3yI/HsE8VjCyp2LzRZnCFJ7qX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCyhyA/dJMb99LW3yI/HsE8VjCyp2LzRZnCFJ7qX1/img.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;880&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCyhyA/dJMb99LW3yI/HsE8VjCyp2LzRZnCFJ7qX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCyhyA%2FdJMb99LW3yI%2FHsE8VjCyp2LzRZnCFJ7qX1%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;433&quot; height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본과 필터를 적용한 이미지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘적용되는게 보이네요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 모자이크필터와 색상필터를 구현해서 적용해볼게요&lt;/p&gt;
&lt;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/MAK8n/dJMcab34RoP/kUgnmMtNCRsyD4DkXgYQFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MAK8n/dJMcab34RoP/kUgnmMtNCRsyD4DkXgYQFK/img.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;880&quot; data-is-animation=&quot;false&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/MAK8n/dJMcab34RoP/kUgnmMtNCRsyD4DkXgYQFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMAK8n%2FdJMcab34RoP%2FkUgnmMtNCRsyD4DkXgYQFK%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;433&quot; height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ul0NR/dJMcab34RwD/T23NfuDWkbeCx6CFr1nrv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ul0NR/dJMcab34RwD/T23NfuDWkbeCx6CFr1nrv1/img.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;880&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ul0NR/dJMcab34RwD/T23NfuDWkbeCx6CFr1nrv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ful0NR%2FdJMcab34RwD%2FT23NfuDWkbeCx6CFr1nrv1%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;433&quot; height=&quot;880&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;
&lt;pre id=&quot;code_1769499612464&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ViewModel.swift

@Observable
final class ViewModelOnlyFilter {
    enum FilterOption {
        case sepia
        case mosaic
        case blur
    }
    
    var maskImages: [UIImage] = []
    private var images: [UIImage] = []
   
    private let filterFactory = ImageFilter()
    
    init() {
        setupImage()
        maskImages = images.map {
            // 원하는 필터적용부분
            applyFilter(image: $0, filter: .blur)
        }
    }
    
    private func setupImage() {
        self.images = [
            UIImage(resource: ._8),
            UIImage(resource: ._10),
            UIImage(resource: .이미지6),
            UIImage(resource: .이미지1),
            UIImage(resource: .이미지2),
        ]
    }
    
    private func applyFilter(image: UIImage, filter: FilterOption) -&amp;gt; UIImage {
        /// 기기 사이즈에 대응하는 이미지 가로길이
        let imageWidth: CGFloat = UIDevice.screenWidth
        /// 기기 사이즈에 대응하는 이미지 세로길이
        let imageHeight: CGFloat = imageWidth * image.size.height / image.size.width
        /// 이미지뷰의 사이즈
        let imageViewSize: CGSize = .init(width: imageWidth, height: imageHeight)
        /// 이미지 해상도
        let imageSize: CGSize = image.size
        let render = UIGraphicsImageRenderer(size: .init(width: imageWidth, height: imageHeight))
        
        let lineWidth: CGFloat = 3
        let lineColor = CGColor(red: 1, green: 0, blue: 0, alpha: 0.6)
        
        /// 가로 반만 필터적용
        let halfWidthPixelRect: CGRect = .init(
            origin: .zero,
            size: .init(width: imageSize.width/2, height: imageSize.height)
        )
        let cropImage: UIImage = applyFilterWithCrop(
            image: image,
            cropPixel: halfWidthPixelRect,
            filter: filter
        )
        let newImage = render.image { context in
            image.draw(in: .init(origin: .zero, size: imageViewSize))
            cropImage.draw(
                in: .init(
                    origin: .zero,
                    size: .init(width: imageWidth/2, height: imageHeight)
                )
            )
            // 중앙 세로선 그리기
            context.cgContext.setStrokeColor(lineColor)
            context.cgContext.setLineWidth(lineWidth)
            context.cgContext.setLineDash(phase: 1, lengths: [10,10])
            context.cgContext.move(to: .init(x: imageWidth / 2, y: 0))
            context.cgContext.addLine(to: .init(x: imageWidth / 2, y: imageHeight))
            context.cgContext.strokePath()
        }
        
        return newImage
    }
    
    private func applyFilterWithCrop(
        image: UIImage,
        cropPixel cropRect: CGRect,
        filter: FilterOption
    ) -&amp;gt; UIImage {
        /// 필요영역만 크롭후 필터적용해서 그려주는 방법
        if let cropImage = image.cgImage?.cropping(to: cropRect) {
            /**
             UIKit, CoreImage 좌표다름 주의
             cgContext이용
             해상도는 원본이미지 Pixel사이즈기준으로 크롭해야해서 해상도 변경전 rect으로해야함 주의
             그릴땐 해상도 변경후 테두리좌표에 그려야함 주의
             */
            switch filter {
            case .sepia:
                return filterFactory.applySepiaTone(image: CIImage(cgImage: cropImage))
            case .mosaic:
                return filterFactory.applyPixelate(image: CIImage(cgImage: cropImage))
            case .blur:
                return filterFactory.applyBlur(image: CIImage(cgImage: cropImage))
            }
        }
        return image
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1769499700058&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ImageFilter.swift

final class ImageFilter {
    let ciContext: CIContext = .init()
    
    func applySepiaTone(image: CIImage) -&amp;gt; UIImage {
        let filter = CIFilter.sepiaTone()
        filter.inputImage = image
        filter.intensity = 0.8
        
        if let ciImage = filter.outputImage,
           let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
            return UIImage(cgImage: cgImage)
        }
        
        return UIImage(ciImage: image)
    }
    
    func applyPixelate(image: CIImage) -&amp;gt; UIImage {
        let pixelateFilter = CIFilter.pixellate()
        pixelateFilter.inputImage = image
        pixelateFilter.scale = 7
        
        if let ciImage = pixelateFilter.outputImage,
           let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
            return UIImage(cgImage: cgImage)
        }
        
        return UIImage(ciImage: image)
    }
    
    func applyBlur(image: CIImage) -&amp;gt; UIImage {
        let blurFilter = CIFilter.boxBlur()
        blurFilter.inputImage = image
        blurFilter.radius = 10
        
        if let ciImage = blurFilter.outputImage,
           let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
            return UIImage(cgImage: cgImage)
        }
        
        return UIImage(ciImage: image)
    }
}&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;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/254</guid>
      <comments>https://nsios.tistory.com/254#entry254comment</comments>
      <pubDate>Tue, 27 Jan 2026 22:46:12 +0900</pubDate>
    </item>
    <item>
      <title>2025 회고</title>
      <link>https://nsios.tistory.com/253</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;25년 한 해를 돌아보면서 정리하는 시간을 가져보려합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;# 월별 기록&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;1월&lt;/b&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일이 적적한 시즌이라 스스로 일을 만들어내면서 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이것저것 어색하거나 성능적인부분을 리팩토링하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;앱테스트시 컨트롤쉽도록 개발했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 유명하다는 하이디라오를 처음가서 먹어봣어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;웨이팅을 몇시간전에 미리걸어두고 갔던 기억이나네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;CRM도구를 연결하는 일을했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유다빈밴드 - 좋지아니한가 밴드합주를 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매쉬업의 파트장을 맡게되서 면접관으로 참가했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;광고 SDK를 전체적으로 변경하는 일을했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매쉬업활동이 본격적으로 시작하면서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 나이에..? 1달에 엠티를 2번이나 가게됬네요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;WWDC 스터디와 모듈화 스터디를 진행했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유다빈밴드 - 사포닌 같은 너 밴드합주를 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;건강검진을 받았어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;전회사보다 검진종목이 많이없어서 아쉬웠어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매쉬업 프로젝트 아이디어회의를 많이 진행했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;5월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;심심해서 춤이나 춰볼까하다가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정말 제니 - like JENNIE 춤을 배우고 추게됬어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이밍 기록을 잘안했었는데 기록을 시작했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;본격적으로 뭔가 재밌고 흥미가 붙어서 자주하게 됬어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;6월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;광고 이슈와 crash 이슈들을 해결했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매쉬업에서 해커톤도 하고 본격적인 프로젝트 개발을 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;WWDC25가 있던 달이에요 영상들을 챙겨보기 시작했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;VisionOCR을 학습하면서 데모앱을 만들었어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;7월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;WWDC가 나오고 새버전이 올라갈 예정임에따라서 미니멈타겟 상향을 고려하고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;변경시 대응해야할 이슈들을 체크했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아이들 - 아픈건 딱 질색이니까 밴드합주를 진행했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;8월&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;주변 친구들이 결혼을 많이하는 시즌이되고 있어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;청모를 가기 시작했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;양양으로 서핑을 갔다왔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이때 인식이 좀 안좋기도했고 성수기 전이라 그런지 사람이 생각보다 없고 휑 했던거같아요(개꿀이였어요)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;9월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;음율 - 피차일반 밴드합주를 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;렛츠락페스티벌을 갔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;한번쯤 도전해보고싶어서 살면서 처음 가본 페스티벌이였어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;DeviceActivity에 대해서 학습하면서 적용해볼수 있는 아이디어를 고민했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;메시지필터에서 이벤트를 기록하려고 여러방법을 시도했지만 실패를 맛봤어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이밍 첫핑크를 풀었어요(1~11단계중 7단계)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;10월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;KISA에서 소명서가 날라와서 대응하는 작업을했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이런부분도 날라오는구나.. 새로운 경험을 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;iOS26 디자인 대응을 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;흑백요리사에 나왔던 포노 부오노, 티엔미미 를 가봤어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;지출이 조금 크지만 후회없이 맛있게 먹은 좋은 경험이였어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;11월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;딥링크를 구현했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;크라임씬 방탈출을 했어요 재밌어서 1번 더 예약해서 1달에 2번에나 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;화담숲예약에 성공해서 단풍이 피크일때 갓다왔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;렛스25에 갔다왔어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;확실히 컨퍼런스들이 죽어가고있는게 느껴졌어요..ㅠ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예전엔 예매하기 힘들었는데 지금은 남기도하고... 오는 사람들이 많이 줄었어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만 적당히 있어서 쾌적한부분도 좋긴해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;30년인생 처음으로 파마를 해봤어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;살면서 한 번 해보고싶어서 큰 변화를 줘봤어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;결과는 망한거같아요... 다들 웃더라구요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;12월&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;모자이크 기능관련 개발을 하고있어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기획과 디자인 모두 주도적으로 참여한 경험이에요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;운전해서 성심당을 가서 빵을 한가득 사왔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;한로로 - 사랑하게 될거야 밴드합주를 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매쉬업에서 첫 송년회를 했어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;준비를 너무잘해줘서 좋은 추억을 남긴거같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;배구경기 관람을 했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이케아를 갔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서산으로 여행을 다녀왔어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이밍 핑크깬지 3개월만에 첫보라를 풀었어요&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;# 커리어 회고&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;도전적인 일&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해보지않은 기술들을 도전하며 회사 서비스내에 어떻게 녹여낼지 고민했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Core Image의 CIFilter의 기본개념과 적용을 할 수 있게됬어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;온디바이스 AI를 적용할 수있는 기술들을 학습했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Family Controls를 학습하고 앱잠금 및 스크린타임 차트를 구현했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;아쉬운 일&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;올해도 회사에서 iOS 영향력이 너무 적다고 느껴졌어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이것 저것 시도해서 영향력을 키워보려했지만 실패했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일이 줄어들다보니 나태해지는 경우가 있었어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;남는시간에 개발이 아니더라도 유익하게 시간을 보내도록&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;성장을 위한 글을 읽거나 강의를 들어야겠어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;# 인생 회고&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;경제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;부동산책을 읽고 뭔가 무섭고 미지의 세계였던 부동산에 입문을 할 수 있게됬어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;더 공부하고 직접 임장을 다니면서 미래에 내집을 고르거나 투자안목을 길러보고 싶어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;돈을 모으면서 세금이 되게 다양하게 많은걸 느끼게 되는거같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;내 돈을 잃지않도록 잘 관리하고 공부해야 할필요성을 많이느꼇어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;너무 방대해서 어떤것부터 해야할지 모르겟지만&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;제일 가까운 주식관련 세금부터 부동산 세금, 세금절약 하는 법을 찾아보려고해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;경제흐름을 파악하기위해 매일 뉴스를 찾아보고 지표들을 확인했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이를 주식과 비교했을때 맞을때도 아닐때도 있었어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;주관적으로 생각하고 뉴스가 뜨기전에 미리 투자할 수 있는 능력이 길러지도록 노력해보려해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;주식을 하면서 자산이 흩어지고 관리하기가 힘들어지는 과정들이 있어서&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자산관리 해보는 습관도 길러보려고해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;취미&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이밍을 한지 몇년이됫는데 올해 제일 많은 성장을 한거같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;올해초만해도 파랑(5단계)였던거같은데 어느덧 꿀 보라(8단계)라는 걸 해보려고 탐내고있어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이밍을 하면서 다치기도했지만 건강해지고 재밋게 운동해서 근육도 생기는거같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;지금까지 크게기록을 하지않았는데 내년부터는 기록을 남겨보려고해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;밴드가 올해에 5곡을 합주했어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유다빈밴드 - 좋지 아니한가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유다빈밴드 - 사포닌 같은 너&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아이들 - 아픈건 딱 질색이니까&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;음율 - 피차일반&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;한로로 - 사랑하게 될거야&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;어렵기도했고 쉽기도했는데 예전에 비해서 의욕이 조금 떨어져서 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;연습량이 줄어든것을&lt;/span&gt;&amp;nbsp;느꼈어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;동기부여라던가 으쌰으쌰 분위기가 없어서 쳐질때도있는데 그래도 열정 있는 사람들이 있어서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다시 마음을 고쳐잡고 열심히 해야할 것같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;미래&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;올해 특히나 찬바람이 많이불었죠(채용시장이 얼어붙고 구조조정도 많은 한해였어요..)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이런 과정을 보면서 회사에 의존된 상태라면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;언제 어떻게될지 모르는데 회사를 벗어나면 스스로 뭘할 수 있지? 라는 생각을 하면서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;개발자를 하다보니 많은 다양한 분야가 있음에도 생각하지 못해서 시야가 좁다는것도 느꼈고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;좀 더 내가 좋아하는것 내가 직접 현금 흐름구조를 만들수 있는것들을 찾아보려고해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;# 2026 목표&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유튜브에서 핑계고 시상식이 화제가 됬는데 저도 보면서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;'무탈하다'라는 말이 정말 좋게 와닿았어서 인상적이였어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;26년을 무탈하게 보내면 좋을 것 같아요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정체된 커리어가 아니라 향상된 커리어를 만들어 가고싶어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;25년 자산을 정리하고 26년 자산을 비교했을때 최소 10% 불어난 상태로 만들고싶어요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;나를 좀 더 알아가고 나를 자주 기록해보려고 해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;새로운 일들에 대해서 겁먹지말고 도전해보고 많은 경험을 해보고싶어요&lt;/span&gt;&lt;/p&gt;</description>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/253</guid>
      <comments>https://nsios.tistory.com/253#entry253comment</comments>
      <pubDate>Wed, 31 Dec 2025 23:13:59 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Vision FaceDetect</title>
      <link>https://nsios.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사진에 있는 얼굴을 인식해서 위치를 특정하는 기능을 해보려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoreImage의 CIDetector와 Vision의 VNDetectFaceRectanglesRequest를 사용하는 2가지 방법이있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS11부터 도입된 후자를 권장하고 더 강력한 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번엔 VNDetectFaceRectanglesRequest를 이용한 코딩을 할예정입니다.&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;VNDetectFaceRectanglesRequest란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에서 얼굴을 찾도록 요청하는 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vision을 쓰다보면 000Reqeust형식을 많이보게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 요청중 하나입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764387035329&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class FaceDetector {
    func detectFaceRectangles(image: UIImage) throws -&amp;gt; [VNFaceObservation]? {
        guard let image = image.cgImage else { return nil }
        let request = VNDetectFaceRectanglesRequest()
        
        request.revision = VNDetectFaceRectanglesRequestRevision3
        
        let handler = VNImageRequestHandler(cgImage: image)
        try handler.perform([request])
        let results = request.results
        return results
    }
}&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;결과값으로 VNFaceObservation을 반환받습니다.&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;VNFaceObservation란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에서 감지하는 얼굴 또는 얼굴 특징 정보입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1764387148287&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let observation = try detector.detectFaceRectangles(image: image)
var boundingBoxes: [CGRect] = []
observation?.forEach {
    print(&quot;description&quot;, $0.description)
    print(&quot;boundingBox&quot;, $0.boundingBox)
    print(&quot;faceCaptureQuality&quot;, $0.faceCaptureQuality)
    print(&quot;confidence&quot;, $0.confidence)
    print(&quot;landmarks&quot;, $0.landmarks)
    print(&quot;pitch&quot;, $0.pitch)
    print(&quot;roll&quot;, $0.roll)
    print(&quot;timeRange&quot;, $0.timeRange)
    print(&quot;uuid&quot;, $0.uuid)
    print(&quot;yaw&quot;, $0.yaw)
    
    boundingBoxes.append($0.boundingBox)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1764387164488&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;description &amp;lt;VNFaceObservation: 0x15ceac800&amp;gt; 484BBF54-1E67-404A-B45B-20BB5A7257BF VNDetectFaceRectanglesRequestRevision3 confidence=0.683225 boundingBox=[0.613351, 0.484306, 0.140112, 0.270106]

boundingBox (0.6133507490158081, 0.4843055307865143, 0.14011242985725403, 0.27010563015937805)

faceCaptureQuality nil

confidence 0.68322533

landmarks nil

pitch Optional(-0.317534)

roll Optional(1.027767)

timeRange CMTimeRange(start: __C.CMTime(value: 0, timescale: 1, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0), duration: __C.CMTime(value: 0, timescale: 1, flags: __C.CMTimeFlags(rawValue: 1), epoch: 0))

uuid 484BBF54-1E67-404A-B45B-20BB5A7257BF

yaw Optional(-0.638136)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;description&lt;/b&gt; - 요약정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;boundingBox&lt;/b&gt; - 감지된 얼굴의 위치와 크기를 나타내는 정규화된 좌표, 얼굴영역표시에 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CGRect좌표값으로 이미지의 상대적인 위치를 나타냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용시 뷰의 좌표계에 맞게 변환해서 사용해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Vision은 왼쪽하단이 원점(0,0)이고 UIKit/SwiftUI는 왼쪽상단이 원점이므로 Y축을 뒤집어서 사용&lt;/b&gt;&lt;/span&gt;해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boundingBox가 (0.6, 0.4, 0.1, 0.2)일때 &amp;lt;&amp;lt;&amp;lt; (x위치, y위치, 가로길이, 세로길이)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 가로길이가 100, 세로길이가 100이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로로 0.6지점인 60포인트부터 길이가 0.1인 10에 얼굴이 인식됫다는 것을 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 60~70 포인트에 얼굴이 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세로는 0.4지점 40부터 세로길이 0.2인 20에 얼굴이 인식됬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 40~60입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 종합하면 (60, 40), (70, 40), (70, 60), (60, 60)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HT6Re/dJMcaajrtTk/BDLmZ52lfFDjTDXFrTd291/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HT6Re/dJMcaajrtTk/BDLmZ52lfFDjTDXFrTd291/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HT6Re/dJMcaajrtTk/BDLmZ52lfFDjTDXFrTd291/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHT6Re%2FdJMcaajrtTk%2FBDLmZ52lfFDjTDXFrTd291%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;525&quot; height=&quot;328&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 돕기위해 그림을 그려봤어요 ㅎ...(그림에서 Y축 반전은 우선 신경쓰지 마세요 이해용입니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoreImage를 이용한 처리에는 좌표를 그대로 써도되서 무관하지만 보통 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;CGImage나 View로 그리기때문에 Y축은 각별히 신경&lt;/b&gt;&lt;/span&gt;써야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764389208035&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func convert(boundingBox: CGRect, to bounds: CGRect) -&amp;gt; CGRect {
        let imageWidth = bounds.width
        let imageHeight = bounds.height
        
        let frame = CGRect(
            origin: CGPoint(
                x: boundingBox.origin.x * imageWidth,
                y: (1 - boundingBox.maxY) * imageHeight
            ),
            size: .init(
                width: boundingBox.width * imageWidth,
                height: boundingBox.height * imageHeight
            )
        )
        
        return frame
    }
    
    // CIImage용
    func ciImageRect(boundingBox: CGRect, to bounds: CGRect) -&amp;gt; CGRect {
        let imageWidth = bounds.width
        let imageHeight = bounds.height
        
        let frame = CGRect(
            origin: CGPoint(
                x: boundingBox.origin.x * imageWidth,
                y: boundingBox.origin.y * imageHeight
            ),
            size: .init(
                width: boundingBox.width * imageWidth,
                height: boundingBox.height * imageHeight
            )
        )
        
        return frame
    }&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;x축은 비례로 좌표를 변환하고 y축은 뒤집어서 비례로 좌표를 변환했습니다.&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;faceCaptureQuality&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감지된 얼굴이 얼마나 품질이 좋은지&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 잘나온 사진 판별가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;face capture quality request시 제공&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;confidence&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감지신뢰도 1에 가까울수록 신뢰성 높음&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;landmarks&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈,코,입,얼굴윤곽선 등 세부얼굴 특징점 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;face landmark request시에만 제공됨&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;pitch&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼굴의 세로 회전각 (x축 기준으로 머리를 위아래로 움직일때 각도)&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;roll&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼굴의 회전정도 (z축)&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;timeRange&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼굴인식이 적용되는 비디오구간 정보(이미지는 존재x)&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;yaw&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼굴의 수평방향 회전 (y축, 고개를 좌우로 돌리는각도)&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;좌표를 계산해서 아래와같이 인식된 얼굴영역에 테두리를 그려볼수있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btyoj5/dJMcafLOVLt/rrMklBr15ntwACKxk5tZcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btyoj5/dJMcafLOVLt/rrMklBr15ntwACKxk5tZcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btyoj5/dJMcafLOVLt/rrMklBr15ntwACKxk5tZcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtyoj5%2FdJMcafLOVLt%2FrrMklBr15ntwACKxk5tZcK%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;367&quot; height=&quot;796&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/252</guid>
      <comments>https://nsios.tistory.com/252#entry252comment</comments>
      <pubDate>Sat, 29 Nov 2025 13:18:19 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Screen Time API(DeviceActivity)</title>
      <link>https://nsios.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 게시물에서 정리하지않은 Screen Time관련 기능중&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeviceActivity를 정리하려합니다.&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;1. DeviceActivity를 사용해서 특정시간에 코드를 실행시키는 방법과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기기 사용시간을 모니터링 하는 방법을 알아볼거에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 마지막으로는 사용시간 데이터로 시각화를 위해 차트화 하는 방법까지 해볼예정입니다.&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;DeviceActivity란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 실행되지않아도 코드를 실행 할 수 있는 방법을 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 기기를 모니터링하고 조건 충족시 자동으로 코드실행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 시간대에 코드실행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 앱을 일정 시간 이상 사용시 이벤트 발생하여 코드실행 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Device Activity Monitor Extension과 Device Activity Report Extension으로 구분됩니다.&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;Device Activity Monitor Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 기기사용을 모니터링 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 조건시 이벤트를 트리거합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File -&amp;gt; New -&amp;gt; Target에서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CBqlM/dJMcaksDC1K/4m9UPBZSCpoweCjzoK1RIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CBqlM/dJMcaksDC1K/4m9UPBZSCpoweCjzoK1RIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CBqlM/dJMcaksDC1K/4m9UPBZSCpoweCjzoK1RIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCBqlM%2FdJMcaksDC1K%2F4m9UPBZSCpoweCjzoK1RIK%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;150&quot; height=&quot;114&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeviceActivityMonitor를 준수해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현할 수 있는 함수는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761547872088&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 활동 시작전에 startMonitoring의 warningTime분 전에 알림 (시작 n분전, 종료 n분전)
override func intervalWillStartWarning(for activity: DeviceActivityName)
override func intervalWillEndWarning(for activity: DeviceActivityName)

/// 장치 활동 간격이 시작 (startMonitoring의 intervalStart 시간에 해당시 호출됨)
override func intervalDidStart(for activity: DeviceActivityName) 
/// 장치 활동 간격이 끝 (startMonitoring의 intervalEnd 시간에 해당시 호출됨)
override func intervalDidEnd(for activity: DeviceActivityName)

/// 활동이 지정한 임계값에 도달할 예정일때 호출됨
override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName)
/// 활동이 지정한 임계값에 도달하면 호출됨
override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수를 실행시키기위해선 DeviceActivityCenter 인스턴스를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deviceActivityCenter.startMonitoring를 호출해서 모니터링을 등록합니다&lt;/p&gt;
&lt;pre id=&quot;code_1761547929767&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let deviceActivityCenter = DeviceActivityCenter()
// 이벤트 이름에 맞는 이벤트 생성
private let events: [DeviceActivityEvent.Name: DeviceActivityEvent] = [
    .encouraged: .init(threshold: DateComponents(minute: 1))
]
var startDate: Date = .now
var startDateComponent: DateComponents { DateComponents(from: startDate) }
var endDate: Date = .now
var endDateComponent: DateComponents { DateComponents(from: endDate) }

func startMonitor() {
    do {
        try deviceActivityCenter.startMonitoring(
            .testName,
            during: .init(
                intervalStart: startDateComponent,
                intervalEnd: endDateComponent,
                repeats: true,
                warningTime: .init(minute: 1) // 1분전
            ),
            events: events
        )
        syncActivityList()
        print(&quot;설정완료&quot;)
    } catch {
        print(error)
    }
}

// MARK: - 이름정의

extension DeviceActivityName {
    static let testName = Self(&quot;testName&quot;)
}

extension DeviceActivityEvent.Name {
    static let encouraged = Self(&quot;encouraged&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 직접 테스트해본결과 start와 end시간 사이에 최소 15분 이상 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15분내 간격일때 너무 짧다고 에러를 발생시킵니다&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;intervalStart값을 넣기위해 DateComponents타입생성시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Calendar&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;current&lt;span&gt;.&lt;/span&gt;dateComponents&lt;span&gt;([.&lt;/span&gt;year&lt;span&gt;, .&lt;/span&gt;month&lt;span&gt;, .&lt;/span&gt;day&lt;span&gt;, .&lt;/span&gt;hour&lt;span&gt;, .&lt;/span&gt;minute&lt;span&gt;, .&lt;/span&gt;second&lt;span&gt;], &lt;/span&gt;from&lt;span&gt;: &lt;/span&gt;date&lt;span&gt;) 이런식으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;년월일시간분초 값이 모두 존재해야 정상적으로 등록되서 이벤트발생합니다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러도 없고 이벤트도 없어서 디버깅하기 어려웠습니다. ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트전엔 반드시 권한을 가져와야합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761549068552&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let authCenter = AuthorizationCenter.shared

func checkAuth() {
    Task {
        do {
            try await authCenter.requestAuthorization(for: .individual)
        } catch {
            print(&quot;Fail: \(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;활성화중인 이벤트 리스트를 보는 방법은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761549189568&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var allActivieString: [String] = []

private func syncActivityList() {
    allActivieString = deviceActivityCenter.activities.map { activity in
        &quot;\(activity.rawValue) &quot; + startToEndTimeString(name: activity)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DMGcp/dJMcagqeOem/A96grmEabR7npKSqTknYk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DMGcp/dJMcagqeOem/A96grmEabR7npKSqTknYk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DMGcp/dJMcagqeOem/A96grmEabR7npKSqTknYk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDMGcp%2FdJMcagqeOem%2FA96grmEabR7npKSqTknYk0%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;283&quot; height=&quot;572&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞 게시글에서 정리한 ManagedSetting을 이용한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정시간에 앱을 차단하는 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761549406982&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class DeviceActivityMonitorExtension: DeviceActivityMonitor {
    let store = ManagedSettingsStore()

    /// 장치 활동 간격이 시작되었음을 나타냅니다
    override func intervalDidStart(for activity: DeviceActivityName) {
        super.intervalDidStart(for: activity)
        
        if let selection = loadSelectedApps() {
            print(&quot;앱 목록 로드성공&quot;)
            blockSelectedApps(selection)
        }
    }

    private func loadSelectedApps() -&amp;gt; FamilyActivitySelection? {
        let userDefaults = UserDefaults(suiteName: &quot;group.test.familyControl&quot;)
        guard let data = userDefaults?.data(forKey: &quot;testKey&quot;) else {
            return nil
        }
        
        do {
            let model = try JSONDecoder().decode(AppModel.self, from: data)
            return model.selection
        } catch {
            print(&quot;앱 목록 로드 실패: \(error)&quot;)
            return nil
        }
    }
    
    private func blockSelectedApps(_ selection: FamilyActivitySelection) {
        // 앱 차단 설정
        store.shield.applications = selection.applicationTokens.isEmpty ?
            nil : selection.applicationTokens
        
        // 카테고리 차단 설정
        store.shield.applicationCategories = selection.categoryTokens.isEmpty
        ? nil
        : .specific(selection.categoryTokens)
        
        // 웹 도메인 차단 설정 (필요한 경우)
        store.shield.webDomains = selection.webDomainTokens.isEmpty
        ? nil
        : selection.webDomainTokens
    }
       
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정시간이 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loadSelectedApps()를 통해서 활동을 선택한 앱목록을 가져와서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blockSelectedApps()함수로 전달해서 선택된 앱들을 차단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터링 중단은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deviceActivityCenter&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stopMonitoring&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앱차단 해제는&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761549551061&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func unblockApps() {
        // 모든 차단 해제
        store.shield.applications = nil
        store.shield.applicationCategories = nil
        store.shield.webDomains = nil
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nil을 넣어주면 해제됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 앱별 사용량을 체크할 수 있는 기능을 봐볼까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Device Activity Report Extension&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 기기 사용 데이터 수집가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 앱을 얼마나 사용했는지, 특정시간대에 얼마나 사용했는지&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1분주기로 업데이트가능&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;File -&amp;gt; New -&amp;gt; Target에서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boSqpO/dJMcaiBABEB/Sm0VHysDfYfMa0cFwht4yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boSqpO/dJMcaiBABEB/Sm0VHysDfYfMa0cFwht4yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boSqpO/dJMcaiBABEB/Sm0VHysDfYfMa0cFwht4yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboSqpO%2FdJMcaiBABEB%2FSm0VHysDfYfMa0cFwht4yK%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;161&quot; height=&quot;119&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 이 기능을 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DeviceActivityReport.Context&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeviceActivity 데이터를 기반으로 그릴 View 유형을 보고서에 알려주는 사용자 지정 가능한 유형입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761549735943&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public extension DeviceActivityReport.Context {
    // If your app initializes a DeviceActivityReport with this context, then the system will use
    // your extension's corresponding DeviceActivityReportScene to render the contents of the
    // report.
    static let totalActivity = Self(&quot;Total Activity&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성하면 기본적으로 생성되있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 타입은 어떤 DeviceActivityReportScene타입을 부를것인지 선택하는 이름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761549864075&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol DeviceActivityReportScene : AppExtensionScene {

    /// The context of the scene.
    ///
    /// When your app creates a `DeviceActivityReport` with this context, the
    /// system uses this scene to render the report's content.
    var context: DeviceActivityReport.Context { 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;기본적으로 context를 작성해야하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 정의한 context가 앱타겟에서 불러올 ReportScene이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761549888595&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct TotalActivityReport: DeviceActivityReportScene {
    // Define which context your scene will represent.
    // DeviceActivityReport호출시 해당 컨텍스트에 해당하는 Report 불러옴
    let context: DeviceActivityReport.Context = .totalActivity  
    
    // Define the custom configuration and the resulting view for this report.
    let content: ([AppUsage]) -&amp;gt; TotalActivityView&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Report를 구현할 수도 있으므로 각 범위에 따라서 다르게 선택할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱타겟에서 사용하는 뷰 예시는 아래와같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761550061994&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct DeviceReportView: View {
    var body: some View {
        VStack {
            Text(&quot;사용량 통계&quot;)
            DeviceActivityReport(context, filter: filter)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeviceActivityReport 뷰타입에 넣어줄 context가 여기에 해당합니다.&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;span data-token-index=&quot;0&quot;&gt;DeviceActivityResults&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;필터링된 장치 활동 결과의 비동기 시퀀스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;for await in 으로 요소에 접근가능합니다. &lt;/span&gt;&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;DeviceActivityData&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS16+&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 디바이스에서 특정 사용자의 활동을 나타내는 핵심 데이터 타입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;주요 속성&amp;nbsp;(Instance Properties)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1&lt;/span&gt;. &lt;span&gt;activitySegments&lt;/span&gt;: 사용자의&amp;nbsp;활동을 세그먼트별로 구분한&amp;nbsp;데이터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2&lt;/span&gt;. &lt;span&gt;device&lt;/span&gt;: 활동&amp;nbsp;보고서와 연관된 디바이스 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3&lt;/span&gt;. &lt;span&gt;lastUpdatedDate&lt;/span&gt;: 시스템이&amp;nbsp;이&amp;nbsp;디바이스의&amp;nbsp;데이터를 마지막으로 업데이트한 날짜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4&lt;/span&gt;. &lt;span&gt;segmentInterval&lt;/span&gt;: 각&amp;nbsp;활동 세그먼트의&amp;nbsp;간격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;5&lt;/span&gt;. &lt;span&gt;user&lt;/span&gt;: 활동 보고서와 연관된 사용자 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 하위 구조체들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;DeviceActivityData&lt;span&gt;.&lt;/span&gt;ActivitySegment&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;간격&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;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;categories&lt;/span&gt;: 활동 세그먼트&amp;nbsp;동안의 카테고리별&amp;nbsp;디바이스 활동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;dateInterval&lt;/span&gt;: 활동 세그먼트의 날짜 간격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;firstPickup&lt;/span&gt;: 활동 세그먼트&amp;nbsp;동안&amp;nbsp;사용자가 처음 디바이스를&amp;nbsp;집어든 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;longestActivity&lt;/span&gt;: 활동 세그먼트 동안 가장&amp;nbsp;긴 활동 세션의 날짜&amp;nbsp;간격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;totalActivityDuration&lt;/span&gt;: 활동 세그먼트 동안의&amp;nbsp;총 활동 시간&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;totalPickupsWithoutApplicationActivity&lt;span&gt;:&amp;nbsp;앱을 사용하지 않고&amp;nbsp;디바이스를&amp;nbsp;집어든&amp;nbsp;횟수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;DeviceActivityData&lt;span&gt;.&lt;/span&gt;Device&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;주요 속성:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;model&lt;/span&gt;: 디바이스&amp;nbsp;모델 (iPhone, iPad, &lt;span&gt;Mac&lt;/span&gt; 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;name&lt;/span&gt;: 사용자가 설정한&amp;nbsp;디바이스&amp;nbsp;이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;DeviceActivityData&lt;span&gt;.&lt;/span&gt;User&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;- &lt;span&gt;appleID&lt;/span&gt;: 사용자의&amp;nbsp;Apple &lt;span&gt;ID&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;nameComponents&lt;span&gt;: 사용자&amp;nbsp;이름&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;role&lt;/span&gt;: 사용자&amp;nbsp;유형 (가족 역할&amp;nbsp;등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;DeviceActivityData&lt;span&gt;.&lt;/span&gt;CategoryActivity&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;앱&amp;nbsp;및 웹 도메인&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;- &lt;span&gt;applications&lt;/span&gt;: 이 카테고리에 기여한 앱 활동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;category&lt;/span&gt;: 활동의 카테고리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;totalActivityDuration&lt;span&gt;:&amp;nbsp;이 카테고리의&amp;nbsp;총 활동 시간&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span&gt;webDomains&lt;/span&gt;: 이 카테고리에 기여한 웹&amp;nbsp;도메인 활동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그 코드를 참고해서 필요한 속성을 골라쓰시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761550435350&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func printLog(data: DeviceActivityResults&amp;lt;DeviceActivityData&amp;gt;) async -&amp;gt; [AppUsage] {
    let formatter = DateComponentsFormatter()
    formatter.allowedUnits = [.day, .hour, .minute, .second]
    formatter.unitsStyle = .abbreviated
    formatter.zeroFormattingBehavior = .dropAll
    let thisDevice = UIDevice.current.model
    
    var appUsages: [AppUsage] = []
    for await value in data {
        print(&quot; device.name&quot;,value.device.name) // 연결된 모든 디바이스 (아이폰, 아이패드) NS, NS iPad
        print(&quot; lastUpdatedDate&quot;,formatter.string(for: value.lastUpdatedDate))
        print(&quot; segmentInterval&quot;, value.segmentInterval) // 측정 간격
        print(&quot; name&quot;, value.user.nameComponents) // 사용자이름 ( given: 남수 family: 김)
        print(&quot; user apple id&quot;, value.user.appleID) // apple id
        print(&quot; user role&quot;, value.user.role) // individual
        
        
        for await activity in value.activitySegments {
            print(&quot; activity dateInterval&quot;, activity.dateInterval) // 측정 간격
            print(&quot; activity firstPickup&quot;, activity.firstPickup) // 처음 폰든시간 9/21 오후 11시2분2초
            print(&quot; activity longestActivity&quot;, activity.longestActivity) // 제일긴 활동시간 Date to Date
            print(&quot; activity totalActivityDuration&quot;, activity.totalActivityDuration) // 3072초
            print(&quot; activity totalPickupsWithoutApplicationActivity&quot;, activity.totalPickupsWithoutApplicationActivity)
            
            for await category in activity.categories {
                print(&quot; category.localizedDisplayName&quot;, category.category.localizedDisplayName) // social, Other, 큰카테고리
                print(&quot; category.debugDescription&quot;, category.category.token.debugDescription) // 128 bytes, 토큰 해독불가
                print(&quot; category.totalActivityDuration&quot;, category.totalActivityDuration) // 1668초, 690초

                for await application in category.applications {
                    print(&quot; app localizedDisplayName&quot;, application.application.localizedDisplayName) // instagram, 카카오톡
                    print(&quot; app bundleIdentifier&quot;, application.application.bundleIdentifier) // com.burbn.instagram. com.iwilab.KakaoTalk
                    print(&quot; app numberOfNotifications&quot;, application.numberOfNotifications) // 0, 10
                    print(&quot; app numberOfPickups&quot;, application.numberOfPickups) // 1, 5
                    print(&quot; app totalActivityDuration&quot;, application.totalActivityDuration) // 1228초, 430초
                    let usage = AppUsage(
                        title: application.application.localizedDisplayName?.lowercased() ?? &quot;&quot;,
                        time: Int(application.totalActivityDuration)
                    )
                    appUsages.append(usage)
                }
                
                for await webDomain in category.webDomains {
                    print(&quot;⚫️web domain&quot;, webDomain.webDomain.domain) // weather.naver.com, naver.com
                    print(&quot;⚫️web totalActivityDuration&quot;, webDomain.totalActivityDuration) // 5초
                    print(&quot;⚫️web debugDescription&quot;, webDomain.webDomain.token.debugDescription) // 128bytes
                }
            }
            
        }
    }
    print(&quot;------------------&quot;)
    return appUsages
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1761550562116&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  device.name Optional(&quot;NS&quot;)
  lastUpdatedDate nil
  segmentInterval daily(during: 2025-09-21 15:00:00 +0000 to 2025-09-22 15:00:00 +0000)
  name Optional(givenName: 남수 familyName: 김 )
  user apple id Optional(&quot;ㅁㅁㅁㅁ@naver.com&quot;)
  user role individual
  activity dateInterval 2025-09-21 15:00:00 +0000 to 2025-09-22 15:00:00 +0000
  activity firstPickup Optional(2025-09-21 23:02:02 +0000)
  activity longestActivity Optional(2025-09-22 01:14:31 +0000 to 2025-09-22 01:24:45 +0000)
  activity totalActivityDuration 3072.8046337366104
  activity totalPickupsWithoutApplicationActivity 2
  category.localizedDisplayName Optional(&quot;Social&quot;)
  category.debugDescription Optional(ActivityCategoryToken(data: 128 bytes: &amp;auml;&amp;eacute;&amp;icirc;/&amp;Ntilde;I&amp;copy;&amp;eacute;jMM&amp;ocirc;
 j7;&amp;ordf;$.&amp;egrave;&amp;yacute;&amp;ocirc;`g&amp;oslash;c&amp;Oslash;~v.&amp;iexcl;&amp;Agrave;0&amp;Otilde;&amp;yen;
 &amp;cent;2&amp;macr;&amp;laquo;&amp;ucirc;^
 S&amp;aring;Rg&amp;Acirc;&amp;AElig;^8&amp;pound;&amp;plusmn;j&amp;oacute;&amp;Auml;&amp;laquo;&amp;Acirc;&amp;egrave;,&amp;middot;&amp;Ucirc;&amp;shy;9&amp;pound;:&amp;Aring;&amp;Auml;&amp;auml;&amp;atilde;CV&amp;ordf;&amp;para;&amp;Ugrave;B&amp;ccedil;&amp;acirc;6d&amp;nbsp;&amp;Aring;&quot;g/))
  category.totalActivityDuration 1668.274539232254
  app localizedDisplayName Optional(&quot;Instagram&quot;)
  app bundleIdentifier Optional(&quot;com.burbn.instagram&quot;)
  app bundleIdentifier Optional(&quot;com.burbn.instagram&quot;)
  app numberOfNotifications 0
  app numberOfPickups 1
  app totalActivityDuration 1228.57754611969
  app localizedDisplayName Optional(&quot;카카오톡&quot;)
  app bundleIdentifier Optional(&quot;com.iwilab.KakaoTalk&quot;)
  app bundleIdentifier Optional(&quot;com.iwilab.KakaoTalk&quot;)
  app numberOfNotifications 10
  app numberOfPickups 5
  app totalActivityDuration 430.60449719429016
  category.localizedDisplayName Optional(&quot;Productivity &amp;amp; Finance&quot;)
  category.debugDescription Optional(ActivityCategoryToken(data: 128 bytes: &amp;auml;&amp;eacute;&amp;icirc;/&amp;Ntilde;I&amp;copy;&amp;eacute;jMM&amp;ocirc;
 j7;&amp;ordf;$.&amp;egrave;&amp;yacute;&amp;ocirc;`g&amp;oslash;c&amp;Oslash;~v.&amp;iexcl;&amp;Agrave;0&amp;Otilde;&amp;yen;
 &amp;cent;2&amp;macr;&amp;laquo;&amp;ucirc;^
 S&amp;aring;Rg&amp;Acirc;&amp;AElig;^8&amp;pound;&amp;plusmn;j&amp;oacute;&amp;Auml;&amp;laquo;&amp;Acirc;&amp;egrave;,&amp;middot;&amp;Ucirc;&amp;shy;9&amp;pound;:&amp;Aring;&amp;Auml;&amp;auml;&amp;atilde;CV&amp;ordf;&amp;para;*L&amp;yacute;&amp;igrave;&amp;Oacute;)&amp;ucirc;ZD&amp;sect;9))
  category.totalActivityDuration 417.86487793922424
  app localizedDisplayName Optional(&quot;토스&quot;)
  app bundleIdentifier Optional(&quot;com.vivarepublica.cash&quot;)
  app bundleIdentifier Optional(&quot;com.vivarepublica.cash&quot;)
  app numberOfNotifications 6
  app numberOfPickups 4
  app totalActivityDuration 408.36211693286896
  app localizedDisplayName Optional(&quot;모바일출입카드&quot;)
  app bundleIdentifier Optional(&quot;kr.co.adcaps.mobilecard&quot;)
  app bundleIdentifier Optional(&quot;kr.co.adcaps.mobilecard&quot;)
  app numberOfNotifications 0
  app numberOfPickups 0
  app totalActivityDuration 9.502761006355286
  category.localizedDisplayName Optional(&quot;Entertainment&quot;)
  category.debugDescription Optional(ActivityCategoryToken(data: 128 bytes: &amp;auml;&amp;eacute;&amp;icirc;/&amp;Ntilde;I&amp;copy;&amp;eacute;jMM&amp;ocirc;
 j7;&amp;ordf;$.&amp;egrave;&amp;yacute;&amp;ocirc;`g&amp;oslash;c&amp;Oslash;~v.&amp;iexcl;&amp;Agrave;0&amp;Otilde;&amp;yen;&amp;cent;2&amp;macr;&amp;laquo;&amp;ucirc;^
 S&amp;aring;Rg&amp;Acirc;&amp;AElig;^8&amp;pound;&amp;plusmn;j&amp;oacute;&amp;Auml;&amp;laquo;&amp;Acirc;&amp;egrave;,&amp;middot;&amp;Ucirc;&amp;shy;9&amp;pound;:&amp;Aring;&amp;Auml;&amp;auml;&amp;atilde;CV&amp;ordf;&amp;para;~&amp;sup2;=&amp;euml;&amp;iexcl;&amp;Euml;&amp;Atilde;A(C&amp;Iuml;&amp;Yacute;))
  category.totalActivityDuration 31.47321093082428
  app localizedDisplayName Optional(&quot;네이버 웹툰&quot;)
  app bundleIdentifier Optional(&quot;com.nhncorp.NaverWebtoon&quot;)
  app bundleIdentifier Optional(&quot;com.nhncorp.NaverWebtoon&quot;)
  app numberOfNotifications 0
  app numberOfPickups 0
  app totalActivityDuration 22.481863021850586
  app localizedDisplayName Optional(&quot;YouTube&quot;)
  app bundleIdentifier Optional(&quot;com.google.ios.youtube&quot;)
  app bundleIdentifier Optional(&quot;com.google.ios.youtube&quot;)
  app numberOfNotifications 1
  app numberOfPickups 0
  app totalActivityDuration 2.891474962234497
 ⚫️web domain Optional(&quot;weather.naver.com&quot;)
 ⚫️web totalActivityDuration 5.285457968711853
 ⚫️web debugDescription Optional(WebDomainToken(data: 128 bytes: &amp;auml;&amp;eacute;&amp;icirc;/&amp;Ntilde;I&amp;copy;&amp;eacute;jMM&amp;ocirc;
 j7;&amp;ordf;$.&amp;uuml;&amp;euml;0&amp;uuml;bw&amp;szlig;cg&amp;amp;&amp;icirc;&amp;Oslash;e&amp;frac12;&amp;acute;&amp;Ucirc;&amp;eth;J&amp;reg;&amp;ucirc;&amp;frac34;&amp;egrave;
 P
 q&amp;Acirc;&amp;cent;&amp;plusmn;&amp;iuml;w&amp;acute;7&amp;Igrave;&amp;AElig;&amp;oacute;&amp;Auml;&amp;laquo;&amp;Acirc;&amp;egrave;,&amp;middot;&amp;Ucirc;&amp;shy;9&amp;pound;:&amp;Aring;&amp;Auml;&amp;auml;&amp;atilde;CV&amp;ordf;&amp;para;&amp;divide;&amp;auml;]r    &amp;amp;vm&amp;frac14;&amp;Ograve;))
 ⚫️web domain Optional(&quot;naver.com&quot;)
 ⚫️web totalActivityDuration 0.8144149780273438
 ⚫️web debugDescription Optional(WebDomainToken(data: 128 bytes: &amp;auml;&amp;eacute;&amp;icirc;/&amp;Ntilde;I&amp;copy;&amp;eacute;jMM&amp;ocirc;
 j7;&amp;ordf;$.&amp;uuml;&amp;euml;0&amp;uuml;bw&amp;szlig;cg&amp;amp;&amp;icirc;&amp;Oslash;|&amp;sup1;&amp;pound;&amp;Aacute;&amp;raquo;[&amp;iuml;&amp;lt;&amp;eth;&amp;divide;&amp;cedil;JY&amp;euml;:&amp;Acirc;&amp;egrave;&amp;ograve;&amp;AElig;^8&amp;pound;&amp;plusmn;j&amp;oacute;&amp;Auml;&amp;laquo;&amp;Acirc;&amp;egrave;,&amp;middot;&amp;Ucirc;&amp;shy;9&amp;pound;:&amp;Aring;&amp;Auml;&amp;auml;&amp;atilde;CV&amp;ordf;&amp;para;0?&amp;Atilde;3&amp;atilde;(&amp;yacute;&amp;auml;R0))
  device.name Optional(&quot;NS iPad&quot;)
  lastUpdatedDate nil
  segmentInterval daily(during: 2025-09-21 15:00:00 +0000 to 2025-09-22 15:00:00 +0000)
  name Optional(givenName: 남수 familyName: 김 )
  user apple id Optional(&quot;ㅁㅁㅁㅁ@naver.com&quot;)
  user role individual&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기별로, 활동별로, 카테고리별로, 앱, 웹 이런식으로 하위까지 사용량을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene타입의 content에 사용할 타입을 먼저 정의할겁니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 타이틀과 시간을 저장할 구조체도 같이 정의합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761551045141&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let content: (String) -&amp;gt; TotalActivityView // 기본 생성시 

let content: ([AppUsage]) -&amp;gt; TotalActivityView // &amp;lt;&amp;lt; 수정


struct AppUsage {
    let title: String
    let time: Int
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 앱타겟에서 사용할 뷰를 정의합니다&lt;/p&gt;
&lt;pre id=&quot;code_1761551645677&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct DeviceReportView: View {
    @State private var context: DeviceActivityReport.Context = .totalActivity
    @State private var filter: DeviceActivityFilter = .init(
        segment: .daily(
            during: .init(
                start: .now.addingTimeInterval(-3600),
                end: .now.addingTimeInterval(3600)
            )
        ),
        devices: .all
    )
    var body: some View {
        VStack {
            Text(&quot;사용량 통계&quot;)
            DeviceActivityReport(context, filter: filter)
        }
    }
}&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;로그부분은 다제거하고 필요한 코드만 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761551742136&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct TotalActivityReport: DeviceActivityReportScene {
    // Define which context your scene will represent.
    let context: DeviceActivityReport.Context = .totalActivity  // DeviceActivityReport호출시 해당 컨텍스트에 해당하는 Report 불러옴
    
    // Define the custom configuration and the resulting view for this report.
    let content: ([AppUsage]) -&amp;gt; TotalActivityView
    
    func makeConfiguration(representing data: DeviceActivityResults&amp;lt;DeviceActivityData&amp;gt;) async -&amp;gt; [AppUsage] {
        // Reformat the data into a configuration that can be used to create
        // the report's view.
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.day, .hour, .minute, .second]
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .dropAll
        
        return await printLog(data: data)
    }
    
    func containsIPhone(_ name: String) -&amp;gt; Bool {
      return name.range(of: &quot;iPhone&quot;, options: .caseInsensitive) != nil
    }
    
    private func printLog(data: DeviceActivityResults&amp;lt;DeviceActivityData&amp;gt;) async -&amp;gt; [AppUsage] {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.day, .hour, .minute, .second]
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .dropAll
        let thisDevice = UIDevice.current.model
        
        var appUsages: [AppUsage] = []
        for await value in data {
            for await activity in value.activitySegments {
                for await category in activity.categories {
                    for await application in category.applications {
                        let usage = AppUsage(
                            title: application.application.localizedDisplayName?.lowercased() ?? &quot;&quot;,
                            time: Int(application.totalActivityDuration)
                        )
                        appUsages.append(usage)
                    }
                }
                
            }
        }

        return appUsages
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4중 for문이라니 ,,, (가능하다면 flatMap으로 배열 평탄화 가공하셔도 좋을거같네요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리포트 데이터를 받아서 구현할 뷰를 작성해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1761551857942&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
import SwiftUI
import Charts

struct AppUsage {
    let title: String
    let time: Int
}

struct TotalActivityView: View {
    let totalActivity: String
    let total: Int
    let data: [AppUsage]
    @State private var selectedAngle: Int?
    private let dataRange: [Range&amp;lt;Int&amp;gt;]
    // 선택한 차트 각도에 해당하는 선택된 아이템반환
    private var selectedItem: AppUsage? {
        guard let selectedAngle else { return nil }
        if let selectedIndex = dataRange.firstIndex(where: { range in
            range.contains(selectedAngle)
        }) {
            return data[selectedIndex]
        }
        return nil
    }
    
    init(data: [AppUsage]) {
        self.data = data
        var total: Int = 0
        self.dataRange = data.map {
            let newTotal = total + $0.time
            let result = total..&amp;lt;newTotal
            total = newTotal
            return result
       }
        self.totalActivity = &quot;\(total)s&quot;
        self.total = total
    }
    
    var body: some View {
        Text(&quot;전체 \(totalActivity)&quot;)
        
        Chart(data, id: \.title) { value in
            SectorMark(
                angle: .value(&quot;appName&quot;, value.time),
                innerRadius: .ratio(0.618), // 황금비율 (안쪽원)
                outerRadius: .ratio(1), // 전체원의 크기
                angularInset: 3 // 차트 데이터간 간격
            )
            .cornerRadius(10)
            .opacity(selectedItem?.title == value.title ? 1 : 0.5) // 선택된아이템 강조
            .foregroundStyle(by: .value(&quot;color&quot;, value.title))
        }
        .chartLegend(.visible) // 범주 노출
        .chartAngleSelection(value: $selectedAngle) // 선택된 각도 바인딩 (Double 혹은 Int 타입만 가능)
        .chartBackground { proxy in
            GeometryReader { geometry in
                if let plotFrame = proxy.plotFrame { // 차트가 그려진프레임
                    let frame = geometry[plotFrame] // Ahchor&amp;lt;CGRect&amp;gt; -&amp;gt; CGRect 형변환
                    VStack {
                        Text(&quot;사용량&quot;)
                        if let selectedItem {
                            Text(selectedItem.title)
                                .bold()
                            Text(&quot;time: \(selectedItem.time)s&quot;)
                            Text(&quot;\(Double(selectedItem.time) / Double(total) * 100)%&quot;)
                        }
                    }
                    .position(x: frame.midX, y: frame.midY) // 차트프레임 좌표 적용시켜야 중앙에뜸
                }
            }
        }
        .scaledToFit()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tqpQA/dJMcaj8kR5y/jHR4V5QES69t8W0kgXnTUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tqpQA/dJMcaj8kR5y/jHR4V5QES69t8W0kgXnTUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tqpQA/dJMcaj8kR5y/jHR4V5QES69t8W0kgXnTUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtqpQA%2FdJMcaj8kR5y%2FjHR4V5QES69t8W0kgXnTUk%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;375&quot; height=&quot;813&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차트를 터치하면 해당 앱의 이름, 사용시간, 비율을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시뮬레이터에선 앱사용량 제공을 해주지않아서 실기기에서 테스트하는걸 권장드려요!!&lt;/p&gt;</description>
      <category>iyOmSd/Title: Swift</category>
      <author>냄수</author>
      <guid isPermaLink="true">https://nsios.tistory.com/251</guid>
      <comments>https://nsios.tistory.com/251#entry251comment</comments>
      <pubDate>Mon, 27 Oct 2025 22:08:18 +0900</pubDate>
    </item>
  </channel>
</rss>