<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>lgvv98</title>
    <link>https://rldd.tistory.com/</link>
    <description>iOS, Swift, UIKit, SwiftUI</description>
    <language>ko</language>
    <pubDate>Thu, 6 Aug 2026 04:06:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>lgvv</managingEditor>
    <image>
      <title>lgvv98</title>
      <url>https://tistory1.daumcdn.net/tistory/4492386/attach/290c903c478048eca8cef02346d8b71b</url>
      <link>https://rldd.tistory.com</link>
    </image>
    <item>
      <title>Express req.query와 query parser</title>
      <link>https://rldd.tistory.com/819</link>
      <description>&lt;h2 data-heading=&quot;Express req.query와 query parser&quot; data-ke-size=&quot;size26&quot;&gt;Express req.query와 query parser&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express에서 query string이 req.query로 어떻게 들어오는지 정리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 같은 query key를 여러 번 보냈을 때 왜 배열로 바뀌는지 헷갈려서 정리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 글은 Express 5의 기본 query parser인 simple 기준으로 하며, Express 4는 기본값이 extended였으므로 버전에 따라 parser가 다를 수 있음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-heading=&quot;예제 요청&quot; data-ke-size=&quot;size20&quot;&gt;예제 요청&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 같은 key인 tags를 여러 번 보낸다고 가정.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;GET /example/info/b?tags=node&amp;amp;tags=express&amp;amp;tags=javascript
&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;Express에서 req.query를 찍으면 아래처럼 보임.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;[Object: null prototype] {
  tags: [&quot;node&quot;, &quot;express&quot;, &quot;javascript&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;tags가 하나의 문자열이 아니라 배열로 들어옴.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;app.get(&quot;/example/info/b&quot;, (req, res) =&amp;gt; {
  console.log(req.query);

  const tags = req.query.tags;

  res.send({
    tags,
    message: `선택한 태그: ${Array.isArray(tags) ? tags.join(&quot;, &quot;) : tags}`,
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;왜 배열이 되는지&quot; data-ke-size=&quot;size20&quot;&gt;왜 배열이 되는지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express가 URL의 query string을 읽고 req.query 객체를 만드는 과정에서 생김.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Express가 직접 같은 key를 하나씩 검사해서 배열로 만드는 구조는 아니며, Express는 설정된 query parser를 호출하고, 실제 변환은 parser가 담당.&lt;/p&gt;
&lt;h2 data-heading=&quot;query parser 설정&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-heading=&quot;query parser 설정&quot; data-ke-size=&quot;size20&quot;&gt;query parser 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express에는 query parser 설정이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express 5 기준 기본값은 simple.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;app.set(&quot;query parser&quot;, &quot;simple&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 class=&quot;routeros&quot;&gt;&lt;code&gt;query string을 파싱할 때 simple parser 사용
&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;처음에는 app.set이 배열 변환을 직접 하는 줄 알았는데, 그건 아니었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.set은 Express 앱의 설정값을 저장하는 메서드로 아래처럼 나눌 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ocaml&quot;&gt;&lt;code&gt;app.set(&quot;query parser&quot;, &quot;simple&quot;)
-&amp;gt; 어떤 query parser를 쓸지 설정

parser
-&amp;gt; query string을 실제 객체로 변환
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;simple query parser&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-heading=&quot;simple query parser&quot; data-ke-size=&quot;size20&quot;&gt;simple query parser&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express에서 query parser 설정이 simple이면 Node 내장 모듈인 querystring.parse를 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;case &quot;simple&quot;:
  fn = querystring.parse;
  break;
&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;즉 simple은 query string 파싱에 querystring.parse에 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;실제 배열 변환&quot; data-ke-size=&quot;size20&quot;&gt;실제 배열 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 query string을 객체로 바꾸는 동작은 querystring.parse에서 일어남.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;const querystring = require(&quot;querystring&quot;);

querystring.parse(&quot;tags=node&amp;amp;tags=express&amp;amp;tags=javascript&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 class=&quot;css&quot;&gt;&lt;code&gt;[Object: null prototype] {
  tags: [&quot;node&quot;, &quot;express&quot;, &quot;javascript&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;같은 key인 tags가 여러 번 나오므로 값들을 배열로 묶음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node 문서 예제도 같은 흐름.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;querystring.parse(&quot;foo=bar&amp;amp;abc=xyz&amp;amp;abc=123&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;결과는 아래처럼 abc가 배열이 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;{
  foo: &quot;bar&quot;,
  abc: [&quot;xyz&quot;, &quot;123&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;전체 흐름&quot; data-ke-size=&quot;size20&quot;&gt;전체 흐름&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 아래 흐름.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;URL 요청
/example/info/b?tags=node&amp;amp;tags=express&amp;amp;tags=javascript

-&amp;gt; Express가 ? 뒤의 query string 확인
tags=node&amp;amp;tags=express&amp;amp;tags=javascript

-&amp;gt; app 설정에서 query parser 확인
query parser = &quot;simple&quot;

-&amp;gt; simple 설정이므로 querystring.parse 사용

-&amp;gt; 같은 key가 여러 번 있으므로 배열로 변환
{
  tags: [&quot;node&quot;, &quot;express&quot;, &quot;javascript&quot;]
}

-&amp;gt; req.query에 들어감
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할만 다시 보면 아래처럼 정리해볼 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;app.set(...)
-&amp;gt; 어떤 parser를 쓸지 설정

querystring.parse(...)
-&amp;gt; query string을 실제 객체로 변환
-&amp;gt; 같은 key가 여러 번 있으면 배열로 만듦
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;[Object: null prototype]은 뭔지&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-heading=&quot;[Object: null prototype]은 뭔지&quot; data-ke-size=&quot;size20&quot;&gt;[Object: null prototype] 정리&lt;/h4&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 class=&quot;css&quot;&gt;&lt;code&gt;[Object: null prototype] { tags: [&quot;node&quot;, &quot;express&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;Node의 querystring.parse가 일반 객체 {}와 조금 다른 객체를 만들어서 그렇게 보이는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node 문서 기준으로도 querystring.parse가 반환하는 객체는 Object.prototype을 상속하지 않음.&lt;/p&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;중요한 건 req.query.tags가 배열로 들어올 수 있다는 점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const tags = req.query.tags;

console.log(Array.isArray(tags)); // true
console.log(tags[0]); // &quot;node&quot;
console.log(tags[1]); // &quot;express&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;하나만 보내면 문자열&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;하나만 보내면 문자열&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 key가 여러 번 들어오면 배열.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나만 들어오면 문자열.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /example/info/b?tags=node
&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 class=&quot;css&quot;&gt;&lt;code&gt;{
  tags: &quot;node&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;그래서 tags를 무조건 배열이라고 생각하면 헷갈릴 수 있음.&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 class=&quot;ebnf&quot;&gt;&lt;code&gt;const tags = req.query.tags;

const tagList = Array.isArray(tags)
  ? tags
  : tags
  ? [tags]
  : [];
&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;이렇게 하면 이후 로직에서는 tagList를 배열로만 다룰 수 있음.&lt;/p&gt;
&lt;h4 data-heading=&quot;extended parser를 쓰면?&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;extended parser를 쓰면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;query parser를 extended로 바꾸면 qs 모듈 기반으로 파싱.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;app.set(&quot;query parser&quot;, &quot;extended&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;simple보다 복잡한 query string을 다룰 수 있음.&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 class=&quot;pgsql&quot;&gt;&lt;code&gt;GET /users?filter[name]=kim
&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;다만 지금 글에서 헷갈린 포인트는 같은 key가 여러 번 들어왔을 때 배열이 되는 이유.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 케이스는 simple 기준으로는 querystring.parse가 처리한다고 이해할 수 있음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-heading=&quot;정리&quot; data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;req.query는 Express가 query string을 파싱해서 만들어주는 객체로 Express 5 기준 기본 query parser는 simple.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;simple은 Node 내장 querystring.parse를 사용하며, 같은 key가 여러 번 오면 querystring.parse가 값을 배열로 묶음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;tags=node&amp;amp;tags=express&amp;amp;tags=javascript
&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 class=&quot;css&quot;&gt;&lt;code&gt;{
  tags: [&quot;node&quot;, &quot;express&quot;, &quot;javascript&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 class=&quot;css&quot;&gt;&lt;code&gt;{
  tags: &quot;node&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;따라서 req.query.tags를 사용할 때는 문자열과 배열 가능성을 같이 고려하는 편이 안전해 보임.&lt;/p&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;&lt;a href=&quot;https://expressjs.com/en/5x/api/application/&quot;&gt;https://expressjs.com/en/5x/api/application/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://expressjs.com/en/5x/api/request/&quot;&gt;https://expressjs.com/en/5x/api/request/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://nodejs.org/api/querystring.html&quot;&gt;https://nodejs.org/api/querystring.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>백엔드</category>
      <category>express</category>
      <category>JS</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/819</guid>
      <comments>https://rldd.tistory.com/819#entry819comment</comments>
      <pubDate>Tue, 7 Jul 2026 00:58:37 +0900</pubDate>
    </item>
    <item>
      <title>Express app.param vs app.get</title>
      <link>https://rldd.tistory.com/818</link>
      <description>&lt;h2 data-heading=&quot;Express app.param vs app.get&quot; data-ke-size=&quot;size26&quot;&gt;Express app.param vs app.get&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express에서 app.get과 app.param에 대해서 간단하게 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;둘 다 라우팅 과정에서 나오고, 둘 다 userId 같은 값을 다룰 수 있어서 처음에는 비슷해 보임.&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 class=&quot;pgsql&quot;&gt;&lt;code&gt;app.get(path, handler)
-&amp;gt; GET 요청을 실제로 처리하는 라우트 핸들러

app.param(name, handler)
-&amp;gt; 특정 path parameter가 있는 라우트 앞에서 먼저 실행되는 전처리 함수
&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;여기서 말하는 app.get은 app.get(&quot;port&quot;)처럼 앱 설정값을 읽는 메서드가 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP GET 라우트를 등록하는 app.get(path, handler) 기준으로 정리.&lt;/p&gt;
&lt;h4 data-heading=&quot;기준&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;기준&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;역할&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.get&lt;/td&gt;
&lt;td&gt;요청을 받아 응답을 보냄&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app.param&lt;/td&gt;
&lt;td&gt;라우트 핸들러 전에 path parameter를 미리 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req.params&lt;/td&gt;
&lt;td&gt;/users/:userId처럼 경로에 들어간 값을 읽음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req.query&lt;/td&gt;
&lt;td&gt;/users?role=admin처럼 ? 뒤에 붙은 값을 읽음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.get은 실제 요청을 처리하는 곳.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param은 그 전에 공통 검증이나 조회를 끼워 넣는 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-heading=&quot;app.get&quot; data-ke-size=&quot;size26&quot;&gt;app.get&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.get은 특정 경로로 GET 요청이 들어왔을 때 실행.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.send(`userId: ${req.params.userId}`);
});
&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 class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/users/:userId 라우트에 GET 요청이 들어오면 콜백 함수가 실행됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 :userId 자리에 들어온 값은 req.params.userId로 꺼냄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  console.log(req.params.userId); // &quot;1&quot;
  res.send(&quot;OK&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;라우트 핸들러 안에서는 query string도 읽을 수 있음.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users/1?tab=posts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  console.log(req.params.userId); // &quot;1&quot;
  console.log(req.query.tab); // &quot;posts&quot;

  res.send(&quot;OK&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;따라서 app.get은 요청을 받고, 필요한 값을 읽고, 응답을 보내는 곳이라고 보면 됨.&lt;/p&gt;
&lt;h4 data-heading=&quot;app.param&quot; data-ke-size=&quot;size20&quot;&gt;app.param&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param은 라우트 경로에 특정 path parameter가 있을 때 먼저 실행.&lt;/p&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  console.log(&quot;userId:&quot;, id);
  next();
});
&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;Express는 param callback에 (req, res, next, value, name) 순서로 값을 넘김.&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;여기서 &quot;userId&quot;는 라우트의 :userId와 이름이 같아야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.send(&quot;User page&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 class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 순서는 아래처럼 됨.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, ...)
-&amp;gt; app.get(&quot;/users/:userId&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;즉 app.get으로 바로 가는 게 아니라 app.param(&quot;userId&quot;)를 먼저 거침.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express가 라우트를 매칭하는 과정에서 :userId를 발견하고, 그 이름에 등록된 param callback을 먼저 실행하는 흐름.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;왜 app.param을 쓰는지&quot; data-ke-size=&quot;size20&quot;&gt;왜 app.param을 쓰는지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 정보를 가져오는 라우트가 여러 개 있다고 가정.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  const user = getUserById(req.params.userId);

  if (!user) {
    return res.status(404).send(&quot;User not found&quot;);
  }

  res.json(user);
});

app.get(&quot;/blogs/:userId&quot;, (req, res) =&amp;gt; {
  const user = getUserById(req.params.userId);

  if (!user) {
    return res.status(404).send(&quot;User not found&quot;);
  }

  res.json({
    message: `${user.name}님의 블로그입니다.`,
    user,
  });
});
&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;위 코드에서는 사용자 조회와 404 처리가 반복됨.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const user = getUserById(req.params.userId);

if (!user) {
  return res.status(404).send(&quot;User not found&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;이런 코드를 app.param으로 분리 가능.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  const user = getUserById(id);

  if (!user) {
    return res.status(404).send(&quot;User not found&quot;);
  }

  req.user = user;
  next();
});
&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;이후 라우트 핸들러에서는 이미 조회된 req.user만 사용.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.json(req.user);
});

app.get(&quot;/blogs/:userId&quot;, (req, res) =&amp;gt; {
  res.json({
    message: `${req.user.name}님의 블로그입니다.`,
    user: req.user,
  });
});
&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;다만 모든 로직을 app.param으로 빼는 게 항상 좋은 건 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 라우트에서만 쓰는 코드라면 그냥 app.get 안에 두는 편이 더 읽기 쉬움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 라우트에서 같은 path parameter를 기준으로 같은 검증이나 조회를 반복할 때 app.param을 사용하면 좋음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;전체 예제&quot; data-ke-size=&quot;size20&quot;&gt;전체 예제&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();

app.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  const user = getUserById(id);

  if (!user) {
    return res.status(404).send(&quot;User not found&quot;);
  }

  req.user = user;
  next();
});

app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.json(req.user);
});

app.get(&quot;/blogs/:userId&quot;, (req, res) =&amp;gt; {
  res.json({
    message: `${req.user.name}님의 블로그입니다.`,
    user: req.user,
  });
});

function getUserById(id) {
  const users = {
    &quot;1&quot;: { id: 1, name: &quot;Alice&quot;, role: &quot;Admin&quot; },
    &quot;2&quot;: { id: 2, name: &quot;Bob&quot;, role: &quot;User&quot; },
  };

  return users[id];
}

app.listen(3000, () =&amp;gt; {
  console.log(&quot;Server running on port 3000&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;요청예제&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-heading=&quot;요청예제&quot; data-ke-size=&quot;size20&quot;&gt;요청예제&lt;/h4&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;GET http://localhost:3000/users/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;id&quot;: 1,
  &quot;name&quot;: &quot;Alice&quot;,
  &quot;role&quot;: &quot;Admin&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;GET http://localhost:3000/blogs/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;message&quot;: &quot;Alice님의 블로그입니다.&quot;,
  &quot;user&quot;: {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Alice&quot;,
    &quot;role&quot;: &quot;Admin&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 class=&quot;awk&quot;&gt;&lt;code&gt;GET http://localhost:3000/users/999
&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 class=&quot;crmsh&quot;&gt;&lt;code&gt;User not found
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-heading=&quot;실행 순서&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-heading=&quot;실행 순서&quot; data-ke-size=&quot;size20&quot;&gt;실행 순서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GET /users/1 요청이 들어오면 흐름은 대략 아래와 같음.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;1. Express가 /users/:userId 라우트 확인
2. :userId가 있으므로 app.param(&quot;userId&quot;) 실행
3. getUserById(&quot;1&quot;) 실행
4. req.user에 사용자 정보 저장
5. next() 호출
6. app.get(&quot;/users/:userId&quot;) 실행
7. res.json(req.user) 응답
&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;GET /blogs/1도 동일.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트는 다르지만 둘 다 :userId를 가지고 있으므로 같은 app.param(&quot;userId&quot;)를 먼저 거침.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 app.param은 라우트마다 반복되는 parameter 기반 전처리에 잘 맞음.&lt;/p&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 class=&quot;angelscript&quot;&gt;&lt;code&gt;id 형식 검증
사용자 조회
권한 확인
없는 리소스에 대한 404 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;path params와 query params 차이&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;path params와 query params 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param을 이해하려면 path params와 query params를 먼저 구분해야 함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;path params&quot;&gt;path params&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users/1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  console.log(req.params.userId); // &quot;1&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;/users/:userId에서 :userId 자리에 들어간 값이 req.params.userId에 들어감.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 특정 리소스를 식별할 때 사용.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;/users/1
-&amp;gt; 1번 사용자
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;query params&quot;&gt;query params&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users?userId=1
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app.get(&quot;/users&quot;, (req, res) =&amp;gt; {
  console.log(req.query.userId); // &quot;1&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;? 뒤에 붙은 값들은 req.query에 들어감.&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 class=&quot;pgsql&quot;&gt;&lt;code&gt;/users?role=admin
-&amp;gt; 사용자 목록 중 role이 admin인 것만 조회
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-heading=&quot;그러면 query도 app.get에서 가져올 수 있는데?&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;그러면 query도 app.get에서 가져올 수 있을까? (O)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.get 안에서는 req.params도 읽을 수 있고 req.query도 읽을 수 있음.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  console.log(req.params.userId);
  console.log(req.query.tab);
});
&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;다만 app.param은 query parameter에는 반응하지 않음.&lt;/p&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  console.log(id);
  next();
});

app.get(&quot;/users&quot;, (req, res) =&amp;gt; {
  res.send(req.query.userId);
});
&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 class=&quot;routeros&quot;&gt;&lt;code&gt;GET /users?userId=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param(&quot;userId&quot;)는 실행되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트 경로에 :userId가 없기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param(&quot;userId&quot;)가 실행되려면 라우트에 아래처럼 :userId가 있어야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.send(req.params.userId);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아래 두 요청은 비슷해 보여도 Express 입장에서는 완전히 다르게 처리됨.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;/users/1
-&amp;gt; req.params.userId
-&amp;gt; app.param(&quot;userId&quot;) 실행 가능

/users?userId=1
-&amp;gt; req.query.userId
-&amp;gt; app.param(&quot;userId&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;userId라는 이름이 같아도 하나는 path parameter이고, 하나는 query parameter.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app.param은 이름만 보는 게 아니라 라우트 경로에 선언된 :paramName을 봄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;비교&quot; data-ke-size=&quot;size20&quot;&gt;비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 app.get app.param&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;역할&lt;/td&gt;
&lt;td&gt;GET 요청 처리&lt;/td&gt;
&lt;td&gt;path parameter 전처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 시점&lt;/td&gt;
&lt;td&gt;라우트가 매칭될 때&lt;/td&gt;
&lt;td&gt;라우트 핸들러 실행 전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;응답&lt;/td&gt;
&lt;td&gt;보통 여기서 응답을 보냄&lt;/td&gt;
&lt;td&gt;보통 검증 후 next() 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;다루는 값&lt;/td&gt;
&lt;td&gt;req.params, req.query 등&lt;/td&gt;
&lt;td&gt;특정 :paramName&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;/users/:userId 처리&lt;/td&gt;
&lt;td&gt;:userId 값으로 사용자 미리 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-heading=&quot;헷갈리기 쉬운 부분&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;헷갈리기 쉬운 부분&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;app.param은 라우트를 만드는 게 아님&quot;&gt;app.param은 라우트를 만드는 게 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, callback);
&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;이 코드만 있다고 해서 /users/1 주소가 생기는 건 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 라우트는 따로 있어야 함.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;app.get(&quot;/users/:userId&quot;, callback);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;이름이 같아야 함&quot;&gt;이름이 같아야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, callback);
app.get(&quot;/users/:id&quot;, callback);
&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;위 코드는 &quot;userId&quot;와 :id 이름이 달라서 app.param(&quot;userId&quot;)가 실행되지 않음.&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 class=&quot;stata&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, callback);
app.get(&quot;/users/:userId&quot;, callback);
&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;또 app.param(&quot;:userId&quot;, callback)처럼 콜론을 붙이지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트에는 :userId라고 쓰지만 app.param에 넘기는 이름은 &quot;userId&quot;.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;next를 호출해야 넘어감&quot;&gt;next를 호출해야 넘어감&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;app.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  req.userId = id;
  next();
});
&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;next()를 호출해야 뒤의 app.get으로 넘어감.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자를 찾지 못해서 여기서 응답을 끝낼 때는 return res.status(...).send(...)처럼 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;if (!user) {
  return res.status(404).send(&quot;User not found&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;같은 요청에서는 한 번만 실행됨&quot;&gt;같은 요청에서는 한 번만 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Express 공식 문서 기준으로 param callback은 같은 request-response cycle에서 한 번만 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 같은 요청에서 같은 :id가 여러 라우트 핸들러를 거치더라도 app.param(&quot;id&quot;, ...)는 매번 반복 실행되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 조회를 줄여준다는 장점이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 app.param 안에 라우트별로 달라져야 하는 로직을 넣으면 흐름이 꼬일 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;router.param과 범위가 다름&quot;&gt;router.param과 범위가 다름&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터를 분리해서 쓰기 시작하면 app.param과 router.param의 범위도 신경 써야 함.&lt;/p&gt;
&lt;pre class=&quot;moonscript&quot;&gt;&lt;code&gt;const router = require(&quot;express&quot;).Router();

router.param(&quot;userId&quot;, (req, res, next, id) =&amp;gt; {
  // 이 router 안의 :userId 라우트에 적용
  next();
});

router.get(&quot;/users/:userId&quot;, (req, res) =&amp;gt; {
  res.send(&quot;OK&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;처음 Express를 배울 때는 app.param만 알아도 충분하지만, 나중에 라우터를 파일별로 나누면 param callback도 그 라우터에 맞게 가까운 곳에 두는 편이 읽기 쉬움.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;언제 app.param을 쓰면 좋을까&quot; data-ke-size=&quot;size20&quot;&gt;언제 app.param을 쓰면 좋을까&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지는 아래와 같은 기준을 가지고 사용&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;여러 라우트에서 같은 :userId를 사용
각 라우트마다 같은 사용자 조회 코드가 반복
없는 사용자를 같은 방식으로 404 처리
조회된 user 객체를 뒤 라우트에서 공통으로 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;반대로 아래라면 굳이 쓰지 않아도 됨.&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;한 라우트에서만 쓰는 로직
parameter 검증보다 라우트 본문 로직이 중심
query string 조건을 처리하려는 목적
&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;이 경우에는 app.get 안에서 직접 처리하는 편이 더 단순함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;정리&quot; data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;app.get
-&amp;gt; GET 요청의 본문 처리
-&amp;gt; 보통 여기서 응답을 보냄

app.param
-&amp;gt; path parameter 기반 전처리
-&amp;gt; 보통 검증하거나 미리 조회한 뒤 next()로 넘김
&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;app.get 안에서는 req.params와 req.query를 모두 읽을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 app.param은 req.query가 아니라 라우트 경로에 선언된 :paramName에만 반응.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 /users/:userId처럼 특정 사용자를 가져오는 라우트에서는 app.param으로 사용자 조회나 검증을 먼저 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 /users?role=admin&amp;amp;page=2처럼 검색 조건이나 페이지 조건을 다룰 때는 app.get 안에서 req.query로 처리하는 편이 자연스러움.&lt;/p&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;br /&gt;&lt;a href=&quot;https://expressjs.com/en/5x/api/application/&quot;&gt;https://expressjs.com/en/5x/api/application/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://expressjs.com/en/5x/guide/routing/&quot;&gt;https://expressjs.com/en/5x/guide/routing/&lt;/a&gt;&lt;/p&gt;</description>
      <category>백엔드</category>
      <category>express</category>
      <category>JS</category>
      <category>nodejs</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/818</guid>
      <comments>https://rldd.tistory.com/818#entry818comment</comments>
      <pubDate>Tue, 7 Jul 2026 00:56:27 +0900</pubDate>
    </item>
    <item>
      <title>Rust의 Copy와 Clone 차이점 정리</title>
      <link>https://rldd.tistory.com/816</link>
      <description>&lt;h2 data-heading=&quot;Rust의 Copy와 Clone 차이점 정리&quot; data-ke-size=&quot;size26&quot;&gt;Rust의 Copy와 Clone 차이점 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust의 소유권과 관련하여 Copy와 Clone의 차이점 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;// 컴파일 O
let x = 5;
let y = x;

println!(&quot;{x}, {y}&quot;);

// 컴파일 X

let s1 = String::from(&quot;hello&quot;);
let s2 = s1;

println!(&quot;{s1}&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;둘 다 값을 다른 변수에 대입했을 뿐인데, 왜 하나는 되고 하나는 안 되는지 이 차이를 설명하는 개념이 Copy와 Clone&lt;/p&gt;
&lt;h3 data-heading=&quot;목차&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-heading=&quot;목차&quot; data-ke-size=&quot;size23&quot;&gt;목차&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요약&lt;/li&gt;
&lt;li&gt;i32는 왜 대입 후에도 사용할 수 있을까?&lt;/li&gt;
&lt;li&gt;String은 왜 move될까?&lt;/li&gt;
&lt;li&gt;Copy란?&lt;/li&gt;
&lt;li&gt;Clone이란?&lt;/li&gt;
&lt;li&gt;헷갈리는 부분 정리&lt;/li&gt;
&lt;li&gt;구조체에서 Copy와 Clone 사용하기&lt;/li&gt;
&lt;li&gt;Copy를 붙일지 판단하는 기준&lt;/li&gt;
&lt;li&gt;정리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;먼저 결론&quot; data-ke-size=&quot;size20&quot;&gt;요약&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 150px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Copy&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Clone&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;호출 방식&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;암묵적&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;명시적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;사용 형태&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;let y = x;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;let y = x.clone();&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;복제 비용&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;일반적으로 매우 작음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;타입에 따라 다름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;동작 방식&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;단순한 bit-wise copy&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;타입이 정의한 clone() 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;Drop 타입&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;구현 불가&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;구현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;대표 타입&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;i32, bool, char, &amp;amp;T&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;String, Vec&amp;lt;T&amp;gt;, Rc&amp;lt;T&amp;gt;, Arc&amp;lt;T&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 명시성으로 Rust에서 비용이 크거나 의미가 타입마다 달라질 수 있는 복사를 자동으로 숨기지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 String, Vec&amp;lt;T&amp;gt; 같은 타입은 대입할 때 자동 복사되지 않고 move됨. 따라서 완전한 복제가 필요하다면 clone()을 명시적으로 직접 호출해야 함.&lt;/p&gt;
&lt;h4 data-heading=&quot;i32는 왜 대입 후에도 사용할 수 있을까?&quot; data-ke-size=&quot;size20&quot;&gt;i32는 왜 대입 후에도 사용할 수 있을까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 i32 예제를 보면 이해가 쉬움.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let x = 5;
let y = x;

println!(&quot;{x}, {y}&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;x를 y에 대입했는데도 x를 계속 사용할 수 있음. (이유는 i32가 Copy 타입이기 때문)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copy 타입은 대입하거나 함수에 넘길 때 소유권이 이동(move)하지 않고 값이 복사됨.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수처럼 크기가 고정되어 있고, 별도의 Heap 리소스를 관리하지 않는 값은 복사 비용이 작음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 값은 Rust가 조용히 복사해도 큰 문제가 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;Rust에서 기본타입은 대표적인 Copy 타입은 아래와 같음.&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;/li&gt;
&lt;li&gt;부동소수점 타입&lt;/li&gt;
&lt;li&gt;bool&lt;/li&gt;
&lt;li&gt;char&lt;/li&gt;
&lt;li&gt;함수 포인터&lt;/li&gt;
&lt;li&gt;공유 참조 &amp;amp;T&lt;/li&gt;
&lt;li&gt;모든 요소가 Copy인 tuple&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 (i32, i32)는 Copy 가능.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let a = (1, 2);
let b = a;

println!(&quot;{a:?}, {b:?}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 (i32, String)은 Copy가 아님.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let a = (1, String::from(&quot;hello&quot;));
let b = a;

println!(&quot;{a:?}&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;tuple 안에 String이 들어있기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copy는 타입의 모든 구성 요소가 Copy일 때만 가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;String은 왜 move될까?&quot; data-ke-size=&quot;size20&quot;&gt;String은 왜 move될까?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드에서는 소유권이 이동되어서 s1을 더이상 사용할 수 없는 예제&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let s1 = String::from(&quot;hello&quot;);
let s2 = s1; // move 

println!(&quot;{s2}&quot;); // ok
println!(&quot;{s1}&quot;); // error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 동작은 String의 메모리 구조를 같이 봐야 하는데, String은 Stack에 포인터, 길이, 용량을 저장하고 실제 문자열 데이터는 Heap에 저장.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-04 오후 7.49.34.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccMjNF/dJMcaa0bpLb/AOoQlbggWvi8QyJFIDy7y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccMjNF/dJMcaa0bpLb/AOoQlbggWvi8QyJFIDy7y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccMjNF/dJMcaa0bpLb/AOoQlbggWvi8QyJFIDy7y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccMjNF%2FdJMcaa0bpLb%2FAOoQlbggWvi8QyJFIDy7y0%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;454&quot; height=&quot;225&quot; data-filename=&quot;스크린샷 2026-07-04 오후 7.49.34.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;만약 s1의 소유권이 이동하지 않은 경우에는 같은 Heap을 바라보기 때문에 s1, s2 중 누군가 메모리를 정리할 경우 문제가 발생할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로는 대입처럼 보이지만, Rust 입장에서는 소유권이 이동한 것(move)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Copy란?&quot; data-ke-size=&quot;size20&quot;&gt;Copy란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서 Copy는 아래처럼 정의.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;pub trait Copy: Clone { }
&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;Rust의 대부분의 원시타입은 Copy trait을 구현하고 있어서 `=` 연산자를 사용하면 Copy가 자동으로 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn print_number(value: i32) {
    println!(&quot;{value}&quot;);
}

fn main() {
    let x = 10;

    print_number(x);
    println!(&quot;{x}&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;i32는 Copy이므로 print_number(x) 이후에도 x를 계속 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 Copy의 동작을 타입이 마음대로 바꿀 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서 기준으로 Copy는 단순한 bit-wise copy임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Copy를 구현하려면 조건이 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입의 모든 필드가 Copy여야 함&lt;/li&gt;
&lt;li&gt;Drop을 구현한 타입은 Copy가 될 수 없음&lt;/li&gt;
&lt;li&gt;리소스를 직접 관리하는 타입은 보통 Copy가 될 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Drop 타입이 Copy가 될 수 없는 이유는 명확함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 암묵적으로 복사되면 어떤 값이 리소스를 정리해야 하는지 애매해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 리소스 정리 책임이 흐려지는 상황을 막음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Clone이란?&quot; data-ke-size=&quot;size20&quot;&gt;Clone이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clone은 값을 명시적으로 복제하기 위한 trait.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서 Clone은 아래처럼 정의됨.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;pub trait Clone: Sized {
    fn clone(&amp;amp;self) -&amp;gt; Self;
}
&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;Clone에는 clone() 메서드가 있음.&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 class=&quot;rust&quot;&gt;&lt;code&gt;let s1 = String::from(&quot;hello&quot;);
let s2 = s1.clone();

println!(&quot;{s1}, {s2}&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;String의 clone()은 Heap에 있는 문자열 데이터까지 새로 복사함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 s1이 가진 메모리 구조를 단순화하면 아래처럼 볼 수 있음.&lt;/p&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-filename=&quot;스크린샷 2026-07-04 오후 8.02.59.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0XwcP/dJMcaaFXDl2/8sHRYokVR89KGRRrzCHB7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0XwcP/dJMcaaFXDl2/8sHRYokVR89KGRRrzCHB7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0XwcP/dJMcaaFXDl2/8sHRYokVR89KGRRrzCHB7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0XwcP%2FdJMcaaFXDl2%2F8sHRYokVR89KGRRrzCHB7K%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;446&quot; height=&quot;241&quot; data-filename=&quot;스크린샷 2026-07-04 오후 8.02.59.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;s1은 스택에 있고, 실제 문자열 hello는 힙에 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clone()을 호출하면 이 구조와 같은 새 String이 s2에도 만들어져서 s1과 s2는 서로 다른 Heap 데이터를 가리킴&lt;br /&gt;이 경우 각자 자기 데이터를 정리하면 되므로 double free 문제가 생기지 않음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Rust에서는 메모리 문제가 발생할 수 있는 영역에 대해서 개발자가 clone()을 직접 호출하게 만들어서 복제가 일어난다는 것을 명확하게 표현.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;오해 정리&quot; data-ke-size=&quot;size20&quot;&gt;Copy와 Clone의 헷갈리는 부분 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust의 Copy와 Clone에서 헷갈리는 부분 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;오해 1. Copy는 Stack 타입만 가능할까?&quot;&gt;&lt;b&gt;1) Copy는 Stack 타입만 가능할까? -&amp;gt; (X)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대체로 단순한 Stack 값이 Copy인 경우가 많음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &quot;Stack이면 Copy, Heap이면 non-Copy&quot;는 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 공유 참조 &amp;amp;T는 Copy임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let s = String::from(&quot;hello&quot;);

let r1 = &amp;amp;s;
let r2 = r1;

println!(&quot;{r1}&quot;);
println!(&quot;{r2}&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;p data-ke-size=&quot;size16&quot;&gt;반대로 String은 Heap 데이터를 소유하고 정리할 책임이 있으므로 Copy가 아님.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;오해 2. Clone은 항상 깊은 복사일까?&quot;&gt;&lt;b&gt;2) Clone은 항상 깊은 복사일까? -&amp;gt; (X)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String의 clone()은 Heap 문자열 데이터를 새로 복사하므로 깊은 복사처럼 보임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Clone 자체가 항상 깊은 복사를 의미하지는 않음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Rc&amp;lt;T&amp;gt;의 clone()은 내부 데이터를 새로 복사하지 않으며, 참조 카운트를 증가시키고 같은 값을 공유.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;use std::rc::Rc;

let a = Rc::new(String::from(&quot;hello&quot;));
let b = Rc::clone(&amp;amp;a);

println!(&quot;{a}&quot;);
println!(&quot;{b}&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;이 코드에서 a와 b는 같은 내부 데이터를 공유하기 때문에 `Clone = 깊은 복사`가 아닐수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, `Clone = 명시적 복제`가 더 가까움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-heading=&quot;오해 3. Copy면 Clone도 되는 걸까?&quot;&gt;&lt;b&gt;3) Copy면 Clone도 되는 걸까? -&amp;gt; (O)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copy의 trait 정의 자체가 Copy: Clone이기 때문에, Copy 타입은 Clone도 구현해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let x = 10;
let y = x.clone();

println!(&quot;{x}, {y}&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;i32는 Copy이면서 Clone도 가능한데, Copy 타입에서는 보통 clone()을 직접 호출할 필요가 없음. (그냥 대입하면 끝)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;구조체에서 Copy와 Clone 사용하기&quot; data-ke-size=&quot;size20&quot;&gt;구조체에서 Copy와 Clone 사용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체도 모든 필드가 Copy라면 Copy를 구현할 수 있음. (보통은 derive를 사용)&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 10, y: 20 };
    let p2 = p1;

    println!(&quot;{p1:?}&quot;);
    println!(&quot;{p2:?}&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;Point의 필드는 모두 i32. (i32는 Copy.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Point의 모든 필드가 Copy가 될 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 구조체 안에 String이 있으면 Copy를 구현할 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#[derive(Copy, Clone, Debug)]
struct User {
    name: String,
}
&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;이 코드는 컴파일되지 않는데, String이 Copy가 아니기 때문에 이 경우에는 Clone만 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#[derive(Clone, Debug)]
struct User {
    name: String,
}

fn main() {
    let user1 = User {
        name: String::from(&quot;lgvv&quot;),
    };

    let user2 = user1.clone();

    println!(&quot;{user1:?}&quot;);
    println!(&quot;{user2:?}&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;이렇게 하면 user1과 user2를 둘 다 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 String 필드도 함께 clone되므로 비용이 발생할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Copy를 붙일지 판단하는 기준&quot; data-ke-size=&quot;size20&quot;&gt;Copy를 붙일지 판단하는 기준&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입이 Copy가 될 수 있다고 해서 항상 붙이는 게 좋은 것은 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서도 Copy 구현은 public API의 일부라고 나와 있는데, 한번 Copy를 제공하면 그 타입을 대입해도 원래 값이 계속 유효하다고 개발자기 기대함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 Copy를 제공하면 사용자는 그 타입을 대입해도 원래 값이 계속 유효하다고 기대하게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 구조체에 String, Vec&amp;lt;T&amp;gt;, 파일 핸들 같은 non-Copy 필드가 추가되면 Copy를 유지할 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 Copy를 제거하면 기존 코드가 깨질 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Copy는 현재 컴파일을 위해 붙이기 보다는 해당 타입이 앞으로 값 타입처럼 쓰일지 생각하고 붙이는 편이 좋음.&lt;/p&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;Copy를 쓰는게 자연스러운 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좌표, 크기, 색상, 플래그처럼 작은 값&lt;/li&gt;
&lt;li&gt;숫자 기반 ID처럼 복사 의미가 단순한 값&lt;/li&gt;
&lt;li&gt;리소스 정리 책임이 없는 값&lt;/li&gt;
&lt;li&gt;대입 후에도 기존 값을 계속 쓰는 것이 자연스러운 값&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;#[derive(Copy, Clone, Debug)]
struct UserId(u64);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Clone만 두는 편이 자연스러운 경우.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;String, Vec&amp;lt;T&amp;gt;처럼 Heap 데이터를 소유하는 값&lt;/li&gt;
&lt;li&gt;파일, 소켓, 락처럼 외부 리소스와 연결된 값&lt;/li&gt;
&lt;li&gt;복제 비용이 클 수 있는 값&lt;/li&gt;
&lt;li&gt;복제 의미를 코드에서 명시적으로 보여주는 편이 좋은 값&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#[derive(Clone, Debug)]
struct Profile {
    name: String,
    tags: Vec&amp;lt;String&amp;gt;,
}
&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;Profile은 name과 tags가 Heap 데이터를 소유함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 대입할 때 자동으로 복사되면 비용도 숨겨지고 의미도 애매해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는 clone()을 명시적으로 호출하는 편이 Rust답다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-heading=&quot;정리&quot; data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;b&gt;Copy&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;b&gt;Clone&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;Copy의 경우에는 암묵적으로 일어남 (`=` 연산자)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;`clone`을 통해 명시적으로 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;Copy의 경우에는 스택에 있는 데이터 복사, 힙 데이터를 복사하는 경우는 발생하지 않음.&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;clone()을 통해 힙 영역까지 명시적 복사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;따라서 String에 대해서 스택 정보만 복사되어 소유권 이동만 발생함.&lt;br /&gt;즉, 스택 영역만 새롭게 생성되고, Heap 데이터가 연결됨. (move)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;스택과 힙에 대한 정보가 모두 복사&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-heading=&quot;출처&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-heading=&quot;출처&quot; data-ke-size=&quot;size20&quot;&gt;참고&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html&quot; data-tooltip-position=&quot;top&quot;&gt;Rust Book - What Is Ownership?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/std/marker/trait.Copy.html&quot; data-tooltip-position=&quot;top&quot;&gt;std::marker::Copy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/std/clone/trait.Clone.html&quot; data-tooltip-position=&quot;top&quot;&gt;std::clone::Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/reference/special-types-and-traits.html&quot; data-tooltip-position=&quot;top&quot;&gt;Rust Reference - Special types and traits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/book/appendix-03-derivable-traits.html&quot; data-tooltip-position=&quot;top&quot;&gt;Rust Book Appendix C - Derivable Traits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wikidocs.net/272767&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wikidocs.net/272767&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Project/개발 업무</category>
      <category>Clone</category>
      <category>copy</category>
      <category>ios</category>
      <category>ownership</category>
      <category>Rust</category>
      <category>swift</category>
      <category>소유권</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/816</guid>
      <comments>https://rldd.tistory.com/816#entry816comment</comments>
      <pubDate>Sat, 4 Jul 2026 20:04:33 +0900</pubDate>
    </item>
    <item>
      <title>Rust 소유권 (Ownership) 이해하기</title>
      <link>https://rldd.tistory.com/815</link>
      <description>&lt;h2 data-heading=&quot;Rust 소유권 (Ownership)&quot; data-ke-size=&quot;size26&quot;&gt;Rust&amp;nbsp;소유권&amp;nbsp;(Ownership)&amp;nbsp;이해하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust에서 소유권은 단순한 문법이 아니라 메모리를 관리하는 기본 규칙.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 GC가 메모리를 정리하는 방식도 아니고, 개발자가 직접 free를 호출하는 방식도 아님. 대신 컴파일 시점에 값의 owner, scope, 참조 관계를 검사해서 메모리 안전성을 보장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;목차&quot; data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rust가 메모리를 관리하는 방식&lt;/li&gt;
&lt;li&gt;소유권 규칙&lt;/li&gt;
&lt;li&gt;Stack과 Heap&lt;/li&gt;
&lt;li&gt;Move, Clone, Copy&lt;/li&gt;
&lt;li&gt;함수 호출과 소유권 이동&lt;/li&gt;
&lt;li&gt;참조와 빌림&lt;/li&gt;
&lt;li&gt;Mutable reference 규칙&lt;/li&gt;
&lt;li&gt;Dangling reference를 막는 방식&lt;/li&gt;
&lt;li&gt;Slice 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Rust가 소유권을 사용하는 이유&quot; data-ke-size=&quot;size20&quot;&gt;Rust가 소유권을 사용하는 이유&lt;/h4&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC가 있는 언어는 런타임이 더 이상 쓰지 않는 메모리를 찾아서 정리&lt;/li&gt;
&lt;li&gt;C/C++ 같은 언어는 개발자가 직접 메모리 할당과 해제를 관리&lt;/li&gt;
&lt;li&gt;Rust는 컴파일러가 소유권 규칙을 검사해서 메모리 안전성을 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust의 특징은 런타임이 아니라 컴파일 타임에 최대한 많이 확인해 소유권 규칙을 어기면 프로그램이 실행 중에 터지는 게 아니라 컴파일 자체가 되지 않음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 소유권은 Rust가 GC 없이 메모리 안전성을 얻기 위한 규칙.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Stack과 Heap&quot; data-ke-size=&quot;size20&quot;&gt;Stack과 Heap&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소유권을 이해하려면 Stack과 Heap의 차이를 먼저 알아야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Stack은 크기를 컴파일 타임에 알 수 있는 값을 빠르게 넣고 빼는 공간. 함수 호출, 지역 변수, 정수처럼 크기가 고정된 값이 주로 Stack에 올라감.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 실행 중에 크기가 달라질 수 있는 데이터를 저장하는 공간. 예를 들어 String은 문자열 길이가 실행 중에 바뀔 수 있으므로 실제 문자열 데이터는 Heap에 저장됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 Stack에는 Heap 데이터를 가리키는 포인터, 길이, 용량 같은 메타데이터가 들어감.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br1WoU/dJMb99Uvl4u/prEBesp0UKkiJv8ca4T1x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br1WoU/dJMb99Uvl4u/prEBesp0UKkiJv8ca4T1x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br1WoU/dJMb99Uvl4u/prEBesp0UKkiJv8ca4T1x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr1WoU%2FdJMb99Uvl4u%2FprEBesp0UKkiJv8ca4T1x0%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;384&quot; height=&quot;269&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;840&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-heading=&quot;소유권 규칙&quot; data-ke-size=&quot;size20&quot;&gt;소유권 규칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust Book에서는 소유권 규칙을 세 가지로 정리함.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Rust의 각 값은 owner라고 부르는 변수를 가진다.&lt;/li&gt;
&lt;li&gt;한 번에 owner는 하나만 존재할 수 있다.&lt;/li&gt;
&lt;li&gt;owner가 scope 밖으로 벗어나면 값은 버려진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 아래 코드에서 s는 String 값의 owner.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;{
    let s = String::from(&quot;hello&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;s가 scope를 벗어나는 순간 Rust는 drop을 호출해서 String이 사용하던 Heap 메모리를 정리함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 메모리를 정리하는 시점이 명확하다는 점. owner가 사라지면 값도 함께 정리됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Move&quot; data-ke-size=&quot;size20&quot;&gt;Move&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust에서는 값을 다른 변수에 대입할 때 소유권이 이동할 수 있음.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let s1 = String::from(&quot;hello&quot;);
let s2 = s1;

println!(&quot;{s2}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 경우 s1이 가지고 있던 String의 소유권이 s2로 이동함. 이후에는 s1을 사용할 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;String의 Stack 데이터에는 Heap을 가리키는 정보가 들어있음. 이 값이 단순히 복사되어 두 변수가 같은 Heap 데이터를 정리하려고 하면 double free 문제가 생길 수 있음.&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;1200&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSk1q/dJMcabxZ13r/P664kAGhCWhQVb78WUUQ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSk1q/dJMcabxZ13r/P664kAGhCWhQVb78WUUQ1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSk1q/dJMcabxZ13r/P664kAGhCWhQVb78WUUQ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSk1q%2FdJMcabxZ13r%2FP664kAGhCWhQVb78WUUQ1k%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;287&quot; height=&quot;287&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&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;br /&gt;Rust는 이런 상황을 막기 위해 s1을 더 이상 유효하지 않은 값으로 봄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 아래 코드는 컴파일되지 않음.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let s1 = String::from(&quot;hello&quot;);
let s2 = s1;

println!(&quot;{s1}&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;s1의 값은 이미 s2로 move 되었기 때문.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDtSpX/dJMb99Uvl9b/gaMrRzcMkFAkwKWRiiJYw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDtSpX/dJMb99Uvl9b/gaMrRzcMkFAkwKWRiiJYw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDtSpX/dJMb99Uvl9b/gaMrRzcMkFAkwKWRiiJYw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDtSpX%2FdJMb99Uvl9b%2FgaMrRzcMkFAkwKWRiiJYw1%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;357&quot; height=&quot;357&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-heading=&quot;Clone&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;Clone&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 데이터까지 실제로 복사하고 싶다면 clone을 사용.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let s1 = String::from(&quot;hello&quot;);
let s2 = s1.clone();

println!(&quot;{s1}, {s2}&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;clone은 명시적인 깊은 복사임. 그래서 비용이 발생할 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edX4O5/dJMcahkMzAd/RBzNkKcQeG9TffHbhoHWCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edX4O5/dJMcahkMzAd/RBzNkKcQeG9TffHbhoHWCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edX4O5/dJMcahkMzAd/RBzNkKcQeG9TffHbhoHWCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedX4O5%2FdJMcahkMzAd%2FRBzNkKcQeG9TffHbhoHWCK%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;176&quot; height=&quot;229&quot; data-origin-width=&quot;1200&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;Rust는 비용이 있는 복사를 조용히 처리하지 않음. 복사가 필요하다면 개발자가 직접 clone을 호출해서 의도를 드러내야 함.&lt;/p&gt;
&lt;h4 data-heading=&quot;Copy&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;Copy&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수처럼 Stack에만 저장되는 단순한 값은 대입 후에도 기존 변수를 계속 사용할 수 있음.&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;let x = 5;
let y = x;

println!(&quot;{x}, {y}&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;이 경우 x와 y 모두 사용 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 타입은 Copy trait을 구현함. Copy가 가능한 값은 move가 아니라 복사가 일어남.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;대표적으로 아래 타입들이 Copy 가능.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정수 타입&lt;/li&gt;
&lt;li&gt;부동소수점 타입&lt;/li&gt;
&lt;li&gt;bool&lt;/li&gt;
&lt;li&gt;char&lt;/li&gt;
&lt;li&gt;Copy 가능한 타입만 담은 tuple&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 String처럼 Heap 리소스를 관리하는 타입은 기본적으로 Copy가 아님.&lt;/p&gt;
&lt;h4 data-heading=&quot;함수와 소유권&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;함수와 소유권&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에 값을 넘길 때도 대입과 비슷하게 move 또는 copy가 일어남.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn main() {
    let s = String::from(&quot;hello&quot;);
    takes_ownership(s);
}

fn takes_ownership(value: String) {
    println!(&quot;{value}&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;s의 소유권은 takes_ownership 함수의 매개변수 value로 이동함. 함수가 끝나면 value가 scope를 벗어나고 String도 정리됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 i32 같은 Copy 타입은 함수에 넘겨도 기존 변수를 계속 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn main() {
    let x = 5;
    makes_copy(x);

    println!(&quot;{x}&quot;);
}

fn makes_copy(value: i32) {
    println!(&quot;{value}&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn main() {
    let s1 = gives_ownership();
    let s2 = String::from(&quot;hello&quot;);
    let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -&amp;gt; String {
    String::from(&quot;hello&quot;)
}

fn takes_and_gives_back(value: String) -&amp;gt; String {
    value
}
&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;그래서 Rust에는 참조와 빌림이 있음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-heading=&quot;참조와 빌림&quot; data-ke-size=&quot;size20&quot;&gt;참조와 빌림&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조는 값을 소유하지 않고 접근하는 방법.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn main() {
    let s = String::from(&quot;hello&quot;);
    let len = calculate_length(&amp;amp;s);

    println!(&quot;{s}의 길이는 {len}&quot;);
}

fn calculate_length(value: &amp;amp;String) -&amp;gt; usize {
    value.len()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;amp;s는 s의 소유권을 넘기지 않음. s를 잠깐 참조할 뿐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 소유권을 가져오지 않고 참조로 사용하는 것을 borrowing이라고 함. 한국어로는 보통 빌림이라고 표현.&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 class=&quot;cs&quot;&gt;&lt;code&gt;fn change(value: &amp;amp;String) {
    value.push_str(&quot;, world&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 코드는 컴파일되지 않음. immutable reference로는 값을 수정할 수 없기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Mutable Reference&quot; data-ke-size=&quot;size20&quot;&gt;Mutable Reference&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌린 값을 수정하려면 mutable reference를 사용해야 함.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn main() {
    let mut s = String::from(&quot;hello&quot;);

    change(&amp;amp;mut s);

    println!(&quot;{s}&quot;);
}

fn change(value: &amp;amp;mut String) {
    value.push_str(&quot;, world&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;mutable reference에는 제한이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 값에 대한 mutable reference는 동시에 하나만 만들 수 있음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let mut s = String::from(&quot;hello&quot;);

let r1 = &amp;amp;mut s;
let r2 = &amp;amp;mut s;

println!(&quot;{r1}, {r2}&quot;);
&lt;/code&gt;&lt;/pre&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;Rust는 한 시점에 하나의 mutable reference만 허용해서 같은 데이터를 동시에 수정하는 상황을 막음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;immutable reference와 mutable reference를 동시에 사용할 수도 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let mut s = String::from(&quot;hello&quot;);

let r1 = &amp;amp;s;
let r2 = &amp;amp;s;
let r3 = &amp;amp;mut s;

println!(&quot;{r1}, {r2}, {r3}&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;p data-ke-size=&quot;size16&quot;&gt;다만 참조의 scope는 마지막으로 사용되는 지점까지로 판단됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;let mut s = String::from(&quot;hello&quot;);

let r1 = &amp;amp;s;
let r2 = &amp;amp;s;

println!(&quot;{r1}, {r2}&quot;);

let r3 = &amp;amp;mut s;
println!(&quot;{r3}&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;r1, r2는 println! 이후 더 이상 사용되지 않음. 그래서 그 뒤에는 mutable reference를 만들 수 있음.&lt;/p&gt;
&lt;h4 data-heading=&quot;Dangling Reference&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;Dangling Reference&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dangling reference는 이미 사라진 값을 가리키는 참조.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C/C++에서는 잘못된 포인터 사용으로 이런 문제가 생길 수 있음. Rust는 컴파일 단계에서 dangling reference를 막음.&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn dangle() -&amp;gt; &amp;amp;String {
    let s = String::from(&quot;hello&quot;);

    &amp;amp;s
}
&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;br /&gt;s는 함수가 끝나면 사라짐. 그런데 &amp;amp;s를 반환하면 호출한 쪽은 이미 사라진 값을 참조하게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust는 이런 코드를 허용하지 않음.&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 class=&quot;rust&quot;&gt;&lt;code&gt;fn no_dangle() -&amp;gt; String {
    let s = String::from(&quot;hello&quot;);

    s
}
&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;h4 data-heading=&quot;참조 규칙&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;참조 규칙&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 규칙은 이렇게 정리할 수 있음.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 시점에 여러 immutable reference를 만들 수 있음.&lt;/li&gt;
&lt;li&gt;한 시점에 하나의 mutable reference를 만들 수 있음.&lt;/li&gt;
&lt;li&gt;immutable reference와 mutable reference는 동시에 사용할 수 없음.&lt;/li&gt;
&lt;li&gt;참조는 항상 유효해야 함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 규칙을 검사하는 것이 Rust의 borrow checker.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-heading=&quot;Slice 타입&quot; data-ke-size=&quot;size20&quot;&gt;Slice 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slice는 컬렉션 전체가 아니라 연속된 일부를 참조하는 타입.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예가 string slice인 &amp;amp;str.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;let s = String::from(&quot;hello world&quot;);

let hello = &amp;amp;s[0..5];
let world = &amp;amp;s[6..11];
&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;hello와 world는 문자열 데이터를 소유하지 않음. 기존 String의 일부를 참조할 뿐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust Book에서는 first_word 예제로 slice가 왜 필요한지 설명함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 첫 번째 단어의 끝 위치를 usize로 반환할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn first_word(s: &amp;amp;String) -&amp;gt; usize {
    let bytes = s.as_bytes();

    for (i, &amp;amp;item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}
&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;반환된 숫자는 원래 문자열과 분리되어 있음. 문자열이 바뀌면 이전에 구한 index는 더 이상 의미가 없을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;slice를 반환하면 문자열의 일부를 참조하는 값이 됨. 원본 문자열과 관계가 유지되는 셈.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;fn first_word(s: &amp;amp;String) -&amp;gt; &amp;amp;str {
    let bytes = s.as_bytes();

    for (i, &amp;amp;item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &amp;amp;s[0..i];
        }
    }

    &amp;amp;s[..]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 매개변수를 &amp;amp;String이 아니라 &amp;amp;str로 받으면 String과 string literal을 모두 받을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fn first_word(s: &amp;amp;str) -&amp;gt; &amp;amp;str {
    let bytes = s.as_bytes();

    for (i, &amp;amp;item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &amp;amp;s[0..i];
        }
    }

    &amp;amp;s[..]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;amp;str은 문자열 데이터를 소유하지 않는 참조. 문자열 전체나 일부를 가리킬 때 자연스럽게 사용됨.&lt;/p&gt;
&lt;h4 data-heading=&quot;String Literal과 Slice&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;String Literal과 Slice&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 리터럴의 타입은 &amp;amp;str.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;let s = &quot;hello&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;s는 프로그램 바이너리에 저장된 문자열 데이터를 참조함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 문자열 리터럴은 기본적으로 immutable.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-heading=&quot;다른 Slice&quot; data-ke-size=&quot;size23&quot;&gt;다른 Slice&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slice는 문자열에만 있는 개념이 아님. 배열에서도 slice를 만들 수 있음.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;let a = [1, 2, 3, 4, 5];
let slice = &amp;amp;a[1..3];
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이때 slice의 타입은 &amp;amp;[i32].&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 slice는 어떤 컬렉션의 일부를 소유하지 않고 참조하는 방식.&lt;/p&gt;
&lt;h3 data-heading=&quot;정리&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-heading=&quot;정리&quot; data-ke-size=&quot;size20&quot;&gt;정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rust의 소유권은 값을 누가 책임지는지 정하는 규칙.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소유권 덕분에 Rust는 값이 언제 정리되어야 하는지 알 수 있음. move는 하나의 Heap 데이터를 여러 owner가 동시에 정리하는 문제를 막고, borrowing은 소유권을 넘기지 않고도 값을 읽거나 수정할 수 있게 해줌.&lt;/p&gt;
&lt;h4 data-heading=&quot;용어 정리&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;용어 정리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ownership: 값을 정리할 책임을 가진 상태&lt;/li&gt;
&lt;li&gt;owner: 값을 소유한 변수&lt;/li&gt;
&lt;li&gt;move: 소유권이 다른 변수나 함수로 이동하는 것&lt;/li&gt;
&lt;li&gt;borrow: 소유권을 가져오지 않고 참조로 빌려 쓰는 것&lt;/li&gt;
&lt;li&gt;immutable reference: 읽기 전용 참조&lt;/li&gt;
&lt;li&gt;mutable reference: 수정 가능한 참조&lt;/li&gt;
&lt;li&gt;dangling reference: 이미 사라진 값을 가리키는 참조&lt;/li&gt;
&lt;li&gt;slice: 컬렉션의 일부를 소유하지 않고 참조하는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-heading=&quot;참고&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-heading=&quot;참고&quot; data-ke-size=&quot;size20&quot;&gt;참고&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-lang/book/blob/main/src/ch04-00-understanding-ownership.md&quot; data-tooltip-position=&quot;top&quot;&gt;ch04-00-understanding-ownership.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-lang/book/blob/main/src/ch04-01-what-is-ownership.md&quot; data-tooltip-position=&quot;top&quot;&gt;ch04-01-what-is-ownership.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md&quot; data-tooltip-position=&quot;top&quot;&gt;ch04-02-references-and-borrowing.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md&quot; data-tooltip-position=&quot;top&quot;&gt;ch04-03-slices.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Project/개발 업무</category>
      <category>ownership</category>
      <category>Rust</category>
      <category>swift</category>
      <category>소유권</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/815</guid>
      <comments>https://rldd.tistory.com/815#entry815comment</comments>
      <pubDate>Sat, 4 Jul 2026 13:49:03 +0900</pubDate>
    </item>
    <item>
      <title>What's new in SwiftUI (WWDC26)</title>
      <link>https://rldd.tistory.com/814</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;What's new in SwiftUI (WWDC26)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Refreshed look and feel&lt;/li&gt;
&lt;li&gt;Document-based apps&lt;/li&gt;
&lt;li&gt;Presentation and interaction&lt;/li&gt;
&lt;li&gt;Data flow and performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;Refreshed look and feel&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새롭게 앱을 빌드하고 실행하면 Liquid Glass 디자인이 업데이트 된 모습으로 자동 적용&lt;br /&gt;&lt;br /&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/mnYN7/dJMcacw3Qc1/VZfUEUjjpFd2CE3MAc13WK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnYN7/dJMcacw3Qc1/VZfUEUjjpFd2CE3MAc13WK/img.jpg&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;724&quot; data-is-animation=&quot;false&quot; style=&quot;width: 59.863%; margin-right: 10px;&quot; data-widthpercent=&quot;60.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnYN7/dJMcacw3Qc1/VZfUEUjjpFd2CE3MAc13WK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmnYN7%2FdJMcacw3Qc1%2FVZfUEUjjpFd2CE3MAc13WK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1680&quot; height=&quot;724&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMQPjq/dJMcacw3Qc2/bpvUFr6AQFWWK5lwENtK2k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMQPjq/dJMcacw3Qc2/bpvUFr6AQFWWK5lwENtK2k/img.jpg&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;838&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.9742%;&quot; data-widthpercent=&quot;39.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMQPjq/dJMcacw3Qc2/bpvUFr6AQFWWK5lwENtK2k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMQPjq%2FdJMcacw3Qc2%2FbpvUFr6AQFWWK5lwENtK2k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;838&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;br /&gt;appearsActive를 사용해서 조건부로 버튼의 불투명도를 조절할 수 있음.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9D0sY/dJMcad3JDw2/G0438QaYCKdz82slDrdEk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9D0sY/dJMcad3JDw2/G0438QaYCKdz82slDrdEk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9D0sY/dJMcad3JDw2/G0438QaYCKdz82slDrdEk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9D0sY%2FdJMcad3JDw2%2FG0438QaYCKdz82slDrdEk0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;208&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Xcode 27부터는 Preview에 리사이징이 가능하도록 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI로 만든 앱은 이런 기능을 자동으로 지원하지만 UIKit과 SwiftUI를 모두 사용하고 있다면 고려해야할 사항 존재.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqLWOZ/dJMcadby0fy/MkRyWQJufzKKbPYaeoCEc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqLWOZ/dJMcadby0fy/MkRyWQJufzKKbPYaeoCEc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqLWOZ/dJMcadby0fy/MkRyWQJufzKKbPYaeoCEc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqLWOZ%2FdJMcadby0fy%2FMkRyWQJufzKKbPYaeoCEc0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;455&quot; height=&quot;254&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Screen Geometry 확인, 디바이스 대신에 size class 사용, 화면 회전 대응 등이 추가되는데 WWDC26의 Modernize your UIKit app 확인&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIRf1a/dJMcad3JDw6/nAHyGcIRm6vmGoQwGXEzi0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIRf1a/dJMcad3JDw6/nAHyGcIRm6vmGoQwGXEzi0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIRf1a/dJMcad3JDw6/nAHyGcIRm6vmGoQwGXEzi0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIRf1a%2FdJMcad3JDw6%2FnAHyGcIRm6vmGoQwGXEzi0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;367&quot; height=&quot;194&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;916&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;우측 하단에 새로운 탭 형태가 생겼고, 띄워서 배치하려면 role을 prominent로 지정.&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;1744&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8ZmCO/dJMcadby0fD/H0bUaLNrUKChpvUmO48TZ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8ZmCO/dJMcadby0fD/H0bUaLNrUKChpvUmO48TZ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8ZmCO/dJMcadby0fD/H0bUaLNrUKChpvUmO48TZ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8ZmCO%2FdJMcadby0fD%2FH0bUaLNrUKChpvUmO48TZ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;198&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;592&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;앱의 윈도우 크기를 조정하면 Toolbar 아이템이 시스템에 의해서 자동으로 조정되며, 공간이 부족할 때 어떤 버튼을 계속 표시할 지 여부도 지정할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visibilityPriority를 높이므로써 공간이 좁아져도 가시성 있게 유지할 수 있음.&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;1780&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3gYun/dJMcad3JDw9/mddQOiP12oCant3c0lsyPK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3gYun/dJMcad3JDw9/mddQOiP12oCant3c0lsyPK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3gYun/dJMcad3JDw9/mddQOiP12oCant3c0lsyPK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3gYun%2FdJMcad3JDw9%2FmddQOiP12oCant3c0lsyPK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;239&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;676&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;반대로 숨기고 싶다면 ToolbarOverflowMenu를 사용하면 됨.&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;1806&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWRWjH/dJMcadby0fJ/LFjIIY1PPGvum0G2YMWApK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWRWjH/dJMcadby0fJ/LFjIIY1PPGvum0G2YMWApK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWRWjH/dJMcadby0fJ/LFjIIY1PPGvum0G2YMWApK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWRWjH%2FdJMcadby0fJ%2FLFjIIY1PPGvum0G2YMWApK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;205&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;714&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;절대로 숨기고 싶지 않은 버튼의 경우에는 placement에 topBarPinned를 지정하면 됨.&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/dpkcW7/dJMcadby0fL/eTkpgNujHpXZkD02X3n5fk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpkcW7/dJMcadby0fL/eTkpgNujHpXZkD02X3n5fk/img.jpg&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;488&quot; data-is-animation=&quot;false&quot; width=&quot;599&quot; height=&quot;161&quot; data-widthpercent=&quot;52.88&quot; style=&quot;width: 52.2634%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpkcW7/dJMcadby0fL/eTkpgNujHpXZkD02X3n5fk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpkcW7%2FdJMcadby0fL%2FeTkpgNujHpXZkD02X3n5fk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1818&quot; height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyALAv/dJMcad3JDxd/fv6qLAp3VCdmFOyfgGz631/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyALAv/dJMcad3JDxd/fv6qLAp3VCdmFOyfgGz631/img.jpg&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;544&quot; data-is-animation=&quot;false&quot; style=&quot;width: 46.5738%;&quot; data-widthpercent=&quot;47.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyALAv/dJMcad3JDxd/fv6qLAp3VCdmFOyfgGz631/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyALAv%2FdJMcad3JDxd%2Ffv6qLAp3VCdmFOyfgGz631%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1806&quot; height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;toolbarMinimizeBehavior를 사용하면 스크롤 시 네비게이션 바를 시스템이 자동으로 숨길 수 있음.&lt;br /&gt;&lt;br /&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/cU1D70/dJMcad3JDxg/hEK6SDvEHw2A75AHmnk9M0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU1D70/dJMcad3JDxg/hEK6SDvEHw2A75AHmnk9M0/img.jpg&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;984&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.2271%; margin-right: 10px;&quot; data-widthpercent=&quot;44.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU1D70/dJMcad3JDxg/hEK6SDvEHw2A75AHmnk9M0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU1D70%2FdJMcad3JDxg%2FhEK6SDvEHw2A75AHmnk9M0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGDa4O/dJMcad3JDxj/bZ3wYn0MadKTibTexuXEMk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGDa4O/dJMcad3JDxj/bZ3wYn0MadKTibTexuXEMk/img.jpg&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;812&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.6101%;&quot; data-widthpercent=&quot;55.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGDa4O/dJMcad3JDxj/bZ3wYn0MadKTibTexuXEMk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGDa4O%2FdJMcad3JDxj%2FbZ3wYn0MadKTibTexuXEMk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1830&quot; height=&quot;812&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 Document 기반 앱을 지원했으나 이번 iOS27부터 더 확장된 API를 제공. DocumentGroupLaunchScene에 source를 통해 버튼을 지정하고 DocumemtGroup의 context를 클로저를 통해 받아서 해당 source에 따라서 초기표현할 UI를 결정할 수 있음.&lt;/p&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;1810&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn9vKb/dJMcadby0fU/HyIbixvGQ8gd8phORz6nxK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn9vKb/dJMcadby0fU/HyIbixvGQ8gd8phORz6nxK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn9vKb/dJMcadby0fU/HyIbixvGQ8gd8phORz6nxK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn9vKb%2FdJMcadby0fU%2FHyIbixvGQ8gd8phORz6nxK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;116&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;문서 기반앱은 특히 Performance 중 데이터를 읽고 쓰는 작업이 많음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Document 앱은 우선 View 영역에서 @Observable 매크로를 이용해 의존하는 프로퍼티가 변경될 때만 View를 업데이트 해서 이것 만으로도 View 성능을 향상.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 문서 기반 앱은 읽기와 쓰기 성능을 개선하는게 가장 중요함.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDBWsw/dJMcacw3Qdw/AIEe8ugSgBpRUiJKHnA4Q0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDBWsw/dJMcacw3Qdw/AIEe8ugSgBpRUiJKHnA4Q0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDBWsw/dJMcacw3Qdw/AIEe8ugSgBpRUiJKHnA4Q0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDBWsw%2FdJMcacw3Qdw%2FAIEe8ugSgBpRUiJKHnA4Q0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;251&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;826&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;성능 향상을 위해 WritableDocument를 채택&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;writableContentTypes 변수를 정의해야 하는데 커스텀 한 타입으로 지정 가능.&lt;/li&gt;
&lt;li&gt;쓰기용으로 현재 문서 콘텐츠를 반환하는 snapshot 메서드로 콘텐츠를 나타내기 위해 PageSnapshot 구조체를 사용.&lt;/li&gt;
&lt;li&gt;writer를 작성하는데, 지정된 형식으로 디스크에 쓰는 방법에 대한 정의&lt;/li&gt;
&lt;/ol&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;1806&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R3Gax/dJMcacw3Qdz/xnFOrjBYTySYcD3fMQk151/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R3Gax/dJMcacw3Qdz/xnFOrjBYTySYcD3fMQk151/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R3Gax/dJMcacw3Qdz/xnFOrjBYTySYcD3fMQk151/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR3Gax%2FdJMcacw3Qdz%2FxnFOrjBYTySYcD3fMQk151%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;298&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;820&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;DocumentWriter 프로토콜에는 Snapshot이라는 개념이 존재하는데, 유일한 요구사항은 write 메서드를 작성하여 성능을 최적화 할 수 있는 여러 기회를 제공함.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;nonisolated asnyc로 비동기 메서드로 백그라운드에서 실행&lt;/li&gt;
&lt;li&gt;실제로 업데이트 필요한 부분만 작성하는데 previous와 snapshot을 비교하여 결정&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이런 모든 최적화에도 불구하고 시간이 오래 걸릴수도 있어서 process 를 통해 SwiftUI가 현재 쓰기 작업에 대해 진행경과를 확인할 수 있음.&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;1836&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8daKS/dJMcadby0fZ/aL3G8U7NbOTrVQvJblcDK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8daKS/dJMcadby0fZ/aL3G8U7NbOTrVQvJblcDK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8daKS/dJMcadby0fZ/aL3G8U7NbOTrVQvJblcDK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8daKS%2FdJMcadby0fZ%2FaL3G8U7NbOTrVQvJblcDK1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;310&quot; data-origin-width=&quot;1836&quot; data-origin-height=&quot;846&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;ReadableDocuments를 통해서 DocumentWriter의 쌍둥이 프로토콜로 읽기를 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;각 프로토콜은 지원퇴는 콘텐츠 유형에 대한 타입이 필요하며, Writer는 Snapshot을 제공하고 Readble은 apply를 통해 대칭됨. 또한, Writer 프로토콜은 write 메서드를 통해 Readable 프로토콜은 read를 통해 대칭됨.&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/bEPlHs/dJMcacw3QdG/Lh5Vx2q7ZZfzgoKVkiKUf0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEPlHs/dJMcacw3QdG/Lh5Vx2q7ZZfzgoKVkiKUf0/img.jpg&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;552&quot; data-is-animation=&quot;false&quot; style=&quot;width: 60.8082%; margin-right: 10px;&quot; data-widthpercent=&quot;61.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEPlHs/dJMcacw3QdG/Lh5Vx2q7ZZfzgoKVkiKUf0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEPlHs%2FdJMcacw3QdG%2FLh5Vx2q7ZZfzgoKVkiKUf0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1814&quot; height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHNKWT/dJMcad3JDxu/zTt59Adqbk8fdzG4e6Oyi1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHNKWT/dJMcad3JDxu/zTt59Adqbk8fdzG4e6Oyi1/img.jpg&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;906&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.029%;&quot; data-widthpercent=&quot;38.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHNKWT/dJMcad3JDxu/zTt59Adqbk8fdzG4e6Oyi1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHNKWT%2FdJMcad3JDxu%2FzTt59Adqbk8fdzG4e6Oyi1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;906&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;또한 앱이 없는 사람들과도 공유할 수 있도록 페이지를 이미지로 저장하는 기능도 제공 CoreGraphics를 활용해 PNG로 변환하는 것처럼 페이지를 다른 타입으로 저장할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적인 형식을 지원하기 위해 ContentType을 추가하고, 앱이 지원하는 형식에 대해서 확인하고 PNG 형식으로 만들기 위해서 CoreGraphics를 활용해 스티커랑 배경 사진을 하나의 이미지로 만들고 최종적으로 결과물을 만들어 냄.&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/Tb8ni/dJMcacw3QdK/opjKAdvNTmyKvf5xjn1Hm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tb8ni/dJMcacw3QdK/opjKAdvNTmyKvf5xjn1Hm0/img.jpg&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;648&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.9541%; margin-right: 10px;&quot; data-widthpercent=&quot;48.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tb8ni/dJMcacw3QdK/opjKAdvNTmyKvf5xjn1Hm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTb8ni%2FdJMcacw3QdK%2FopjKAdvNTmyKvf5xjn1Hm0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1840&quot; height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HPm02/dJMcadby0f8/GyqakhhpesMdb5e0SsK0Wk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HPm02/dJMcadby0f8/GyqakhhpesMdb5e0SsK0Wk/img.jpg&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;618&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.8831%;&quot; data-widthpercent=&quot;51.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HPm02/dJMcadby0f8/GyqakhhpesMdb5e0SsK0Wk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHPm02%2FdJMcadby0f8%2FGyqakhhpesMdb5e0SsK0Wk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;618&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;p data-ke-size=&quot;size16&quot;&gt;reorderable modifier를 추가하고 reorderContainer에 apply를 적용하여 손쉽게 순서 변경을 지원할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply 메서드는 내부적으로 오픈소스은 swift-collection 패키지를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reorderable API는 List 뿐만 아니라 LazyVGrid 등 모든 컨테이너에서 동작함.&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;1770&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djri78/dJMcacw3QdQ/KPqVBLME3DxGsHAn9FOFJK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djri78/dJMcacw3QdQ/KPqVBLME3DxGsHAn9FOFJK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djri78/dJMcacw3QdQ/KPqVBLME3DxGsHAn9FOFJK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjri78%2FdJMcacw3QdQ%2FKPqVBLME3DxGsHAn9FOFJK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;288&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;1014&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;SwiftUI는 이제 List 뿐만 아니라 모든 View에서 Swipe 동작을 지원하며, swipeActionContiner를 modifier로 추가하면 됨.&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/efyhEr/dJMcad3JDxE/3sM0EMVgNNywwePekCukpk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efyhEr/dJMcad3JDxE/3sM0EMVgNNywwePekCukpk/img.jpg&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;1026&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.3372%; margin-right: 10px;&quot; data-widthpercent=&quot;50.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efyhEr/dJMcad3JDxE/3sM0EMVgNNywwePekCukpk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefyhEr%2FdJMcad3JDxE%2F3sM0EMVgNNywwePekCukpk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1772&quot; height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDScV/dJMcad3JDxH/FB6ETxyErLP1364pFx8wPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDScV/dJMcad3JDxH/FB6ETxyErLP1364pFx8wPk/img.jpg&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1024&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.5%;&quot; data-widthpercent=&quot;49.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDScV/dJMcad3JDxH/FB6ETxyErLP1364pFx8wPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDScV%2FdJMcad3JDxH%2FFB6ETxyErLP1364pFx8wPk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1704&quot; height=&quot;1024&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;만약 특정 항목만 제거하고 싶은 경우에는 contextMenu를 통해 삭제 아이템에 배치한 후 confirmationDialog를 통해서 삭제 동작을 정의할 수 있고, 이는 .alert 대체 가능&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;1694&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXt1v/dJMcad3JDxJ/oy5sJbPaKuOjIexfARvFkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXt1v/dJMcad3JDxJ/oy5sJbPaKuOjIexfARvFkk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXt1v/dJMcad3JDxJ/oy5sJbPaKuOjIexfARvFkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXt1v%2FdJMcad3JDxJ%2Foy5sJbPaKuOjIexfARvFkk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;247&quot; data-origin-width=&quot;1694&quot; data-origin-height=&quot;670&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&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/xXl5T/dJMcad3JDxK/gXHaKZaCrFQJ3Lh3nacvSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xXl5T/dJMcad3JDxK/gXHaKZaCrFQJ3Lh3nacvSK/img.jpg&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;712&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.8588%; margin-right: 10px;&quot; data-widthpercent=&quot;55.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xXl5T/dJMcad3JDxK/gXHaKZaCrFQJ3Lh3nacvSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxXl5T%2FdJMcad3JDxK%2FgXHaKZaCrFQJ3Lh3nacvSK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1822&quot; height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coEc1l/dJMcacw3QdR/OeZ98K6MWN6G2vTkVfdI0K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coEc1l/dJMcacw3QdR/OeZ98K6MWN6G2vTkVfdI0K/img.jpg&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;894&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.9784%;&quot; data-widthpercent=&quot;44.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coEc1l/dJMcacw3QdR/OeZ98K6MWN6G2vTkVfdI0K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoEc1l%2FdJMcacw3QdR%2FOeZ98K6MWN6G2vTkVfdI0K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1834&quot; height=&quot;894&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;AsyncImage가 표준 HTTP 캐싱을 지원(서버의 캐시 헤더)하며 기본적으로 이미지가 캐싱됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 다운로드 방식을 더 세밀하게 제어하려면 URLRequest를 직접 만들어서 더 세밀하게 제어할 수 있음. 이를 통해서 요청별로 다양한 커스터마이즈가 가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 URLSeesion을 직접 만들어서 필요한 용량으로 URLCache를 설정할 수도 있음. 이 경우 .asyncImageURLSession modifer를 통해서 전달&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;1852&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E8jlA/dJMcad3JDxM/8T2vPq2h1mSOClxktclGO0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E8jlA/dJMcad3JDxM/8T2vPq2h1mSOClxktclGO0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E8jlA/dJMcad3JDxM/8T2vPq2h1mSOClxktclGO0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE8jlA%2FdJMcad3JDxM%2F8T2vPq2h1mSOClxktclGO0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;223&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;682&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;SwiftUI는 앱이 데이터를 모델링하고 저장하는 다양한 방법을 제공하는데 앱 데이터를 저장하는 가장 좋은 방법은 Observable 클래스를 사용하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 26까지는 View가 초기화 되면 store가 새로운 인스턴스를 생성. 기존 인스턴스가 여전히 @State를 통해 저장되고 있음에도 불구하고 버려지기 때문에 성능이 좋지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, iOS 27 부터는 State 프로퍼티를 사용하여 초기화되고, 저장되는 클래스가 lazy하게 동작함. (이제 한번만 초기화 됨)&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/EQU9V/dJMcad3JDxN/LQrOse2digbtg9RtMgwmmK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EQU9V/dJMcad3JDxN/LQrOse2digbtg9RtMgwmmK/img.jpg&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;782&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.475%; margin-right: 10px;&quot; data-widthpercent=&quot;50.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EQU9V/dJMcad3JDxN/LQrOse2digbtg9RtMgwmmK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEQU9V%2FdJMcad3JDxN%2FLQrOse2digbtg9RtMgwmmK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1860&quot; height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqplcs/dJMcacw3QdS/XS02e5qnkBLaVTcNpexJDk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqplcs/dJMcacw3QdS/XS02e5qnkBLaVTcNpexJDk/img.jpg&quot; data-origin-width=&quot;1870&quot; data-origin-height=&quot;788&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.3622%;&quot; data-widthpercent=&quot;49.94&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqplcs/dJMcacw3QdS/XS02e5qnkBLaVTcNpexJDk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdqplcs%2FdJMcacw3QdS%2FXS02e5qnkBLaVTcNpexJDk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1870&quot; height=&quot;788&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;이는 @State가 Dynamic Property에서 Macro로 전환되었기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이제부터 초기화 이후에는 새 클래스 인스턴스가 생성되지 않으며, 해당 동작은 하위 호환되어 iOS 17부터 지원하는 것으로 백포팅되었음.&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/by7qrh/dJMcacw3QdT/1Tn3mTGZZwb7MdPbMa2TD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by7qrh/dJMcacw3QdT/1Tn3mTGZZwb7MdPbMa2TD1/img.jpg&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;768&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.0367%; margin-right: 10px;&quot; data-widthpercent=&quot;50.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by7qrh/dJMcacw3QdT/1Tn3mTGZZwb7MdPbMa2TD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby7qrh%2FdJMcacw3QdT%2F1Tn3mTGZZwb7MdPbMa2TD1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1856&quot; height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rkLzf/dJMcad3JDxV/amOjT5zzum2WafRh98b3GK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rkLzf/dJMcad3JDxV/amOjT5zzum2WafRh98b3GK/img.jpg&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;790&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.8005%;&quot; data-widthpercent=&quot;49.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rkLzf/dJMcad3JDxV/amOjT5zzum2WafRh98b3GK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrkLzf%2FdJMcad3JDxV%2FamOjT5zzum2WafRh98b3GK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1862&quot; height=&quot;790&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;하지만 이로 인해서 init에서 동일한 @State 변수에 값을 할당할 경우 Xcode가 초기화 전 사용할 수 없다는 오류를 발생.&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;1842&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oVWjG/dJMcacw3QdU/iHXlO1yB58WSy0mPKiBV8K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oVWjG/dJMcacw3QdU/iHXlO1yB58WSy0mPKiBV8K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oVWjG/dJMcacw3QdU/iHXlO1yB58WSy0mPKiBV8K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoVWjG%2FdJMcacw3QdU%2FiHXlO1yB58WSy0mPKiBV8K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;633&quot; height=&quot;204&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;594&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;br /&gt;View가 깊게 중첩되어 있다면 컴파일러가 오류를 발생 시키는 오류를 만날 수 있음.&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;1896&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpWOC/dJMcad3JDxX/rVNANG7YJp54gPUXairwj1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpWOC/dJMcad3JDxX/rVNANG7YJp54gPUXairwj1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpWOC/dJMcad3JDxX/rVNANG7YJp54gPUXairwj1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxpWOC%2FdJMcad3JDxX%2FrVNANG7YJp54gPUXairwj1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;278&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;914&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;&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;1866&quot; data-origin-height=&quot;1034&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWiPdl/dJMcacw3QdV/34D5wMDnblHfZnr8OmLz30/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWiPdl/dJMcacw3QdV/34D5wMDnblHfZnr8OmLz30/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWiPdl/dJMcacw3QdV/34D5wMDnblHfZnr8OmLz30/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWiPdl%2FdJMcacw3QdV%2F34D5wMDnblHfZnr8OmLz30%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;300&quot; data-origin-width=&quot;1866&quot; data-origin-height=&quot;1034&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;가장 일반적인 Builder 집합이 이제 단일 initializer를 공유하여 단 하나의 명확한 경로만 남김.&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;1828&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhJyuL/dJMcac4RDGJ/efF4XPzOUa2payZFpnUYJ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhJyuL/dJMcac4RDGJ/efF4XPzOUa2payZFpnUYJ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhJyuL/dJMcac4RDGJ/efF4XPzOUa2payZFpnUYJ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhJyuL%2FdJMcac4RDGJ%2FefF4XPzOUa2payZFpnUYJ0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;179&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;546&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;@ContentBuilder를 통해 단일 빌더를 위해 여러 다른 빌더의 유형을 하나로 통합. SwiftUI API 전반에 걸쳐 통합된 Builder를 지향하는 첫 걸음으로 기존 ViewBuilder를 내부적인 개선으로 모든 SwiftUI 버전에서 사용할 수 있음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ContentBuilder는 SwiftUI로 된 앱을 빌드할 때 빌드 성능을 크게 향상시킴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=s8BB8q2MyXc&quot;&gt;https://www.youtube.com/watch?v=s8BB8q2MyXc&lt;/a&gt;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>ios</category>
      <category>swift</category>
      <category>SwiftUI</category>
      <category>WWDC</category>
      <category>WWDC26</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/814</guid>
      <comments>https://rldd.tistory.com/814#entry814comment</comments>
      <pubDate>Thu, 2 Jul 2026 00:14:23 +0900</pubDate>
    </item>
    <item>
      <title>UIKit 앱 현대화하기 (Modernize your UIKit app) - WWDC26</title>
      <link>https://rldd.tistory.com/813</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;UIKit&amp;nbsp;앱&amp;nbsp;현대화하기&amp;nbsp;(Modernize&amp;nbsp;your&amp;nbsp;UIKit&amp;nbsp;app)&amp;nbsp;-&amp;nbsp;WWDC26&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Adaptivity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이폰 앱에서 새롭게 적용되는 적응형 레이아웃 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Bar and menus
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;탭 바, 네비게이션 바, 메뉴 등 새로운 API 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Apple Intelligence&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;새로운 기능과 몇가지 변경사항 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;Agentic coding
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;에이전트가 도와주는 새로운 스킬 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Adaptivity&lt;/h4&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/blepxo/dJMcadvIGci/JGZ1D4djDGLiaKQRcZVi40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blepxo/dJMcadvIGci/JGZ1D4djDGLiaKQRcZVi40/img.png&quot; data-origin-width=&quot;1214&quot; data-origin-height=&quot;880&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.33.09.png&quot; style=&quot;width: 49.4285%; margin-right: 10px;&quot; data-widthpercent=&quot;50.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blepxo/dJMcadvIGci/JGZ1D4djDGLiaKQRcZVi40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblepxo%2FdJMcadvIGci%2FJGZ1D4djDGLiaKQRcZVi40%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;1214&quot; height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjUQy/dJMcag0j2gW/OPSnUsJAS4nOXs6YT0f7c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjUQy/dJMcag0j2gW/OPSnUsJAS4nOXs6YT0f7c1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;876&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.32.49.png&quot; data-widthpercent=&quot;49.99&quot; style=&quot;width: 49.4088%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjUQy/dJMcag0j2gW/OPSnUsJAS4nOXs6YT0f7c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjUQy%2FdJMcag0j2gW%2FOPSnUsJAS4nOXs6YT0f7c1%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;876&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;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-filename=&quot;스크린샷 2026-07-01 오후 10.39.24.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXdy3/dJMcagMUt7I/gVS7jwIXLoB6MBkYU9sL2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXdy3/dJMcagMUt7I/gVS7jwIXLoB6MBkYU9sL2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXdy3/dJMcagMUt7I/gVS7jwIXLoB6MBkYU9sL2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXdy3%2FdJMcagMUt7I%2FgVS7jwIXLoB6MBkYU9sL2k%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;768&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.39.24.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;768&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;App lifecycle
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적응형 앱의 기반을 위해서는 App LifeCycle에서 Scene 라이프사이클이 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Main Screen
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UIScreen.main 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User interface idiom&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pad 혹은 phone 형태의 레이아웃 선택 x&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;interface orientation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스 방향 확인을 처리하는 방법도 다룸&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.48.16.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZc5zK/dJMb99NEKck/bNzss5qEtGlqDEXgbnkxI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZc5zK/dJMb99NEKck/bNzss5qEtGlqDEXgbnkxI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZc5zK/dJMb99NEKck/bNzss5qEtGlqDEXgbnkxI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZc5zK%2FdJMb99NEKck%2FbNzss5qEtGlqDEXgbnkxI1%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;1822&quot; height=&quot;998&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.48.16.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;998&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;UIScene 라이프사이클은 최신 SDK로 빌드시 필수로 해당 작업 없이는 이제 더이상 앱이 실행되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.50.57.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lg1Qv/dJMcahkKpJs/ES3sr9pkXc932HCV7sbHv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lg1Qv/dJMcahkKpJs/ES3sr9pkXc932HCV7sbHv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lg1Qv/dJMcahkKpJs/ES3sr9pkXc932HCV7sbHv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flg1Qv%2FdJMcahkKpJs%2FES3sr9pkXc932HCV7sbHv1%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;1850&quot; height=&quot;624&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.50.57.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;624&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;iPhone이 Mac에서 미러링되거나 혹은 iPad의 앱을 다른 외부 디스플레이로 이동하는 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 리사이징이 가능한 환경에서는 UIScreen.main에 대한 사용은 앱이 실행되는 환경에 대해 잘못된 정보를 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIScreen.main을 참조하는 대신 window의 scene으로 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Screen을 전역으로 참조하는 유틸리티 함수가 존재한다면 UIScreen을 파라미터로 받는 형태로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.59.55.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lIjoq/dJMcadCsMwK/jFx1sfbjtagWdB2t9X1jdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lIjoq/dJMcadCsMwK/jFx1sfbjtagWdB2t9X1jdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lIjoq/dJMcadCsMwK/jFx1sfbjtagWdB2t9X1jdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlIjoq%2FdJMcadCsMwK%2FjFx1sfbjtagWdB2t9X1jdk%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;1814&quot; height=&quot;986&quot; data-filename=&quot;스크린샷 2026-07-01 오후 10.59.55.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;986&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;다만, 가장 좋은 방법은 UIScreen에 대한 참조를 완전히 제거하고 적응형(반응형) 앱에 더 적합한 다른 API를 사용할 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 배율에 접근하는 코드를 예로 들자면 trait collection의 displayScale으로 대체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View와 ViewController는 자동으로 trait collection을 업데이트하고 Visible한 계층이 아닌 경우에도 자동으로 fallback을 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, override되는 모든 지점들도 추적하고 있어서 변경사항을 명시적으로 업데이트할 필요가 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;layoutSubviews, updateProperties와 같은 레이아웃 및 draw 메서드 등 자동으로 추적됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.01.24.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq3wLk/dJMcabLCqO8/awKgqflpUAKoLl0XPjqeE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq3wLk/dJMcabLCqO8/awKgqflpUAKoLl0XPjqeE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq3wLk/dJMcabLCqO8/awKgqflpUAKoLl0XPjqeE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq3wLk%2FdJMcabLCqO8%2FawKgqflpUAKoLl0XPjqeE0%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;1796&quot; height=&quot;998&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.01.24.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;998&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;해당 메서드를 통해 특정 trait이 변경될 때 실행한 내용을 클로저에 정의할 수 있음,&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/cjrIYe/dJMcaff8Z1K/HHQKkFObEAB74bMBNRev80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjrIYe/dJMcaff8Z1K/HHQKkFObEAB74bMBNRev80/img.png&quot; data-origin-width=&quot;1780&quot; data-origin-height=&quot;962&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.03.32.png&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.6765%; margin-right: 10px;&quot; data-widthpercent=&quot;41.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjrIYe/dJMcaff8Z1K/HHQKkFObEAB74bMBNRev80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjrIYe%2FdJMcaff8Z1K%2FHHQKkFObEAB74bMBNRev80%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;1780&quot; height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwLKjs/dJMcafApL1Y/D4lUTyvIuSJZKxcOvlUzHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwLKjs/dJMcafApL1Y/D4lUTyvIuSJZKxcOvlUzHK/img.png&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.06.10.png&quot; data-origin-height=&quot;666&quot; data-origin-width=&quot;1762&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.1607%;&quot; data-widthpercent=&quot;58.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwLKjs/dJMcafApL1Y/D4lUTyvIuSJZKxcOvlUzHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwLKjs%2FdJMcafApL1Y%2FD4lUTyvIuSJZKxcOvlUzHK%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;1762&quot; height=&quot;666&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;Scene이 항상 전체 화면이 아니기 때문에 view의 bounds나 scene의 geometry를 통해서 코드를 변경해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`UIWindowScene.effectiveGeometry`는 앱이 사용할 수 있는 공간에 대한 정보를 제공하며 geometry에 대한 변경 감지는 UIWindowSCeneDelegate의 didUpdateEffectiveGeometry을 구현함으로써 확인할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SplitView를 사용할 때 내부에서 화면이 나타나는 경우가 있어서 view.bounds로 변경하면 레이아웃을 더 동적으로 안전하게 사용할 수 있음&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/KMfVO/dJMcaiKBBNG/8i3K7pKn5oUCgfqnQVcQ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KMfVO/dJMcaiKBBNG/8i3K7pKn5oUCgfqnQVcQ61/img.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;720&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.07.47.png&quot; style=&quot;width: 48.348%; margin-right: 10px;&quot; data-widthpercent=&quot;48.92&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KMfVO/dJMcaiKBBNG/8i3K7pKn5oUCgfqnQVcQ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKMfVO%2FdJMcaiKBBNG%2F8i3K7pKn5oUCgfqnQVcQ61%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;1766&quot; height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnWbeW/dJMcacjkgAG/rZLn9i6geKWqKZo6ALe531/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnWbeW/dJMcacjkgAG/rZLn9i6geKWqKZo6ALe531/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;684&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.08.34.png&quot; style=&quot;width: 50.4892%;&quot; data-widthpercent=&quot;51.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnWbeW/dJMcacjkgAG/rZLn9i6geKWqKZo6ALe531/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnWbeW%2FdJMcacjkgAG%2FrZLn9i6geKWqKZo6ALe531%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;1752&quot; height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;user interface idiom은 더이상 레이아웃을 결정하는데 의미가 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미러링이 가능한 환경에서는 리사이징이 가능하기 때문에 `size class`를 활용하고 idiom에 관계 없이 공간을 활용해야 함.&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/VppVX/dJMcahdWGtA/NadJFNzitICOK0cdudypr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VppVX/dJMcahdWGtA/NadJFNzitICOK0cdudypr0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;666&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.13.20.png&quot; style=&quot;width: 29.2535%; margin-right: 10px;&quot; data-widthpercent=&quot;29.95&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VppVX/dJMcahdWGtA/NadJFNzitICOK0cdudypr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVppVX%2FdJMcahdWGtA%2FNadJFNzitICOK0cdudypr0%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;1818&quot; height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDCdag/dJMcagMUuJA/fd3JLAdXlaT0KA0rdJVKuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDCdag/dJMcagMUuJA/fd3JLAdXlaT0KA0rdJVKuK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;544&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.13.31.png&quot; style=&quot;width: 34.9866%; margin-right: 10px;&quot; data-widthpercent=&quot;35.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDCdag/dJMcagMUuJA/fd3JLAdXlaT0KA0rdJVKuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDCdag%2FdJMcagMUuJA%2Ffd3JLAdXlaT0KA0rdJVKuK%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;1776&quot; height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A83Jo/dJMcafHdseC/v6bbHCCaOVEKUVS6cZ3qGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A83Jo/dJMcafHdseC/v6bbHCCaOVEKUVS6cZ3qGk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;584&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.39.56.png&quot; style=&quot;width: 33.4344%;&quot; data-widthpercent=&quot;34.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A83Jo/dJMcafHdseC/v6bbHCCaOVEKUVS6cZ3qGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA83Jo%2FdJMcafHdseC%2Fv6bbHCCaOVEKUVS6cZ3qGk%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;1822&quot; height=&quot;584&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;interface orientation도 더이상 레이아웃을 결정하는데 의미가 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac에서 iPhone Mirroring에서 앱은 가로 및 세로 비울과 무관하게 항상 세로 인터페이스 방향(portrait)으로만 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 방향 확인도 size class를 사용하도록 업데이트 해야함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoreMotion/CoreLocation 등 데이터에는 view를 연결하여 뷰의 좌표 기준으로 계산된 데이터를 얻을 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TwzXt/dJMcaiX7ddJ/AsLD7VSJg1W5SiOGgq8bG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TwzXt/dJMcaiX7ddJ/AsLD7VSJg1W5SiOGgq8bG0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;890&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.41.46.png&quot; style=&quot;width: 33.9885%; margin-right: 10px;&quot; data-widthpercent=&quot;34.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TwzXt/dJMcaiX7ddJ/AsLD7VSJg1W5SiOGgq8bG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwzXt%2FdJMcaiX7ddJ%2FAsLD7VSJg1W5SiOGgq8bG0%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;1668&quot; height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwILZJ/dJMcajiuZdZ/jDhdkbFDfkSUkSFo239Bx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwILZJ/dJMcajiuZdZ/jDhdkbFDfkSUkSFo239Bx1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1722&quot; data-origin-height=&quot;946&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.41.33.png&quot; data-widthpercent=&quot;33.8&quot; style=&quot;width: 33.0117%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwILZJ/dJMcajiuZdZ/jDhdkbFDfkSUkSFo239Bx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwILZJ%2FdJMcajiuZdZ%2FjDhdkbFDfkSUkSFo239Bx1%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;1722&quot; height=&quot;946&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ON6LW/dJMcabLCrvQ/4vpXTPKNfhvSLTQZh15ACk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ON6LW/dJMcabLCrvQ/4vpXTPKNfhvSLTQZh15ACk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;1024&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.42.19.png&quot; style=&quot;width: 30.6742%;&quot; data-widthpercent=&quot;31.4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ON6LW/dJMcabLCrvQ/4vpXTPKNfhvSLTQZh15ACk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FON6LW%2FdJMcabLCrvQ%2F4vpXTPKNfhvSLTQZh15ACk%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;1732&quot; height=&quot;1024&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;p data-ke-size=&quot;size16&quot;&gt;아이폰에서는 기본적으로 탭바를 모두 지원하지만 iOS27의 새 기능으로 iPhone 앱도 사이드 바를 선택할 수 있음.&lt;/p&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-filename=&quot;스크린샷 2026-07-01 오후 11.49.57.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GkrTB/dJMcabkwtTh/uSEFbBHenBV0wiiKY2uji1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GkrTB/dJMcabkwtTh/uSEFbBHenBV0wiiKY2uji1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GkrTB/dJMcabkwtTh/uSEFbBHenBV0wiiKY2uji1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGkrTB%2FdJMcabkwtTh%2FuSEFbBHenBV0wiiKY2uji1%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;1786&quot; height=&quot;968&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.49.57.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;968&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;사이드바를 현재 사용할 수 있는지 확인하며 사용할 수 없다면 UI를 앱의 다른 부분에 표시해야 함.&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/A9nDH/dJMcac4IWbz/76DKKEWSwhszrRzCthkGz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A9nDH/dJMcac4IWbz/76DKKEWSwhszrRzCthkGz1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;852&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.51.02.png&quot; style=&quot;width: 28.4935%; margin-right: 10px;&quot; data-widthpercent=&quot;29.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A9nDH/dJMcac4IWbz/76DKKEWSwhszrRzCthkGz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA9nDH%2FdJMcac4IWbz%2F76DKKEWSwhszrRzCthkGz1%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;1798&quot; height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wO6sE/dJMcaiKBCgQ/rtdgOuKBZCkcucolsOZKz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wO6sE/dJMcaiKBCgQ/rtdgOuKBZCkcucolsOZKz1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;718&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.55.31.png&quot; style=&quot;width: 34.5634%; margin-right: 10px;&quot; data-widthpercent=&quot;35.39&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wO6sE/dJMcaiKBCgQ/rtdgOuKBZCkcucolsOZKz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwO6sE%2FdJMcaiKBCgQ%2FrtdgOuKBZCkcucolsOZKz1%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;1838&quot; height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w4tbM/dJMcadoYW60/ILf2mlgxH7CjfXDEfPmeCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w4tbM/dJMcadoYW60/ILf2mlgxH7CjfXDEfPmeCK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;720&quot; data-filename=&quot;스크린샷 2026-07-01 오후 11.59.24.png&quot; style=&quot;width: 34.6175%;&quot; data-widthpercent=&quot;35.44&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w4tbM/dJMcadoYW60/ILf2mlgxH7CjfXDEfPmeCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw4tbM%2FdJMcadoYW60%2FILf2mlgxH7CjfXDEfPmeCK%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;1846&quot; height=&quot;720&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;탭바에서 prominent 탭을 지정되면 강조되고 스크롤 중 탭 바가 축소되어도 항상 표시됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션에서 최소화에 대한 부분도 달라지는데 필요한 경우 설정할 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scroll Edge effect에서 .automatic은 더이상 soft와 hard 스타일 간에 전환하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.04.37.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;992&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KDvLY/dJMcacwVdH0/ZBpendvkZisUJ3TQdImrVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KDvLY/dJMcacwVdH0/ZBpendvkZisUJ3TQdImrVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KDvLY/dJMcacwVdH0/ZBpendvkZisUJ3TQdImrVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKDvLY%2FdJMcacwVdH0%2FZBpendvkZisUJ3TQdImrVK%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;1798&quot; height=&quot;992&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.04.37.png&quot; data-origin-width=&quot;1798&quot; data-origin-height=&quot;992&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;이미지를 계속 표시해야 하는 경우 preferredImageVisibility 속성을 설정&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/mdLsG/dJMcagMUvBa/ZoNplxGn7GP3hy14DKvlxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdLsG/dJMcagMUvBa/ZoNplxGn7GP3hy14DKvlxk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;978&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.05.40.png&quot; style=&quot;width: 44.2355%; margin-right: 10px;&quot; data-widthpercent=&quot;44.76&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdLsG/dJMcagMUvBa/ZoNplxGn7GP3hy14DKvlxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdLsG%2FdJMcagMUvBa%2FZoNplxGn7GP3hy14DKvlxk%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;1802&quot; height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvGkHn/dJMcahdWHm7/uhp0HjtAjL1fmv9xhj9vkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvGkHn/dJMcahdWHm7/uhp0HjtAjL1fmv9xhj9vkK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;802&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.08.45.png&quot; style=&quot;width: 54.6017%;&quot; data-widthpercent=&quot;55.24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvGkHn/dJMcahdWHm7/uhp0HjtAjL1fmv9xhj9vkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvGkHn%2FdJMcahdWHm7%2Fuhp0HjtAjL1fmv9xhj9vkK%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;1824&quot; height=&quot;802&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;앱 내에서 바로 Siri를 사용할 수 있는데 앱에 더 특화된 정보를 제공하려며 AppEntity를 주석으로 추가할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 앱에 Drag and Drop을 지원한다면 Siri가 리소스를 로드할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 메뉴에서 Apple Intelligence가 호출되면 시스템이 사용 가능한 Drag and Drop Delegate 메서드를 호출하여 콘텐츠를 로드함. (UIDragInteractionDelegate)&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 드래그를 시작할 때 표시되는 상태 있는 UI가 앱에 있다면 sessionDidMove에 넣기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.09.09.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clkuhc/dJMcagzia3D/2QeRr6HXRZNtvpT64XAs2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clkuhc/dJMcagzia3D/2QeRr6HXRZNtvpT64XAs2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clkuhc/dJMcagzia3D/2QeRr6HXRZNtvpT64XAs2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclkuhc%2FdJMcagzia3D%2F2QeRr6HXRZNtvpT64XAs2K%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;1792&quot; height=&quot;636&quot; data-filename=&quot;스크린샷 2026-07-02 오전 12.09.09.png&quot; data-origin-width=&quot;1792&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Agentic Coding을 지원하며 Xcode가 아닌 다른 도구에서 사용하려면 위의 명령어를 입력하면 마크다운 파일이 생성되어 워크플로에서 가져올 수 있음.&lt;/p&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://www.youtube.com/watch?v=rAvlt9Dvgbo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=rAvlt9Dvgbo&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rAvlt9Dvgbo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/rssD1/dJMb9ia1YK1/QtcPzdteZUkxeK3zK2lT11/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=560_100_670_220,https://scrap.kakaocdn.net/dn/hDc5t/dJMb9kUdMbh/O41mk4GGGhZbTIhs3YqRiK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=560_100_670_220&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;WWDC26: Modernize your UIKit app | Apple&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rAvlt9Dvgbo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>Apple</category>
      <category>ios</category>
      <category>WWDC26</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/813</guid>
      <comments>https://rldd.tistory.com/813#entry813comment</comments>
      <pubDate>Wed, 1 Jul 2026 22:29:22 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI의 Observation 알아보기 (Discover Observation in SwiftUI) - WWDC23</title>
      <link>https://rldd.tistory.com/807</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwiftUI의 Observation 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 마법적인 기능에 대해서 소개할 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observation은 SwiftUI의 뷰가 반응형으로 동작할 수 있도록 도와줌.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;What is Observation&lt;/li&gt;
&lt;li&gt;SwiftUI property wrappers
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유용한 프로퍼티래퍼 알아볼 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advanced uses
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observable의 고급 사용 사례&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ObservableObject와 새로운 @Observable 매크로로 코드를 업데이트 하는 예시&lt;/li&gt;
&lt;/ul&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/b9vBRl/dJMcaii2FfF/A1Z8K6hUz9kvyWXqMo6vYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9vBRl/dJMcaii2FfF/A1Z8K6hUz9kvyWXqMo6vYK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;912&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.20.59.png&quot; style=&quot;width: 49.7993%; margin-right: 10px;&quot; data-widthpercent=&quot;50.39&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9vBRl/dJMcaii2FfF/A1Z8K6hUz9kvyWXqMo6vYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9vBRl%2FdJMcaii2FfF%2FA1Z8K6hUz9kvyWXqMo6vYK%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;1792&quot; height=&quot;912&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caoX3x/dJMcahdnNfI/pDcbFoEaLw8P7DGm9PO8IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caoX3x/dJMcahdnNfI/pDcbFoEaLw8P7DGm9PO8IK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;952&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.23.39.png&quot; style=&quot;width: 49.038%;&quot; data-widthpercent=&quot;49.61&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caoX3x/dJMcahdnNfI/pDcbFoEaLw8P7DGm9PO8IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaoX3x%2FdJMcahdnNfI%2FpDcbFoEaLw8P7DGm9PO8IK%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;1842&quot; height=&quot;952&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;Observation은 프로퍼티의 변화를 추적하는 Swift의 신기능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평범한 Swift의 타입을 매크로의 형태로 바꿔둠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable을 추가하므로써 UI가 데이터 모델의 변화에 대응할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift의 새로운 매크로 시스템 덕분에 Swift 5.9에서는 모델이 더 단순해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Observable은 Swift 컴파일러에 명령을 내려서 우리가 작성한 코드를 확장된 Observable 타입으로 바꿔둠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable 타입을 통해 SwiftUI 뷰를 작동시킬 수 있으며, 다른 프로퍼티래퍼가 없어도 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 body가 평가될 때 모든 프로퍼티의 모든 접근을 추적하고, 이 정보를 이용해 특정 인스턴스에서 프로퍼티에 다음 변화가 언제 일어날지를 예측함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable의 장점은 모델에서 order가 변경되면 그 영역만 뷰가 무효화되며, donuts에는 영향을 주지 않음.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.32.26.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tMZL1/dJMcajoDJ0T/n9inKzPhjvionKwhYADr71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tMZL1/dJMcajoDJ0T/n9inKzPhjvionKwhYADr71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tMZL1/dJMcajoDJ0T/n9inKzPhjvionKwhYADr71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtMZL1%2FdJMcajoDJ0T%2Fn9inKzPhjvionKwhYADr71%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;1792&quot; height=&quot;958&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.32.26.png&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;@Observable 매크로를 사용하면 타입이 확장되어서 Observation을 지원할 수 있게 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 프로퍼티 접근을 추적할 수 있고, Observation에서 다음 프로퍼티가 언제 변할지 관찰할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추적이 가능해지면 프로퍼티에 변화가 생겼을 때, UI를 업데이트 할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI는 View의 body를 재계산만 하면됨.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.34.21.png&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uevoG/dJMcahRW5yH/paI2HBHDbnQoqeSX6dkNUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uevoG/dJMcahRW5yH/paI2HBHDbnQoqeSX6dkNUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uevoG/dJMcahRW5yH/paI2HBHDbnQoqeSX6dkNUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuevoG%2FdJMcahRW5yH%2FpaI2HBHDbnQoqeSX6dkNUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1804&quot; height=&quot;690&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.34.21.png&quot; data-origin-width=&quot;1804&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 핵심적인 프로퍼티 래퍼 3개지는 State, Environment, Bindable이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI Observable 타입과의 인터페이스에서 프로퍼티 래퍼가 필요 없이도 사용하는 예제는 설명해서, 이번에는 필요하지 않은 부분을 알아볼 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.44.17.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgG4CO/dJMcaii2FJ2/XaavNBAFupCpcQpQfH88wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgG4CO/dJMcaii2FJ2/XaavNBAFupCpcQpQfH88wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgG4CO/dJMcaii2FJ2/XaavNBAFupCpcQpQfH88wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgG4CO%2FdJMcaii2FJ2%2FXaavNBAFupCpcQpQfH88wK%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;1824&quot; height=&quot;820&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.44.17.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;첫 번째로는 @State가 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 안에 View 전용 상태를 저장해야 할 때 사용 @State 프로퍼티를 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;donutToAdd 프로퍼티는 뷰의 생명주기 동안에만 유지됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.45.54.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFHi47/dJMcabEd60R/bkbmhQy2J2GTb4Q2tKLemk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFHi47/dJMcabEd60R/bkbmhQy2J2GTb4Q2tKLemk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFHi47/dJMcabEd60R/bkbmhQy2J2GTb4Q2tKLemk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFHi47%2FdJMcabEd60R%2FbkbmhQy2J2GTb4Q2tKLemk%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;1814&quot; height=&quot;766&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.45.54.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;766&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;두 번째로는 Environment로 각 값을 어디서든 접근할 수 있도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 곳에서 접근 가능하도록 하여, Observable 타입이 Environment에 아주 잘 맞음.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pH62r/dJMcabEd67B/Xch8AMi9eV4xSanCaYjbk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pH62r/dJMcabEd67B/Xch8AMi9eV4xSanCaYjbk0/img.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;706&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.49.56.png&quot; style=&quot;width: 53.4876%; margin-right: 10px;&quot; data-widthpercent=&quot;54.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pH62r/dJMcabEd67B/Xch8AMi9eV4xSanCaYjbk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpH62r%2FdJMcabEd67B%2FXch8AMi9eV4xSanCaYjbk0%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;1796&quot; height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnxHK/dJMcabqIsfa/yHeclwoZHxqTT9KXFYVXX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnxHK/dJMcabqIsfa/yHeclwoZHxqTT9KXFYVXX0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;612&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.50.40.png&quot; style=&quot;width: 45.3496%;&quot; data-widthpercent=&quot;45.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnxHK/dJMcabqIsfa/yHeclwoZHxqTT9KXFYVXX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnxHK%2FdJMcabqIsfa%2FyHeclwoZHxqTT9KXFYVXX0%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;1320&quot; height=&quot;612&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;@Bindable은 가장 최근에 추가된 프로퍼티 래퍼로 굉장히 가벼움.&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-filename=&quot;스크린샷 2026-05-16 오후 2.52.02.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBM98d/dJMcafs49Y3/QlnyQ0kJKCkQKpUPXkvQ1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBM98d/dJMcafs49Y3/QlnyQ0kJKCkQKpUPXkvQ1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBM98d/dJMcafs49Y3/QlnyQ0kJKCkQKpUPXkvQ1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBM98d%2FdJMcafs49Y3%2FQlnyQ0kJKCkQKpUPXkvQ1K%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;1816&quot; height=&quot;1010&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.52.02.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1010&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;SwiftUI에서 프로퍼티 래퍼를 사용할 때 이렇게 위와 같은 기준을 가지고 사용하면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.57.27.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYE9wy/dJMcajvtPUS/I7Xx8LTYYSF3WKlqj0ilk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYE9wy/dJMcajvtPUS/I7Xx8LTYYSF3WKlqj0ilk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYE9wy/dJMcajvtPUS/I7Xx8LTYYSF3WKlqj0ilk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYE9wy%2FdJMcajvtPUS%2FI7Xx8LTYYSF3WKlqj0ilk0%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;1830&quot; height=&quot;854&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.57.27.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;854&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;SwiftUI는 필드에 일어나는 접근을 인스턴스마다 추적하기 때문에 배열이나 옵셔널 뿐만 아니라 Observable에 포함된 그 어떤 타입이라도 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.59.59.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLKMgc/dJMcabYyhG6/OMRf9xXSMv39sMIpmJvBq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLKMgc/dJMcabYyhG6/OMRf9xXSMv39sMIpmJvBq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLKMgc/dJMcabYyhG6/OMRf9xXSMv39sMIpmJvBq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLKMgc%2FdJMcabYyhG6%2FOMRf9xXSMv39sMIpmJvBq0%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;1830&quot; height=&quot;666&quot; data-filename=&quot;스크린샷 2026-05-16 오후 2.59.59.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 프로퍼티가 업데이트 된다면 View도 업데이트 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 연산 프로퍼티에 저장 프로퍼티가 없다면 Observation에서 사용하기 전에 두 단계를 더 거쳐야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관찰 대상이 된 프로퍼티가 Observable 타입에 저장된 구성에 의해 변경되지 않았을 때만 필요한 조치임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 프로퍼티에 접근과 변경이 언제 이루어졌는지만 Observation에 전달되면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시라도 더 고급 기능이 필요한 경우에는 스스로 Observation을 만들어서 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티에 keyPath를 통해 언제 읽혔고, 언제 바뀌었는지만 통지해주면 되기 때문.&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-filename=&quot;스크린샷 2026-05-16 오후 3.18.29.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s484i/dJMb99TWgiS/40Phwy5Z4gCMeSQuI72PVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s484i/dJMb99TWgiS/40Phwy5Z4gCMeSQuI72PVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s484i/dJMb99TWgiS/40Phwy5Z4gCMeSQuI72PVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs484i%2FdJMb99TWgiS%2F40Phwy5Z4gCMeSQuI72PVK%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;1826&quot; height=&quot;674&quot; data-filename=&quot;스크린샷 2026-05-16 오후 3.18.29.png&quot; data-origin-width=&quot;1826&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 구성의 변화를 바로 인식하는데, 프로퍼티 접근을 바탕으로 Observable 타입을 추적하기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 저장 프로퍼티에서 연산 프로퍼티가 구성되어도 Observation이 잘 작동한다는 의미.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 드물게 Observation이 작동하지 않는 경우에는 사용자가 직접 Observation을 사용해 수동으로 접근 및 변형 플래그 호출을 추가할 수 있음.&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/nV5pE/dJMcahEpkcq/wZR1MlAkbGgS2x4SLGCLKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nV5pE/dJMcahEpkcq/wZR1MlAkbGgS2x4SLGCLKK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;596&quot; data-filename=&quot;스크린샷 2026-05-16 오후 3.18.51.png&quot; style=&quot;width: 29.0746%; margin-right: 10px;&quot; data-widthpercent=&quot;29.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nV5pE/dJMcahEpkcq/wZR1MlAkbGgS2x4SLGCLKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnV5pE%2FdJMcahEpkcq%2FwZR1MlAkbGgS2x4SLGCLKK%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;1288&quot; height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbRdz/dJMcaicj2ij/hpK7bfiKz6NlnPbKk6zmo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbRdz/dJMcaicj2ij/hpK7bfiKz6NlnPbKk6zmo0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;562&quot; data-filename=&quot;스크린샷 2026-05-16 오후 3.19.06.png&quot; style=&quot;width: 31.2645%; margin-right: 10px;&quot; data-widthpercent=&quot;32.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbRdz/dJMcaicj2ij/hpK7bfiKz6NlnPbKk6zmo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbRdz%2FdJMcaicj2ij%2FhpK7bfiKz6NlnPbKk6zmo0%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;1306&quot; height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRl1RT/dJMcafmlLKp/TPiMLdFN8Kebb2UvfjksMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRl1RT/dJMcafmlLKp/TPiMLdFN8Kebb2UvfjksMk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;578&quot; data-filename=&quot;스크린샷 2026-05-16 오후 3.19.43.png&quot; style=&quot;width: 37.3354%;&quot; data-widthpercent=&quot;38.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRl1RT/dJMcafmlLKp/TPiMLdFN8Kebb2UvfjksMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRl1RT%2FdJMcafmlLKp%2FTPiMLdFN8Kebb2UvfjksMk%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;1604&quot; height=&quot;578&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;ObservableObject에서 @Observable로 넘어갈 때는 이렇게 넘어갈 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 기존에는 View에 프로퍼티 래퍼가 많았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ObservedObject의 경우에는 일부는 사라지고 나머지는 바인딩되어 새로운 @Bindable로 변경되었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnvironmentObject 래퍼는 @Environment로 변경됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ObservedObject에서 @Observable로 넘어가려면 대부분 기존 키워드를 삭제하거나 세 가지의 프로퍼티 래퍼로 단순화 하기만 하면 됨. (@State, @Environment, @Bindable)&lt;/p&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;(참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=W6FF70H_GwI&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=W6FF70H_GwI&lt;/a&gt;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>ios</category>
      <category>observation</category>
      <category>SwiftUI</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/807</guid>
      <comments>https://rldd.tistory.com/807#entry807comment</comments>
      <pubDate>Sat, 16 May 2026 01:17:35 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI 핵심 기능 (SwiftUI essential) - WWDC24</title>
      <link>https://rldd.tistory.com/806</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwiftUI 핵심 기능 (SwiftUI essential) - WWDC24&lt;/h2&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/b4L7Nx/dJMcacpB7qj/6EgSjIn5RzI0AXJQ7zDNL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4L7Nx/dJMcacpB7qj/6EgSjIn5RzI0AXJQ7zDNL0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;836&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.01.09.png&quot; style=&quot;width: 33.6862%; margin-right: 10px;&quot; data-widthpercent=&quot;34.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4L7Nx/dJMcacpB7qj/6EgSjIn5RzI0AXJQ7zDNL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4L7Nx%2FdJMcacpB7qj%2F6EgSjIn5RzI0AXJQ7zDNL0%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;1810&quot; height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4TMkd/dJMcaakZUXr/pZjMP8Zcf1jgkxinjEMVkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4TMkd/dJMcaakZUXr/pZjMP8Zcf1jgkxinjEMVkk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1888&quot; data-origin-height=&quot;814&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.01.01.png&quot; data-widthpercent=&quot;36.95&quot; style=&quot;width: 36.0875%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4TMkd/dJMcaakZUXr/pZjMP8Zcf1jgkxinjEMVkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4TMkd%2FdJMcaakZUXr%2FpZjMP8Zcf1jgkxinjEMVkk%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;1888&quot; height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV9jtD/dJMcabjXLAw/umTFSddMsBxKWk9QAr3q6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV9jtD/dJMcabjXLAw/umTFSddMsBxKWk9QAr3q6K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;1064&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.01.53.png&quot; style=&quot;width: 27.9008%;&quot; data-widthpercent=&quot;28.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV9jtD/dJMcabjXLAw/umTFSddMsBxKWk9QAr3q6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV9jtD%2FdJMcabjXLAw%2FumTFSddMsBxKWk9QAr3q6K%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;1908&quot; height=&quot;1064&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; text-align: start;&quot;&gt;Apple 플랫폼에서&lt;span&gt; 자연스럽게 통합되고 더 적은 코드로도 프로덕션을 더 빠르게 만들 수 있음.&lt;/span&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fundamentals of views
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰가 작동하는 방식에 대한 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Built-in capability
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내장된 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Across all platform
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Apple 플랫폼에서 어떻게 작동하는지 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SDK interoperability
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 프레임워크와 통합되는 SwiftUI&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.14.48.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mTqvG/dJMcaaSRQVY/O57LPEEpnW5dgxlkEvjfr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mTqvG/dJMcaaSRQVY/O57LPEEpnW5dgxlkEvjfr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mTqvG/dJMcaaSRQVY/O57LPEEpnW5dgxlkEvjfr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmTqvG%2FdJMcaaSRQVY%2FO57LPEEpnW5dgxlkEvjfr1%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;1150&quot; height=&quot;618&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.14.48.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View는 UI의 기본 구성 단위로 화면에 표시되는 모든 픽셀은 어떤 식으로든 뷰에 의해서 정의.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 선언형과 명령형은 서로 배타적이지 않으며, SwiftUI는 이 두가지를 모두 수용.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.17.32.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br5BLK/dJMcajhVr4L/iNKDxjqTKhGJUlv9VW8tXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br5BLK/dJMcajhVr4L/iNKDxjqTKhGJUlv9VW8tXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br5BLK/dJMcajhVr4L/iNKDxjqTKhGJUlv9VW8tXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr5BLK%2FdJMcajhVr4L%2FiNKDxjqTKhGJUlv9VW8tXK%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;796&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.17.32.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;796&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;SwiftUI의 View는 UI의 현재 상태가 어떤지를 기술하는 설명(description)으로 시간이 지남에 따라서 명령형 명령을 받는 오랜 시간 지속되는 객체 인스턴스가 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래서 SwiftUI에서는 View가 값 타입으로 클래스 대신에 구조체로 정의되어 있음.&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/cF4DV9/dJMcaa6ptZg/lvgqsfnIpWz5zXy9jUhMK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF4DV9/dJMcaa6ptZg/lvgqsfnIpWz5zXy9jUhMK0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;830&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.19.03.png&quot; style=&quot;width: 53.1884%; margin-right: 10px;&quot; data-widthpercent=&quot;53.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF4DV9/dJMcaa6ptZg/lvgqsfnIpWz5zXy9jUhMK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF4DV9%2FdJMcaa6ptZg%2FlvgqsfnIpWz5zXy9jUhMK0%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;1816&quot; height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK9W69/dJMcaayzrlu/IrlOxMZAVax99WEq7ewzLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK9W69/dJMcaayzrlu/IrlOxMZAVax99WEq7ewzLk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;982&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.26.06.png&quot; style=&quot;width: 45.6488%;&quot; data-widthpercent=&quot;46.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK9W69/dJMcaayzrlu/IrlOxMZAVax99WEq7ewzLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK9W69%2FdJMcaayzrlu%2FIrlOxMZAVax99WEq7ewzLk%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;1844&quot; height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 SwiftUI에서는 합성을 사용하는데, 이 과정에서는 ViewBuilder라는 클로저를 사용해서 컨테이너의 하위 항목들을 선언.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 모든 컨테이너 뷰에서 사용되는 패턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.27.07.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqGO2/dJMb99M8w1m/OkqKVKDkmwH0oX88hcNmrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqGO2/dJMb99M8w1m/OkqKVKDkmwH0oX88hcNmrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqGO2/dJMb99M8w1m/OkqKVKDkmwH0oX88hcNmrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqGO2%2FdJMb99M8w1m%2FOkqKVKDkmwH0oX88hcNmrK%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;1832&quot; height=&quot;980&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.27.07.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 View modifiers를 통해서 뷰를 수정할 수 있으며, View의 컨테이너와 유사한 계층 구조로 동작함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View modifiers의 계층 구조와 순 서는 정확한 순서를 따라서 정의됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.30.07.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vZVLk/dJMcaiQQUfm/E7KQRP4MMfgko6yZvmIxr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vZVLk/dJMcaiQQUfm/E7KQRP4MMfgko6yZvmIxr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vZVLk/dJMcaiQQUfm/E7KQRP4MMfgko6yZvmIxr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvZVLk%2FdJMcaiQQUfm%2FE7KQRP4MMfgko6yZvmIxr1%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;1784&quot; height=&quot;650&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.30.07.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 뷰 계층은 View와 View modifilers의 조합으로 캡슐화될 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 선언하는 뷰는 View 프로토콜을 준수하여 자신이 나타내는 뷰를 반환하는 body를 프로퍼티로 가짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.40.52.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wEY6u/dJMcafmlvSS/us0YYPqAT28mWy7D5Z4ta1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wEY6u/dJMcafmlvSS/us0YYPqAT28mWy7D5Z4ta1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wEY6u/dJMcafmlvSS/us0YYPqAT28mWy7D5Z4ta1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwEY6u%2FdJMcafmlvSS%2Fus0YYPqAT28mWy7D5Z4ta1%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;1782&quot; height=&quot;724&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.40.52.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;724&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;SwiftUI는 자동으로 boilerplatate를 제거하거 업데이트 버그를 제거하여 UI를 최신 상태로 유지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 백그라운드에서 자동으로 UI 표현을 유지함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 변경되면, 새로운 뷰 값이 생성되어 SwiftUI에 제공됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 해당 값을 사용하여 출력을 업데이트 하는 방법을 결정.&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/GV02j/dJMcaf0TCEH/wuH10ydxn90XXphctntkP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GV02j/dJMcaf0TCEH/wuH10ydxn90XXphctntkP1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;952&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.41.54.png&quot; style=&quot;width: 62.1589%; margin-right: 10px;&quot; data-widthpercent=&quot;62.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GV02j/dJMcaf0TCEH/wuH10ydxn90XXphctntkP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGV02j%2FdJMcaf0TCEH%2FwuH10ydxn90XXphctntkP1%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;1856&quot; height=&quot;952&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWs8IO/dJMcafzSbyk/KkdcZWLtA3wnKaVFS3AJ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWs8IO/dJMcafzSbyk/KkdcZWLtA3wnKaVFS3AJ9k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;758&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.44.00.png&quot; style=&quot;width: 36.6783%;&quot; data-widthpercent=&quot;37.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWs8IO/dJMcafzSbyk/KkdcZWLtA3wnKaVFS3AJ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWs8IO%2FdJMcafzSbyk%2FKkdcZWLtA3wnKaVFS3AJ9k%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;872&quot; height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;SwiftUI에서 pet의 hasAward 상태가 변경되면 pet에 의존하는 영역을 재확인하고, 해당 영역에서 body를 재평가하여 새로운 결과를 반환.&lt;/p&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; iOS 17 이상부터는 @Observable을 사용해서 객체의 특정 프로퍼티만 뷰를 갱신하는 형태로 동작.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서는 특정 프로퍼티만 참조하여 종속성을 생성함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.45.36.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbpWiH/dJMcagS45tn/67A7pB4VQeWUXAWwYMawK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbpWiH/dJMcagS45tn/67A7pB4VQeWUXAWwYMawK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbpWiH/dJMcagS45tn/67A7pB4VQeWUXAWwYMawK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbpWiH%2FdJMcagS45tn%2F67A7pB4VQeWUXAWwYMawK1%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;1786&quot; height=&quot;662&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.45.36.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;662&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;SwiftUI에서는 상태 관리를 위한 여러가지 도구가 있으며, @State와 @Binding이 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰의 프로퍼티를 @State로 표시하면 SwiftUI는 저장 공간을 관리하고 뷰가 읽고 쓸 수 있도록 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Binding은 다른 뷰 상태에 대한 양방향 참조를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.53.24.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTxwgi/dJMb990KjS9/JVAtnio7qjub8M99tkliUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTxwgi/dJMb990KjS9/JVAtnio7qjub8M99tkliUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTxwgi/dJMb990KjS9/JVAtnio7qjub8M99tkliUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTxwgi%2FdJMb990KjS9%2FJVAtnio7qjub8M99tkliUk%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;1812&quot; height=&quot;944&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.53.24.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;944&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;SwiftUI에서 뷰가 업데이트되는 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용자가 Increase 버튼 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SwiftUI가 @State로 작성된 값의 변화를 감지하고 RatingView에서 body를 호출하여 새로운 텍스트 값을 반환하여 뷰를 업데이트 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.56.26.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDhPes/dJMcaaL3PAm/MvaGRvUgfmtFM0vdYT0Q4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDhPes/dJMcaaL3PAm/MvaGRvUgfmtFM0vdYT0Q4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDhPes/dJMcaaL3PAm/MvaGRvUgfmtFM0vdYT0Q4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDhPes%2FdJMcaaL3PAm%2FMvaGRvUgfmtFM0vdYT0Q4k%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;1854&quot; height=&quot;814&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.56.26.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;814&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;SwiftUI의 애니메이션은 지금까지 설명한 내용과 같은 데이터 기반 업데이트의 위에 만들어짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;withAnimation으로 상태를 래핑하면 결과를 나타내는 텍스트가 기본 애니메이션과 함께 적용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 전환 효과를 커스텀할 수도 있는데, 이 경우에는 Text에 콘텐츠 전환 효과를 사용하면 완벽하게 맞음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.58.08.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mC95h/dJMcadvcygQ/scTypfhHqg1KHnKht7CcIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mC95h/dJMcadvcygQ/scTypfhHqg1KHnKht7CcIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mC95h/dJMcadvcygQ/scTypfhHqg1KHnKht7CcIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmC95h%2FdJMcadvcygQ%2FscTypfhHqg1KHnKht7CcIK%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;1816&quot; height=&quot;818&quot; data-filename=&quot;스크린샷 2026-05-16 오전 12.58.08.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;818&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;@Binding은 양방향 참조를 제공하여 RatingView를 업데이트할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.06.04.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7XBOC/dJMcadvcymM/eYAZpVg5MgVg3f8XSSOc6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7XBOC/dJMcadvcymM/eYAZpVg5MgVg3f8XSSOc6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7XBOC/dJMcadvcymM/eYAZpVg5MgVg3f8XSSOc6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7XBOC%2FdJMcadvcymM%2FeYAZpVg5MgVg3f8XSSOc6k%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;1852&quot; height=&quot;912&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.06.04.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;912&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;SwiftUI는 다크모드, 유동적 글자 크기 조절 등 여러 손쉬운 사용 기능, 현재화도 제공함.&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/RMQzn/dJMcagetm28/MyET2JJ7oLsFbteIYBBdj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RMQzn/dJMcagetm28/MyET2JJ7oLsFbteIYBBdj0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;716&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.09.42.png&quot; style=&quot;width: 36.2585%; margin-right: 10px;&quot; data-widthpercent=&quot;37.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RMQzn/dJMcagetm28/MyET2JJ7oLsFbteIYBBdj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRMQzn%2FdJMcagetm28%2FMyET2JJ7oLsFbteIYBBdj0%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;1766&quot; height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3mnZh/dJMcacpB8vN/4kx5HUcixLLnwhHq1EitO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3mnZh/dJMcacpB8vN/4kx5HUcixLLnwhHq1EitO0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;844&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.09.48.png&quot; style=&quot;width: 32.0485%; margin-right: 10px;&quot; data-widthpercent=&quot;32.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3mnZh/dJMcacpB8vN/4kx5HUcixLLnwhHq1EitO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3mnZh%2FdJMcacpB8vN%2F4kx5HUcixLLnwhHq1EitO0%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;1840&quot; height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRxDn1/dJMcaipOORE/if743fW7QhHrXK6SdeE2A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRxDn1/dJMcaipOORE/if743fW7QhHrXK6SdeE2A1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;874&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.10.23.png&quot; style=&quot;width: 29.3674%;&quot; data-widthpercent=&quot;30.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRxDn1/dJMcaipOORE/if743fW7QhHrXK6SdeE2A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRxDn1%2FdJMcaipOORE%2Fif743fW7QhHrXK6SdeE2A1%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;1746&quot; height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 앱의 진입점에서도 App, Scene을 통해서 신규 구조와 동일하게 사용함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App은 Scene별로 정의된 선언적 구조이며, macOS와 iOS 등 어떤 플랫폼에서든 사용할 수 있음.&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;즉, SwiftUI는 UI가 모든 플랫폼에 적용되어 손쉽게 개발할 수 있는 휼룡한 시작지점이 됨.&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-filename=&quot;스크린샷 2026-05-16 오전 1.13.52.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyYG2t/dJMcadoqDLh/KIUtlgkUsz1ykAs6EYvFW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyYG2t/dJMcadoqDLh/KIUtlgkUsz1ykAs6EYvFW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyYG2t/dJMcadoqDLh/KIUtlgkUsz1ykAs6EYvFW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyYG2t%2FdJMcadoqDLh%2FKIUtlgkUsz1ykAs6EYvFW0%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;1816&quot; height=&quot;612&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.13.52.png&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;612&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;SwiftUI는 이러한 방식으로 코드 공유를 가능하게 하지만, 한 번 작성하면 어디서나 실행되는 것은 아님.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번만 익히면 모든 상황에서 또는 모든 Apple 플랫폼에서 사용할 수 있는 도구 모음 정도임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 각 플랫폼에 특화된 API도 존재&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.14.38.png&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iyYq/dJMcabqIdc5/hLUB34uuXd61zJRteSh5e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iyYq/dJMcabqIdc5/hLUB34uuXd61zJRteSh5e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iyYq/dJMcabqIdc5/hLUB34uuXd61zJRteSh5e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iyYq%2FdJMcabqIdc5%2FhLUB34uuXd61zJRteSh5e1%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;1744&quot; height=&quot;622&quot; data-filename=&quot;스크린샷 2026-05-16 오전 1.14.38.png&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, SwiftUI 사용하면 AppKit이나 UIKit모두 Representable 프로토콜을 활용하여 클라이언트에서는 일관되게 지원할 수 있음.&lt;/p&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;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=HyQgpxX__-A&amp;amp;t=601s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=HyQgpxX__-A&amp;amp;t=601s&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>SwiftUI</category>
      <category>WWDC24</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/806</guid>
      <comments>https://rldd.tistory.com/806#entry806comment</comments>
      <pubDate>Sat, 16 May 2026 00:31:23 +0900</pubDate>
    </item>
    <item>
      <title>머신 러닝 기반 비디오 효과로 앱 향상하기 (Enhance your app with machine-learning based video effects) - WWDC25</title>
      <link>https://rldd.tistory.com/804</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;머신 러닝 기반 비디오 효과로 앱 향상하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Video Toolbox는 비디오 앱에서 많이 사용되는 프레임워크로 여러 비디오 관련 요구사항을 충족하는 기능을 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS 15.4부터 Video Toolbox는 VTFrameProcessor API로 개선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API는 Apple Silicon에 최적화된 새로운 ML 기반 비디오 처리 알고리즘을 포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API를 이제 iOS 26에서도 사용할 수 있음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Video effects overview
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 VTFrameProcessor API를 통해 현재 사용할 수 있는 효과&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add effects to your app
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;효과를 앱에 통합하는 기본 단계에 대해서 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Example use cases
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주요 사용 사례를 위한 몇가지 예시를 구현하는 방법 시연&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.22.46.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cik2ec/dJMcadID0fK/1vU1KV17aVSREs57KDZxPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cik2ec/dJMcadID0fK/1vU1KV17aVSREs57KDZxPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cik2ec/dJMcadID0fK/1vU1KV17aVSREs57KDZxPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcik2ec%2FdJMcadID0fK%2F1vU1KV17aVSREs57KDZxPk%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;1742&quot; height=&quot;850&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.22.46.png&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;850&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;VTFrameProcessor API는 다양하게 사용할 수 있는 여러 효과를 제공하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Frame rate conversion, Super resolution, Motion Blur은 고품질 비디오 편집을 위해 설계되었음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Low latency frame interpolation 및 Low latency super resolution은 실시간 처리를 담당하는 애플리케이션에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Temporal noise Filter는 고품질 비디오 편집이나 실시간 처리 애플리케이션 두 경우 모두 사용할 수 있음.&lt;/p&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-filename=&quot;스크린샷 2026-05-07 오전 1.26.04.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ptRX/dJMcafGwhnS/ucdu2H0kSdxgCqk592Xuhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ptRX/dJMcafGwhnS/ucdu2H0kSdxgCqk592Xuhk/img.png&quot; data-alt=&quot;1분 56초&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ptRX/dJMcafGwhnS/ucdu2H0kSdxgCqk592Xuhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ptRX%2FdJMcafGwhnS%2Fucdu2H0kSdxgCqk592Xuhk%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;1830&quot; height=&quot;840&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.26.04.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;1분 56초&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Frame rate conversion은 클립의 초당 프레임 수를 목표 FPS에 맞추기 위해 조정&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-filename=&quot;스크린샷 2026-05-07 오전 1.45.59.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mo303/dJMcai4eHzc/MkgLkch8OAnp44pCyzKykK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mo303/dJMcai4eHzc/MkgLkch8OAnp44pCyzKykK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mo303/dJMcai4eHzc/MkgLkch8OAnp44pCyzKykK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmo303%2FdJMcai4eHzc%2FMkgLkch8OAnp44pCyzKykK%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;1794&quot; height=&quot;966&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.45.59.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;966&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;Super resolution은 비디오 해상도를 높이고 오래된 비디오의 세밀한 디테일을 복원하는 초고화질 스케일러는 사진 보정 및 미디어 복원과 같은 용도에 적합.&lt;/p&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;초고화질 효과를 위해 두 개의 ML 모델이 제공되는데, 하나는 이미지용 하나는 비디오용임.&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/CqP75/dJMcacJN9sU/aULpTJTf4ZR3UBnScFqd31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CqP75/dJMcacJN9sU/aULpTJTf4ZR3UBnScFqd31/img.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;864&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.47.06.png&quot; style=&quot;width: 50.4962%; margin-right: 10px;&quot; data-widthpercent=&quot;51.09&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CqP75/dJMcacJN9sU/aULpTJTf4ZR3UBnScFqd31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCqP75%2FdJMcacJN9sU%2FaULpTJTf4ZR3UBnScFqd31%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;1852&quot; height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xbUMQ/dJMcagMcPqa/MTfkbanmQ5fiJv8KF5xisk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xbUMQ/dJMcagMcPqa/MTfkbanmQ5fiJv8KF5xisk/img.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;884&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.48.06.png&quot; style=&quot;width: 48.3411%;&quot; data-widthpercent=&quot;48.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xbUMQ/dJMcagMcPqa/MTfkbanmQ5fiJv8KF5xisk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxbUMQ%2FdJMcagMcPqa%2FMTfkbanmQ5fiJv8KF5xisk%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;1814&quot; height=&quot;884&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;Motion Blur는 영화 제작에서 인기 있는 기능으로 관객이 기대하는 자연스러운 움직임을 생성하는데 필수.&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-filename=&quot;스크린샷 2026-05-07 오전 1.56.11.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O5MuD/dJMcabKUJPB/82r9kqFfMJk76qBgtZeUaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O5MuD/dJMcabKUJPB/82r9kqFfMJk76qBgtZeUaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O5MuD/dJMcabKUJPB/82r9kqFfMJk76qBgtZeUaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO5MuD%2FdJMcabKUJPB%2F82r9kqFfMJk76qBgtZeUaK%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;1830&quot; height=&quot;818&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.56.11.png&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;818&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;Temoporal noise filter도 매우 좋은 기능으로 이는 과거와 미래의 참조 프레임을 활용한 모션 추정을 기반으로 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 효과는 시간에 따라 화면이 바뀔 때 생기는 지저분한 노이즈를 줄이고, 이상한 깨짐이나 잔상 같은 artifact를 없애며, 영상 압축이 더 잘 되게 만들고, 결과적으로 화질도 좋아 보이게 함.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;temporal noise: 한 프레임만 보면 잘 안 보이는데, 영상으로 보면 화면이 자글자글 깜빡이는 노이즈예요. 예를 들어 어두운 장면에서 배경이 계속 미세하게 반짝이는 것.&lt;/li&gt;
&lt;li&gt;부드럽게 처리: 프레임과 프레임 사이의 변화가 너무 튀지 않게 평균화하거나 안정화&lt;/li&gt;
&lt;li&gt;artifact 영상 처리나 압축 때문에 생긴 부자연스러운 흔적. (블록 깨짐, 링잉, 번짐, 계단 현상, 잔상 같은 것들.)&lt;/li&gt;
&lt;li&gt;압축이 용이: 노이즈가 많으면 매 프레임이 조금씩 달라 보여서 압축기가 &amp;ldquo;노이즈도 정보&amp;rdquo;라고 판단해 데이터를 많이 사용. 노이즈를 줄이면 프레임 간 차이가 줄어들어서 더 적은 용량으로 압축하기 쉬워짐.&lt;/li&gt;
&lt;li&gt;품질 개선: 자글거림과 깨짐이 줄어드니 사람이 보기에는 더 깔끔하고 안정적인 영상처럼 느껴짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.57.45.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnphmw/dJMcaicbHYD/wK0tPViIqXORsxQcnaYwFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnphmw/dJMcaicbHYD/wK0tPViIqXORsxQcnaYwFK/img.png&quot; data-alt=&quot;4분 11초&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnphmw/dJMcaicbHYD/wK0tPViIqXORsxQcnaYwFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdnphmw%2FdJMcaicbHYD%2FwK0tPViIqXORsxQcnaYwFK%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;1766&quot; height=&quot;998&quot; data-filename=&quot;스크린샷 2026-05-07 오전 1.57.45.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;998&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;4분 11초&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Low latency frame interpolation은 실시간으로 프레임 속도를 업샘플링 하며, 옵션으로 해상도도 업샘플링할 수 있음.&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;Frame Interpolation (프레임 보간)&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 2.12.09.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0mch/dJMcahj3AW8/T4gWfTEqPTw3n8jbzWZH21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0mch/dJMcahj3AW8/T4gWfTEqPTw3n8jbzWZH21/img.png&quot; data-alt=&quot;프레임 보간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0mch/dJMcahj3AW8/T4gWfTEqPTw3n8jbzWZH21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0mch%2FdJMcahj3AW8%2FT4gWfTEqPTw3n8jbzWZH21%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;614&quot; height=&quot;441&quot; data-filename=&quot;스크린샷 2026-05-07 오전 2.12.09.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프레임 보간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 2.08.44.png&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmkrVV/dJMcabD6Lpp/dkF47fGotK6bWaLFOi4GkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmkrVV/dJMcabD6Lpp/dkF47fGotK6bWaLFOi4GkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmkrVV/dJMcabD6Lpp/dkF47fGotK6bWaLFOi4GkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmkrVV%2FdJMcabD6Lpp%2FdkF47fGotK6bWaLFOi4GkK%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;1762&quot; height=&quot;998&quot; data-filename=&quot;스크린샷 2026-05-07 오전 2.08.44.png&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;998&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;Low Latency Super Resolution은 가벼우면서도 강력한 성능을 갖춘 실시간 업스케일링 기술&lt;br /&gt;&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;있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낮은 해상도의 비디오 프레임을 더 높은 해상도로 복원하고, 압축 과정에서 생기는 코딩 아티팩트를 줄이며, 흐릿해진 가장자리를 선명하게 보정함.&lt;br /&gt;&lt;br /&gt;해당 효과가 적용되면 영상의 해상도가 높아지고 압축 artifact 줄어듦.&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-filename=&quot;스크린샷 2026-05-10 오후 2.37.29.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZWmBy/dJMcahK8YcI/kLnp72yMQpemR2PUyHVUIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZWmBy/dJMcahK8YcI/kLnp72yMQpemR2PUyHVUIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZWmBy/dJMcahK8YcI/kLnp72yMQpemR2PUyHVUIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZWmBy%2FdJMcahK8YcI%2FkLnp72yMQpemR2PUyHVUIk%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;1814&quot; height=&quot;840&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.37.29.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;840&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&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/UbVbu/dJMcahEk3t8/zEXNoAiUFsZexeuYxk3hC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UbVbu/dJMcahEk3t8/zEXNoAiUFsZexeuYxk3hC1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;868&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.42.24.png&quot; style=&quot;width: 33.4751%; margin-right: 10px;&quot; data-widthpercent=&quot;34.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UbVbu/dJMcahEk3t8/zEXNoAiUFsZexeuYxk3hC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbVbu%2FdJMcahEk3t8%2FzEXNoAiUFsZexeuYxk3hC1%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;1776&quot; height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgwswD/dJMcadu7Ymt/s2hNPWotgTK1ObzAc1irM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgwswD/dJMcadu7Ymt/s2hNPWotgTK1ObzAc1irM1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;876&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.42.32.png&quot; style=&quot;width: 32.8332%; margin-right: 10px;&quot; data-widthpercent=&quot;33.61&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgwswD/dJMcadu7Ymt/s2hNPWotgTK1ObzAc1irM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgwswD%2FdJMcadu7Ymt%2Fs2hNPWotgTK1ObzAc1irM1%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;1758&quot; height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btcupR/dJMcabc2MiL/b60zeOhrMGQck5yTWujSvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btcupR/dJMcabc2MiL/b60zeOhrMGQck5yTWujSvK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;966&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.42.43.png&quot; style=&quot;width: 31.3662%;&quot; data-widthpercent=&quot;32.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btcupR/dJMcabc2MiL/b60zeOhrMGQck5yTWujSvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtcupR%2FdJMcabc2MiL%2Fb60zeOhrMGQck5yTWujSvK%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;1852&quot; height=&quot;966&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;VTFrameProcessor API에 접근하려면 앱이 Video Toolbox 프레임워크를 우선 가져와야 함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 단계에서는 앱은 전체 세션 동안 효과를 사용하는 방법을 설명하는 Configuration을 제공하여 처리하는 세션을 시작&lt;/li&gt;
&lt;li&gt;세션이 생성되면 앱은 프레임을 처리할 준비가 된 VTFrameProcessor 객체를 얻음&lt;/li&gt;
&lt;li&gt;VTFrameProcessor은 프레임 기반 API로 앱은 입력 비디오 프레임과 관련 매개변수를 하나씩 전송해야 함.&lt;/li&gt;
&lt;li&gt;처리 완료 후 프레임워크는 출력 프레임을 반환&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yvYMZ/dJMcaffwnCh/68EKJCCLOJNXwOA8d2gko0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yvYMZ/dJMcaffwnCh/68EKJCCLOJNXwOA8d2gko0/img.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;780&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.43.02.png&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.8934%; margin-right: 10px;&quot; data-widthpercent=&quot;52.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yvYMZ/dJMcaffwnCh/68EKJCCLOJNXwOA8d2gko0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyvYMZ%2FdJMcaffwnCh%2F68EKJCCLOJNXwOA8d2gko0%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;1840&quot; height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvrY38/dJMcaarIODL/yRBYXDKRVhsnTnXaT67hX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvrY38/dJMcaarIODL/yRBYXDKRVhsnTnXaT67hX1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;836&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.46.24.png&quot; style=&quot;width: 46.9438%;&quot; data-widthpercent=&quot;47.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvrY38/dJMcaarIODL/yRBYXDKRVhsnTnXaT67hX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvrY38%2FdJMcaarIODL%2FyRBYXDKRVhsnTnXaT67hX1%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;1784&quot; height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;예제, 비디오 편집에 적합한 기능, 실시간 영상에 적합한 기능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비디오 편집과 실시간 비디오 향상 등 두가지 사례에 초점을 맞춰 예제를 알아볼 것.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Frame rate conversion은 비디오 클립의 프레임 수를 늘리는 과정으로 새로운 프레임을 합성하여 기존 프레임 사이에 삽입하는 방식으로 진행.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주로 비디오 재생의 부드러움을 개선하기 위해 사용되며 특히 source 및 대상 디스플레이 간의 프레임 속도 불일치로 인한 judder(저더) 현상을 완화하는데 사용됨.&lt;/li&gt;
&lt;li&gt;누락된 프레임으로 인해 발생한 간격을 채우는 데도 도움이 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;슬로우 모션 효과를 만들 때 사용될 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영화에서는 느린 재생을 통해 시각적으로 더 인상적인 액션 장면을 만들기 위해 이 효과를 자주 활용&lt;/li&gt;
&lt;li&gt;스포츠에서는 중요한 영역은 강조하고 분석하기 위해 슬로우 모션을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.56.43.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQA0Y/dJMcaiwtZnG/cBzMt13U3QA1oob31dvkL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQA0Y/dJMcaiwtZnG/cBzMt13U3QA1oob31dvkL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQA0Y/dJMcaiwtZnG/cBzMt13U3QA1oob31dvkL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQA0Y%2FdJMcaiwtZnG%2FcBzMt13U3QA1oob31dvkL1%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;1824&quot; height=&quot;730&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.56.43.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;730&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;VTFrameProcessor API 사용하여 프레임 전환율을 구현하는 방법을 시연&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Processor 객체 생성&lt;/li&gt;
&lt;li&gt;Configuration 객체 생성&lt;/li&gt;
&lt;/ul&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/qWaGv/dJMcahEk3Ll/Ekg2DSkk2OnGNwCNR4r3iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qWaGv/dJMcahEk3Ll/Ekg2DSkk2OnGNwCNR4r3iK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;804&quot; data-filename=&quot;스크린샷 2026-05-10 오후 2.58.42.png&quot; style=&quot;width: 47.654%; margin-right: 10px;&quot; data-widthpercent=&quot;48.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qWaGv/dJMcahEk3Ll/Ekg2DSkk2OnGNwCNR4r3iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqWaGv%2FdJMcahEk3Ll%2FEkg2DSkk2OnGNwCNR4r3iK%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;1858&quot; height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCdZbw/dJMcahqQMyM/h34aQ2ZUFxSHsekZlAJ350/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCdZbw/dJMcahqQMyM/h34aQ2ZUFxSHsekZlAJ350/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;726&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.05.00.png&quot; style=&quot;width: 51.1832%;&quot; data-widthpercent=&quot;51.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCdZbw/dJMcahqQMyM/h34aQ2ZUFxSHsekZlAJ350/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCdZbw%2FdJMcahqQMyM%2Fh34aQ2ZUFxSHsekZlAJ350%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;1802&quot; height=&quot;726&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VTFrameProcessorParameters&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;이를 사용하기 전에 매개변수 객체에 필요한 모든 버퍼를 먼저 할당해야 함.&lt;/p&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;Configuration 소스 및 픽셀 버퍼 속성을 사용하여 CVPixelBuffer 풀을 설정할 수 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 현재 소스 및 다음 프레임 객체를 생성&lt;/li&gt;
&lt;li&gt;interpolationPhase 배열을 통해 보간된 프레임을 삽입할 위치를 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열의 크기는 보간할 프레임 수를 나타냄&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마지막으로 출력을 받을 버퍼로 구성된 destinationFrames 배열을 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 배열의 크기는 interpolationPhase 배열과 동일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼가 준비되었다면 나머지 Parameters를 설정할 수 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;optioncalFlow를 nil로 설정할 경우 프로세서가 흐름을 계산하도록 함.&lt;/li&gt;
&lt;li&gt;submissionMode를 설정하여 프레임이 순서에 따라 전송되는지 혹은 무작위로 전송되는지 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.09.16.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Anj0/dJMcahxB1oc/frRMrQhKXIMXKEOu5Mk0K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Anj0/dJMcahxB1oc/frRMrQhKXIMXKEOu5Mk0K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Anj0/dJMcahxB1oc/frRMrQhKXIMXKEOu5Mk0K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Anj0%2FdJMcahxB1oc%2FfrRMrQhKXIMXKEOu5Mk0K0%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;1808&quot; height=&quot;706&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.09.16.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;706&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VTFrameProcessorConfiguration 클래스는 효과의 처리 세션을 설정하는 방법을 설명&lt;/li&gt;
&lt;li&gt;VTFrameProcessorParameters 클래스는 입력 및 출력 프레임과 관련된 모든 매개변수를 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.10.50.png&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uwaIe/dJMcaffwode/htC9NgmP4sIOoI4PJGWqH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uwaIe/dJMcaffwode/htC9NgmP4sIOoI4PJGWqH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uwaIe/dJMcaffwode/htC9NgmP4sIOoI4PJGWqH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuwaIe%2FdJMcaffwode%2FhtC9NgmP4sIOoI4PJGWqH1%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;1758&quot; height=&quot;670&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.10.50.png&quot; data-origin-width=&quot;1758&quot; data-origin-height=&quot;670&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;&lt;br /&gt;Optical flow는 광학 흐름을 사전에 계산하기 위해서 VTOpticalFlowConfiguration과 VTOpticalFlowParameters를 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VTOpticalFlowConfiguration에서 사전 계산된 flow 매개변수에 false로 설정하면 프레임워크는 흐름을 실시간으로 계산함&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. 사전 계산(Pre-computed) 모드(영상 전체를 미리 분석 -&amp;gt; flow 데이터 저장 -&amp;gt; 필요할 때 꺼내 사용)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점: 재생/처리 시 매우 빠름 (이미 계산되어 있으니까)&lt;/li&gt;
&lt;li&gt;단점: 초기 분석 시간이 오래 걸림, 메모리/디스크 사용량 큼&lt;/li&gt;
&lt;li&gt;용도: 영상 편집, 후처리(post-processing), 고품질 슬로우 모션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2. 실시간(Real-time) 모드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레임이 들어올 때마다 즉석에서 flow 계산&lt;/li&gt;
&lt;li&gt;장점: 초기 지연 없음, 라이브 스트림에 적합&lt;/li&gt;
&lt;li&gt;단점: 매 프레임마다 계산 비용 발생 (성능 부담)&lt;/li&gt;
&lt;li&gt;용도: 실시간 카메라 효과, 라이브 비디오 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBnfc/dJMcaaFgTWq/ALmRGCCSOck4omkCuV5pwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBnfc/dJMcaaFgTWq/ALmRGCCSOck4omkCuV5pwK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;672&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.16.28.png&quot; style=&quot;width: 28.4204%; margin-right: 10px;&quot; data-widthpercent=&quot;29.1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBnfc/dJMcaaFgTWq/ALmRGCCSOck4omkCuV5pwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBnfc%2FdJMcaaFgTWq%2FALmRGCCSOck4omkCuV5pwK%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;1148&quot; height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7K7KW/dJMcai4hpoK/TGxLemBOlcNCY8wMA4bQDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7K7KW/dJMcai4hpoK/TGxLemBOlcNCY8wMA4bQDK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;590&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.17.54.png&quot; style=&quot;width: 32.2575%; margin-right: 10px;&quot; data-widthpercent=&quot;33.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7K7KW/dJMcai4hpoK/TGxLemBOlcNCY8wMA4bQDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7K7KW%2FdJMcai4hpoK%2FTGxLemBOlcNCY8wMA4bQDK%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;1144&quot; height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enQ1lZ/dJMcagk8G2q/Nc95oKt5uikiZSCtZvVYB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enQ1lZ/dJMcagk8G2q/Nc95oKt5uikiZSCtZvVYB0/img.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;822&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.18.05.png&quot; style=&quot;width: 36.9966%;&quot; data-widthpercent=&quot;37.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enQ1lZ/dJMcagk8G2q/Nc95oKt5uikiZSCtZvVYB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenQ1lZ%2FdJMcagk8G2q%2FNc95oKt5uikiZSCtZvVYB0%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;1828&quot; height=&quot;822&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;p data-ke-size=&quot;size16&quot;&gt;API를 통해 블러 강도를 조정해서 화면에서의 흘미 강도를 제어할 수 있음.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.24.21.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Gjl7/dJMcahRSo5S/pb0AxIySnFcB1RXnhgIYrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Gjl7/dJMcahRSo5S/pb0AxIySnFcB1RXnhgIYrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Gjl7/dJMcahRSo5S/pb0AxIySnFcB1RXnhgIYrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Gjl7%2FdJMcahRSo5S%2Fpb0AxIySnFcB1RXnhgIYrk%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;1796&quot; height=&quot;1028&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.24.21.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;1028&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;모션 블러로 처리 세션을 생성하려면 VTMotionBlurConfiguration 클래스를 사용하고 위에서 처리하는 형태와 동일하니 위의 내용 참고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;파라미터는 VTMotionBlurParameters를 사용하며 우선 next와 previous 프레임 등 두 개의 참조 프레임이 필요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 클립에는 previous에서 프레임을 nil로 설정하고 또한 최종 프레임에서 next 프레임을 nil로 설정&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;프레임 버퍼를 확보했으므로 VTMotionBlurParameters를 만들 수 있음&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로세서가 광학 흐름을 계산하도록 OpticalFlow를 nil로 설정하고 strength는 블러 강도를 설정함.&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/bn60ag/dJMcaiwtZ8A/5dxbLO5vjABBuKPanhN0Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn60ag/dJMcaiwtZ8A/5dxbLO5vjABBuKPanhN0Dk/img.png&quot; data-origin-width=&quot;1848&quot; data-origin-height=&quot;792&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.30.23.png&quot; style=&quot;width: 51.1901%; margin-right: 10px;&quot; data-widthpercent=&quot;51.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn60ag/dJMcaiwtZ8A/5dxbLO5vjABBuKPanhN0Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn60ag%2FdJMcaiwtZ8A%2F5dxbLO5vjABBuKPanhN0Dk%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;1848&quot; height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4H3hS/dJMcajhQhas/kbkmyXr5wFpVliwCvNOKB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4H3hS/dJMcajhQhas/kbkmyXr5wFpVliwCvNOKB0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;838&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.31.30.png&quot; style=&quot;width: 47.6471%;&quot; data-widthpercent=&quot;48.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4H3hS/dJMcajhQhas/kbkmyXr5wFpVliwCvNOKB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4H3hS%2FdJMcajhQhas%2FkbkmyXr5wFpVliwCvNOKB0%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;1820&quot; height=&quot;838&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;화상 회의나 실시간 스트리밍과 같은 실시간 영상에 사용하는 예제&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;우측 3개는 모두 실시간 사용을 위해서 설계되었음.&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Low latency frame interpolation은 일반적으로 수신하는 기기에서 진행.&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;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.33.04.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9W4rd/dJMcahK8Y2P/PqVNJ30a1hYIZj9l6xdU70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9W4rd/dJMcahK8Y2P/PqVNJ30a1hYIZj9l6xdU70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9W4rd/dJMcahK8Y2P/PqVNJ30a1hYIZj9l6xdU70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9W4rd%2FdJMcahK8Y2P%2FPqVNJ30a1hYIZj9l6xdU70%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;1766&quot; height=&quot;674&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.33.04.png&quot; data-origin-width=&quot;1766&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Low latency super resolution은 아래 2개로 구성되면 실시간 화상 회의를 위해 구성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VTLowLatencySuperResolutionScalerConfiguration
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레임 너비, 높이, 스케일링 비율만 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;VTLowLatencySuperResolutionScalerParameters
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스 및 목적지 프레임 버퍼만 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.40.22.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btOIR6/dJMcabqDssy/tHJAPKksvOT0NUQAFz5Ruk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btOIR6/dJMcabqDssy/tHJAPKksvOT0NUQAFz5Ruk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btOIR6/dJMcabqDssy/tHJAPKksvOT0NUQAFz5Ruk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtOIR6%2FdJMcabqDssy%2FtHJAPKksvOT0NUQAFz5Ruk%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;1766&quot; height=&quot;670&quot; data-filename=&quot;스크린샷 2026-05-10 오후 3.40.22.png&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;670&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;Low latency frame interpolation은 아래 2개로 구성되면 실시간 화상 회의를 위해 구성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 이 기술은 연결 속도가 느릴 때 영상 통화 앱에 유용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Judder를 완화하여 편안한 스트리밍 경험을 제공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 API에는 프레임 속도 더블링 및 해상도 업스케일링 기능을 하나로 결합한 유틸리티도 존재&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VTLowLatencyFrameConfiguration&lt;/li&gt;
&lt;li&gt;VTLowLatencyFrameParameters&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Video Toolbox 프레임워크는 이미 하드웨어 비디오 인코딩과 디코딩 기능에 대한 직접적인 접근을 비롯해 다양한 기능을 제공&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;VTFrameProcessor API를 통해 더 매력적인 애플리케이션을 만들 수 있음.&lt;/p&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://www.youtube.com/watch?v=EbRvY6j8d7g&amp;amp;list=PLjODKV8YBFHZ0CMQQU55bE_r6i01KSqCA&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=EbRvY6j8d7g&amp;amp;list=PLjODKV8YBFHZ0CMQQU55bE_r6i01KSqCA&lt;/a&gt;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>ios</category>
      <category>ml</category>
      <category>WWDC</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/804</guid>
      <comments>https://rldd.tistory.com/804#entry804comment</comments>
      <pubDate>Thu, 7 May 2026 00:37:06 +0900</pubDate>
    </item>
    <item>
      <title>SwiftUI의 새로운 기능 (Explore related documentation, sample code, and more) -wwdc25</title>
      <link>https://rldd.tistory.com/803</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwiftUI의 새로운 기능 (Explore related documentation, sample code, and more)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI의 저성능 관련 개선사항부터 UI상의 버튼에 이르기까지 시스템 전반에 걸쳐 개선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 4.31.12.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7C1E/dJMcabcXurO/FpTVLMYnaGv7Aa663kqsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7C1E/dJMcabcXurO/FpTVLMYnaGv7Aa663kqsWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7C1E/dJMcabcXurO/FpTVLMYnaGv7Aa663kqsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7C1E%2FdJMcabcXurO%2FFpTVLMYnaGv7Aa663kqsWk%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;1800&quot; height=&quot;732&quot; data-filename=&quot;스크린샷 2026-05-02 오후 4.31.12.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Make the new design shine&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 모든 시스템 기능 리뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Framework foundations
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능과 애니메이션, 레이아웃 관련 개선사항 공유&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SwiftUI across the system
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Apple 플랫폼 전반에 걸쳐 SwiftUI를 사용할 수 있는 새로운 환경 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Expand SwiftUI views
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 콘텐츠, 리치 텍스트 지원 등 SwiftUI 뷰의 새 기능에 대해 알아볼 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Make the new design shine&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네비게이션, 버튼, 탭바, macOS, PadOS 등 디자인이 변경되었음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.41.47.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nfjtn/dJMcaib820l/yQJzkerOy3YhYAkssyWKKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nfjtn/dJMcaib820l/yQJzkerOy3YhYAkssyWKKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nfjtn/dJMcaib820l/yQJzkerOy3YhYAkssyWKKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNfjtn%2FdJMcaib820l%2FyQJzkerOy3YhYAkssyWKKk%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;1850&quot; height=&quot;900&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.41.47.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;900&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;아이템 간 간격은 toolbar의 Spacer API를 사용해 조정.&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-filename=&quot;스크린샷 2026-05-02 오후 10.43.53.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXpERZ/dJMcaaZryaX/mAWcOF59xV9kkt7lzgy7E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXpERZ/dJMcaaZryaX/mAWcOF59xV9kkt7lzgy7E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXpERZ/dJMcaaZryaX/mAWcOF59xV9kkt7lzgy7E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXpERZ%2FdJMcaaZryaX%2FmAWcOF59xV9kkt7lzgy7E0%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;1808&quot; height=&quot;852&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.43.53.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;852&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;Liquid Glass는 색조를 지원하기도 하는데 스타일과 색상을 적용하면 됨.&lt;/p&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/bokvKj/dJMcaciFRgv/IkyEvlKUKqgJQGZ0hBA1KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bokvKj/dJMcaciFRgv/IkyEvlKUKqgJQGZ0hBA1KK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;642&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.47.36.png&quot; style=&quot;width: 46.3836%; margin-right: 10px;&quot; data-widthpercent=&quot;46.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bokvKj/dJMcaciFRgv/IkyEvlKUKqgJQGZ0hBA1KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbokvKj%2FdJMcaciFRgv%2FIkyEvlKUKqgJQGZ0hBA1KK%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;1194&quot; height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RHGsM/dJMcad2TylY/B3OVZ1OkzPm3bDJK8in8O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RHGsM/dJMcad2TylY/B3OVZ1OkzPm3bDJK8in8O1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;814&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.47.22.png&quot; data-widthpercent=&quot;53.07&quot; style=&quot;width: 52.4536%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RHGsM/dJMcad2TylY/B3OVZ1OkzPm3bDJK8in8O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRHGsM%2FdJMcad2TylY%2FB3OVZ1OkzPm3bDJK8in8O1%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;1712&quot; height=&quot;814&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;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/zKP0G/dJMcabD39sC/i8Kscgee5nOrT9qtpkGjs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zKP0G/dJMcabD39sC/i8Kscgee5nOrT9qtpkGjs1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;1048&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.49.02.png&quot; style=&quot;width: 41.7105%; margin-right: 10px;&quot; data-widthpercent=&quot;42.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zKP0G/dJMcabD39sC/i8Kscgee5nOrT9qtpkGjs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzKP0G%2FdJMcabD39sC%2Fi8Kscgee5nOrT9qtpkGjs1%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;1484&quot; height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Pk6c/dJMcaiwotUg/43Z9L3B1K8xsKbB3IVsMD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Pk6c/dJMcaiwotUg/43Z9L3B1K8xsKbB3IVsMD1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;924&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.48.28.png&quot; data-widthpercent=&quot;57.8&quot; style=&quot;width: 57.1267%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Pk6c/dJMcaiwotUg/43Z9L3B1K8xsKbB3IVsMD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Pk6c%2FdJMcaiwotUg%2F43Z9L3B1K8xsKbB3IVsMD1%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;1792&quot; height=&quot;924&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;외관을 새롭게 하려면 role에 search를 두어 역할을 갖도록 설정&lt;/p&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-filename=&quot;스크린샷 2026-05-02 오후 10.50.33.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgo6ms/dJMcajaZn9G/iqL5KxxwSZjtlDMhubqhF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgo6ms/dJMcajaZn9G/iqL5KxxwSZjtlDMhubqhF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgo6ms/dJMcajaZn9G/iqL5KxxwSZjtlDMhubqhF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgo6ms%2FdJMcajaZn9G%2FiqL5KxxwSZjtlDMhubqhF1%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;1832&quot; height=&quot;878&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.50.33.png&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;878&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;Liquid Glass는 커스텀하게도 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.52.35.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkALr8/dJMcahxwH0c/vW8Pz8Mo7DW0FTMW9Zizf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkALr8/dJMcahxwH0c/vW8Pz8Mo7DW0FTMW9Zizf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkALr8/dJMcahxwH0c/vW8Pz8Mo7DW0FTMW9Zizf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkALr8%2FdJMcahxwH0c%2FvW8Pz8Mo7DW0FTMW9Zizf1%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;1784&quot; height=&quot;896&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.52.35.png&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;896&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;macOS에서 메뉴 막대 구축시 `.commands` API를 사용했었고, 아이패드에서도 같은 결과를 낼 수 있음.&lt;/p&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/bcIj1T/dJMcabqyfPn/8Y0pAoCz0xEw67u1unkEBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcIj1T/dJMcabqyfPn/8Y0pAoCz0xEw67u1unkEBK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1560&quot; data-origin-height=&quot;878&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.54.56.png&quot; style=&quot;width: 48.8351%; margin-right: 10px;&quot; data-widthpercent=&quot;49.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcIj1T/dJMcabqyfPn/8Y0pAoCz0xEw67u1unkEBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcIj1T%2FdJMcabqyfPn%2F8Y0pAoCz0xEw67u1unkEBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1560&quot; height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl1hz8/dJMcadu2KmR/EpRHTJ80lOn3jlkuU2rdYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl1hz8/dJMcadu2KmR/EpRHTJ80lOn3jlkuU2rdYK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;874&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.54.42.png&quot; data-widthpercent=&quot;50.59&quot; style=&quot;width: 50.0021%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl1hz8/dJMcadu2KmR/EpRHTJ80lOn3jlkuU2rdYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl1hz8%2FdJMcadu2KmR%2FEpRHTJ80lOn3jlkuU2rdYK%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;874&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;p data-ke-size=&quot;size16&quot;&gt;SplitView를 사용하는 앱은 시스템이 가능한 공간에 따라 자동으로 열을 숨기거나 보임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.56.37.png&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8OtTF/dJMcabYnpJt/wNTZJuxk7mZJjmSgRvEPGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8OtTF/dJMcabYnpJt/wNTZJuxk7mZJjmSgRvEPGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8OtTF/dJMcabYnpJt/wNTZJuxk7mZJjmSgRvEPGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8OtTF%2FdJMcabYnpJt%2FwNTZJuxk7mZJjmSgRvEPGk%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;1620&quot; height=&quot;862&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.56.37.png&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;862&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;앱 크기 조정 기능을 구현하려면 UIRequiresFullScreen과 같이 화면을 전체 크기로 고정하는 API를 외부로 마이그레이션.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIRequiresFullScreen 이건 PadOS 26에서 더 이상 지원되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이패드 사이즈 조정에 대한 부분을 확인하려면 Elevate the design of your iPad app(WWDC25) 확인&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/efovzO/dJMcaakQwC2/uWvzebqKtLVuDxqO1CKUGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efovzO/dJMcaakQwC2/uWvzebqKtLVuDxqO1CKUGk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;918&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.59.36.png&quot; style=&quot;width: 49.015%; margin-right: 10px;&quot; data-widthpercent=&quot;49.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efovzO/dJMcaakQwC2/uWvzebqKtLVuDxqO1CKUGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefovzO%2FdJMcaakQwC2%2FuWvzebqKtLVuDxqO1CKUGk%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;1840&quot; height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgTURU/dJMcafzH9I2/hI2WoWhT8mNdarEeDUNEqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgTURU/dJMcafzH9I2/hI2WoWhT8mNdarEeDUNEqK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;910&quot; data-filename=&quot;스크린샷 2026-05-02 오후 10.59.47.png&quot; style=&quot;width: 49.8222%;&quot; data-widthpercent=&quot;50.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgTURU/dJMcafzH9I2/hI2WoWhT8mNdarEeDUNEqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgTURU%2FdJMcafzH9I2%2FhI2WoWhT8mNdarEeDUNEqK%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;1854&quot; height=&quot;910&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;macOS에서도 windowResizeAnchor를 이용해서 사이즈를 동적으로 대응할 수 있음.&lt;/p&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;Framework foundations&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.00.53.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJgGX/dJMcabYnpPh/xVLrIjdXOSk9WaHiwehSQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJgGX/dJMcabYnpPh/xVLrIjdXOSk9WaHiwehSQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJgGX/dJMcabYnpPh/xVLrIjdXOSk9WaHiwehSQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJgGX%2FdJMcabYnpPh%2FxVLrIjdXOSk9WaHiwehSQK%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;1800&quot; height=&quot;696&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.00.53.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;696&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;강력한 성능 부터 사용 편의성 개선 및 새로운 콘텐츠 레이아웃에 이르기까지 SwiftUI로 앱을 개발하기 좋은 시점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크 성능 개선 또한 여러 앱을 포함해 Apple 플랫폼 전반의 모든 앱에 이점을 가져다 줌.&lt;/p&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/y0VEO/dJMcaf0JLhs/ZhkMwRb0dYGxySDIkTpXaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y0VEO/dJMcaf0JLhs/ZhkMwRb0dYGxySDIkTpXaK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;608&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.03.35.png&quot; style=&quot;width: 25.1831%; margin-right: 10px;&quot; data-widthpercent=&quot;25.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y0VEO/dJMcaf0JLhs/ZhkMwRb0dYGxySDIkTpXaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy0VEO%2FdJMcaf0JLhs%2FZhkMwRb0dYGxySDIkTpXaK%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;844&quot; height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLKjVY/dJMcagSUM0q/OPp5w3apXCckZqWVUTRhJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLKjVY/dJMcagSUM0q/OPp5w3apXCckZqWVUTRhJk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;926&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.04.09.png&quot; style=&quot;width: 35.891%; margin-right: 10px;&quot; data-widthpercent=&quot;36.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLKjVY/dJMcagSUM0q/OPp5w3apXCckZqWVUTRhJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLKjVY%2FdJMcagSUM0q%2FOPp5w3apXCckZqWVUTRhJk%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;1832&quot; height=&quot;926&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b39HFx/dJMcadPlWZD/TDh9MaQeyHuSMprV0ZFNBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b39HFx/dJMcadPlWZD/TDh9MaQeyHuSMprV0ZFNBk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;914&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.04.48.png&quot; style=&quot;width: 36.6004%;&quot; data-widthpercent=&quot;37.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b39HFx/dJMcadPlWZD/TDh9MaQeyHuSMprV0ZFNBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb39HFx%2FdJMcadPlWZD%2FTDh9MaQeyHuSMprV0ZFNBk%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;1844&quot; height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 Lists, Scrolling, Profiling and Debugging이 개선되었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS에서는 10만개 항목 리스트 로딩이 6배, 업데이트는 최대 16배 더 빠르게 업데이트 됨.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTvxPQ/dJMcad2TyK2/kKKqd7nm2UmumnRqVp1qaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTvxPQ/dJMcad2TyK2/kKKqd7nm2UmumnRqVp1qaK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;928&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.07.39.png&quot; style=&quot;width: 32.7402%; margin-right: 10px;&quot; data-widthpercent=&quot;33.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTvxPQ/dJMcad2TyK2/kKKqd7nm2UmumnRqVp1qaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTvxPQ%2FdJMcad2TyK2%2FkKKqd7nm2UmumnRqVp1qaK%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;1890&quot; height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FrQKz/dJMcaakQwMX/mrQcaC3XH5Zejfj612Y8X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FrQKz/dJMcaakQwMX/mrQcaC3XH5Zejfj612Y8X1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;916&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.08.20.png&quot; style=&quot;width: 32.2916%; margin-right: 10px;&quot; data-widthpercent=&quot;33.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FrQKz/dJMcaakQwMX/mrQcaC3XH5Zejfj612Y8X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFrQKz%2FdJMcaakQwMX%2FmrQcaC3XH5Zejfj612Y8X1%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;1840&quot; height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b175S2/dJMb990z765/yqaevSRBhY4oHg0xTrrxp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b175S2/dJMb990z765/yqaevSRBhY4oHg0xTrrxp1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;916&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.09.00.png&quot; style=&quot;width: 32.6426%;&quot; data-widthpercent=&quot;33.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b175S2/dJMb990z765/yqaevSRBhY4oHg0xTrrxp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb175S2%2FdJMb990z765%2FyqaevSRBhY4oHg0xTrrxp1%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;1860&quot; height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI가 다음 스크롤링하면 다음 프레임을 렌더링할 수 있도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Deadline까지 모든 작업이 완료되지 않으면 프레임이 떨어지게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;iOS 및 macOS는 UI 업데이트를 통해 SwiftUI 스케줄링 경험이 개선&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응성을 향상시키고 SwiftUI가 다음 프레임에 대비해 더 많은 작업을 처리하도록 해줌.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M3Dyq/dJMcai4cbUi/fyhP3wcBOnLKn8r0Y7KkA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M3Dyq/dJMcai4cbUi/fyhP3wcBOnLKn8r0Y7KkA0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;1002&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.10.52.png&quot; style=&quot;width: 48.5695%; margin-right: 10px;&quot; data-widthpercent=&quot;49.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M3Dyq/dJMcai4cbUi/fyhP3wcBOnLKn8r0Y7KkA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM3Dyq%2FdJMcai4cbUi%2FfyhP3wcBOnLKn8r0Y7KkA0%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;1152&quot; height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AD90Z/dJMcabKR8Na/iTDkWyiWKZ1Xd0jfSN8TN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AD90Z/dJMcabKR8Na/iTDkWyiWKZ1Xd0jfSN8TN1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;990&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.11.06.png&quot; style=&quot;width: 50.2677%;&quot; data-widthpercent=&quot;50.86&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AD90Z/dJMcabKR8Na/iTDkWyiWKZ1Xd0jfSN8TN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAD90Z%2FdJMcabKR8Na%2FiTDkWyiWKZ1Xd0jfSN8TN1%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;1178&quot; height=&quot;990&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;LazyVStack을 ScrollView 안에 넣을 때 SwiftUI가 뷰가 나타나기 직전까지 로딩을 지연시킴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩된 ScrollView도 이와 같이 동일하게 동작함.&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-filename=&quot;스크린샷 2026-05-02 오후 11.13.33.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dotbE5/dJMcadV8V2O/4YH0QikzjkyTeKxapkOM4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dotbE5/dJMcadV8V2O/4YH0QikzjkyTeKxapkOM4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dotbE5/dJMcadV8V2O/4YH0QikzjkyTeKxapkOM4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdotbE5%2FdJMcadV8V2O%2F4YH0QikzjkyTeKxapkOM4k%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;1850&quot; height=&quot;814&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.13.33.png&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;814&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;앱에 어떤 성능 문제가 남아 있는지 이해하려면 Xcode에서 SwiftUI 성능 도구를 사용하면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 body를 업데이트 또는 View 업데이트와 같은 다양한 성능 문제 영역에서 문제를 확인하고 조치할 여러가지 방법을 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Optimize SwiftUI performance with instruments에서 상세한 정보를 확인할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.15.54.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfLOfx/dJMcafGtHDC/L8ZmwUgV76j52J3CmKpIQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfLOfx/dJMcafGtHDC/L8ZmwUgV76j52J3CmKpIQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfLOfx/dJMcafGtHDC/L8ZmwUgV76j52J3CmKpIQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfLOfx%2FdJMcafGtHDC%2FL8ZmwUgV76j52J3CmKpIQK%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;1808&quot; height=&quot;1002&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.15.54.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;1002&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.18.34.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QPdKA/dJMcab47XhA/FWU6rTYT14J00Gaa6luMcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QPdKA/dJMcab47XhA/FWU6rTYT14J00Gaa6luMcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QPdKA/dJMcab47XhA/FWU6rTYT14J00Gaa6luMcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQPdKA%2FdJMcab47XhA%2FFWU6rTYT14J00Gaa6luMcK%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;1840&quot; height=&quot;982&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.18.34.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;982&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;애니메이션에는 AnimatableData이라는 프로토콜을 사용함.&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;전체 애니메이션 데이터 선언을 추가할 때 드로잉 방향을 제외하는 것만 해도 코드가 정말 많은데, 새로운 애니메이션 매크로(@Animatable)를 사용하면, 사용자 지정 애니메이션 데이터 속성을 삭제하고, SwiftUI로 하여금 자동으로 합성하도록 만들 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드로잉 방향처럼 애니메이션을 원하지 않는 속성의 경우 @AnimatableIgnored을 사용해서 제외.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.21.35.png&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPmhcX/dJMcahRNjzP/LkMmE1scFFjhvO8KUC5dP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPmhcX/dJMcahRNjzP/LkMmE1scFFjhvO8KUC5dP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPmhcX/dJMcahRNjzP/LkMmE1scFFjhvO8KUC5dP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPmhcX%2FdJMcahRNjzP%2FLkMmE1scFFjhvO8KUC5dP0%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;1842&quot; height=&quot;656&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.21.35.png&quot; data-origin-width=&quot;1842&quot; data-origin-height=&quot;656&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;레이아웃은 이제 3차원으로 작업하는데 SwiftUI에는 이미 익숙한 수정자에 새로운 깊이 기반 변형이 도입되어, 직접적인 volumetic layout 없이도 더 많은 작업이 가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.24.25.png&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NfuU2/dJMcahRNjAL/fOwCOOx8ILVq2bab5xMESK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NfuU2/dJMcahRNjAL/fOwCOOx8ILVq2bab5xMESK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NfuU2/dJMcahRNjAL/fOwCOOx8ILVq2bab5xMESK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNfuU2%2FdJMcahRNjAL%2FfOwCOOx8ILVq2bab5xMESK%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;1876&quot; height=&quot;894&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.24.25.png&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;894&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;VisionOS 앱에서 3D가 적합한데, 새로운 Alignment3D를 활용하면 시간에 따른 정렬을 조정할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 VisionOS 26의 공간 레이아웃 개선사항으로 Meet SwiftUI spatial layout (WWDC25)를 확인.&lt;/p&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;h4 data-ke-size=&quot;size20&quot;&gt;SwiftUI across the system&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱은 시스템 전반에 걸쳐 SwiftUI의 이점을 활용할 수 있음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.27.03.png&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLLDwQ/dJMcadaKbiD/TW07S5gY80Z76n7zsPgoZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLLDwQ/dJMcadaKbiD/TW07S5gY80Z76n7zsPgoZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLLDwQ/dJMcadaKbiD/TW07S5gY80Z76n7zsPgoZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLLDwQ%2FdJMcadaKbiD%2FTW07S5gY80Z76n7zsPgoZ0%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;1860&quot; height=&quot;702&quot; data-filename=&quot;스크린샷 2026-05-02 오후 11.27.03.png&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;702&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;Scene에 대한 개선사항과 Widget 및 Control과 관련한 새로운 API 덕분에 앱이 플랫폼과 정말 잘 어우러짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI는 다른 프레임워크와는 훨씬 더 잘 작동함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.33.48.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfiqBC/dJMcabxjwxQ/lrTq2FkgBVna0tXp0SaDAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfiqBC/dJMcabxjwxQ/lrTq2FkgBVna0tXp0SaDAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfiqBC/dJMcabxjwxQ/lrTq2FkgBVna0tXp0SaDAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfiqBC%2FdJMcabxjwxQ%2FlrTq2FkgBVna0tXp0SaDAk%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;1782&quot; height=&quot;660&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.33.48.png&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;660&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;Scene은 앱의 뷰에 대한 루트 컨테이너로 인터페이스의 분리된 부분을 나타냄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 `body` 영역에 선언하여 Scene에 접근할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.39.05.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vvVGX/dJMcadPosXQ/dGj5kkPO85CIfN3DwZxFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vvVGX/dJMcadPosXQ/dGj5kkPO85CIfN3DwZxFCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vvVGX/dJMcadPosXQ/dGj5kkPO85CIfN3DwZxFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvvVGX%2FdJMcadPosXQ%2FdGj5kkPO85CIfN3DwZxFCK%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;1786&quot; height=&quot;702&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.39.05.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해부터는 Scene 브릿징을 통해 UIKit과 AppKit 라이프사이클 앱에서 SwiftUI의 Scene 요청이 가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene bridging을 통해서 UIKit과 AppKit에서 라이프사이클 상호운용 가능함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해서 SwiftUI에 있는 Scene 타입을 열거나 UIKit 또는 AppKit에서 바로 SwiftUI 기능을 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MenuBarExtra나 ImmersiveSpace 같은 Scene 타입 등을 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WindowStyle 등 다양한 Modifier와도 잘 작동함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.41.25.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVtk4F/dJMb997mcqJ/l8plV7feuV1pvwXOtXTpc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVtk4F/dJMb997mcqJ/l8plV7feuV1pvwXOtXTpc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVtk4F/dJMb997mcqJ/l8plV7feuV1pvwXOtXTpc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVtk4F%2FdJMb997mcqJ%2Fl8plV7feuV1pvwXOtXTpc1%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;1748&quot; height=&quot;1014&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.41.25.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;1014&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;인지 장애가 있는 사용자를 위한 특수 모드인 AssistiveAcess가 존재.&lt;/p&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-filename=&quot;스크린샷 2026-05-06 오후 11.46.44.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4frl/dJMcahEil9D/guwcdW8KFbHJqaqylduP40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4frl/dJMcahEil9D/guwcdW8KFbHJqaqylduP40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4frl/dJMcahEil9D/guwcdW8KFbHJqaqylduP40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4frl%2FdJMcahEil9D%2FguwcdW8KFbHJqaqylduP40%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;1812&quot; height=&quot;712&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.46.44.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;712&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;올해 SwiftUI에는 AppKit 작업에 몇가지 큰 개선사항이 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scene bridging 외에도 SwiftUI 뷰가 있는 시트도 표시할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppKit 제스처도 SwiftUI로 연결할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface Builder에서 NSHostingView를 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.49.54.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yVNoH/dJMcacQxp14/VzeJNWiR8prODATeraLixK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yVNoH/dJMcacQxp14/VzeJNWiR8prODATeraLixK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yVNoH/dJMcacQxp14/VzeJNWiR8prODATeraLixK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyVNoH%2FdJMcacQxp14%2FVzeJNWiR8prODATeraLixK%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;1108&quot; height=&quot;984&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.49.54.png&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;984&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;watchOS 26과 macOS Tahoe는 커스텀 제어 기능을 제공함.&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/bdKL8x/dJMcad2V6TH/kLCZoK5cHcLyKM7Ocv5gr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdKL8x/dJMcad2V6TH/kLCZoK5cHcLyKM7Ocv5gr1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;726&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.56.01.png&quot; style=&quot;width: 55.957%; margin-right: 10px;&quot; data-widthpercent=&quot;56.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdKL8x/dJMcad2V6TH/kLCZoK5cHcLyKM7Ocv5gr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdKL8x%2FdJMcad2V6TH%2FkLCZoK5cHcLyKM7Ocv5gr1%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;1732&quot; height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9E5B2/dJMcaja1WZp/luXSO68NITvWDZjs7f8cx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9E5B2/dJMcaja1WZp/luXSO68NITvWDZjs7f8cx0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1766&quot; data-origin-height=&quot;966&quot; data-filename=&quot;스크린샷 2026-05-06 오후 11.58.29.png&quot; style=&quot;width: 42.8802%;&quot; data-widthpercent=&quot;43.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9E5B2/dJMcaja1WZp/luXSO68NITvWDZjs7f8cx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9E5B2%2FdJMcaja1WZp%2FluXSO68NITvWDZjs7f8cx0%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;1766&quot; height=&quot;966&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;올해부터는 위젯을 VisionOS와 CarPlay에서 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VisionOS는 새로운 레벨 환경 변수를 사용해서 공간을 사용자가 지정할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸시 기반 업데이트 API 등 올해 여러가지가 새롭게 나옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.03.57.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I6XPx/dJMcahqNT8N/FqTSSYMYgxGhpkQozDP8y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I6XPx/dJMcahqNT8N/FqTSSYMYgxGhpkQozDP8y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I6XPx/dJMcahqNT8N/FqTSSYMYgxGhpkQozDP8y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI6XPx%2FdJMcahqNT8N%2FFqTSSYMYgxGhpkQozDP8y1%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;1746&quot; height=&quot;816&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.03.57.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;816&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;SwiftUI View 기능이 확장되었는데, 리치 텍스트 편집부터 3D 차트에 이르기까지 기존 SwiftUI의 기능과 외관이 새롭게 단장됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서 웹 콘텐츠를 곧바로 임베드 할 때 WebKit에서 WebView와 같은 SwiftUI API 전체를 활용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebView는 앱에서 웹 콘텐츠를 표시하는 새로운 SwiftUI 뷰로 Safari와 동일하게 WebKit으로 구동.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지를 커스텀하고 이와 상호작용하려면 WebView로 새 observable 모델 유형인 WebPage를 표시할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebPage는 웹과 풍부한 상호작용을 가능하도록 하는데, 프로그래밍을 통해 페이지에서 탐색하고 페이지 속성에 접근함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.08.16.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3Szmo/dJMcahRPPRW/4Of47V3X19qA8iX56HyAsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3Szmo/dJMcahRPPRW/4Of47V3X19qA8iX56HyAsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3Szmo/dJMcahRPPRW/4Of47V3X19qA8iX56HyAsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3Szmo%2FdJMcahRPPRW%2F4Of47V3X19qA8iX56HyAsk%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;1800&quot; height=&quot;1004&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.08.16.png&quot; data-origin-width=&quot;1800&quot; data-origin-height=&quot;1004&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;WebKit이 SwiftUI를 지원하는 새로운 방법으로 사용자 에이전트 커스텀이나 JavaScript 호출, 커스텀 URL 스킴 등이 생겼고 더 다양해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.10.04.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciTplC/dJMcafNjcv4/7KLF0OGfLrejuTdpq4k421/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciTplC/dJMcafNjcv4/7KLF0OGfLrejuTdpq4k421/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciTplC/dJMcafNjcv4/7KLF0OGfLrejuTdpq4k421/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciTplC%2FdJMcafNjcv4%2F7KLF0OGfLrejuTdpq4k421%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;1776&quot; height=&quot;990&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.10.04.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;990&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;Chart3D를 통해서 3차원 플롯을 보여줄수도 있음.&lt;/p&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/IEMuS/dJMcac33u9a/OlfW51UUEwh8R8kDe3Mf90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IEMuS/dJMcac33u9a/OlfW51UUEwh8R8kDe3Mf90/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;1034&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.13.48.png&quot; style=&quot;width: 43.7214%; margin-right: 10px;&quot; data-widthpercent=&quot;44.24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IEMuS/dJMcac33u9a/OlfW51UUEwh8R8kDe3Mf90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIEMuS%2FdJMcac33u9a%2FOlfW51UUEwh8R8kDe3Mf90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1804&quot; height=&quot;1034&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pQJoE/dJMcaarF1Nq/nwqHMakH0PVYSr7ObUl0ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pQJoE/dJMcaarF1Nq/nwqHMakH0PVYSr7ObUl0ck/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;632&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.16.38.png&quot; style=&quot;width: 55.1158%;&quot; data-widthpercent=&quot;55.76&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pQJoE/dJMcaarF1Nq/nwqHMakH0PVYSr7ObUl0ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpQJoE%2FdJMcaarF1Nq%2FnwqHMakH0PVYSr7ObUl0ck%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;1390&quot; height=&quot;632&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;올해는 mac에 드래그를 도입해서 여러 항목 주변으로 드래그를 할 수 있음. &lt;br /&gt;새로운 dragContainer도 macOS에서 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 뷰가 드래그 항목을 담는 컨테이너와 동일해지며, 커스텀 선택 동작과도 함께 작동함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드래그 앤 드롭이 실행될 때 항목은 지연 드래그하도록 SwiftUI가 자동으로 요청함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.17.31.png&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xk6wV/dJMcaiDdiNK/iGQ8m0kBYalA0Ntn0V8joK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xk6wV/dJMcaiDdiNK/iGQ8m0kBYalA0Ntn0V8joK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xk6wV/dJMcaiDdiNK/iGQ8m0kBYalA0Ntn0V8joK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxk6wV%2FdJMcaiDdiNK%2FiGQ8m0kBYalA0Ntn0V8joK%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;1698&quot; height=&quot;864&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.17.31.png&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;864&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;신규 `dragCofiguration` api로 드래그 작업을 사용자화 할 수도 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`onDragSessionUpdated`는 이벤트를 관찰 가능하도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dragPreviewsFormation`은 드래그 할 때 미리보기가 어떻게 뜨는지 확인하기 위해서 사용&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/ccpQe8/dJMcaakS39h/sOnudWC0AC9rlnALsezpQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccpQe8/dJMcaakS39h/sOnudWC0AC9rlnALsezpQ0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;822&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.22.11.png&quot; style=&quot;width: 52.4487%; margin-right: 10px;&quot; data-widthpercent=&quot;53.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccpQe8/dJMcaakS39h/sOnudWC0AC9rlnALsezpQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccpQe8%2FdJMcaakS39h%2FsOnudWC0AC9rlnALsezpQ0%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;822&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zsRrW/dJMb99M1l2m/BY9XLet2pkN3Qqam1neipK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zsRrW/dJMb99M1l2m/BY9XLet2pkN3Qqam1neipK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;966&quot; data-filename=&quot;스크린샷 2026-05-07 오전 12.22.51.png&quot; style=&quot;width: 46.3885%;&quot; data-widthpercent=&quot;46.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zsRrW/dJMb99M1l2m/BY9XLet2pkN3Qqam1neipK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzsRrW%2FdJMb99M1l2m%2FBY9XLet2pkN3Qqam1neipK%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;1794&quot; height=&quot;966&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;TextView에 이제 AttributedString이 지원됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TextEditor에서 AttributedString에 바인딩을 통해 내장형 텍스트 포맷팅 제어 기능을 사용할 수 있음.&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;(참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=cETgTtu6atM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=cETgTtu6atM&lt;/a&gt;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>ios</category>
      <category>SwiftUI</category>
      <category>wwdc25</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/803</guid>
      <comments>https://rldd.tistory.com/803#entry803comment</comments>
      <pubDate>Sat, 2 May 2026 16:21:35 +0900</pubDate>
    </item>
    <item>
      <title>클로저 안에 락을 두어 생기는 데드락 (Apple 시스템 API)</title>
      <link>https://rldd.tistory.com/801</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;클로저 안에 락을 두어 생기는 데드락 (Apple 시스템 API)&lt;/h2&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;애플 시스템 API를 쓰는 영역에서 이 형태로 데드락이 발생. lock을 잡고 시스템 API를 호출한 뒤 완료 클로저 안에서 unlock을 하던 기존 코드였음. 그 클로저가 끝내 호출되지 않으면 lock이 풀리지 않아 무한 대기에 빠짐.&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;완료 클로저 안의 unlock이 실행되지 않는 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락을 잡은 채로 완료 클로저 안에서만 unlock을 하는 형태로 실제 코드를 단순화하면 아래와 같음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_801_1&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func activateAudioSession() {
    lock.lock()
    // Apple 오디오 세션 관련 시스템 API 호출
    // 완료가 비동기 클로저로 전달됨
    audioSession.requestRecordPermission { [weak self] granted in
        // 응답이 와야 여기서 unlock
        self?.lock.unlock()
    }
    // 함수는 여기서 반환. lock은 아직 잡힌 상태
}&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;정상 흐름에서는 완료 클로저가 불리면서 unlock까지 이어짐. 문제는 시스템 API가 응답을 주지 않는 경우였음. 콜백이 오지 않으면 클로저 안의 unlock도 실행되지 않고, lock은 잡힌 채로 남음. 이후 같은 lock을 잡으려는 호출은 전부 &lt;code&gt;lock.lock()&lt;/code&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;근본 원인은 unlock을 클로저 안에 둔 것 자체가 아니라, lock을 푸는 시점을 외부 시스템의 응답에 통째로 맡긴 것. 응답이 없으면 해제 시점도 같이 사라짐.&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;클로저에 콜백이 오지 않을 수 있는 Apple 시스템 API&lt;/h4&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;모두 클로저나 delegate 콜백이 불릴지 여부가 앱이 아니라 시스템 상태에 달려 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오디오 권한: &lt;code&gt;AVAudioSession&lt;/code&gt;의 마이크 권한 요청(&lt;code&gt;requestRecordPermission&lt;/code&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜백이 오지 않아 데드락이 발생했고, 해당 경우에 30초 정도 지나면 콜백이 내려옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;페이지 전환: &lt;code&gt;UIPageViewController&lt;/code&gt;의 &lt;code&gt;setViewControllers(_:direction:animated:completion:)&lt;/code&gt;로 앞선 전환 애니메이션이 끝나기 전에 다시 호출하면 completion이 불리지 않고 유실되는 경우가 발생.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해결 방법으로는 셋업 단계에서 &lt;code&gt;isEnabledUserInteraction = false&lt;/code&gt;로 설정하고 클로저를 받은 후에 다시 활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;알림 권한: &lt;code&gt;UNUserNotificationCenter&lt;/code&gt;의 &lt;code&gt;requestAuthorization(options:completionHandler:)&lt;/code&gt;와 &lt;code&gt;getNotificationSettings(completionHandler:)&lt;/code&gt;. 문서에도 적혀 있듯 메인 스레드를 보장하지 않으면 특정 Xcode 버전에서 배포한 특정 iOS 버전에서만 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 케이스에서는 Concurrency API도 동일하게 동작하지 않음&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lgvv/unusernotificationcenter-requestauthorization-blocking-simulation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/lgvv/unusernotificationcenter-requestauthorization-blocking-simulation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lock 해제와 흐름 진행을 완료 클로저 하나에만 의존하지 않도록 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;lock은 공유 상태를 읽고 쓰는 최소 구간에만 사용함. 외부 응답을 기다리는 작업은 lock 밖으로 빼서, lock을 잡은 채 콜백을 기다리지 않게 함.&lt;/li&gt;
&lt;li&gt;애플의 시스템 API도 클로저로 콜백이 오지 않을 수 있어서 타임아웃을 함께 두어 예방할 수 있음. 정해진 시간 안에 콜백이 없으면 실패로 처리하고 상태를 원래대로 되돌림.&lt;/li&gt;
&lt;li&gt;완료 처리가 한 번만 실행되도록 보호함. 타임아웃과 실제 콜백이 둘 다 도착해도 unlock이나 상태 변경이 중복되지 않게 함.&lt;/li&gt;
&lt;/ol&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;/h4&gt;
&lt;pre id=&quot;code_801_2&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func requestMicPermission() {
    // lock은 상태 변경 구간에만 사용
    lock.lock()
    markRequesting()
    lock.unlock()

    audioSession.requestRecordPermission { [weak self] granted in
        self?.finishPermission(with: granted ? .granted : .denied)
    }

    // 콜백이 오지 않는 경우를 대비한 타임아웃
    scheduleTimeout { [weak self] in
        self?.finishPermission(with: .timedOut)
    }
}&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;code&gt;finishPermission(with:)&lt;/code&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lgvv/unusernotificationcenter-requestauthorization-blocking-simulation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/lgvv/unusernotificationcenter-requestauthorization-blocking-simulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rldd.tistory.com/770&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://rldd.tistory.com/770&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/avfaudio/avaudiosession/requestrecordpermission(_:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/avfaudio/avaudiosession/requestrecordpermission(_:)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uipageviewcontroller/setviewcontrollers(_:direction:animated:completion:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/uikit/uipageviewcontroller/setviewcontrollers(_:direction:animated:completion:)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/requestauthorization(options:completionhandler:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/requestauthorization(options:completionhandler:)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Project/개발 업무</category>
      <category>AVAudioSession</category>
      <category>Closure</category>
      <category>DEADLOCK</category>
      <category>defer</category>
      <category>lock</category>
      <category>UIPageViewController</category>
      <category>UNUserNotificationCenter</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/801</guid>
      <comments>https://rldd.tistory.com/801#entry801comment</comments>
      <pubDate>Thu, 26 Mar 2026 17:02:00 +0900</pubDate>
    </item>
    <item>
      <title>기기별 RAM 용량에 따른 이미지 동적 캐시 사이즈 설계</title>
      <link>https://rldd.tistory.com/800</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기기별 RAM 용량에 따른 이미지 동적 캐시 사이즈 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 서비스를 운영하다 보면, 여전히 RAM 2GB~3GB 수준의 구형 기기 사용자를 무시할 수 없음. iPhone 8, iPhone SE 1세대, 일부 보급형 기기처럼 메모리 여유가 크지 않은 환경에서는, 캐시 전략 하나만으로도 앱 안정성이 크게 달라질 수 있음. 이미지 캐시는 사용자 경험을 빠르게 만들기 위한 중요한 장치지만, 동시에 메모리를 많이 사용하는 기능이기도 함. 그래서 캐시 크기를 고정값으로 두면 다음과 같은 문제가 생김&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저사양 기기에서는 메모리 압박으로 크래시 위험이 커짐&lt;/li&gt;
&lt;li&gt;고사양 기기에서는 더 많은 자원을 활용하지 못해 비효율이 발생함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기의 물리적 RAM 용량을 기준으로 이미지 캐시 사이즈를 동적으로 결정하는 방식을 적용&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS는 앱이 사용할 수 있는 메모리 한도를 초과하면 명시적인 에러 없이 프로세스를 종료할 수 있음. &lt;b&gt;저사양 기기의 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RAM 2GB 기기에서 캐시를 1GB로 고정하는 것은 실제 사용 가능한 메모리 대비 과도한 점유가 될 수 있음.&lt;/li&gt;
&lt;li&gt;경우에 따라서는 결과적으로 메모리 부족으로 인한 크래시 확률이 높아짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 디스크 접근 증가&lt;/li&gt;
&lt;li&gt;네트워크 재요청 증가&lt;/li&gt;
&lt;li&gt;이미지 디코딩 비용 증가&lt;/li&gt;
&lt;li&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;즉, 캐시를 너무 작게 잡아도 손해가 커서 기기별 메모리 특성에 맞게끔 균형을 찾고자 함.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 캐시 사이즈에 기기별 가용 가능한 메모리 크기를 기반으로 상한값을 다르게 설정. physicalMemory는 기기 스펙의 메모리 용량과 일치하지 않음(스펙보다 작게 잡힘).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 코드에서 1/8 값을 사용하고 있으나 프로젝트 특성에 맞게 변경해도 됨.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 공식 문서에서 1/8 사용하고 있는 예제가 있어서 그대로 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_800_1&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func configureImageCacheLimits() {
        // 기기의 물리적 RAM 전체 용량 (단위: bytes)
        // 예) 6GB RAM &amp;rarr; 6,442,450,944 bytes
        let physicalMemory = ProcessInfo.processInfo.physicalMemory
        // 이미지 캐시에 허용할 최소 메모리 한도 (단위: MB)
        let minimumLimitMB: UInt64 = 100
        
        // 이미지 캐시에 허용할 최대 메모리 한도 (단위: MB)
        let maximumLimitMB: UInt64 = 512
        
        // physicalMemory(bytes) &amp;rarr; MB 변환 과정:
        //   &amp;divide; 8          : 전체 RAM의 1/8만 캐시에 사용 (나머지는 OS or 앱 등이 사용)
        //   &amp;divide; 1024       : bytes &amp;rarr; KB
        //   &amp;divide; 1024       : KB &amp;rarr; MB
        //
        // 예) 6GB RAM 기기:
        //   6,442,450,944 &amp;divide; 8 = 805,306,368 bytes (약 768MB, RAM의 1/8)
        //   805,306,368 &amp;divide; 1024 = 786,432 KB
        //   786,432 &amp;divide; 1024 = 768 MB
        //   &amp;rarr; min(512, 768) = 512 &amp;rarr; max(100, 512) = 512MB 적용
        //
        // 예) 2GB RAM 기기:
        //   2,147,483,648 &amp;divide; 8 = 268,435,456 bytes (약 256MB)
        //   &amp;rarr; 256MB &amp;rarr; max(100, 256) = 256MB 적용
        let computedLimitMB = max(
            minimumLimitMB,
            min(maximumLimitMB, physicalMemory / 8 / 1024 / 1024)
        )
        
        // 최종 캐시 한도를 다시 bytes로 변환 (SDImageCache에 bytes 단위로 전달해야 하므로)
        // 예) 512MB &amp;rarr; 512 &amp;times; 1024 &amp;times; 1024 = 536,870,912 bytes
        let physicalMemoryMB = physicalMemory / 1024 / 1024
        let physicalMemoryGB = physicalMemoryMB / 1024
        
        var systemInfo = utsname()
        uname(&amp;amp;systemInfo)
        let deviceModel = withUnsafePointer(to: &amp;amp;systemInfo.machine) {
            $0.withMemoryRebound(to: CChar.self, capacity: 1) {
                String(validatingUTF8: $0) ?? &quot;Unknown&quot;
            }
        }
        
        print(&quot;기기 모델: \(deviceModel)&quot;)
        print(&quot;물리 메모리: \(physicalMemoryGB) GB&quot;)
        print(&quot;캐시 한도: \(computedLimitMB) MB&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;iPhone 14 Pro (모델 식별자 &lt;code&gt;iPhone15,2&lt;/code&gt;, physicalMemory 약 5GB)&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;1044&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/orZcs/dJMcaaragzC/gaoQ6KEYLPXAKVuyw3aNC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/orZcs/dJMcaaragzC/gaoQ6KEYLPXAKVuyw3aNC1/img.png&quot; data-alt=&quot;이미지 01 - iPhone 14 Pro (iPhone15,2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/orZcs/dJMcaaragzC/gaoQ6KEYLPXAKVuyw3aNC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2ForZcs%2FdJMcaaragzC%2FgaoQ6KEYLPXAKVuyw3aNC1%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; alt=&quot;이미지 01 - iPhone 14 Pro (iPhone15,2)&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;122&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 01 - iPhone 14 Pro (iPhone15,2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iPhone 12 mini (모델 식별자 &lt;code&gt;iPhone13,1&lt;/code&gt;, physicalMemory 약 3GB)&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;1032&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otUih/dJMcaivVcTZ/Yio0p0oX522SkOpuNfqve1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otUih/dJMcaivVcTZ/Yio0p0oX522SkOpuNfqve1/img.png&quot; data-alt=&quot;이미지 02 - iPhone 12 mini (iPhone13,1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otUih/dJMcaivVcTZ/Yio0p0oX522SkOpuNfqve1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotUih%2FdJMcaivVcTZ%2FYio0p0oX522SkOpuNfqve1%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; alt=&quot;이미지 02 - iPhone 12 mini (iPhone13,1)&quot; loading=&quot;lazy&quot; width=&quot;1032&quot; height=&quot;156&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 02 - iPhone 12 mini (iPhone13,1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 식별자는 마케팅 이름과 다름. &lt;code&gt;iPhone15,2&lt;/code&gt;는 iPhone 14 Pro, &lt;code&gt;iPhone13,1&lt;/code&gt;은 iPhone 12 mini임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기기&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;th&gt;1/8 계산값&lt;/th&gt;
&lt;th&gt;최종 적용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iPhone SE (1st)&lt;/td&gt;
&lt;td&gt;2GB&lt;/td&gt;
&lt;td&gt;256MB&lt;/td&gt;
&lt;td&gt;256MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 11&lt;/td&gt;
&lt;td&gt;4GB&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;td&gt;512MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iPhone 15 Pro&lt;/td&gt;
&lt;td&gt;8GB&lt;/td&gt;
&lt;td&gt;1024MB&lt;/td&gt;
&lt;td&gt;512MB (상한 제한)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고) &lt;a href=&quot;https://developer.android.com/topic/performance/graphics/cache-bitmap?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android 비트맵 캐싱&lt;/a&gt;&lt;/p&gt;</description>
      <category>Project/개발 업무</category>
      <category>Android</category>
      <category>ios</category>
      <category>swift</category>
      <category>동적캐시</category>
      <category>이미지캐시</category>
      <category>캐시</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/800</guid>
      <comments>https://rldd.tistory.com/800#entry800comment</comments>
      <pubDate>Wed, 25 Mar 2026 23:02:57 +0900</pubDate>
    </item>
    <item>
      <title>Socket과 WebSocket 정리 (RFC 6455)</title>
      <link>https://rldd.tistory.com/798</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Socket과 WebSocket 정리 (RFC 6455)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실시간 통신을 개발하면서 Socket과 WebSocket 그리고 Polling과 Long Polling에 대해서 정리하고자 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 &lt;a href=&quot;https://www.rfc-editor.org/info/rfc6455/&quot;&gt;RFC 6455 (The WebSocket Protocol)&lt;/a&gt; 섹션 1을 기반으로 정리.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;목차&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 정리&lt;/li&gt;
&lt;li&gt;Socket이란?&lt;/li&gt;
&lt;li&gt;폴링: 계속 물어보는 방식&lt;/li&gt;
&lt;li&gt;롱폴링: 물어보고 기다리는 방식&lt;/li&gt;
&lt;li&gt;Socket과 WebSocket의 차이&lt;/li&gt;
&lt;li&gt;WebSocket: 하나의 TCP 연결로 양방향&lt;/li&gt;
&lt;li&gt;오프닝 핸드셰이크: HTTP로 시작해서 HTTP를 떠남&lt;/li&gt;
&lt;li&gt;클로징 핸드셰이크: TCP FIN만으로 부족한 이유&lt;/li&gt;
&lt;li&gt;TCP, HTTP와의 관계&lt;/li&gt;
&lt;li&gt;서브프로토콜&lt;/li&gt;
&lt;li&gt;비교 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;HTTP 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 요청-응답(request-response) 모델임.&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;1087&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO5Mnz/dJMcadvOxCX/7vNWtng7ixUsd2Frv4TpP1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO5Mnz/dJMcadvOxCX/7vNWtng7ixUsd2Frv4TpP1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO5Mnz/dJMcadvOxCX/7vNWtng7ixUsd2Frv4TpP1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO5Mnz%2FdJMcadvOxCX%2F7vNWtng7ixUsd2Frv4TpP1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1087&quot; height=&quot;398&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;398&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;HTTP 모델의 한계는 서버가 먼저 데이터를 전송할 수 없음.&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;RFC 6455의 1.1 Background에서는 WebSocket 이전의 웹이 이 문제를 HTTP 남용(abuse of HTTP)으로 풀어왔다고 지적하며, 그 비용으로 세 가지를 언급함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 클라이언트마다 여러 개의 TCP 연결을 사용하게 됨. 보내는 용도 하나, 받는 용도 하나씩.&lt;/li&gt;
&lt;li&gt;모든 메시지에 HTTP 헤더가 붙음. 메시지 한 줄을 보내는 데 수백 바이트의 헤더 오버헤드가 발생.&lt;/li&gt;
&lt;li&gt;클라이언트가 &quot;보내는 연결&quot;과 &quot;받는 연결&quot;의 대응 관계를 직접 추적해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴링과 롱폴링은 이 한계 안에서 실시간을 흉내 내는 기법이고, WebSocket은 이 한계 자체를 없애는 프로토콜임.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;Socket이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket으로 가기 전에, 이름의 원조인 소켓부터 정리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소켓(socket)은 운영체제가 제공하는 네트워크 통신 인터페이스임. 프로세스가 네트워크로 데이터를 보내고 받으려면 커널의 네트워크 스택을 거쳐야 하는데, 그 출입구가 소켓.&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;828&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n7j3p/dJMcadvOxCY/bYaPsp9fBIsBNZ7pcs1oj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n7j3p/dJMcadvOxCY/bYaPsp9fBIsBNZ7pcs1oj0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n7j3p/dJMcadvOxCY/bYaPsp9fBIsBNZ7pcs1oj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn7j3p%2FdJMcadvOxCY%2FbYaPsp9fBIsBNZ7pcs1oj0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;343&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;TCP 연결 하나는 (출발지 IP, 출발지 Port, 목적지 IP, 목적지 Port) 조합으로 식별되고, 소켓은 그 연결의 양 끝단(endpoint)임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱 입장에서 소켓은 파일 디스크립터라는 숫자 핸들로 보이며, read, write로 바이트를 흘려보내는 통로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;여기서 중요한 성질이 두 가지 있음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소켓은 프로토콜이 아니라 인터페이스. TCP 위에서 무엇을 주고받을지(HTTP인지, WebSocket인지, 자체 프로토콜인지)는 소켓이 관여하지 않음.&lt;/li&gt;
&lt;li&gt;TCP 소켓은 스트림(stream). 바이트가 순서대로 흐를 뿐, &quot;메시지의 시작과 끝&quot;이라는 경계가 없음. 경계를 나누는 일은 위 계층의 몫.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 성질이 뒤에서 WebSocket과의 차이, 그리고 WebSocket이 프레이밍을 도입한 이유로 이어짐.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;폴링: 계속 물어보는 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴링(polling)은 클라이언트가 일정 주기로 서버에 &quot;새로운 것 있어?&quot;라고 묻는 방식.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGGovE/dJMcadWYAXw/1rYqTOMF1tWtS0kZ1UOKg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGGovE/dJMcadWYAXw/1rYqTOMF1tWtS0kZ1UOKg0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGGovE/dJMcadWYAXw/1rYqTOMF1tWtS0kZ1UOKg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGGovE%2FdJMcadWYAXw%2F1rYqTOMF1tWtS0kZ1UOKg0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;303&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;구현이 가장 단순함. 타이머 하나와 평범한 HTTP 요청이면 충분.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 주기 설정이 딜레마라는 점.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주기를 짧게 잡으면: 실시간성은 올라가지만, 대부분의 요청이 빈 응답으로 끝남. 매 요청마다 HTTP 헤더, 경우에 따라 TCP 핸드셰이크와 TLS 협상 비용까지 반복해서 지불.&lt;/li&gt;
&lt;li&gt;주기를 길게 잡으면: 낭비는 줄지만, 이벤트 발생부터 전달까지 최대 한 주기만큼 지연됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 폴링에서 실시간성과 자원 효율은 교환 관계임. 어느 쪽으로 조정해도 한쪽을 포기하게 됨.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;롱폴링: 물어보고 기다리는 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;롱폴링(long polling)은 폴링의 &quot;빈 응답&quot; 낭비를 줄이려는 개선.&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;1023&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QJVjI/dJMcadWYAXy/VaaQlKyNkvgcH3usWczseK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QJVjI/dJMcadWYAXy/VaaQlKyNkvgcH3usWczseK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QJVjI/dJMcadWYAXy/VaaQlKyNkvgcH3usWczseK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQJVjI%2FdJMcadWYAXy%2FVaaQlKyNkvgcH3usWczseK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1023&quot; height=&quot;438&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;438&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;br /&gt;하지만 요청-응답 모델을 벗어난 것은 아님.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 하나당 전달할 수 있는 이벤트는 한 번. 응답을 받고 다음 요청을 다시 거는 사이에 공백이 생기고, 이벤트가 잦으면 사실상 폴링과 비슷해짐.&lt;/li&gt;
&lt;li&gt;서버는 응답을 보류하는 동안 클라이언트 수만큼 연결을 계속 점유함.&lt;/li&gt;
&lt;li&gt;매 사이클마다 HTTP 헤더 오버헤드는 여전히 반복됨.&lt;/li&gt;
&lt;li&gt;클라이언트에서 서버로 보내는 방향은 여전히 별도의 요청이 필요함. RFC 6455가 지적한 &quot;보내는 연결과 받는 연결의 이중 관리&quot; 문제가 그대로 남음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 롱폴링은 서버에서 클라이언트로 오는 지연을 줄였을 뿐, 양방향 통신 자체를 만든 것은 아님.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;Socket과 WebSocket의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소켓은 전송 계층 연결(주로 TCP)의 끝단을 다루는 OS 인터페이스. WebSocket은 그 TCP 소켓 위에서 동작하는 응용 계층 프로토콜. WebSocket 연결도 내부적으로는 결국 TCP 소켓 하나를 사용함.&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;740&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beNW8a/dJMcadvOxC0/wk26mXroDpzqCp9F4YPlJ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beNW8a/dJMcadvOxC0/wk26mXroDpzqCp9F4YPlJ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beNW8a/dJMcadvOxC0/wk26mXroDpzqCp9F4YPlJ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeNW8a%2FdJMcadvOxC0%2Fwk26mXroDpzqCp9F4YPlJ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;240&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;구분(TCP) SocketWebSocket&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정체&lt;/td&gt;
&lt;td&gt;OS가 제공하는 통신 인터페이스 (API)&lt;/td&gt;
&lt;td&gt;표준 문서(RFC 6455)로 정의된 프로토콜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;계층&lt;/td&gt;
&lt;td&gt;전송 계층 연결의 끝단&lt;/td&gt;
&lt;td&gt;응용 계층&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 단위&lt;/td&gt;
&lt;td&gt;바이트 스트림 (경계 없음)&lt;/td&gt;
&lt;td&gt;메시지/프레임 (경계 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 수립&lt;/td&gt;
&lt;td&gt;TCP 3-way handshake&lt;/td&gt;
&lt;td&gt;TCP 위에서 HTTP Upgrade 핸드셰이크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 주체&lt;/td&gt;
&lt;td&gt;임의의 네트워크 프로그램&lt;/td&gt;
&lt;td&gt;주로 웹 환경 (브라우저 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안 모델&lt;/td&gt;
&lt;td&gt;없음 (앱이 알아서)&lt;/td&gt;
&lt;td&gt;origin 기반 보안 모델 내장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉 &quot;Socket vs WebSocket&quot;은 대등한 비교가 아니라 층이 다른 개념임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket을 이해한다는 것은 &quot;TCP 소켓 위에 어떤 규칙을 얹었는가&quot;를 이해하는 일.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;WebSocket: 하나의 TCP 연결로 양방향&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RFC 6455가 정의하는 WebSocket은 폴링, 롱폴링이 우회하던 문제를 정면으로 풂.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 단 하나의 TCP 연결 위에서 클라이언트와 서버가 대등하게, 아무 때나, 양방향으로 메시지를 보내는 것.프로토콜은 크게 두 부분으로 구성됨 (RFC 6455 &amp;sect;1.2).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핸드셰이크: HTTP 형식을 빌린 연결 수립 절차&lt;/li&gt;
&lt;li&gt;데이터 전송: 연결 수립 후, 프레임(frame) 단위로 메시지를 주고받는 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 폴링의 세 가지 비용이 모두 사라짐. 연결은 하나고, 메시지마다 붙는 것은 HTTP 헤더가 아니라 몇 바이트짜리 프레임 헤더이며, 보내는 연결과 받는 연결을 구분해서 관리할 필요가 없음.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;오프닝 핸드셰이크: HTTP로 시작해서 HTTP를 떠남&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket 연결은 평범한 HTTP GET 요청으로 시작함. RFC 6455 &amp;sect;1.2의 예시 그대로.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13&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;서버가 WebSocket을 지원하면 아래처럼 응답함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat&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;상태 코드 101 Switching Protocols가 교환되는 순간, 이 TCP 연결 위의 대화는 더 이상 HTTP가 아님. 같은 연결이 그대로 WebSocket 프레임을 실어 나르는 통로가 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 HTTP 형식으로 시작하는 이유는 호환성 (&amp;sect;1.3). 80/443 포트, HTTP 서버, 프록시 같은 웹의 기존 인프라를 그대로 통과하기 위해, 이미 웹이 이해하는 문법인 HTTP Upgrade 메커니즘을 입구로 삼음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 헤더의 역할:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Upgrade: websocket + Connection: Upgrade: 이 연결을 WebSocket으로 전환하겠다는 선언&lt;/li&gt;
&lt;li&gt;Sec-WebSocket-Key: 16바이트 난수를 base64 인코딩한 값 (다음 절에서 정리)&lt;/li&gt;
&lt;li&gt;Sec-WebSocket-Version: 13: 프로토콜 버전. RFC 6455가 13&lt;/li&gt;
&lt;li&gt;Origin: 브라우저 환경에서 요청 출처를 서버에 알려주는 값. 승인되지 않은 웹 페이지의 크로스 오리진 연결을 서버가 거부할 수 있게 함&lt;/li&gt;
&lt;li&gt;Sec-WebSocket-Protocol: 서브프로토콜 협상 (뒤에서 정리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;클로징 핸드셰이크: TCP FIN만으로 부족한 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP에도 이미 연결 종료 절차(FIN/ACK)가 있음. 그런데 WebSocket은 그 위에 자체 종료 핸드셰이크를 하나 더 정의함 (&amp;sect;1.4).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;절차는 단순함. 한쪽이 Close 프레임을 보내고, 상대가 Close 프레임으로 응답하면, 먼저 보낸 쪽이 TCP 연결을 닫음. 양쪽이 동시에 시작해도 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 종료를 그대로 쓰지 않는 이유를 RFC는 아래처럼 설명함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TCP의 종료 신호는 프록시 같은 중간 장치를 거치는 환경에서 끝(end-to-end)까지 항상 신뢰성 있게 전달된다고 보장할 수 없음.&lt;/li&gt;
&lt;li&gt;응용 계층에서 명시적으로 Close를 주고받으면, 양쪽 모두 &quot;상대가 내 마지막 데이터까지 받았고, 연결이 의도적으로 끝났다&quot;는 사실을 확실히 알 수 있음. 갑작스러운 연결 유실과 정상 종료를 구분할 수 있게 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Close 프레임을 받은 뒤에 도착하는 데이터는 폐기됨.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;TCP, HTTP와의 관계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket은 TCP 위에서 동작하는 독립적인 프로토콜. HTTP와의 관계는 핸드셰이크가 HTTP 서버에게 Upgrade 요청으로 해석.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핸드셰이크가 끝난 뒤의 WebSocket 트래픽은 HTTP가 아님. HTTP의 메서드, 상태 코드, 헤더 같은 개념은 더 이상 등장하지 않음.&lt;/li&gt;
&lt;li&gt;기본 포트는 HTTP와 같음. ws://는 80, TLS 위에서 동작하는 wss://는 443. 기존 웹 인프라와 공존하기 위한 선택.&lt;/li&gt;
&lt;li&gt;같은 포트를 HTTP 서버와 공유할 수 있음. 이 경우 WebSocket 연결 시도는 서버 입장에서 &quot;Upgrade 헤더가 붙은 평범한 GET 요청&quot;으로 보임 (&amp;sect;1.8).&lt;/li&gt;
&lt;li&gt;RFC는 당시 기준으로 443 포트(wss)의 연결 성공률이 80 포트보다 유의미하게 높았다고 언급함. 중간 장치들이 TLS 트래픽에는 개입하지 못하기 때문.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;서브프로토콜&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket은 메시지의 경계와 타입(텍스트/바이너리)까지만 정의함. 그 메시지 안에 무엇을 어떤 형식으로 담을지는 프로토콜이 관여하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 &quot;안의 규칙&quot;을 협상하는 장치가 서브프로토콜 (&amp;sect;1.9).&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 class=&quot;avrasm&quot;&gt;&lt;code&gt;클라이언트: Sec-WebSocket-Protocol: chat, superchat   (이 중에 골라줘)
서버:      Sec-WebSocket-Protocol: chat              (chat으로 하자)&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;RFC가 권장하는 관례는 두 가지.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름 충돌을 피하기 위해, 서브프로토콜 이름에 만든 주체의 도메인을 포함. 예: chat.example.com&lt;/li&gt;
&lt;li&gt;버전 관리: 기존 클라이언트와 호환되지 않는 변경이라면 새 이름을 사용 (예: v2.chat.example.com). 같은 이름을 유지하며 확장하려면 하위 호환을 신중히 설계해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STOMP 같은 메시징 프로토콜이 WebSocket 위에서 동작하는 것이 바로 이 서브프로토콜 계층의 활용.&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;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;폴링&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;롱폴링&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;통신 모델&lt;/td&gt;
&lt;td&gt;요청-응답 반복&lt;/td&gt;
&lt;td&gt;요청-응답 (응답 보류)&lt;/td&gt;
&lt;td&gt;양방향 (full-duplex)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 &amp;rarr; 클라이언트 지연&lt;/td&gt;
&lt;td&gt;최대 폴링 주기만큼&lt;/td&gt;
&lt;td&gt;거의 즉시 (요청 보류 중일 때)&lt;/td&gt;
&lt;td&gt;즉시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;클라이언트 &amp;rarr; 서버&lt;/td&gt;
&lt;td&gt;별도 요청&lt;/td&gt;
&lt;td&gt;별도 요청&lt;/td&gt;
&lt;td&gt;같은 연결로 즉시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지당 오버헤드&lt;/td&gt;
&lt;td&gt;HTTP 헤더 전체&lt;/td&gt;
&lt;td&gt;HTTP 헤더 전체&lt;/td&gt;
&lt;td&gt;프레임 헤더 몇 바이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈 응답 낭비&lt;/td&gt;
&lt;td&gt;있음 (주기가 짧을수록 큼)&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 관리&lt;/td&gt;
&lt;td&gt;요청마다 새로 (keep-alive 여부에 따라 다름)&lt;/td&gt;
&lt;td&gt;사이클마다 반복&lt;/td&gt;
&lt;td&gt;하나의 TCP 연결 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 부담&lt;/td&gt;
&lt;td&gt;잦은 요청 처리&lt;/td&gt;
&lt;td&gt;대기 연결 다수 점유&lt;/td&gt;
&lt;td&gt;연결당 상태 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현 복잡도&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;프로토콜/라이브러리 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인프라 호환성&lt;/td&gt;
&lt;td&gt;어디서나 동작&lt;/td&gt;
&lt;td&gt;어디서나 동작 (타임아웃 주의)&lt;/td&gt;
&lt;td&gt;중간 장치에 따라 차단될 수 있음 (wss 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;폴링: 갱신 주기가 길고 실시간성이 중요하지 않은 데이터에 적합. 예를 들어 몇 분 단위의 상태 확인. 구현과 운영이 가장 단순하고, 어떤 인프라에서도 동작함.&lt;/li&gt;
&lt;li&gt;롱폴링: 실시간성이 필요하지만 WebSocket을 쓸 수 없는 환경의 폴백(fallback). SockJS 같은 라이브러리가 WebSocket 실패 시 롱폴링으로 내려가는 전략을 쓰는 이유.&lt;/li&gt;
&lt;li&gt;WebSocket: 양방향 메시지가 잦은 경우. 채팅, 실시간 게임, 협업 편집, 시세 스트리밍처럼 서버 푸시와 클라이언트 전송이 모두 빈번한 서비스.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판단 기준을 하나로 줄이면 이렇게 됨. 서버가 먼저 클라이언트에게 데이터를 주어야 하는 빈도가 높고 양방향인가이라면 WebSocket, 아니라면 더 단순한 쪽이 나음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소켓은 인터페이스, WebSocket은 프로토콜. WebSocket도 결국 TCP 소켓 하나 위에서 동작함.&lt;/li&gt;
&lt;li&gt;HTTP의 근본 한계는 서버가 먼저 말을 걸 수 없다는 것. 폴링과 롱폴링은 그 한계 안의 우회책.&lt;/li&gt;
&lt;li&gt;WebSocket 핸드셰이크는 HTTP로 시작하지만, 101 응답 이후 그 연결은 더 이상 HTTP가 아님.&lt;/li&gt;
&lt;li&gt;Sec-WebSocket-Key와 Accept는 암호화나 인증이 아니라 &quot;상대가 WebSocket을 이해했는가&quot;의 검증.&lt;/li&gt;
&lt;li&gt;WebSocket이 TCP에 얹은 것은 네 가지뿐. origin 보안 모델, 주소 지정, 프레이밍, 클로징 핸드셰이크.&lt;/li&gt;
&lt;li&gt;메시지 안의 형식은 서브프로토콜의 몫.&lt;/li&gt;
&lt;/ul&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/b5Ug5M/dJMcadvOxC3/lOkfQqiHl62kpXIoHmCQXk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Ug5M/dJMcadvOxC3/lOkfQqiHl62kpXIoHmCQXk/img.jpg&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;488&quot; data-is-animation=&quot;false&quot; width=&quot;510&quot; height=&quot;226&quot; data-widthpercent=&quot;51.93&quot; style=&quot;width: 51.3225%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Ug5M/dJMcadvOxC3/lOkfQqiHl62kpXIoHmCQXk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Ug5M%2FdJMcadvOxC3%2FlOkfQqiHl62kpXIoHmCQXk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1102&quot; height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC48JA/dJMcadvOxC4/irWfopUO00w7Qauj77L8pK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC48JA/dJMcadvOxC4/irWfopUO00w7Qauj77L8pK/img.jpg&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;353&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.5147%;&quot; data-widthpercent=&quot;48.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC48JA/dJMcadvOxC4/irWfopUO00w7Qauj77L8pK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC48JA%2FdJMcadvOxC4%2FirWfopUO00w7Qauj77L8pK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;738&quot; height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;a href=&quot;https://www.rfc-editor.org/info/rfc6455/&quot;&gt;RFC 6455 - The WebSocket Protocol&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>백엔드</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/798</guid>
      <comments>https://rldd.tistory.com/798#entry798comment</comments>
      <pubDate>Wed, 11 Feb 2026 03:33:15 +0900</pubDate>
    </item>
    <item>
      <title>HTTPURLResponse.allHeaderFields 파싱 시 NSNumber 헤더 누락 문제</title>
      <link>https://rldd.tistory.com/797</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPURLResponse.allHeaderFields&amp;nbsp;파싱&amp;nbsp;시&amp;nbsp;NSNumber&amp;nbsp;헤더&amp;nbsp;누락&amp;nbsp;문제&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 응답 처리 레이어에서 `HTTPURLResponse.allHeaderFields`를 [String: String] 형태로 변환해 상위 모듈에 전달.&lt;br /&gt;&lt;br /&gt;기존 변환 사용 로직은 Any를 `as? String`으로 캐스팅하는 방식인데, 이 방식이 Swift 타입 시스템과 Foundation의 NSNumber 브리징 동작이 결합되어 일부 헤더를 조용히 누락시키는 현상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;코드 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 헤더에는 동일한 key가 중복을 허용하고 있어서 header는 동일 키 값에 대해서도 여러개 받을 수 있도록 해야함.&lt;/p&gt;
&lt;pre id=&quot;code_1770138797059&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// ❌ 기존 코드 
var responseHeader: [String: String] = [:]

for (headerKey, headerValue) in httpResponse.allHeaderFields {
    guard let key = headerKey as? String,
          let value = headerValue as? String   // &amp;larr; ❌ NSNumber면 nil &amp;rarr; continue
    else { continue }

    responseHeader[key] = value
}

/// ✅ 안전한 방식
var responseHeader: [String: String] = [:]

for (key, value) in httpResponse.allHeaderFields {
    guard let keyString = key as? String else { continue }
    responseHeader[keyString] = String(describing: value)
}

///   NSNumber가 데이터로 들어오는 경우 
Content-Length: 123&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;br /&gt;&lt;br /&gt;(참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778421973682&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;allHeaderFields | Apple Developer Documentation&quot; data-og-description=&quot;All HTTP header fields of the response.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8gOkc/dJMb85WXP4m/H2ek8oaGBnCPtc0PoBsgwk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ihuwj/dJMb81G1QFV/nJc5hkniaIQsmHqhcWxy5k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse/allheaderfields&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8gOkc/dJMb85WXP4m/H2ek8oaGBnCPtc0PoBsgwk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ihuwj/dJMb81G1QFV/nJc5hkniaIQsmHqhcWxy5k/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;allHeaderFields | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;All HTTP header fields of the response.&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/documentation/foundation/httpurlresponse/value(forhttpheaderfield:)&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/foundation/httpurlresponse/value(forhttpheaderfield:)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778422111033&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;HTTPURLResponse | Apple Developer Documentation&quot; data-og-description=&quot;The metadata associated with the response to an HTTP protocol URL load request.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/foundation/httpurlresponse&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFowCV/dJMb81G1QF9/9YS8j20fPAl27K2LVVkxL1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qKGwJ/dJMb88e49R4/jdOpsNW6j7URF3Wja2CbEk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/foundation/httpurlresponse&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFowCV/dJMb81G1QF9/9YS8j20fPAl27K2LVVkxL1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qKGwJ/dJMb88e49R4/jdOpsNW6j7URF3Wja2CbEk/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HTTPURLResponse | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The metadata associated with the response to an HTTP protocol URL load request.&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/documentation/swift/string/init(describing:)-67ncf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/swift/string/init(describing:)-67ncf&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778422120599&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;init(describing:) | Apple Developer Documentation&quot; data-og-description=&quot;Creates a string representing the given value.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/string/init(describing:)-67ncf&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/string/init(describing:)-67ncf&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFMD3f/dJMb8SpMq9V/cjNQZOE8gEx7kF4bAPnCo1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/A2UYD/dJMb9kmg9ET/6rwcIO7XnU5Fet74DTKOZ0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/string/init(describing:)-67ncf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/string/init(describing:)-67ncf&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFMD3f/dJMb8SpMq9V/cjNQZOE8gEx7kF4bAPnCo1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/A2UYD/dJMb9kmg9ET/6rwcIO7XnU5Fet74DTKOZ0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;init(describing:) | Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Creates a string representing the given value.&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://github.com/swiftlang/swift-corelibs-foundation/pull/287&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/swiftlang/swift-corelibs-foundation/pull/287&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778427074997&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;Implement NSHTTPURLResponse by danieleggert &amp;middot; Pull Request #287 &amp;middot; swiftlang/swift-corelibs-foundation&quot; data-og-description=&quot;This implements both the trivial attributes (statusCode and allHeaderFields), and also parses the headers in order to set the following attributes: public var mimeType: String? { get } public var e...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/swiftlang/swift-corelibs-foundation/pull/287&quot; data-og-url=&quot;https://github.com/swiftlang/swift-corelibs-foundation/pull/287&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxD1yo/dJMb9eTT33X/QDTytqstGsK0Q4s4hsVQJ1/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_133_1042_181,https://scrap.kakaocdn.net/dn/hHuhJ/dJMb8Z3vPsJ/udUns4kTH5x0XdWU8Qa7sK/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_133_1042_181&quot;&gt;&lt;a href=&quot;https://github.com/swiftlang/swift-corelibs-foundation/pull/287&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/swiftlang/swift-corelibs-foundation/pull/287&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxD1yo/dJMb9eTT33X/QDTytqstGsK0Q4s4hsVQJ1/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_133_1042_181,https://scrap.kakaocdn.net/dn/hHuhJ/dJMb8Z3vPsJ/udUns4kTH5x0XdWU8Qa7sK/img.png?width=1200&amp;amp;height=600&amp;amp;face=999_133_1042_181');&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;Implement NSHTTPURLResponse by danieleggert &amp;middot; Pull Request #287 &amp;middot; swiftlang/swift-corelibs-foundation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This implements both the trivial attributes (statusCode and allHeaderFields), and also parses the headers in order to set the following attributes: public var mimeType: String? { get } public var e...&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;</description>
      <category>Project/개발 업무</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/797</guid>
      <comments>https://rldd.tistory.com/797#entry797comment</comments>
      <pubDate>Wed, 4 Feb 2026 02:13:19 +0900</pubDate>
    </item>
    <item>
      <title>Instrumets로 행 분석하기 (Analyze hangs with Instruments) - WWDC23</title>
      <link>https://rldd.tistory.com/794</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Instrumets로&amp;nbsp;행&amp;nbsp;분석하기&amp;nbsp;(Analyze&amp;nbsp;hangs&amp;nbsp;with&amp;nbsp;Instruments)&amp;nbsp;-&amp;nbsp;WWDC23&lt;/h2&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;행의 기초 지식으로 시작하여 3가지 다른 행과 insturment를 통해 문제를 분석&lt;/p&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;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;What is hang?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인간의 지각하는 것과 행의 개요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Event handling and rendering loop
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 처리와 렌더링 루프라는 행을 일으키는 원인의 기초&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;nbsp;Busy main thread hang
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;main thread가 busy함으로써 나타나는 행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Asnyc hang
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blokced main thread hang
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드가 차단되어 발생하는 행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.21.57.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RLUPm/dJMcaiIJq7m/z5Idt7TicxcGUOn5zToink/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RLUPm/dJMcaiIJq7m/z5Idt7TicxcGUOn5zToink/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RLUPm/dJMcaiIJq7m/z5Idt7TicxcGUOn5zToink/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRLUPm%2FdJMcaiIJq7m%2Fz5Idt7TicxcGUOn5zToink%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;1840&quot; height=&quot;1010&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.21.57.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;1010&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;해당 세션에서는 이미 찾은 행을 instruments를 사용하여 분석하는 것을 위주로 진행.&lt;/p&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/bIfJ3F/dJMcac9zNEv/EgXtEoie67uWXOkdBL1Y5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIfJ3F/dJMcac9zNEv/EgXtEoie67uWXOkdBL1Y5K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;734&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.23.44.png&quot; style=&quot;width: 52.3945%; margin-right: 10px;&quot; data-widthpercent=&quot;53.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIfJ3F/dJMcac9zNEv/EgXtEoie67uWXOkdBL1Y5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIfJ3F%2FdJMcac9zNEv%2FEgXtEoie67uWXOkdBL1Y5K%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;1622&quot; height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xi5vP/dJMcabiAbst/vmUTrBA7xC2D2rpFtENCXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xi5vP/dJMcabiAbst/vmUTrBA7xC2D2rpFtENCXk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;922&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.24.45.png&quot; style=&quot;width: 46.4427%;&quot; data-widthpercent=&quot;46.99&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xi5vP/dJMcabiAbst/vmUTrBA7xC2D2rpFtENCXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxi5vP%2FdJMcabiAbst%2FvmUTrBA7xC2D2rpFtENCXk%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;1806&quot; height=&quot;922&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거북이와 유니콘이 대화하는 건 Request-Response 형태이 상호작용은 적정한 행이&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;즉, 실제 시뮬레이션도 즉각 반응해야 하며, 그렇지 않은 경우에 사용자의 illusion이 깨짐.&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/bpPEl1/dJMcaiPuyTF/BqzeadXGj9KdphF7cEKiM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpPEl1/dJMcaiPuyTF/BqzeadXGj9KdphF7cEKiM0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;880&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.40.49.png&quot; style=&quot;width: 49.0526%; margin-right: 10px;&quot; data-widthpercent=&quot;49.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpPEl1/dJMcaiPuyTF/BqzeadXGj9KdphF7cEKiM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpPEl1%2FdJMcaiPuyTF%2FBqzeadXGj9KdphF7cEKiM0%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;1830&quot; height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzc0Dx/dJMcacPjSKf/7PZMXGHTLniGkNHksHhK40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzc0Dx/dJMcacPjSKf/7PZMXGHTLniGkNHksHhK40/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;832&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.41.23.png&quot; style=&quot;width: 49.7846%;&quot; data-widthpercent=&quot;50.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzc0Dx/dJMcacPjSKf/7PZMXGHTLniGkNHksHhK40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzc0Dx%2FdJMcacPjSKf%2F7PZMXGHTLniGkNHksHhK40%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;1756&quot; height=&quot;832&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;100ms: 즉각적으로 느껴지면 이거보다 짧아야하는 특수한 상황도 있지만 목표로 삼기에 좋음&lt;/li&gt;
&lt;li&gt;~250ms: 상황에 따라 달라지면 250ms까지는 괜찮을 수 있음&lt;/li&gt;
&lt;li&gt;~500ms: 상황에 따라 다르지만 즉각적인 느낌이 들지 않음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 대부분의 tool이 250ms부터 행을 보고&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이런건 쉽게 무시할 수 있고, 맥락에 따라 괜찮을 수도 있고 아닐수도 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;500ms~: 정식 행으로 간주&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.45.05.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JrGaM/dJMcad1H3T0/4KL6UG7T9EGE5EAXQB5kMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JrGaM/dJMcad1H3T0/4KL6UG7T9EGE5EAXQB5kMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JrGaM/dJMcad1H3T0/4KL6UG7T9EGE5EAXQB5kMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJrGaM%2FdJMcad1H3T0%2F4KL6UG7T9EGE5EAXQB5kMk%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;1794&quot; height=&quot;894&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.45.05.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;894&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Instant 상호작용이면 &amp;lt; 100ms 이어야 하며 Request - Response 상호작용 형태라면 500ms 동안 추가 피드백이 없어도 괜찮음.&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;gt; 이메일 API Request -&amp;gt; 이메일 API Response -&amp;gt; 이메일 창 닫기)의 흐름이 존재할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이메일 버튼 클릭 -&amp;gt; 이메일 전송 버튼 UI 즉시 변경 -&amp;gt;이메일 API Request -&amp;gt; 이메일 API Response -&amp;gt; 이메일 창 닫기)흐름으로 작업한다면 실제 이메일 UI가 바뀜으로써 사용자는 지연이라고 느끼지 못함.&lt;/p&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-filename=&quot;스크린샷 2026-01-06 오전 1.46.16.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NtmXw/dJMcai21bou/oLGMhoDecZugTGzWBawuD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NtmXw/dJMcai21bou/oLGMhoDecZugTGzWBawuD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NtmXw/dJMcai21bou/oLGMhoDecZugTGzWBawuD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNtmXw%2FdJMcai21bou%2FoLGMhoDecZugTGzWBawuD0%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;1746&quot; height=&quot;762&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.46.16.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;762&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;UI가 빠르게 작동할 수 있도록 메인이 아닌 작업은 UI에서 빼내야 함.&lt;/p&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-filename=&quot;스크린샷 2026-01-06 오전 1.50.12.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yPNv3/dJMcadglNaD/7sLFmdcvPjLFYMxgkLzPC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yPNv3/dJMcadglNaD/7sLFmdcvPjLFYMxgkLzPC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yPNv3/dJMcadglNaD/7sLFmdcvPjLFYMxgkLzPC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyPNv3%2FdJMcadglNaD%2F7sLFmdcvPjLFYMxgkLzPC1%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;1794&quot; height=&quot;988&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.50.12.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;988&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;Hardware: 사용자의 클릭을 받고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS: 운영체제에서 어떤 프로세스가 이벤트를 처리할지 알아내서 해당 프로세스를 앱으로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App: MainThread에서 UI 처리를 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Render Server: 각각의 UI Layer를 합성하고 다음 프레임을 렌더링하는 별도의 프로세스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Display: Render Server가 준비한 비트맵을 화면 픽셀에 맞게 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.51.24.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RXX6W/dJMcachtVNU/Cxx0kPLoKBP3KWAAorOUdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RXX6W/dJMcachtVNU/Cxx0kPLoKBP3KWAAorOUdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RXX6W/dJMcachtVNU/Cxx0kPLoKBP3KWAAorOUdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRXX6W%2FdJMcachtVNU%2FCxx0kPLoKBP3KWAAorOUdk%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;1828&quot; height=&quot;840&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.51.24.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;840&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&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/Nxv62/dJMcacIzge6/r9kw8eEIsMPy9bUpMQjpq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nxv62/dJMcacIzge6/r9kw8eEIsMPy9bUpMQjpq0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;728&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.58.37.png&quot; style=&quot;width: 33.4621%; margin-right: 10px;&quot; data-widthpercent=&quot;34.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nxv62/dJMcacIzge6/r9kw8eEIsMPy9bUpMQjpq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNxv62%2FdJMcacIzge6%2Fr9kw8eEIsMPy9bUpMQjpq0%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;1814&quot; height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B23RW/dJMcacoftjz/uIrOB93rPwTkqWDmqKqL71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B23RW/dJMcacoftjz/uIrOB93rPwTkqWDmqKqL71/img.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;736&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.58.49.png&quot; style=&quot;width: 32.9889%; margin-right: 10px;&quot; data-widthpercent=&quot;33.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B23RW/dJMcacoftjz/uIrOB93rPwTkqWDmqKqL71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB23RW%2FdJMcacoftjz%2FuIrOB93rPwTkqWDmqKqL71%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;1808&quot; height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbOKyd/dJMcad1H363/D5raWD939bk4K0odAdvZY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbOKyd/dJMcad1H363/D5raWD939bk4K0odAdvZY0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;806&quot; data-filename=&quot;스크린샷 2026-01-06 오전 1.59.03.png&quot; style=&quot;width: 31.2235%;&quot; data-widthpercent=&quot;31.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbOKyd/dJMcad1H363/D5raWD939bk4K0odAdvZY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbOKyd%2FdJMcad1H363%2FD5raWD939bk4K0odAdvZY0%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;1874&quot; height=&quot;806&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;Main Thread로 가기 전 마지막 프로세싱 단계와 이후의 렌더링과 디스플레이 업데이트 단계는 걸리는 시간을 예측하는게 쉬운편 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 interaction이 너무 오래 걸린다면 MainThread의 작업이 오래 걸렸거나&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainThread에서 이미 다른 작업을 수행하고 있어서 작업이 끝날 때까지 기다린 뒤 수행되는 경우&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-filename=&quot;스크린샷 2026-01-06 오전 2.00.14.png&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beMBH9/dJMcajgyWkJ/BMEPBmMNn2d25kyD2lUDb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beMBH9/dJMcajgyWkJ/BMEPBmMNn2d25kyD2lUDb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beMBH9/dJMcajgyWkJ/BMEPBmMNn2d25kyD2lUDb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeMBH9%2FdJMcajgyWkJ%2FBMEPBmMNn2d25kyD2lUDb1%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;1810&quot; height=&quot;1010&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.00.14.png&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1010&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;메인 스레드의 모든 작업이 100ms를 넘으면 안되며 빠를수록 좋음.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.04.39.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mxApT/dJMcacV5dPU/qvMz75YfPoYtMMxNa1yi90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mxApT/dJMcacV5dPU/qvMz75YfPoYtMMxNa1yi90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mxApT/dJMcacV5dPU/qvMz75YfPoYtMMxNa1yi90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmxApT%2FdJMcacV5dPU%2FqvMz75YfPoYtMMxNa1yi90%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;1786&quot; height=&quot;862&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.04.39.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;862&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;Insturment는 반응이 없는 메인 스레드를 잠재적으로 행을 발생시킬 수 있다고 기록.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Busy Main Thread: 메인 스레드가 바쁨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 스레드에 다양한 CPU 활동이 나타남&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blocked Main Thread: 메인 스레드가 차단됨.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 곳에서 완료해야 하는 작업을 기다리고 있어서 메인 스레드의 CPU 활동이 없거나 미미&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.06.46.png&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diphGY/dJMcahb1hJO/VUlG3pM1VFN0KZF0jkurLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diphGY/dJMcahb1hJO/VUlG3pM1VFN0KZF0jkurLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diphGY/dJMcahb1hJO/VUlG3pM1VFN0KZF0jkurLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiphGY%2FdJMcahb1hJO%2FVUlG3pM1VFN0KZF0jkurLK%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;1882&quot; height=&quot;884&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.06.46.png&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;884&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;해당 예제에서는 CPU 사용량이 100을 넘지는 않지만 꽤 높은 수준은 60% ~ 90% 사이를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.09.08.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqSKsr/dJMcaajFFfa/oCzm4RvwsdWn5Sw9Rxqw6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqSKsr/dJMcaajFFfa/oCzm4RvwsdWn5Sw9Rxqw6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqSKsr/dJMcaajFFfa/oCzm4RvwsdWn5Sw9Rxqw6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqSKsr%2FdJMcaajFFfa%2FoCzm4RvwsdWn5Sw9Rxqw6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1054&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.09.08.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1054&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;Heaviest Stack Trace에는 소스 코드에서 유래하지 않은 이후의 함수 호출에 관련되지 않는 부분들을 기본적으로 숨겨서&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;Call Tree에서도 동일한 효과를 낼 수 있는데, Hide System Libraries를 체크&lt;/p&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-filename=&quot;스크린샷 2026-01-06 오전 2.10.50.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MhmjZ/dJMcafkYCmf/baeJJmbKsnDPatK48VK1F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MhmjZ/dJMcafkYCmf/baeJJmbKsnDPatK48VK1F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MhmjZ/dJMcafkYCmf/baeJJmbKsnDPatK48VK1F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMhmjZ%2FdJMcafkYCmf%2FbaeJJmbKsnDPatK48VK1F0%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;1828&quot; height=&quot;820&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.10.50.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;820&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;Busy Main Thread인 이유에는 2가지가 있는데, 경우에 따라서 메인 스레드 작업을 줄이는 방법이 다름.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드가 오랜기간 실행&lt;/li&gt;
&lt;li&gt;호출 횟수가 많아서 빈번&lt;/li&gt;
&lt;/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&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/b0UTVk/dJMcah399Co/1k43ISU75djRVP5mJUuyw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0UTVk/dJMcah399Co/1k43ISU75djRVP5mJUuyw1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1784&quot; data-origin-height=&quot;976&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.13.25.png&quot; style=&quot;width: 49.7588%; margin-right: 10px;&quot; data-widthpercent=&quot;50.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0UTVk/dJMcah399Co/1k43ISU75djRVP5mJUuyw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0UTVk%2FdJMcah399Co%2F1k43ISU75djRVP5mJUuyw1%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;1784&quot; height=&quot;976&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brHKK8/dJMcaajFFi8/frzjWcayLlr8yhanVGzCm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brHKK8/dJMcaajFFi8/frzjWcayLlr8yhanVGzCm0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;974&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.13.43.png&quot; style=&quot;width: 49.0784%;&quot; data-widthpercent=&quot;49.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brHKK8/dJMcaajFFi8/frzjWcayLlr8yhanVGzCm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrHKK8%2FdJMcaajFFi8%2FfrzjWcayLlr8yhanVGzCm0%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;1756&quot; height=&quot;974&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 사례는 something()에서 호출하는 Turtles 함수가 너무 많이 수행하고 있음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 경우에는 작업을 줄여야 함.&lt;/li&gt;
&lt;li&gt;오래 실행되는 함수의 경우에는 함수의 적용과 호출 대상을 살펴봐야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wP8S2/dJMcabXbBEG/lEYYXfaruPXUKEAKzJVHx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wP8S2/dJMcabXbBEG/lEYYXfaruPXUKEAKzJVHx1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;1004&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.15.40.png&quot; style=&quot;width: 50.6491%; margin-right: 10px;&quot; data-widthpercent=&quot;51.24&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wP8S2/dJMcabXbBEG/lEYYXfaruPXUKEAKzJVHx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwP8S2%2FdJMcabXbBEG%2FlEYYXfaruPXUKEAKzJVHx1%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;1746&quot; height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO2Cf9/dJMcabJEYWd/LwpJzyC4C0l3pa74CZU75K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO2Cf9/dJMcabJEYWd/LwpJzyC4C0l3pa74CZU75K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;990&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.15.58.png&quot; style=&quot;width: 48.1881%;&quot; data-widthpercent=&quot;48.76&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO2Cf9/dJMcabJEYWd/LwpJzyC4C0l3pa74CZU75K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO2Cf9%2FdJMcabJEYWd%2FLwpJzyC4C0l3pa74CZU75K%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;990&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 사례는 something()에서 Unicorns을 너무 많이 호출하고 있음.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 케이스가 발생하는건 Unicorn 함수를 호출하는 호출자가 loop 같은 영역에서 호출하기 때문.&lt;/li&gt;
&lt;li&gt;이 경우에는 Unicorn 함수의 기능을 최적화하는 대신 더 적게 호출하는 방법을 조사하는게 나을 수도 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.17.06.png&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSmLI/dJMcaiIJrLo/QMNADGhOqBkYuQEhQhKW40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSmLI/dJMcaiIJrLo/QMNADGhOqBkYuQEhQhKW40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSmLI/dJMcaiIJrLo/QMNADGhOqBkYuQEhQhKW40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSmLI%2FdJMcaiIJrLo%2FQMNADGhOqBkYuQEhQhKW40%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;1692&quot; height=&quot;998&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.17.06.png&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;998&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;Turtle 함수처럼 오래 실행되는 코드의 경우에는 흐름의 더 아래쪽을 살펴봐야 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unicorn 함수처럼 빈번하게 실행된다면 흐름의 위를 살펴봐야 함.&lt;/p&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-filename=&quot;스크린샷 2026-01-06 오전 2.18.33.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W7wvH/dJMcagYtzIf/40nyfIl3vl7k4ExZuEMeHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W7wvH/dJMcagYtzIf/40nyfIl3vl7k4ExZuEMeHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W7wvH/dJMcagYtzIf/40nyfIl3vl7k4ExZuEMeHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW7wvH%2FdJMcagYtzIf%2F40nyfIl3vl7k4ExZuEMeHK%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;1776&quot; height=&quot;1012&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.18.33.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;1012&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;위의 이미지처럼 샘플별로 어떤 함수가 CPU에서 실행 중인지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Time Profiler에서는 현재 4가지 시나리오가 존재하여 어떤 경우인지는 알려줄 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.20.25.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnZtjd/dJMcaiWgSyp/XmimjJRIorK5szKqZFTvsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnZtjd/dJMcaiWgSyp/XmimjJRIorK5szKqZFTvsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnZtjd/dJMcaiWgSyp/XmimjJRIorK5szKqZFTvsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnZtjd%2FdJMcaiWgSyp%2FXmimjJRIorK5szKqZFTvsK%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;1794&quot; height=&quot;972&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.20.25.png&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;972&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;특정 함수의 실행 시간을 재려면 `os_signposts`를 사용.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2019년 Instrument 시작하기 세션에서 사용 방법을 다룸.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/440cz/dJMcahJR2h7/zsTMXnYajnVlxtr5LFswkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/440cz/dJMcahJR2h7/zsTMXnYajnVlxtr5LFswkk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;814&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.23.53.png&quot; style=&quot;width: 79.3173%; margin-right: 10px;&quot; data-widthpercent=&quot;80.25&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/440cz/dJMcahJR2h7/zsTMXnYajnVlxtr5LFswkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F440cz%2FdJMcahJR2h7%2FzsTMXnYajnVlxtr5LFswkk%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;1912&quot; height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddHcmo/dJMcaajFFo7/JkHfwL62kPttkLuU8XHkik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddHcmo/dJMcaajFFo7/JkHfwL62kPttkLuU8XHkik/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;948&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.25.07.png&quot; style=&quot;width: 19.5199%;&quot; data-widthpercent=&quot;19.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddHcmo/dJMcaajFFo7/JkHfwL62kPttkLuU8XHkik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddHcmo%2FdJMcaajFFo7%2FJkHfwL62kPttkLuU8XHkik%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;548&quot; height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI에서 각 구간은 하나의 View Body의 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 개의 Body를 실행하는데 얼마나 걸렸는지를 정확하게 알 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 너무 많이 실행되는게 문제이고, BackgroundThumbnailView가 70번이나 실행되었는데 평균 실행시간이 약 50ms이며, 총 소요 시간도 3s가 넘음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 6개만 보여주면 되는데 70번의 뷰 업데이트는 너무 과하다는 것을 유추할 수 있음.&lt;/p&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/ccIxzA/dJMcaivbUmt/okkUw9NgFjgfMvEKI4K5lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccIxzA/dJMcaivbUmt/okkUw9NgFjgfMvEKI4K5lK/img.png&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.27.26.png&quot; data-origin-height=&quot;702&quot; data-origin-width=&quot;1844&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.3356%; margin-right: 10px;&quot; data-widthpercent=&quot;44.86&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccIxzA/dJMcaivbUmt/okkUw9NgFjgfMvEKI4K5lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccIxzA%2FdJMcaivbUmt%2FokkUw9NgFjgfMvEKI4K5lK%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;1844&quot; height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FM2bU/dJMcabpkU0i/8YEFnOHxbTJgdNWNcBErVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FM2bU/dJMcabpkU0i/8YEFnOHxbTJgdNWNcBErVk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;550&quot; data-filename=&quot;스크린샷 2026-01-06 오전 2.28.05.png&quot; style=&quot;width: 54.5016%;&quot; data-widthpercent=&quot;55.14&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FM2bU/dJMcabpkU0i/8YEFnOHxbTJgdNWNcBErVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFM2bU%2FdJMcabpkU0i%2F8YEFnOHxbTJgdNWNcBErVk%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;1776&quot; height=&quot;550&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;Grid는 전체를 한번에 처리하지만 LazyVGrid를 통해 보여줄 만큼만 처리하는 형태로 개선&lt;/p&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-filename=&quot;스크린샷 2026-01-06 오후 11.10.07.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3neiX/dJMcahwkxOZ/Pw0CwOK4o63TEPfzzZv1k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3neiX/dJMcahwkxOZ/Pw0CwOK4o63TEPfzzZv1k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3neiX/dJMcahwkxOZ/Pw0CwOK4o63TEPfzzZv1k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3neiX%2FdJMcahwkxOZ%2FPw0CwOK4o63TEPfzzZv1k0%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;1702&quot; height=&quot;776&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.10.07.png&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;776&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 LazyVGrid를 통해 화면에 표시되는 뷰만 렌더링할 수 있도록 수정해서 호출 수를 줄여 최적화하는 방법은 사용할 수 없음.&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-filename=&quot;스크린샷 2026-01-06 오후 11.16.29.png&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bey4sm/dJMcadOaRox/DRTngr1nTqf08ekDWhDlxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bey4sm/dJMcadOaRox/DRTngr1nTqf08ekDWhDlxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bey4sm/dJMcadOaRox/DRTngr1nTqf08ekDWhDlxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbey4sm%2FdJMcadOaRox%2FDRTngr1nTqf08ekDWhDlxK%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;1918&quot; height=&quot;1060&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.16.29.png&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;1060&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;시간이 오래 걸린 하나의 BackgroundThumbnailView를 설정하고 Main Thread를 분석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insturments가 View Body의 getter 부분을 Heaviest Back Trace에 보여주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BackyardBackground.thumbnail.getter` 호출하는 것을 보여줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-[UIImage imageByPreparingThumbnailOfSize:]가 `BackyardBackground.thumbnail.getter`를 호출.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 썸네일을 실시간으로 처리하고 해당 작업은 150ms가 걸림.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작업은 백그라운드에서 진행해야 하며, Main Thread를 busy하게 하면 좋지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변화를 주기 전에 `BackyardBackground.thumbnail.getter` 호출하는 맥락을 먼저 살펴봄.&lt;/p&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/7gTdC/dJMcah4awzK/ciFIDVPmelkx1yplwqAmtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7gTdC/dJMcah4awzK/ciFIDVPmelkx1yplwqAmtK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;560&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.23.11.png&quot; width=&quot;349&quot; height=&quot;402&quot; style=&quot;width: 31.6225%; margin-right: 10px;&quot; data-widthpercent=&quot;31.99&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7gTdC/dJMcah4awzK/ciFIDVPmelkx1yplwqAmtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7gTdC%2FdJMcah4awzK%2FciFIDVPmelkx1yplwqAmtK%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;486&quot; height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ope6Z/dJMcaihH3Tt/bkFUwCvTFxEZYckFSMw8V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ope6Z/dJMcaihH3Tt/bkFUwCvTFxEZYckFSMw8V1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;1030&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.27.09.png&quot; data-widthpercent=&quot;68.01&quot; style=&quot;width: 67.2147%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ope6Z/dJMcaihH3Tt/bkFUwCvTFxEZYckFSMw8V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOpe6Z%2FdJMcaihH3Tt%2FbkFUwCvTFxEZYckFSMw8V1%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;1900&quot; height=&quot;1030&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;`BackyardBackground.thumbnail.getter`의 맥락을 살펴보기 위해서 먼저 Open in Source Viewer를 클릭.&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;해당 코드는 BackgroundView가 반환한 썸네일을 포함한 Image를 만듦.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQxFi/dJMcafkY1x4/p4SjKgVMU11eIHzC5cyRh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQxFi/dJMcafkY1x4/p4SjKgVMU11eIHzC5cyRh1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1352&quot; data-origin-height=&quot;438&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.49.23.png&quot; style=&quot;width: 55.8625%; margin-right: 10px;&quot; data-widthpercent=&quot;56.52&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQxFi/dJMcafkY1x4/p4SjKgVMU11eIHzC5cyRh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQxFi%2FdJMcafkY1x4%2Fp4SjKgVMU11eIHzC5cyRh1%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;1352&quot; height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHG6J/dJMcah4aw1U/flToWXkaQx75CaA332Ksfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHG6J/dJMcah4aw1U/flToWXkaQx75CaA332Ksfk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;662&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.50.30.png&quot; style=&quot;width: 42.9747%;&quot; data-widthpercent=&quot;43.48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHG6J/dJMcah4aw1U/flToWXkaQx75CaA332Ksfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHG6J%2FdJMcah4aw1U%2FflToWXkaQx75CaA332Ksfk%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;1572&quot; height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI가 나타날 때 task를 시작하여 background.thumbnail을 호출하고 결과값을 image에 할당하여 View를 업데이트할 예정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이렇게 개선했음에도 iPad에서는 아직도 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;이번에는 행이 조금 뒤에 나타나는데, 이미지 로딩 중에는 저의 탭이 무시됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.53.42.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmsWwX/dJMcahb1Ec9/zEmr692wuXwMxD7QKSluck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmsWwX/dJMcahb1Ec9/zEmr692wuXwMxD7QKSluck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmsWwX/dJMcahb1Ec9/zEmr692wuXwMxD7QKSluck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmsWwX%2FdJMcahb1Ec9%2FzEmr692wuXwMxD7QKSluck%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;1796&quot; height=&quot;816&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.53.42.png&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;816&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.56.58.png&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ueIE1/dJMcahJSp55/7qMgQLkMuXPoonl37DXFBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ueIE1/dJMcahJSp55/7qMgQLkMuXPoonl37DXFBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ueIE1/dJMcahJSp55/7qMgQLkMuXPoonl37DXFBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FueIE1%2FdJMcahJSp55%2F7qMgQLkMuXPoonl37DXFBK%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;1806&quot; height=&quot;988&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.56.58.png&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Synchronous/Direct
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트가 들어왔을 때 이벤트 처리에 오랜 시간이 걸리면 이것을 행이라고 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Asnychronous/Delayed
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트를 빠르게 처리한다고 가정하면 메인 스레드에서 일부 작업을 연기하거나 메인 스레드에서 다른 작업을 처리중일 때 이벤트가 발생하면 이전 작업이 끝날 때까지 기다렸다가 이벤트를 처리할 수 있음.&lt;/li&gt;
&lt;li&gt;이 경우에는 이벤트 처리 코드가 각 작업을 빨리 끝내도 여전히 행이 발생함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.59.16.png&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjRiQ0/dJMcai21zhN/rT5ATkrW6DS4dyfPhHQ2v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjRiQ0/dJMcai21zhN/rT5ATkrW6DS4dyfPhHQ2v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjRiQ0/dJMcai21zhN/rT5ATkrW6DS4dyfPhHQ2v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjRiQ0%2FdJMcai21zhN%2FrT5ATkrW6DS4dyfPhHQ2v0%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;1818&quot; height=&quot;984&quot; data-filename=&quot;스크린샷 2026-01-06 오후 11.59.16.png&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;984&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;Apple 플랫폼에서 행을 감지하는 방식은 메인 스레드의 모든 작업 항목을 살펴보고 너무 길지 않은지 확인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충분히 길다면 이를 잠재적 행(Potential Hang)으로 표시&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;따라서 행을 감지할 때 Asnychronous/Delayed 행도 감지함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠재적인 delayed를 측정한거고 실제로 나타난 delay는 아님&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 12.23.17.png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhER85/dJMcaaKKbYa/TkuQytvaKh8UbHOUapwJ30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhER85/dJMcaaKKbYa/TkuQytvaKh8UbHOUapwJ30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhER85/dJMcaaKKbYa/TkuQytvaKh8UbHOUapwJ30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhER85%2FdJMcaaKKbYa%2FTkuQytvaKh8UbHOUapwJ30%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;1646&quot; height=&quot;702&quot; data-filename=&quot;스크린샷 2026-01-07 오전 12.23.17.png&quot; data-origin-width=&quot;1646&quot; data-origin-height=&quot;702&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;Asnychronous 행을 이라고 부르는 이유는 Main Queue의 dispatch_async 작업이 원인이거나 Swift Concurrency의 Task가 MainActor에 의해서 실행되기 때문인데 Main Thread에 작업을 발생시키는 모든 게 원인이 될 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 탭 하는 동작 자체로 힘든 작업은 아닌데 MainThread에 여전히 작업이 있어서 탭 동작을 처리할 수 없음.&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-filename=&quot;스크린샷 2026-01-07 오전 12.34.32.png&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG3NDX/dJMcaacUczb/oTuk22V3jk89gMZ8ZbY8L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG3NDX/dJMcaacUczb/oTuk22V3jk89gMZ8ZbY8L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG3NDX/dJMcaacUczb/oTuk22V3jk89gMZ8ZbY8L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG3NDX%2FdJMcaacUczb%2FoTuk22V3jk89gMZ8ZbY8L1%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;1998&quot; height=&quot;794&quot; data-filename=&quot;스크린샷 2026-01-07 오전 12.34.32.png&quot; data-origin-width=&quot;1998&quot; data-origin-height=&quot;794&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;총 70회가 실행된 것은 SwiftUI가 Grid를 채우기 위해 40개의 View를 생성했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`BackyardBackground.thumbnail.getter`의 부분이 대부분 2회씩 실행되었기 때문에 70번 정도 수행됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 화면에 표시된건 35개이며, 35개의 View도 이미지를 로딩하기 시작하며 이미지 로딩 후 View를 업데이트하고 body를 다시 호출하여 body의 실행이 총 75회가 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 정리하자면 40개의 View를 생성, 35개만 UI에 표현하지만, 각각 2번씩 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;40개 중 35개는 View에 2번씩 실행되면서 표시되고, 5개는 표시되지 않아서 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;따라서 35 * 2 + 5 * 1 = 75회 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 12.43.40.png&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doGpWc/dJMcad1IqOi/E39WbjMU1kegQxzBjcLaRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doGpWc/dJMcad1IqOi/E39WbjMU1kegQxzBjcLaRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doGpWc/dJMcad1IqOi/E39WbjMU1kegQxzBjcLaRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoGpWc%2FdJMcad1IqOi%2FE39WbjMU1kegQxzBjcLaRk%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;1734&quot; height=&quot;220&quot; data-filename=&quot;스크린샷 2026-01-07 오전 12.43.40.png&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;75개의 body.getter가 실행되는데 1ms도 걸리지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, body.getter는 Progress를 통해서 수정되었지만, 그럼에도 불구하고 아직도 행이 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 1.20.56.png&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Aohmr/dJMb99ZnuXv/inUr4v9Z8iJeFE9Hb9nyU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Aohmr/dJMb99ZnuXv/inUr4v9Z8iJeFE9Hb9nyU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Aohmr/dJMb99ZnuXv/inUr4v9Z8iJeFE9Hb9nyU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAohmr%2FdJMb99ZnuXv%2FinUr4v9Z8iJeFE9Hb9nyU1%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;1112&quot; height=&quot;492&quot; data-filename=&quot;스크린샷 2026-01-07 오전 1.20.56.png&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;492&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;`BackyardBackground.thumbnail.getter`의 클로저에서 `Background.thumbnail.gertter`를 호출하고 있으며 `body.getter`가 직접 호출하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 코드가 실행되어야 하지만 Main Thread에서 실행되면 안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우 Swift Concurreny의 Task가 기대했던 대로 실행되지 않을 때는 Swift Concurrency를 진단하는 Instruemnts를 활용&lt;/p&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-filename=&quot;스크린샷 2026-01-07 오전 1.29.26.png&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1056&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkiocC/dJMcaivchoa/nMO2b39J6fajUapFfwKi91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkiocC/dJMcaivchoa/nMO2b39J6fajUapFfwKi91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkiocC/dJMcaivchoa/nMO2b39J6fajUapFfwKi91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkiocC%2FdJMcaivchoa%2FnMO2b39J6fajUapFfwKi91%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;1898&quot; height=&quot;1056&quot; data-filename=&quot;스크린샷 2026-01-07 오전 1.29.26.png&quot; data-origin-width=&quot;1898&quot; data-origin-height=&quot;1056&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 1.42.14.png&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpb8Tc/dJMcafyvUzE/RywSkaPveZIMbMcNXyxndk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpb8Tc/dJMcafyvUzE/RywSkaPveZIMbMcNXyxndk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpb8Tc/dJMcafyvUzE/RywSkaPveZIMbMcNXyxndk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpb8Tc%2FdJMcafyvUzE%2FRywSkaPveZIMbMcNXyxndk%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;1820&quot; height=&quot;1010&quot; data-filename=&quot;스크린샷 2026-01-07 오전 1.42.14.png&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. SwiftUI View의 body가 `View` 프로토콜에 있는 @MainActor를 암시적으로 상속받음 &lt;/li&gt;
&lt;li&gt;2. `.task { }`가 주변의 context를 통해 actor isolation을 상속 받음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이에 따라서 `.task`안의 클로저도 @MainActor isolation을 암시적으로 상속받고 여기에서 실행되는 모든 코드는 @MainActor임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3. `background.thumbnail`의 `getter`가 동기적으로 동작하기 때문에 메인 스레드에서 동기적으로 실행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.06.09.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbFe8L/dJMcaaqrmJ1/zCJJdKnkACe4sK1NpXayR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbFe8L/dJMcaaqrmJ1/zCJJdKnkACe4sK1NpXayR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbFe8L/dJMcaaqrmJ1/zCJJdKnkACe4sK1NpXayR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbFe8L%2FdJMcaaqrmJ1%2FzCJJdKnkACe4sK1NpXayR1%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;1840&quot; height=&quot;988&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.06.09.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Swift Concurrency Task는 기본적으로 주변 context의 actor isolation을 상속 받음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 SwiftUI의 `.task { }`도 같은 행동을 취함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;main actor에서 분리되는 방법에는 2가지가 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;await을 통해 함수를 비동기적으로 호출해서 Task가 main-actor에서 분리되도록 함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;효율적이고 SwiftUI는 자동으로 생성된 `Task`를 취소함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하지만 이게 가능하지 않다면 `Task.detached {}`에서 주변 context에서 Task를 명시적으로 분리함.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단, `Task.detached {}`를 사용하는건 강압적이고 기존 작업을 유보하는 것보다 비용이 더 많이 들어감.&lt;/li&gt;
&lt;li&gt;`Task.detached {}`는 비구조적 `Task`로 취소가 전파되지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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/eLNAab/dJMcaiBXExp/3IxNZv9Isl3uUMDcP0lDXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eLNAab/dJMcaiBXExp/3IxNZv9Isl3uUMDcP0lDXK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;444&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.08.31.png&quot; style=&quot;width: 58.7394%; margin-right: 10px;&quot; data-widthpercent=&quot;59.43&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eLNAab/dJMcaiBXExp/3IxNZv9Isl3uUMDcP0lDXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeLNAab%2FdJMcaiBXExp%2F3IxNZv9Isl3uUMDcP0lDXK%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;1452&quot; height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbIoW6/dJMcafyvUWF/ekaT6rrTFovQOCw30SPJ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbIoW6/dJMcafyvUWF/ekaT6rrTFovQOCw30SPJ5k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;654&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.08.56.png&quot; style=&quot;width: 40.0978%;&quot; data-widthpercent=&quot;40.57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbIoW6/dJMcafyvUWF/ekaT6rrTFovQOCw30SPJ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbIoW6%2FdJMcafyvUWF%2FekaT6rrTFovQOCw30SPJ5k%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;1460&quot; height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;SwiftUI의 `.task { }` MainActor에 속하기 때문에 thumbnail에서 `getter`를 `async`를 통해 비동기로 전환하고, task에서 `await` 키워드를 추가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.09.49.png&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ReKRf/dJMcac9Adwn/vxORCzNkjuOiNud9IKlg51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ReKRf/dJMcac9Adwn/vxORCzNkjuOiNud9IKlg51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ReKRf/dJMcac9Adwn/vxORCzNkjuOiNud9IKlg51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FReKRf%2FdJMcac9Adwn%2FvxORCzNkjuOiNud9IKlg51%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;1814&quot; height=&quot;788&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.09.49.png&quot; data-origin-width=&quot;1814&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;&lt;br /&gt;코드를 변경하고 instruments를 보면 행이 사라졌지만 CPU 사용량이 조금 높음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.16.54.png&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVQPMg/dJMcagc8aLa/q9VQD7wWPeEasVvkAibHqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVQPMg/dJMcagc8aLa/q9VQD7wWPeEasVvkAibHqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVQPMg/dJMcagc8aLa/q9VQD7wWPeEasVvkAibHqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVQPMg%2FdJMcagc8aLa%2Fq9VQD7wWPeEasVvkAibHqK%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;1914&quot; height=&quot;902&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.16.54.png&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;902&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;메인 스레드를 사용하는 부분을 확인하면 썸네일을 로딩하는 부분으로 모든 `task`의 구간이 아주 짧음&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-filename=&quot;스크린샷 2026-01-07 오전 2.16.28.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxAGvq/dJMcacu1HT8/5JXSzOt98R3vKxDryQEbf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxAGvq/dJMcacu1HT8/5JXSzOt98R3vKxDryQEbf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxAGvq/dJMcacu1HT8/5JXSzOt98R3vKxDryQEbf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxAGvq%2FdJMcacu1HT8%2F5JXSzOt98R3vKxDryQEbf0%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;1892&quot; height=&quot;944&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.16.28.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;944&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;메인 스레드를 적게 사용하고 있기에 다른 스레드 트랙으로 가서 확인해보면 다른 스레드에서 serial이 아닌 parallel로 실행하고 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, CPU의 멀티 코어를 제대로 활용하고 있기에 이를 통해 모든 썸네일을 매우 빠르게 처리 가능.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.28.43.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GOmch/dJMcadm6Uv0/L2CxyCqFuMf45ECeTs0BMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GOmch/dJMcadm6Uv0/L2CxyCqFuMf45ECeTs0BMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GOmch/dJMcadm6Uv0/L2CxyCqFuMf45ECeTs0BMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGOmch%2FdJMcadm6Uv0%2FL2CxyCqFuMf45ECeTs0BMk%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;1826&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2026-01-07 오전 2.28.43.png&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;898&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;1. 메인 스레드가 CPU를 많이 사용하여 행이 나타나던 문제. 즉, 메인 스레드가 busy하여 반응성이 없던 문제를 해결.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Synchronous으로 발생하는 행은 사용자 인터랙션의 일부로 나타날 수 있음을 알았고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Asynchronous적으로 발생하는 건 메인 스레드에 예정되었던 작업이 새로운 이벤트가 처리되는걸 늦춘다는걸 알았음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 경우들 모두 Instrument가 감지할 수 있었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;행을 수정하는 방법은 작업을 적게 하거나 적게 할 수 없는 일은 백그라운드에서 처리한 뒤 UI를 업데이트 할 때만 메인 스레드로 돌아오는 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.03.24.png&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKsQpP/dJMcabQp7Db/mf6aON4PirNRjCh2RH01x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKsQpP/dJMcabQp7Db/mf6aON4PirNRjCh2RH01x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKsQpP/dJMcabQp7Db/mf6aON4PirNRjCh2RH01x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKsQpP%2FdJMcabQp7Db%2Fmf6aON4PirNRjCh2RH01x0%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;1838&quot; height=&quot;898&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.03.24.png&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;898&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;마지막 사례로 메인 스레드가 block된 경우인데 이 경우에는 메인 스레드가 CPU를 아주 적게 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 사례를 분석하려면 다른 instruments가 필요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.06.49.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpZ6Zy/dJMcaiotdmo/VBYXizC8JgGFi8VHYFj6R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpZ6Zy/dJMcaiotdmo/VBYXizC8JgGFi8VHYFj6R0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpZ6Zy/dJMcaiotdmo/VBYXizC8JgGFi8VHYFj6R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpZ6Zy%2FdJMcaiotdmo%2FVBYXizC8JgGFi8VHYFj6R0%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;1896&quot; height=&quot;838&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.06.49.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;838&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;긴 행이 발생한 영역의 메인 스레드의 CPU Usage를 확인해보면 초반에는 CPU를 사용하지만 나중에는 사용량이 없음.&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&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/AsDLD/dJMcaihItOQ/wJTQqhuTUC33uWrrT0BR5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AsDLD/dJMcaihItOQ/wJTQqhuTUC33uWrrT0BR5k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;906&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.29.50.png&quot; style=&quot;width: 41.5855%; margin-right: 10px;&quot; data-widthpercent=&quot;42.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AsDLD/dJMcaihItOQ/wJTQqhuTUC33uWrrT0BR5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAsDLD%2FdJMcaihItOQ%2FwJTQqhuTUC33uWrrT0BR5k%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;1384&quot; height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blYEgS/dJMcaiWhESu/DbXfUvz1Im1hxw19Amk0UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blYEgS/dJMcaiWhESu/DbXfUvz1Im1hxw19Amk0UK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;912&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.29.36.png&quot; data-widthpercent=&quot;57.93&quot; style=&quot;width: 57.2517%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blYEgS/dJMcaiWhESu/DbXfUvz1Im1hxw19Amk0UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblYEgS%2FdJMcaiWhESu%2FDbXfUvz1Im1hxw19Amk0UK%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;1918&quot; height=&quot;912&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;이걸 더 확대해서 확인하면 CPU Usage 그래프가 개별 샘플까지 보여줌.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 마커(R 표시)는 Time Profiler가 가져온 샘&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샘플이 없는 시간대를 Range를 확대하면 이 시간에는 데이터를 녹화하지 않았기 때문에 Time Profiler가 어떤 일이 일어나는지 알려줄 수 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다른 도구가 필요한데, `Thread States Trace` instruments임.&lt;/p&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-filename=&quot;스크린샷 2026-01-07 오후 11.31.53.png&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;912&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPLE1q/dJMcaiBX4na/gJSKwJiw9cHBbXdRaV2Ij0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPLE1q/dJMcaiBX4na/gJSKwJiw9cHBbXdRaV2Ij0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPLE1q/dJMcaiBX4na/gJSKwJiw9cHBbXdRaV2Ij0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPLE1q%2FdJMcaiBX4na%2FgJSKwJiw9cHBbXdRaV2Ij0%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;1908&quot; height=&quot;912&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.31.53.png&quot; data-origin-width=&quot;1908&quot; data-origin-height=&quot;912&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;Thread 정보를 확인할 수 있는데, 메인 스레드에 오랜 시간 차단된 것을 확인할 수 있음.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DHRMf/dJMcabiA4iv/ZDLM6qDo9H1nPVEiOAvts0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DHRMf/dJMcabiA4iv/ZDLM6qDo9H1nPVEiOAvts0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;378&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.33.02.png&quot; style=&quot;width: 80.2411%; margin-right: 10px;&quot; data-widthpercent=&quot;81.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DHRMf/dJMcabiA4iv/ZDLM6qDo9H1nPVEiOAvts0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDHRMf%2FdJMcabiA4iv%2FZDLM6qDo9H1nPVEiOAvts0%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;1904&quot; height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMhwWH/dJMcaajGnv5/Jh1Qe9Vy5PTsUvUF1QEy0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMhwWH/dJMcaajGnv5/Jh1Qe9Vy5PTsUvUF1QEy0K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;490&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.35.39.png&quot; style=&quot;width: 18.5961%;&quot; data-widthpercent=&quot;18.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMhwWH/dJMcaajGnv5/Jh1Qe9Vy5PTsUvUF1QEy0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMhwWH%2FdJMcaajGnv5%2FJh1Qe9Vy5PTsUvUF1QEy0K%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;572&quot; height=&quot;490&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기 정보를 보면 6.64초 동안 차단되었다는 것을 확인할 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;시스템 콜인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;`mach_msg2_trap`가 6.64초를 차단했다는 것을 확인할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Backtrace로 Heaviest Stack Trace와는 다른 정보가 존재.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;시스템 콜인&lt;span&gt; `&lt;/span&gt;&lt;/span&gt;mach_msg2_trap`의 정확한 backtrace로 스레드를 차단한 원인.&lt;/li&gt;
&lt;li&gt;함수 호출이 리프 형태로 표시되어 있고, 호출 스택은 그 위에 표시.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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/bJRgYw/dJMcacV50ar/fUCXPBjuhsKxwcjP6OhLn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJRgYw/dJMcacV50ar/fUCXPBjuhsKxwcjP6OhLn0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;484&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.41.55.png&quot; style=&quot;width: 32.0999%; margin-right: 10px;&quot; data-widthpercent=&quot;32.86&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJRgYw/dJMcacV50ar/fUCXPBjuhsKxwcjP6OhLn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJRgYw%2FdJMcacV50ar%2FfUCXPBjuhsKxwcjP6OhLn0%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;570&quot; height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n5Fkm/dJMcahJSUDb/iE7f0mvlK1af7GTA6KnKz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n5Fkm/dJMcahJSUDb/iE7f0mvlK1af7GTA6KnKz0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;480&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.40.43.png&quot; style=&quot;width: 32.9352%; margin-right: 10px;&quot; data-widthpercent=&quot;33.72&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n5Fkm/dJMcahJSUDb/iE7f0mvlK1af7GTA6KnKz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn5Fkm%2FdJMcahJSUDb%2FiE7f0mvlK1af7GTA6KnKz0%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;580&quot; height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ncQN7/dJMcafkZwN7/8bMGZKSKTNI9kMjx5v6sXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ncQN7/dJMcafkZwN7/8bMGZKSKTNI9kMjx5v6sXk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;476&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.41.02.png&quot; style=&quot;width: 32.6394%;&quot; data-widthpercent=&quot;33.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ncQN7/dJMcafkZwN7/8bMGZKSKTNI9kMjx5v6sXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FncQN7%2FdJMcafkZwN7%2F8bMGZKSKTNI9kMjx5v6sXk%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;570&quot; height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌측부터 1,2,3&lt;/figcaption&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/ugjg3/dJMcabbOQnH/6RWdoC5BSrC1NFacZQ7km0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ugjg3/dJMcabbOQnH/6RWdoC5BSrC1NFacZQ7km0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;502&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.42.34.png&quot; style=&quot;width: 31.7415%; margin-right: 10px;&quot; data-widthpercent=&quot;32.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ugjg3/dJMcabbOQnH/6RWdoC5BSrC1NFacZQ7km0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fugjg3%2FdJMcabbOQnH%2F6RWdoC5BSrC1NFacZQ7km0%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;586&quot; height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c81Ilj/dJMcaiaWUPn/Z6qaPaEKOQXrd7B2ulg2fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c81Ilj/dJMcaiaWUPn/Z6qaPaEKOQXrd7B2ulg2fk/img.png&quot; style=&quot;width: 32.7116%; margin-right: 10px;&quot; width=&quot;422&quot; height=&quot;351&quot; data-widthpercent=&quot;33.49&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.38.40.png&quot; data-origin-height=&quot;532&quot; data-origin-width=&quot;640&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c81Ilj/dJMcaiaWUPn/Z6qaPaEKOQXrd7B2ulg2fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc81Ilj%2FdJMcaiaWUPn%2FZ6qaPaEKOQXrd7B2ulg2fk%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;640&quot; height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chqwoi/dJMcacIzZCH/aFCNmi5THn2NpkYrHBs82k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chqwoi/dJMcacIzZCH/aFCNmi5THn2NpkYrHBs82k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;478&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.43.30.png&quot; style=&quot;width: 33.2214%;&quot; data-widthpercent=&quot;34.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chqwoi/dJMcacIzZCH/aFCNmi5THn2NpkYrHBs82k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchqwoi%2FdJMcacIzZCH%2FaFCNmi5THn2NpkYrHBs82k%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;584&quot; height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌측부터 4,5,6&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 스택을 분석하자면 `mach_msg2_trap` 시스템 콜이 발생한 이유는 MLModel 할당 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ColorizingService Type의 객체 할당 때문이며, 이는 ColorizingService의 싱글톤 객체인 shared의 일부로 호출되었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ColorizingService의 싱글톤 객체는 `BackgroundThumbnailView.body.getter` 클로저에서 호출&lt;/p&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-filename=&quot;스크린샷 2026-01-07 오후 11.48.03.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCkJhe/dJMcabXcmTe/tXbCGkrgpdXy5KqF6GPPf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCkJhe/dJMcabXcmTe/tXbCGkrgpdXy5KqF6GPPf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCkJhe/dJMcabXcmTe/tXbCGkrgpdXy5KqF6GPPf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCkJhe%2FdJMcabXcmTe%2FtXbCGkrgpdXy5KqF6GPPf0%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;1822&quot; height=&quot;938&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.48.03.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;938&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;ColorizingService.shared에 접근하여 로컬 변수에 저장&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 문제는 `shared`에 처음 접근시 ColorizingService 인스턴스를 만들기 때문에 이로 인하여 모델을 로딩하는 전체 과정을 시작&lt;/li&gt;
&lt;li&gt;따라서 스레드를 차단.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.51.23.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blsSsz/dJMcagxqmrs/SkbhCKKdBsFXkzsatwGQJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blsSsz/dJMcagxqmrs/SkbhCKKdBsFXkzsatwGQJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blsSsz/dJMcagxqmrs/SkbhCKKdBsFXkzsatwGQJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblsSsz%2FdJMcagxqmrs%2FSkbhCKKdBsFXkzsatwGQJ1%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;1808&quot; height=&quot;930&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.51.23.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;930&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;이 문제를 해결하기 위해서 이전과 같은 방법으로 `.task { }`를 await 을 통해 분리하는 접근법을 시도할 수 있으나 이 예제에서는 문제를 해결하지 못함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜냐하면 await 키워드는 이후 코드의 비동기 함수 호출에만 적용되기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 예제에서는 `colorize` 메서드가 비동기 메서드이지만 `shared`의 프로퍼티는 그렇지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`static` 프로퍼티이기 때문에 처음 접근했을 순간(lazy 초기화)되며, 이 시점에는 모두 동기적으로 진행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, await 키워드가 shared를 바꾸지 않아서 여전히 메인 동기적인 호출은 메인 스레드에서 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.55.00.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKiUBU/dJMcaf6mndQ/jaJNfrMqv2fj52YqKxHF60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKiUBU/dJMcaf6mndQ/jaJNfrMqv2fj52YqKxHF60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKiUBU/dJMcaf6mndQ/jaJNfrMqv2fj52YqKxHF60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKiUBU%2FdJMcaf6mndQ%2FjaJNfrMqv2fj52YqKxHF60%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;564&quot; height=&quot;469&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.55.00.png&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;978&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;이전 사례 처럼 처리하려면 `shared`도 asnyc로 만들어서 MainActor에서 분리해야 함.&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;스레드 차단의 일반적인 이유는 Lock과 Semaphore.&lt;/p&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-filename=&quot;스크린샷 2026-01-07 오후 11.57.51.png&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xvwpc/dJMcaihIt41/igIkkHxr22LMltwNI7QvI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xvwpc/dJMcaihIt41/igIkkHxr22LMltwNI7QvI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xvwpc/dJMcaihIt41/igIkkHxr22LMltwNI7QvI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXvwpc%2FdJMcaihIt41%2FigIkkHxr22LMltwNI7QvI0%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;1910&quot; height=&quot;854&quot; data-filename=&quot;스크린샷 2026-01-07 오후 11.57.51.png&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;854&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;다른 영역에서 메인 스레드가 Blocked로 표시되어 있지만 이 부분에서는 Instruments가 잠재적인 행으로 표시하지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단지, 사용자 입력이 없어서 메인 스레드가 잠든(aleep) 상태.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-08 오전 12.03.40.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8JomD/dJMcaf6mnnF/5wNJquUTbdjSmiIbrnIo31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8JomD/dJMcaf6mnnF/5wNJquUTbdjSmiIbrnIo31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8JomD/dJMcaf6mnnF/5wNJquUTbdjSmiIbrnIo31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8JomD%2FdJMcaf6mnnF%2F5wNJquUTbdjSmiIbrnIo31%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;1854&quot; height=&quot;910&quot; data-filename=&quot;스크린샷 2026-01-08 오전 12.03.40.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;차단된 스레드가 반응성 문제인지 결정할 때는 Hangs Instruemnts를 먼저 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blocked MainThread가 무조건적으로 Main Thread 반응이 없다는 것을 나타내지(암시하지) 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한&amp;nbsp;CPU Usage가 높다는 것이 단순히 Main Thread 반응이 없는걸 나타내지(암시하지) 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 Main Thread가 반응하지 않은 경우에는 OS가 자원을 아끼기 위해 Blocked으로 처리할 수 있으며, 실제로 반응하지 않은 경우에만 Insturments는 잠재적인 행으로 나타냄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 Unresponsive Main Thread 이라면 blocked 또는 busy하다는 의미&lt;/p&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-filename=&quot;스크린샷 2026-01-08 오전 12.08.02.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dEgjfa/dJMb99SCj8o/GEut1kGqPg2zKwlENqeYQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEgjfa/dJMb99SCj8o/GEut1kGqPg2zKwlENqeYQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEgjfa/dJMb99SCj8o/GEut1kGqPg2zKwlENqeYQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEgjfa%2FdJMb99SCj8o%2FGEut1kGqPg2zKwlENqeYQ1%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;1808&quot; height=&quot;958&quot; data-filename=&quot;스크린샷 2026-01-08 오전 12.08.02.png&quot; data-origin-width=&quot;1808&quot; data-origin-height=&quot;958&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;메인 스레드에서 어떤 작업을 하든 100ms 이하로 작업해야 메인 스레드를 이벤트 처리에 쓸 수 있음.&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;Busy한 상태와 Blocked된 상태의 메인 스레드의 차이점을 명확하게 기억하고 메인 스레드의 비동기 작업으로 발생할 수 있는 행도 기억하기.&lt;/p&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;작업을 적게한다는 것은 작업에 맞는 적절한 API를 사용한다는 것.&lt;/p&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;(링크)&lt;br /&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=lWVuP3Vts_0&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=lWVuP3Vts_0&lt;/a&gt;&lt;/p&gt;</description>
      <category>Apple/WWDC</category>
      <category>Instrumets</category>
      <author>lgvv</author>
      <guid isPermaLink="true">https://rldd.tistory.com/794</guid>
      <comments>https://rldd.tistory.com/794#entry794comment</comments>
      <pubDate>Sun, 4 Jan 2026 00:47:22 +0900</pubDate>
    </item>
  </channel>
</rss>