<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Peppo</title>
    <link>https://peppo.tistory.com/</link>
    <description>흔들리지 말고 꾸준히 하기</description>
    <language>ko</language>
    <pubDate>Sun, 12 Jul 2026 16:34:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Peppo</managingEditor>
    <image>
      <title>Peppo</title>
      <url>https://tistory1.daumcdn.net/tistory/4979542/attach/854344fb179c4d4c9ebf1b1692f14858</url>
      <link>https://peppo.tistory.com</link>
    </image>
    <item>
      <title>[배포] 프로비저닝 프로파일 (Provisioning Profile) 등록</title>
      <link>https://peppo.tistory.com/240</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앱의 신뢰성과 보안을 보장하고, Apple 내에서 앱이 정상적으로 실행될 수 있도록 &lt;b&gt;허가 &lt;/b&gt;하는 역할&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로비저닝 프로파일 (Provisioning Profile)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기에서 앱을 실행하고 특정 서비스를 사용하고자 할 때 사용되는 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저닝 프로파일(이하 pp)를 생성하기 위해 필요한 것들&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-uuid=&quot;c514bc8d-3fd2-4072-84d3-9b86057b27c3&quot;&gt;Identifiers&lt;/li&gt;
&lt;li data-uuid=&quot;939bf831-0263-43f2-a5e1-67b5e6d89f5f&quot;&gt;Certificates&lt;/li&gt;
&lt;li data-uuid=&quot;39713349-2b2c-4bdf-8bc3-15bd90ffb31a&quot;&gt;Devices&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앱 ID (Identifiers)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 고유하게 식별하는 ID&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;생성방법&lt;/b&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;1. &lt;b&gt;애플 디벨로퍼 &amp;gt; Certificates, Identifiers &amp;amp; Profiles &amp;gt;&amp;nbsp; Identifiers 카테고리 &amp;gt; '+' 클릭&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcBZXi/btsQwf7LhLx/0vdS9P62b5K3xV8XzkBPX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcBZXi/btsQwf7LhLx/0vdS9P62b5K3xV8XzkBPX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcBZXi/btsQwf7LhLx/0vdS9P62b5K3xV8XzkBPX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcBZXi%2FbtsQwf7LhLx%2F0vdS9P62b5K3xV8XzkBPX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;354&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;App IDs 선택&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;1166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RA56R/btsQxIafF84/VuYAhV2YbNnca1Tsq7NtD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RA56R/btsQxIafF84/VuYAhV2YbNnca1Tsq7NtD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RA56R/btsQxIafF84/VuYAhV2YbNnca1Tsq7NtD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRA56R%2FbtsQxIafF84%2FVuYAhV2YbNnca1Tsq7NtD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1166&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;1166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;App 선택&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv1gIs/btsQysSgT6L/jS3JRbhNAiymio7zk7kfpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv1gIs/btsQysSgT6L/jS3JRbhNAiymio7zk7kfpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv1gIs/btsQysSgT6L/jS3JRbhNAiymio7zk7kfpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv1gIs%2FbtsQysSgT6L%2FjS3JRbhNAiymio7zk7kfpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;154&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;562&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;4. &lt;b&gt;Description, BundleID, Capabilities 입력&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnFKbA/btsQwe16slc/gezkpXe9DnGgYrNvpUQS51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnFKbA/btsQwe16slc/gezkpXe9DnGgYrNvpUQS51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnFKbA/btsQwe16slc/gezkpXe9DnGgYrNvpUQS51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnFKbA%2FbtsQwe16slc%2FgezkpXe9DnGgYrNvpUQS51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1246&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1246&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;5. 생성완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 id=&quot;AppleDeveloperProvisioningProfile생성플로우-CSR생성(CertificateSigningRequest)&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSR 생성 (CertificateSigningRequest)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Certificate를 만드는데 사용&lt;/b&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;b&gt;생성방법&lt;/b&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;&lt;b&gt;1. 키체인 접근 실행&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NdiaA/btsQzBOIqug/i9Qk3yBqD8zJTA4TOYlVi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NdiaA/btsQzBOIqug/i9Qk3yBqD8zJTA4TOYlVi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NdiaA/btsQzBOIqug/i9Qk3yBqD8zJTA4TOYlVi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNdiaA%2FbtsQzBOIqug%2Fi9Qk3yBqD8zJTA4TOYlVi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;544&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;544&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;2. &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;이메일 주소, 이름 입력 후 아래와 같이 체크&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKINs/btsQvNKnSTn/ZAcQuMZ02cX1HXL90kzogK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKINs/btsQvNKnSTn/ZAcQuMZ02cX1HXL90kzogK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKINs/btsQvNKnSTn/ZAcQuMZ02cX1HXL90kzogK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKINs%2FbtsQvNKnSTn%2FZAcQuMZ02cX1HXL90kzogK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;884&quot; data-origin-width=&quot;1232&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;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;3. &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;'계속' 누르면 CSR이 생성됨&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dakja4/btsQzKLCJ69/TAOcAqaaaT6kVGhFmLaGxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dakja4/btsQzKLCJ69/TAOcAqaaaT6kVGhFmLaGxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dakja4/btsQzKLCJ69/TAOcAqaaaT6kVGhFmLaGxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdakja4%2FbtsQzKLCJ69%2FTAOcAqaaaT6kVGhFmLaGxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;884&quot; data-origin-width=&quot;1232&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;&lt;b&gt;비대칭키: 잠글수 있는 키(public key), 열수있는 키(private key) - &quot;RSA&quot;가 대표적&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;이 private key가 존재해야, xcode에서 실행할 수 있는데 개발자끼리 협업을 할 때 이 private key를 공유해서 써야 원활한 개발이 가능&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;private key를 잊으면 갱신하면 되는데, 이 때 다른 개발자가 기존의 private key는 무효화 되어 동기화에 문제가 생김&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;보통 private key를 공유 하려면, git repository에 Certificate와 private키를 올려놓고 각 개발자들은 이 파일을 다운받아서 keychain에 저장하는 형태로 사용&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;private key를 추출하려면, 인증서에 해당하는 key에 오른쪽 마우스 -&amp;gt; 내보내기로 ~.p12파일 획득 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 생성완료&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vlo5C/btsQwcJXwGQ/kIPEiWBfRfM0q8Cjlhfbe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vlo5C/btsQwcJXwGQ/kIPEiWBfRfM0q8Cjlhfbe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vlo5C/btsQwcJXwGQ/kIPEiWBfRfM0q8Cjlhfbe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVlo5C%2FbtsQwcJXwGQ%2FkIPEiWBfRfM0q8Cjlhfbe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;704&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;AppleDeveloperProvisioningProfile생성플로우-키체인접근에서PublicKey|PrivateKey확인방법&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;키체인 접근에서 Public Key | Private Key 확인 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키체인 접근 &amp;rarr; (좌측 카테고리) 로그인 &amp;rarr; (상단 카테고리) 키 &amp;rarr; 등록했던 CSR 이름 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sw8VE/btsQwGKMD2z/yC5vKfrENGAqTtiNZThGqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sw8VE/btsQwGKMD2z/yC5vKfrENGAqTtiNZThGqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sw8VE/btsQwGKMD2z/yC5vKfrENGAqTtiNZThGqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsw8VE%2FbtsQwGKMD2z%2FyC5vKfrENGAqTtiNZThGqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1374&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;AppleDeveloperProvisioningProfile생성플로우-.p12파일추출방법&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p id=&quot;AppleDeveloperProvisioningProfile생성플로우-.p12파일추출방법&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;.p12 파일 추출 방법&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;위 접근 방법 &amp;rarr; 개인키(private key) 우클릭 &amp;rarr; 내보내기 &amp;rarr; .p12 파일 획득&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kx3si/btsQyxsvREY/Yala7zwB4JZNAIrwWVbui1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kx3si/btsQyxsvREY/Yala7zwB4JZNAIrwWVbui1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kx3si/btsQyxsvREY/Yala7zwB4JZNAIrwWVbui1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkx3si%2FbtsQyxsvREY%2FYala7zwB4JZNAIrwWVbui1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;294&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;388&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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 id=&quot;AppleDeveloperProvisioningProfile생성플로우-CSR생성(CertificateSigningRequest)&quot; style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인증서(Certificates)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발용 | 배포용 인증서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앱을 빌드하고 서명한 개발자 또는 팀이 Apple 개발자 프로그램에 등록된 유효한 회원임을 증명하는 용도&lt;/b&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;b&gt;생성방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;Certificates 카테고리 &amp;gt; '+' 클릭&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvTJYB/btsQyvIcCwJ/PgVtWysH2Fp7K2qgKsML21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvTJYB/btsQyvIcCwJ/PgVtWysH2Fp7K2qgKsML21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvTJYB/btsQyvIcCwJ/PgVtWysH2Fp7K2qgKsML21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvTJYB%2FbtsQyvIcCwJ%2FPgVtWysH2Fp7K2qgKsML21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;258&quot; data-origin-width=&quot;1930&quot; data-origin-height=&quot;258&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;2. &lt;b&gt;필요 목적에 따라 Software, Services 선택&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Development&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(개발용) |&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Distribution&lt;/b&gt;(배포용)&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;2106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BHyW5/btsQwSSfITz/JBUMpOVcHTjM7wmxVRpxO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BHyW5/btsQwSSfITz/JBUMpOVcHTjM7wmxVRpxO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BHyW5/btsQwSSfITz/JBUMpOVcHTjM7wmxVRpxO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBHyW5%2FbtsQwSSfITz%2FJBUMpOVcHTjM7wmxVRpxO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;2106&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;2106&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. &lt;b&gt;CSR 파일 추가 &amp;rarr; 저장해둔 CSR 파일 불러오기&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bACbWy/btsQzC7VjVU/I2UVknpcjTt3pMMwu0nMu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bACbWy/btsQzC7VjVU/I2UVknpcjTt3pMMwu0nMu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bACbWy/btsQzC7VjVU/I2UVknpcjTt3pMMwu0nMu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbACbWy%2FbtsQzC7VjVU%2FI2UVknpcjTt3pMMwu0nMu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;159&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;580&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;4. 다운로드&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2544&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bINH9N/btsQwg6BLjR/ngsoe2OeKtZ3sK1uSzHnFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bINH9N/btsQwg6BLjR/ngsoe2OeKtZ3sK1uSzHnFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bINH9N/btsQwg6BLjR/ngsoe2OeKtZ3sK1uSzHnFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbINH9N%2FbtsQwg6BLjR%2Fngsoe2OeKtZ3sK1uSzHnFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;144&quot; data-origin-width=&quot;2544&quot; data-origin-height=&quot;734&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;5. &lt;b&gt;.cer 파일 확인 &amp;rarr; 다운받은 파일 더블 클릭 &amp;rarr; 자동 등록 (키체인에 인증서 등록 완료)&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z0zEJ/btsQyk1lI4b/X6L8KKe3VhWKLKMFaMu6WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z0zEJ/btsQyk1lI4b/X6L8KKe3VhWKLKMFaMu6WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z0zEJ/btsQyk1lI4b/X6L8KKe3VhWKLKMFaMu6WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ0zEJ%2FbtsQyk1lI4b%2FX6L8KKe3VhWKLKMFaMu6WK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;84&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;84&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 id=&quot;AppleDeveloperProvisioningProfile생성플로우-기기등록(Devices)&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기기등록 (Devices)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;&lt;b&gt;Devices 카테고리 &amp;gt; '+' 클릭&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqnr6S/btsQzOmWJqs/sZb2DPkIrzHS9JcyOkxuMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqnr6S/btsQzOmWJqs/sZb2DPkIrzHS9JcyOkxuMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqnr6S/btsQzOmWJqs/sZb2DPkIrzHS9JcyOkxuMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqnr6S%2FbtsQzOmWJqs%2FsZb2DPkIrzHS9JcyOkxuMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;68&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;262&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;2. &lt;b&gt;기기 이름, 기기ID 입력&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;1302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yh71t/btsQx4dlPog/rJi0BPGK8nlV3Y7pGD1Huk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yh71t/btsQx4dlPog/rJi0BPGK8nlV3Y7pGD1Huk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yh71t/btsQx4dlPog/rJi0BPGK8nlV3Y7pGD1Huk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYh71t%2FbtsQx4dlPog%2FrJi0BPGK8nlV3Y7pGD1Huk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;346&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;1302&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. &lt;b&gt;등록완료&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNHQF5/btsQwAK47cW/iqYBb6hr4tFtWEMcKqIqIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNHQF5/btsQwAK47cW/iqYBb6hr4tFtWEMcKqIqIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNHQF5/btsQwAK47cW/iqYBb6hr4tFtWEMcKqIqIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNHQF5%2FbtsQwAK47cW%2FiqYBb6hr4tFtWEMcKqIqIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;586&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 id=&quot;AppleDeveloperProvisioningProfile생성플로우-프로비저닝프로파일(ProvisioningProfile).1&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로비저닝 프로파일 (Provisioning Profile)&lt;/b&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;&lt;b&gt;생성방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;Profiles 카테고리 &amp;gt; '+' 클릭&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baEvk2/btsQzBVtu5i/SBta79GK4gpAcCpj9IC0ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baEvk2/btsQzBVtu5i/SBta79GK4gpAcCpj9IC0ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baEvk2/btsQzBVtu5i/SBta79GK4gpAcCpj9IC0ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaEvk2%2FbtsQzBVtu5i%2FSBta79GK4gpAcCpj9IC0ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;262&quot; data-origin-width=&quot;1968&quot; data-origin-height=&quot;262&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;2. &lt;b&gt;개발용&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(iOS App Development) |&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;배포용&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(Ad Hoc)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccdX3u/btsQzxFylwd/upmkaXvQEiLsUqwKhzbuuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccdX3u/btsQzxFylwd/upmkaXvQEiLsUqwKhzbuuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccdX3u/btsQzxFylwd/upmkaXvQEiLsUqwKhzbuuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccdX3u%2FbtsQzxFylwd%2FupmkaXvQEiLsUqwKhzbuuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1942&quot; data-origin-width=&quot;1990&quot; data-origin-height=&quot;1942&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. &lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;'&lt;/span&gt;&lt;b&gt;Identifier&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;' 에서 생성한 AppID 등록&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xDn2U/btsQwBDf3Oe/tKJrtnm1u7saR9uw7YNvWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xDn2U/btsQwBDf3Oe/tKJrtnm1u7saR9uw7YNvWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xDn2U/btsQwBDf3Oe/tKJrtnm1u7saR9uw7YNvWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxDn2U%2FbtsQwBDf3Oe%2FtKJrtnm1u7saR9uw7YNvWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1138&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;1138&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;4. &lt;b&gt;'Certificates'에서 생성한 Certificate 등록&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLSWi8/btsQzmYyy5v/u87XUyYr4DHM10rk6JypJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLSWi8/btsQzmYyy5v/u87XUyYr4DHM10rk6JypJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLSWi8/btsQzmYyy5v/u87XUyYr4DHM10rk6JypJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLSWi8%2FbtsQzmYyy5v%2Fu87XUyYr4DHM10rk6JypJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;950&quot; data-origin-width=&quot;1948&quot; data-origin-height=&quot;950&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;5. &lt;b&gt;'Devices'에서 생성한 Device or 팀원 Device 등록&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUPziI/btsQwUifzSM/0ILh387AjIMCvmGgRqeaL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUPziI/btsQwUifzSM/0ILh387AjIMCvmGgRqeaL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUPziI/btsQwUifzSM/0ILh387AjIMCvmGgRqeaL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUPziI%2FbtsQwUifzSM%2F0ILh387AjIMCvmGgRqeaL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;179&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;686&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;6. &lt;b&gt;Provisioning Profile 이름 입력 &amp;rarr; Download&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceMlMX/btsQyBVY4mH/syMvZhRlunB42tCjstblS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceMlMX/btsQyBVY4mH/syMvZhRlunB42tCjstblS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceMlMX/btsQyBVY4mH/syMvZhRlunB42tCjstblS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceMlMX%2FbtsQyBVY4mH%2FsyMvZhRlunB42tCjstblS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;178&quot; data-origin-width=&quot;1950&quot; data-origin-height=&quot;694&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;7. &lt;b&gt;(Xcode) Provisioning Profile 등록&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d; text-align: left;&quot;&gt;Xcode &amp;rarr; Signing &amp;amp; Capabilities &amp;rarr; Provisioning Profile &amp;rarr; Import ... &amp;rarr; 다운받은 pp (Provisioning Profile) 등록&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7hsYn/btsQxxNDTK9/kLrjbpiRcpztdnQsFrA1jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7hsYn/btsQxxNDTK9/kLrjbpiRcpztdnQsFrA1jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7hsYn/btsQxxNDTK9/kLrjbpiRcpztdnQsFrA1jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7hsYn%2FbtsQxxNDTK9%2FkLrjbpiRcpztdnQsFrA1jK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;206&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>AppID</category>
      <category>Apple Certificate</category>
      <category>Apple Developer</category>
      <category>iOS 배포</category>
      <category>iOS 빌드</category>
      <category>pp 파일</category>
      <category>provisioning profile</category>
      <category>Xcode Signing Provisioning Profile</category>
      <category>프로비저닝 프로파일</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/240</guid>
      <comments>https://peppo.tistory.com/240#entry240comment</comments>
      <pubDate>Sat, 13 Sep 2025 15:11:49 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] layoutIfNeed, setNeedsLayout</title>
      <link>https://peppo.tistory.com/236</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;레이아웃 작업을하면서 메소드 정리가 필요할것 같아 작성해봅니다&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;layoutIfNeeded&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:&lt;b&gt; 즉시&amp;nbsp;&lt;/b&gt;업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;setNeedsLayout&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 다음 UI *&lt;b&gt;update cycle&lt;/b&gt;에 업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;들어가기전에 update cycle부터 알아보고 갑시다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;update cycle이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS는 화면을 업데이트할 때, 아래의 과정을 거칩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저의 이벤트 &amp;rarr; 레이아웃 &amp;rarr; 디스플레이 &amp;rarr; 렌더링&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xLYtC/btsMINl5b3i/IHxUxTI11vcQsWBT2N6mOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xLYtC/btsMINl5b3i/IHxUxTI11vcQsWBT2N6mOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xLYtC/btsMINl5b3i/IHxUxTI11vcQsWBT2N6mOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxLYtC%2FbtsMINl5b3i%2FIHxUxTI11vcQsWBT2N6mOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;380&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션이 유저로부터 모든 이벤트 핸들링 코드를 수행하고 다시 *Main Run Loop로 컨트롤을 반환하는 지점 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이론적인 내용이라 이해하기 어려운데 조금더 정리해보자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Event Handling&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Layout Pass&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 레이아웃 재계산이 필요한 경우 &lt;b&gt;setNeedsLayout()&lt;/b&gt;으로 예약하거나, &lt;b&gt;layoutIfNeeded()&lt;/b&gt;로 즉시 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Display Pass&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;4. Rendering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: GPU가 픽셀을 렌더링해 사용자에게 보여줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 과정을 update cycle이라고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 Main Run Loop 는 뭐죠?&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Main Run Loop&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run loop는 어플리케이션의 터치와 같은 input sources를 관리하는 object에 대한 프로그래밍 인터페이스 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run loop는 시스템에서 생성, 관리하며, 각 스레드 객체에 대한 런루프 객체 생성도 담당합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, 입력 소스를 처리하는 이벤트 처리 루프&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;layoutIfNeeded()&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;view가 레이아웃을 즉시 업데이트 하도록 하려면 이 방법을 사용&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Constraint를 이용한 애니메이션을 쓰는 상황에서 자주 사용되며,&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래 예시를 통해 사용하고 안하고 차이를 먼저 봅시다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1hivj/btsMFrpk8bV/L2pjPPXAoAOZYWMEADS8HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1hivj/btsMFrpk8bV/L2pjPPXAoAOZYWMEADS8HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1hivj/btsMFrpk8bV/L2pjPPXAoAOZYWMEADS8HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1hivj%2FbtsMFrpk8bV%2FL2pjPPXAoAOZYWMEADS8HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;519&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;하단의 버튼을 눌렀을 때 가운데 blueView의 height가 10씩 증가하도록 했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;UI property 코드&lt;/p&gt;
&lt;pre id=&quot;code_1741496109869&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private lazy var blueView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .systemBlue
        return v
    }()
    
    private lazy var increaseHeightButton: UIButton = {
        let v = UIButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .systemBlue
        v.setTitle(&quot;increase height&quot;, for: .normal)
        v.setTitleColor(.white, for: .normal)
        v.addTarget(self, action: #selector(increaseHeightTapped), for: .touchUpInside)
        return v
    }()
    
    private var blueViewHeightConstraint: NSLayoutConstraint!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; 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;Constraint&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1741496149914&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func setupUI() {
        self.view.addSubview(self.blueView)
        self.view.addSubview(self.increaseHeightButton)
        
        self.blueViewHeightConstraint = self.blueView.heightAnchor.constraint(equalToConstant: 100)
        
        NSLayoutConstraint.activate([
            self.blueView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            self.blueView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
            self.blueView.widthAnchor.constraint(equalToConstant: 100),
            self.blueViewHeightConstraint
        ])
        
        NSLayoutConstraint.activate([
            self.increaseHeightButton.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 32),
            self.increaseHeightButton.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -32),
            self.increaseHeightButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -32)
        ])
    }&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;버튼을 누르고 blueView의 height값을 하기 위해 print를 남겨놓고 테스트를 해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1741496300018&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 버튼 눌렀을 때
    @objc func increaseHeightTapped() {
        print(&quot;Before increasing blueView height: \(self.blueView.frame.height)&quot;)
        
        self.blueViewHeightConstraint?.constant += 10
        
        print(&quot;After increasing blueView height: \(self.blueView.frame.height)&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;UI적으로는 blueView height가 변하긴 하지만 실질적으로 blueView.height 값은 바뀌기 전과 후가 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 frame은 &lt;b&gt;레이아웃이 업데이트된 뒤에 변경&lt;/b&gt;되기 때문이에요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2025-03-091.59.03-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTXmF/btsMEV5Gbtf/kOXyVQNs9ZA1kvGVAslxuk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTXmF/btsMEV5Gbtf/kOXyVQNs9ZA1kvGVAslxuk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTXmF/btsMEV5Gbtf/kOXyVQNs9ZA1kvGVAslxuk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kTXmF/btsMEV5Gbtf/kOXyVQNs9ZA1kvGVAslxuk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;532&quot; data-filename=&quot;2025-03-091.59.03-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;532&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;iOS에서 constraint 변경 감지 &amp;rarr; 자동으로 &lt;b&gt;다음 화면 리프레시 때 레이아웃 다시 계산&lt;/b&gt; &amp;rarr; 화면 반영 순서로 진행이 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 상에선 레이아웃이 아직 반영되지 않았기 때문에 frame 값이 변하지 않은것 처럼 보이는겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 layoutIfNeeded()를 height 값을 바꾼후 적용해봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1742131437549&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 버튼 눌렀을 때
    @objc func increaseHeightTapped() {
        print(&quot;Before increasing blueView height: \(self.blueView.frame.height)&quot;)
        
        self.blueViewHeightConstraint?.constant += 10
        self.view.layoutIfNeeded()

        print(&quot;After increasing blueView height: \(self.blueView.frame.height)&quot;)
        
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2025-03-1610.24.52-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZRodW/btsMMAUj8YK/EYIg9kuWiy8OTmkL78OdW1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZRodW/btsMMAUj8YK/EYIg9kuWiy8OTmkL78OdW1/img.gif&quot; data-alt=&quot;layoutIfNeeded() 적용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZRodW/btsMMAUj8YK/EYIg9kuWiy8OTmkL78OdW1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bZRodW/btsMMAUj8YK/EYIg9kuWiy8OTmkL78OdW1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;580&quot; data-filename=&quot;2025-03-1610.24.52-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;layoutIfNeeded() 적용&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;layoutIfNeeded가 실행되면서 레이아웃을 즉시 업데이트하기 때문에 After increasing blueView height도 바뀐 frame값이 적용된걸 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 layoutIfNeeded가 필요한 이유를 보려면  애니메이션을 구현해보면 알게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 layoutIfNeeded가 없는 상태에서 애니메이션을 적용했을 때 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1742131338434&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @objc func increaseHeightTapped() {
        
        print(&quot;Before increasing blueView height: \(self.blueView.frame.height)&quot;)
        
        UIView.animate(withDuration: 1) {
            self.blueViewHeightConstraint?.constant += 10
        }
        
        
        print(&quot;After increasing blueView height: \(self.blueView.frame.height)&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;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2025-03-1610.31.49-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8LdLC/btsMLugIltG/gLgz5jV10f41k4xb46kNL1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8LdLC/btsMLugIltG/gLgz5jV10f41k4xb46kNL1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8LdLC/btsMLugIltG/gLgz5jV10f41k4xb46kNL1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c8LdLC/btsMLugIltG/gLgz5jV10f41k4xb46kNL1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;580&quot; data-filename=&quot;2025-03-1610.31.49-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&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;frame값도 변하지 않고 애니메이션도 적용되지 않는 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 layoutIfNeeded적용 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1742132129054&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @objc func increaseHeightTapped() {
        
        print(&quot;Before increasing blueView height: \(self.blueView.frame.height)&quot;)
        
        UIView.animate(withDuration: 1) {
            self.blueViewHeightConstraint?.constant += 10
            self.view.layoutIfNeeded()
        }
        
        print(&quot;After increasing blueView height: \(self.blueView.frame.height)&quot;)
        
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2025-03-1610.32.21-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQEMZD/btsML3WWW5F/WUDYPtQSdL8Y4DgiRtZe5K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQEMZD/btsML3WWW5F/WUDYPtQSdL8Y4DgiRtZe5K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQEMZD/btsML3WWW5F/WUDYPtQSdL8Y4DgiRtZe5K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dQEMZD/btsML3WWW5F/WUDYPtQSdL8Y4DgiRtZe5K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;580&quot; data-filename=&quot;2025-03-1610.32.21-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;580&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;layoutIfNeeded()가 없으면 &lt;b&gt;constraint 변경은 예약만&lt;/b&gt; 되고, 애니메이션 없이 &lt;b&gt;다음 Run Loop 에서 즉시 반영됩니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 애니메이션 효과가 없이 보이게 되는거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;layoutIfNeeded()를 사용하면 애니메이션 블록 내에서 &lt;b&gt;즉시 레이아웃을 업데이트해 초기/최종 상태가 감지되면서 부드러운 애니메이션이 적용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;b&gt;setNeedsLayout()&lt;/b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;layoutSubviews를 가장 적은 부하로 호출할 수 있는 메소드 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비용이 가장 적게 드는 방법으로 해당 메소드를 호출한 view는 &lt;b&gt;재계산 되어야 하는 View로 인식&lt;/b&gt;되며 update cycle에서 해당 view의 layoutSubviews()가 호출되게 됩니다.&lt;/p&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가 알아서 setNeedsLayout을 호출하기 때문에 직접 호출할 필요는 없지만&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;constraint를 건들지 않고, view 속성을 직접 변경하는 경우&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1742132761886&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// constraint를 건들지 않은 경우
self.blueView.frame.size.height += 10
self.view.setNeedsLayout()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 레이아웃 변경을 한 번에 처리하고 싶을 때&lt;/p&gt;
&lt;pre id=&quot;code_1742132795751&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;self.blueViewHeightConstraint?.constant += 10
self.redViewHeightConstraint?.constant += 20
self.view.setNeedsLayout()  // 한 번만 호출해서 최적화&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;부모 뷰 전체가 아닌, 특정 subview에만 레이아웃 갱신을 하고 싶을 때&lt;/p&gt;
&lt;pre id=&quot;code_1742132837320&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;self.blueView.setNeedsLayout()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&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;b&gt;layoutIfNeeded&amp;nbsp;&lt;/b&gt;는 레이아웃을 즉시 업데이트하고 애니메이션, 변경 직후 상태를 바로 확인할 때 사용을 하며, 즉시 반영이기 때문에 성능 저하 우려가 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;setNeedsLayout&amp;nbsp;&lt;/b&gt;은 &lt;b&gt;모든 레이아웃 업데이트를 하나의 업데이트 주기로 통합&lt;/b&gt;할 수 있으므로 &lt;b&gt;성능적으로 이점이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그동안 기존 코드에 뒤 두 메소드의 역할이 어떤것이었는지 궁금했는데&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이번기회에 동작 방식을 알고 가니 어떤 상황에 사용되야할지 명확해진것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://www.youtube.com/watch?v=0loU4SyTfho&quot;&gt;https://www.youtube.com/watch?v=0loU4SyTfho&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://frouo.com/posts/ios-layoutifneeded-vs-setneedslayout&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://frouo.com/posts/ios-layoutifneeded-vs-setneedslayout&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://duwjdtn11.tistory.com/619&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://duwjdtn11.tistory.com/619&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://skytitan.tistory.com/514&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://skytitan.tistory.com/514&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>ios view lifecycle</category>
      <category>layoutIfNeeded</category>
      <category>main run loop</category>
      <category>setNeedsLayout</category>
      <category>update cycle</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/236</guid>
      <comments>https://peppo.tistory.com/236#entry236comment</comments>
      <pubDate>Sun, 16 Mar 2025 22:53:30 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] POP(Protocol Oriented Programming) 한번쯤 들어봤을거고, 어쩌면 나도 모르게 하고 있었던거</title>
      <link>https://peppo.tistory.com/234</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 POP (Protocol Oriented Programming)에 대해 공부해보려고 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그동안 Protocol이란&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1. 채택하면 해당 protocol 내부의 구현을 해야하고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2. struct, enum에도 채택을해서 구현할 수 있고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;3. extension으로 구현부를 작성할 수 있고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;4. delegate로 대리자에게 기능을 위임하도록 할 수 있고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;등등 생각보다 protocol이란 개념은 알겠지만&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;'POP 프로토콜 지향 프로그래밍을 하고 있나?'&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;라는 질문에는 섣불리 대답을 못할것 같다..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이에 어떤 장점이 있고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 상황에써야 좋으며,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;왜 써야하는지에 대해 정리를 해보고자 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프로토콜을 알고 있다는 가정하에 아래 내용을 작성합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;약간의 개념과 예시를 참고 하고싶다면 &lt;b&gt;&lt;a title=&quot;여기&quot; href=&quot;https://peppo.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;&lt;/b&gt;를 읽고 와주세요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;POP (Protocol Oriented Programming)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서 강조하는 프로그래밍 패러다임으로,&amp;nbsp;&lt;b&gt;프로토콜을 중심으로 설계하는 방법 &lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP(객체 지향 프로그래밍)에서는 &lt;b&gt;상속&lt;/b&gt;을 통해 기능을 확장하는 방식이 일반적이었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속은 하나밖에 할 수 없으며, 애플에서 구조체 사용을 권장하는데 구조체(struct)는 상속을 받지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 보완하기 위해 등장한 개념이 POP 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OOP, POP의 차이점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보완하기 위해 나왔다는 POP.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 비교도 한번 해보겠습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 17px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 38.1008%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;OOP (객체 지향)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.8914%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;POP (프로토콜 지향)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.1008%; height: 17px; text-align: center;&quot;&gt;클래스(Class) 중심&lt;/td&gt;
&lt;td style=&quot;width: 45.8914%; height: 17px; text-align: center;&quot;&gt;프로토콜 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;기능 확장&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.1008%; height: 17px; text-align: center;&quot;&gt;상속(Inheritance) 사용&lt;/td&gt;
&lt;td style=&quot;width: 45.8914%; height: 17px; text-align: center;&quot;&gt;프로토콜 준수(Conformance) 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.0077%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;다중 기능 적용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.1008%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;단일 상속(다중 상속 불가)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 45.8914%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;다중 프로토콜&amp;nbsp; 채택 가능&lt;/b&gt;&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 쓰는것보단 예시로 작성하는게 이해가 잘 될것 같아 바로 예시로 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자전거, 자동차는 모두 탈것(Vehicle)에 속하고 공통으로 사용되는 프로퍼티, 메서드가 있을겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시에서는 공통 프로퍼티를 speed, 메서드를 가속(accelerate)으로 예를 들어볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 공통으로 사용되는 부분은 protocol로 선언해주고 (Vehicle)&lt;/p&gt;
&lt;pre id=&quot;code_1738389427636&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Vehicle {
    var speed: Double { get set }
    func accelerate()
}&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;Car, Bicycle 클래스를 만들어 각각 Vehicle 프로토콜을 채택해주면 Vehicle을 따르지 않았다고 에러가 뜹니다.&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;1470&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8E0ou/btsL3mQWgKl/49t2h1G9M8thZd8ULdeaBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8E0ou/btsL3mQWgKl/49t2h1G9M8thZd8ULdeaBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8E0ou/btsL3mQWgKl/49t2h1G9M8thZd8ULdeaBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8E0ou%2FbtsL3mQWgKl%2F49t2h1G9M8thZd8ULdeaBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1470&quot; height=&quot;92&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;92&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;pre id=&quot;code_1738390261993&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Car: Vehicle {
	var speed: Double = 0.0
    
    func accelerate() {
        self.speed += 10.0
        print(&quot;Car is accelerating. Current speed: \(self.speed) km/h&quot;)
    }
}

class Bicycle: Vehicle {
    var speed: Double = 0.0
    
    func accelerate() {
        self.speed += 5.0
        print(&quot;Bicycle is accelerating. Current speed: \(self.speed) km/h&quot;)
    }
}

let myCar = Car()
myCar.accelerate()
myCar.accelerate()
myCar.accelerate()

let myBicycle = Bicycle()
myBicycle.accelerate()
myBicycle.accelerate()

/* 출력
Car is accelerating. Current speed: 10.0 km/h
Car is accelerating. Current speed: 20.0 km/h
Car is accelerating. Current speed: 30.0 km/h
Bicycle is accelerating. Current speed: 5.0 km/h
Bicycle is accelerating. Current speed: 10.0 km/h
*/&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;초기구현(Default Implementation)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 멈췄을때를 공통적으로 구현해주고 싶을때 &lt;b&gt;extension&lt;/b&gt;으로 따로 구현해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738391005889&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Vehicle {
    var speed: Double { get set }
    func accelerate()
}

extension Vehicle {
    func stop(vehicle: String) {
        print(&quot;\(vehicle) is stopped&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;Car에서도, Bicycle에서도 &lt;i&gt;&lt;b&gt;stop()&lt;/b&gt;&lt;/i&gt; 메서드를 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1738391060115&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let myCar = Car()
myCar.accelerate()
myCar.accelerate()
myCar.accelerate()
myCar.stop(vehicle: &quot;Car&quot;)

let myBicycle = Bicycle()
myBicycle.accelerate()
myBicycle.accelerate()
myBicycle.stop(vehicle: &quot;bicycle&quot;)

/* 출력
 Car is accelerating. Current speed: 10.0 km/h
 Car is accelerating. Current speed: 20.0 km/h
 Car is accelerating. Current speed: 30.0 km/h
 Car is stopped.
 Bicycle is accelerating. Current speed: 5.0 km/h
 Bicycle is accelerating. Current speed: 10.0 km/h
 bicycle is stopped.
 */&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;위와 같이 extension으로 프로토콜을 구현하는 방식을 &lt;b&gt;초기구현(Default Implementation)&lt;/b&gt;&amp;nbsp;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오케이, 근데 아직 '와.. 꼭 써야겠는데?' 하는 포인트가 없는것 같은데.&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;b&gt;프로토콜은 프로토콜끼리 상속도 가능하다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여기서 &lt;b&gt;전기차, 전기자전거가&lt;/b&gt; 추가 된다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탈것이긴한데, 배터리 관련해서 정보가 있어야할거고, 충전도 해야할겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 아래와 같이 &lt;b&gt;batteryLevel&lt;/b&gt;, &lt;i&gt;&lt;b&gt;charge()&lt;/b&gt;&lt;/i&gt; 메서드를 추가해보겠습니다.&lt;/p&gt;
&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;이전에 만들어뒀던 Vehicle을 상속시켜보면&lt;/p&gt;
&lt;pre id=&quot;code_1738392245813&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol ElectricVehicle: Vehicle {
    var batteryLevel: Double { get set }
    func charge()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 아래와 같은 코드겠죠.&lt;/p&gt;
&lt;pre id=&quot;code_1738398702150&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol ElectricVehicle: Vehicle {
//  Vehicle 
//  var speed: Double { get set }
//  func acclerate()

    var batteryLevel: Double { get set }
    func charge()
}&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;Tesla라는 Class를 만들어서 ElectricVehicle을 채택시켜보면 아래와 같은 에러가 납니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAa9T1/btsL3yjnbUD/VfnDU8j6U77K3kwTkhYvRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAa9T1/btsL3yjnbUD/VfnDU8j6U77K3kwTkhYvRk/img.png&quot; data-alt=&quot;ElectricVehicle, Vehicle을 준수해라&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAa9T1/btsL3yjnbUD/VfnDU8j6U77K3kwTkhYvRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAa9T1%2FbtsL3yjnbUD%2FVfnDU8j6U77K3kwTkhYvRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;190&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ElectricVehicle, Vehicle을 준수해라&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;그리고 ElectricVehicle을 채택했으니 준수(따라줘야)해줘야겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현해줍시다.&lt;/p&gt;
&lt;pre id=&quot;code_1738399187174&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol ElectricVehicle: Vehicle {
    var batteryLevel: Double { get set }
    func charge()
}

class Tesla: ElectricVehicle {
    var batteryLevel: Double = 60.0
    
    func charge() {
        self.batteryLevel += 20.0
        if self.batteryLevel &amp;gt;= 100 {
            print(&quot;Tesla is fully charged&quot;)
        }
        else {
            print(&quot;Tesla is charging.. batteryLevel: \(self.batteryLevel)&quot;)
        }
    }
    
    // Vehicle
    var speed: Double = 0.0
    
    func accelerate() {
        self.speed += 20
        print(&quot;Tesla is accelerating. Current speed: \(self.speed) km/h&quot;)
    }
}

let myTesla = Tesla()
myTesla.charge()
myTesla.charge()
myTesla.accelerate()
myTesla.stop(vehicle: &quot;Tesla&quot;)

/* 출력
 Tesla is charging.. batteryLevel: 80.0
 Tesla is fully charged
 Tesla is accelerating. Current speed: 20.0 km/h
 Tesla is stopped.
 */&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;b&gt;protocol의 역할을 세분화하게 되면서 필요한 기능들만 채택하게 할 수 있고 (여러 프로토콜 상속)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;extension&lt;/b&gt;을 통해 &lt;b&gt;기본 구현을 제공함&lt;/b&gt;으로써 &lt;b&gt;중복될 코드들을 줄일 수 있고&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift에서 구조체사용을 권장하듯, &lt;b&gt;구조체(값 타입)에도 protocol을 통해 상속이 가능하도록 할 수 있다는 장점&lt;/b&gt;이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 protocol 관련 세션을 들었을때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;protocol 네이밍을&lt;/b&gt; 대부분 &lt;b&gt;~able (~할 수 있는)&lt;/b&gt;로&amp;nbsp;지어주는게 좋다고 했던것도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'가능한 하나의 프로토콜이 하나의 기능을 담당하도록 세분화 하라'&lt;/b&gt;는 의미가 이번 글을 정리하면서 이해가 됐었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 들었을땐 메서드 하나당 프로토콜을 새로 하나 만드는 모습을 보고 '저렇게 까지 세분화 할 필요가 있나?' 라는 생각이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능별로 protocol을 만들어두고 필요한것만 쏙쏙 골라 채택해주고 준수해주기만하면 되니&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 확장성을 고려하면 '해야겠다'는 생각으로 고쳐지게된것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@haanwave/iOS-Swift-Protocol-Oriented-Programming-POP&quot;&gt;https://velog.io/@haanwave/iOS-Swift-Protocol-Oriented-Programming-POP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/nsistanbul/protocol-oriented-programming-in-swift-ad4a647daae2&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/nsistanbul/protocol-oriented-programming-in-swift-ad4a647daae2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.swiftanytime.com/blog/protocol-oriented-programming-in-swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.swiftanytime.com/blog/protocol-oriented-programming-in-swift&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>pop 예시</category>
      <category>pop란?</category>
      <category>protocol extension</category>
      <category>protocol oriented programming</category>
      <category>protocol 상속</category>
      <category>protocol 채택</category>
      <category>swift pop</category>
      <category>프로토콜 지향 프로그래밍</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/234</guid>
      <comments>https://peppo.tistory.com/234#entry234comment</comments>
      <pubDate>Sat, 1 Feb 2025 21:11:36 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Decode 한번 뜯어보자 (feat. container, nestedContainer, decode)</title>
      <link>https://peppo.tistory.com/233</link>
      <description>&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;아래와 같은 JSON data가 있다고 했을때 어떻게 decoding을 하나요?&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;id&quot;: 1,
&amp;nbsp;&amp;nbsp;&quot;name&quot;: &quot;John Doe&quot;,
&amp;nbsp;&amp;nbsp;&quot;details&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;address&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;city&quot;: &quot;Seoul&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;postalCode&quot;: &quot;12345&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;contacts&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;email&quot;: &quot;john.doe@example.com&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;phone&quot;: &quot;010-1234-5678&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;최상위로 id, name, details가 있고&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btMyfk/btsLRk6J543/WxDKJtp19v0V73YSZItws1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btMyfk/btsLRk6J543/WxDKJtp19v0V73YSZItws1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btMyfk/btsLRk6J543/WxDKJtp19v0V73YSZItws1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtMyfk%2FbtsLRk6J543%2FWxDKJtp19v0V73YSZItws1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;430&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;details 내부에 address, contacts 가 있고,&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dd5Hzo/btsLRSIKRh8/ak0kJoDs3OHJ180BdubKm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dd5Hzo/btsLRSIKRh8/ak0kJoDs3OHJ180BdubKm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dd5Hzo/btsLRSIKRh8/ak0kJoDs3OHJ180BdubKm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdd5Hzo%2FbtsLRSIKRh8%2Fak0kJoDs3OHJ180BdubKm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;606&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;address 내부에 city, postalCode,&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH6jt4/btsLQ6HJbpW/zti9KiP88DFTlEUCzfYQGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH6jt4/btsLQ6HJbpW/zti9KiP88DFTlEUCzfYQGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH6jt4/btsLQ6HJbpW/zti9KiP88DFTlEUCzfYQGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH6jt4%2FbtsLQ6HJbpW%2Fzti9KiP88DFTlEUCzfYQGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;411&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;contacts 내부에 email, phone이 있는 구조입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIQoZR/btsLRkTicAF/KTd5Yxk0FKdrj28dmov5Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIQoZR/btsLRkTicAF/KTd5Yxk0FKdrj28dmov5Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIQoZR/btsLRkTicAF/KTd5Yxk0FKdrj28dmov5Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIQoZR%2FbtsLRkTicAF%2FKTd5Yxk0FKdrj28dmov5Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;460&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;보통 저는 decoding을 하게되면 아래와 같이 JSO 중괄호 ( { } )를 기점으로 struct를 하나씩 만들어서 관리를 했었습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let id: Int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let name: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let details: DetailsInfo
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;struct DetailsInfo: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let address: AddressInfo
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let contacts: ContactsInfo
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;struct AddressInfo: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let city: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let postalCode: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;struct ContactsInfo: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let email: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let phone: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;오늘은 이 방법 이외에 nestedContainer라는 인스턴스 메서드를 사용한 decode 방법을 알아보려고 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;예를들어 아래와 같은 UserDTO(Data Transfer Object)가 있다고 가정해봅시다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let id: Int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let name: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum UserKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init(from decoder: Decoder) throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let container = try decoder.container(keyedBy: UserKeys.self)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.id = try container.decode(Int.self, forKey: .id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.name = try container.decode(String.self, forKey: .name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;User struct에 Decodable을 채택해준 후 init()을 해보면 아래와 같이 자동완성이 뜹니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE3ldC/btsLRHALjHm/C33CKCu1qVkNhd6QhNPeN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE3ldC/btsLRHALjHm/C33CKCu1qVkNhd6QhNPeN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE3ldC/btsLRHALjHm/C33CKCu1qVkNhd6QhNPeN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE3ldC%2FbtsLRHALjHm%2FC33CKCu1qVkNhd6QhNPeN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1062&quot; height=&quot;120&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;&lt;br&gt;init에 파라미터인 decoder는 Decoder 프로토콜을 채택하고 있으며,&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 프로퍼티 및 메서드를 정의하고 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQRzJP/btsLSOllzkn/SQqg4nbg9nrA0SG1Xu6bo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQRzJP/btsLSOllzkn/SQqg4nbg9nrA0SG1Xu6bo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQRzJP/btsLSOllzkn/SQqg4nbg9nrA0SG1Xu6bo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQRzJP%2FbtsLSOllzkn%2FSQqg4nbg9nrA0SG1Xu6bo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;260&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;이중 &lt;i&gt;&lt;b&gt;container(keyedBy:)&amp;nbsp; &lt;/b&gt;&lt;/i&gt;를 아래 로직로 예를들어 해석을 해보면&amp;nbsp;&lt;br&gt;&lt;i&gt;&lt;b&gt;container(keyedBy: CodingKeys.self)&lt;/b&gt;&lt;/i&gt;를 호출하면 *&lt;b&gt;CodingKeys&lt;/b&gt;를 채택하는 UserKeys가 갖고 있는 id, name의 key값을 반환합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;*CodingKey 란?&lt;br&gt;encoding과 decoding을 위해 'key'로 사용할 수 있게한 타입&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let container = try decoder.container(keyedBy: UserKeys.self)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이후 container의 key들을 디버깅해보면 name과 id가 반환된걸 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4gBcz/btsLSzhBNfh/Ydmh63kj99tPTgxZ53WGaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4gBcz/btsLSzhBNfh/Ydmh63kj99tPTgxZ53WGaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4gBcz/btsLSzhBNfh/Ydmh63kj99tPTgxZ53WGaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4gBcz%2FbtsLSzhBNfh%2FYdmh63kj99tPTgxZ53WGaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;968&quot; height=&quot;172&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;그 다음줄을 봅시다 (decode)&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;name과, id의 key를 가지고 있는 container (UserKeys container)에 decode(type:, forKey)를 호출하는데,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;decode 메서드 선언부는 아래와 같습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Be3Ew/btsLQ68TdXK/E4rToqa5ydP4TKDSa9vmK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Be3Ew/btsLQ68TdXK/E4rToqa5ydP4TKDSa9vmK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Be3Ew/btsLQ68TdXK/E4rToqa5ydP4TKDSa9vmK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBe3Ew%2FbtsLQ68TdXK%2FE4rToqa5ydP4TKDSa9vmK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1684&quot; height=&quot;516&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;type:&lt;/b&gt;&lt;br&gt;파라미터의 type에는 다양한 데이터 타입이 (String, Bool, Int, Double 등) 들어가고,&amp;nbsp;&lt;br&gt;여기엔 변환하고자 하는 데이터 타입을 넣어줍니다.&lt;br&gt;&lt;b&gt;key:&lt;/b&gt;&lt;br&gt;container에 저장되어있는 key에 해당하는 값을 decoding해 return합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;에러처리는 아래와 같이 되어있습니다.&lt;br&gt;1. &lt;b&gt;typeMismatch&lt;/b&gt;: 타입이 맞지 않는경우&amp;nbsp;&lt;br&gt;2. &lt;b&gt;keyNotFound&lt;/b&gt;: key를 찾을 수 없는 경우&lt;br&gt;3. &lt;b&gt;valueNotFound&lt;/b&gt;: 값을 찾을 수 없는 경우&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 예제 코드를 분석해보자면&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;self.id = try container.decode(Int.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;id라는 key의 데이터를 Int 타입으로 decoding후 self.id에 할당&lt;br&gt;name key의 데이터를 String 타입으로 decoding 후 self.name에 할당 하는게 되겠네요.&lt;br&gt;&amp;nbsp;&lt;br&gt;이렇게 계층이 하나만있는 경우에는 decode하는 흐름을 파악하기 쉽죠&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;container에서 decode까지의 흐름은 알았으니,&lt;br&gt;그럼 좀더 심화해서 아래의 경우처럼 계층이 여러개 있다면?&lt;br&gt;그리고 아래 JSON에서 city라는 데이터를 가져오고 싶다면?&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;id&quot;: 1,
&amp;nbsp;&amp;nbsp;&quot;name&quot;: &quot;John Doe&quot;,
&amp;nbsp;&amp;nbsp;&quot;details&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;address&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;city&quot;: &quot;Seoul&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;postalCode&quot;: &quot;12345&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;contacts&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;email&quot;: &quot;john.doe@example.com&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;phone&quot;: &quot;010-1234-5678&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&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; style=&quot;text-align: left;&quot;&gt;이런 경우에 사용하는게 &lt;b&gt;nestedContainer&lt;/b&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;메서드 입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;nestedContainer 란?&lt;/b&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qdaxR/btsLQQLTC1F/SQyAxi8mbqAD8tOky4iK4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qdaxR/btsLQQLTC1F/SQyAxi8mbqAD8tOky4iK4k/img.png&quot; data-alt=&quot;https://developer.apple.com/documentation/swift/keyedencodingcontainerprotocol/nestedcontainer(keyedby:forkey:)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qdaxR/btsLQQLTC1F/SQyAxi8mbqAD8tOky4iK4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqdaxR%2FbtsLQQLTC1F%2FSQyAxi8mbqAD8tOky4iK4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;742&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developer.apple.com/documentation/swift/keyedencodingcontainerprotocol/nestedcontainer(keyedby:forkey:)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;주어진 key에 대한 key encoding container를 저장하고 반환합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;예시를 들면서 하나씩 뜯어봅시다.&lt;br&gt;&amp;nbsp;&lt;br&gt;CodingKey를 채택하는 Key(enum)가 갖고 있는 case들의 key에 접근합니다&lt;br&gt;만약 city 값을 decoding해서 가져오고 싶다고 가정하면&lt;br&gt;&amp;nbsp;&lt;br&gt;city는 아래와 같이 접근해서 가져와야 합니다.&lt;br&gt;details.address.city&lt;br&gt;&amp;nbsp;&lt;br&gt;위 예제에서 UserKeys를 통해 container를 만들어 id, name key에 접근할 수 있었듯이&lt;br&gt;이번엔 details에 접근하기 위해 CodingKey를 채택하고 있는 DetailsKeys 열거형을 하나 만들어 줍시다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let id: Int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let name: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let city: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let postalCode: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum UserKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case details
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum DetailsKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case address
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case contacts
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init(from decoder: Decoder) throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let container = try decoder.container(keyedBy: UserKeys.self)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.id = try container.decode(Int.self, forKey: .id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.name = try container.decode(String.self, forKey: .name)
&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;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;UserKeys의 key 중 details에 대한 container를 만들어주기 위해 사용하는게 nestedContainer 입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let detailsContainer = try container.nestedContainer(keyedBy: DetailsKeys.self, forKey: .details)&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;코드를 해석하자면 아래와 같겠네요.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UserKeys로된 container내부에 container를 만들건데, DetailsKeys의 key 중 details에 해당하는 container를 만들거야&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;detailsContainer의 key들을 디버깅해보면 아래와 같이 생성된걸 볼수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmtZFu/btsLRj07s40/UHSh64UmZMoEslcRs6a9Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmtZFu/btsLRj07s40/UHSh64UmZMoEslcRs6a9Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmtZFu/btsLRj07s40/UHSh64UmZMoEslcRs6a9Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmtZFu%2FbtsLRj07s40%2FUHSh64UmZMoEslcRs6a9Lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2056&quot; height=&quot;548&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;아직 city에 접근하려면 한단계 더 가야합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;city는 address 내부에 있으므로 위에서 진행한 방법과 같이 nestedContainer를 하나 더 만들어줍니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let id: Int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let name: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let city: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let postalCode: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum UserKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case details
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum DetailsKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case address
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case contacts
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// AddressKeys 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum AddressKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case city
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case postalCode
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init(from decoder: Decoder) throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let container = try decoder.container(keyedBy: UserKeys.self)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.id = try container.decode(Int.self, forKey: .id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.name = try container.decode(String.self, forKey: .name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let detailsContainer = try container.nestedContainer(keyedBy: DetailsKeys.self, forKey: .details)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// addressContainer 추가
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let addressContainer = try detailsContainer.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// decode한 값 할당 (self.city, self.postalCode)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.city = try addressContainer.decode(String.self, forKey: .city)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.postalCode = try addressContainer.decode(String.self, forKey: .postalCode)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;위와 같이 생성 후 디버깅을 해보면 addressContainer가 잘 생성되었고,&lt;br&gt;decoding 된 값이 self.city에 할당된걸 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qRniw/btsLQ7GD6TK/n92kGn4P6cn7oLjYkL1kCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qRniw/btsLQ7GD6TK/n92kGn4P6cn7oLjYkL1kCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qRniw/btsLQ7GD6TK/n92kGn4P6cn7oLjYkL1kCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqRniw%2FbtsLQ7GD6TK%2Fn92kGn4P6cn7oLjYkL1kCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2118&quot; height=&quot;896&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;최종 코드는 아래와 같습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;struct User: Decodable {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let id: Int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let name: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let city: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let postalCode: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let email: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let phone: String
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum UserKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case details
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum DetailsKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case address
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case contacts
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum AddressKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case city
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case postalCode
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enum ContactsKeys: String, CodingKey {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case email
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case phone
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init(from decoder: Decoder) throws {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let container = try decoder.container(keyedBy: UserKeys.self)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.id = try container.decode(Int.self, forKey: .id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.name = try container.decode(String.self, forKey: .name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let detailsContainer = try container.nestedContainer(keyedBy: DetailsKeys.self, forKey: .details)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let addressContainer = try detailsContainer.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.city = try addressContainer.decode(String.self, forKey: .city)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.postalCode = try addressContainer.decode(String.self, forKey: .postalCode)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;let contactsContainer = try detailsContainer.nestedContainer(keyedBy: ContactsKeys.self, forKey: .contacts)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.email = try contactsContainer.decode(String.self, forKey: .email)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.phone = try contactsContainer.decode(String.self, forKey: .phone)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;느낀점&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 nestedContainer를 써야겠다고 생각이 들었던 점은&lt;br&gt;&lt;b&gt;key에 해당하는 value값을 flat하게 접근하기 위한 용도로 사용하면 좋겠다&lt;/b&gt;는 생각이 들었습니다.&lt;br&gt;struct를 사용할경우 User.Details.Address.city 이렇게 타고타고 들어가야 되는점과 비교가 됐던것 같네요.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;오늘은 decoder부터 container, nestedContainer까지 decode하는 또 다른 방법과 흐름을 훑어봤습니다.&lt;br&gt;처음엔 이 코드를 보고 코드양이 많아 '이게 뭐지..' 하면서 한참동안 들여다 봤던 기억이 있는데&amp;nbsp;&lt;br&gt;막상 하나씩 분석해보니 가독성도 늘고 어떤 흐름으로 decode를 할 수 있는지 배울 수 있었던것 같습니다.&lt;br&gt;아는만큼 보이는것 같네요.&lt;br&gt;그동안 사용했던 메서드등도 한번씩 다시봐야겠다는 생각이 들었습니다 :)&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;1. container&lt;br&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/decoder/container(keyedby:)&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/swift/decoder/container(keyedby:)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;container(keyedBy:) | Apple Developer Documentation&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Returns the data stored in this decoder as represented in a container keyed by the given key type.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/decoder/container(keyedby:)&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g6f7w/hyX4tvkDAo/Xgyu3IgfxRm7hultrWhPe0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cSfDJp/hyX4oOjI1d/tGPUj5JGCh1GqauwY6vjEK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/decoder/container(keyedby:)&quot;&gt;&lt;a href=&quot;https://docs.developer.apple.com/documentation/swift/decoder/container(keyedby:)&quot; target=&quot;_blank&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/decoder/container(keyedby:)&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g6f7w/hyX4tvkDAo/Xgyu3IgfxRm7hultrWhPe0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cSfDJp/hyX4oOjI1d/tGPUj5JGCh1GqauwY6vjEK/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;container(keyedBy:) | Apple Developer Documentation&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Returns the data stored in this decoder as represented in a container keyed by the given key type.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;developer.apple.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;2. decode&lt;br&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;decode(_:forKey:) | Apple Developer Documentation&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Decodes a value of the given type for the given key.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/doBPV0/hyX0tqfumO/T25M1suk2qGofslv4jnPUk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/0ZxnI/hyX0wN3vO6/ausEeIBM9F0GR1eqCqylD0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512&quot; data-og-url=&quot;https://docs.developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&quot;&gt;&lt;a href=&quot;https://docs.developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&quot; target=&quot;_blank&quot; data-source-url=&quot;https://developer.apple.com/documentation/swift/keyeddecodingcontainer/decode(_:forkey:)-7vj8e&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/doBPV0/hyX0tqfumO/T25M1suk2qGofslv4jnPUk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/0ZxnI/hyX0wN3vO6/ausEeIBM9F0GR1eqCqylD0/img.jpg?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;decode(_:forKey:) | Apple Developer Documentation&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Decodes a value of the given type for the given key.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;developer.apple.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;3. &lt;a href=&quot;https://gist.github.com/calda/6a83e09ae8a4ee1c04557cc7dbdb25f6&quot; target=&quot;_blank&quot; title=&quot;Swift decoding example&quot;&gt;&lt;span&gt;swift decode example&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Swift Nested JSON Decoding example&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Swift Nested JSON Decoding example. GitHub Gist: instantly share code, notes, and snippets.&quot; data-og-host=&quot;gist.github.com&quot; data-og-source-url=&quot;https://gist.github.com/calda/6a83e09ae8a4ee1c04557cc7dbdb25f6&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bS0hR4/hyX0zbHkYs/6ghVxPwLZ8RyYV8G5kOlk1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/gBOwP/hyX0m4wAq7/euUTscYrI21ny1ZUuaZIT0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot; data-og-url=&quot;https://gist.github.com/calda/6a83e09ae8a4ee1c04557cc7dbdb25f6&quot;&gt;&lt;a href=&quot;https://gist.github.com/calda/6a83e09ae8a4ee1c04557cc7dbdb25f6&quot; target=&quot;_blank&quot; data-source-url=&quot;https://gist.github.com/calda/6a83e09ae8a4ee1c04557cc7dbdb25f6&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bS0hR4/hyX0zbHkYs/6ghVxPwLZ8RyYV8G5kOlk1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/gBOwP/hyX0m4wAq7/euUTscYrI21ny1ZUuaZIT0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Swift Nested JSON Decoding example&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Swift Nested JSON Decoding example. GitHub Gist: instantly share code, notes, and snippets.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;gist.github.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>codingkeys</category>
      <category>decoder</category>
      <category>nestedcontainer</category>
      <category>swift container decode</category>
      <category>swift decode</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/233</guid>
      <comments>https://peppo.tistory.com/233#entry233comment</comments>
      <pubDate>Sat, 18 Jan 2025 15:42:21 +0900</pubDate>
    </item>
    <item>
      <title>2024 회고</title>
      <link>https://peppo.tistory.com/232</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: center;&quot;&gt;개발을 시작하고 벌써 세번째 회고글을 써본다.&lt;br&gt;지난 1년은 정말 빠르게 지나갔던것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;결혼 이후 생활부터 재취업까지 어떤일이 있었고, 어떤걸 배웠는지 적어보려한다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2024의 키워드 '선택과 집중'&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;과연 잘 했을까?&lt;br&gt;상반기때는 '아니다' 인것 같다.&lt;br&gt;그치만 하반기는 집중했던 한 해였다.&lt;br&gt;집중할 수 밖에 없었지만, 덕분에 앱 전체를 리뉴얼 할 수 있는 계기가 되었다.&lt;br&gt;상반기, 하반기 둘을 나눠서 작성해본다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;회사&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;6월 권고사직을 끝으로 많은 일들도 있었고, 많이 배웠던것 같다.&amp;nbsp;&lt;br&gt;마지막은 회사와 나의 가치관 차이로 돌아서게 됐지만, 그래도 불경기속에 iOS 커리어를 시작하게 해준 회사에 감사를 표합니다.&amp;nbsp;&lt;br&gt;개인적으로는 나를 돌아보는시간, 기술적으로는 한 분야를 깊게 파볼 수 있었던 뜻 깊은 시간이었다.&amp;nbsp;&lt;br&gt;WebRTC, AVFoundation, RIBs 등 공부할것도 많았고 흔치 않은 기술이다보니 참고할 수 있는부분도 많지 않아 힘들었지만,&lt;br&gt;그럼에도 해결해야하기 때문에 집요함과 다른 분야의 개발자분들에게 문제를 공유하고 해결해나가는 방법을 배울 수 있었던 회사였던것 같다.&amp;nbsp;&lt;br&gt;100% 개선하지 못한 아쉬움도 있지만 여러모로 좋은 경험이라 생각한다.&lt;br&gt;감사하게도 팀원들이 다들 열정적이어서 자체적으로 스터디하는 문화가 있고, 서로 모르는 부분에 대해 공유하고 문제를 해결해나가는 문화가 재밌었고 배울점도 있었던 회사였다고 생각한다.&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; 다시시작&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;요즘 취업시장이 혹한기다 라는 말을 정말 많이 들었던터라 어느정도 긴장은하고 있었다.&lt;br&gt;생각만큼 공고도 많이 없었고, 연차도 기본이 5년차 이상인곳들이 많이 늘었다.&lt;br&gt;내 iOS 경력은 1~2년 정도였고, 많은 회사들의 자격요건에 부합하지 못했다.&amp;nbsp;&lt;br&gt;지난 회사에서 썼던 기술들과는 조금 거리가 있었고 맞춰나갈 방법을 생각했다.&lt;br&gt;취업 플랫폼의 모든 구인공고를 훑어보고 한번 추려냈던 회사들, 그 다음순위의 회사들, 여긴 피해야겠다 생각했던 회사들을 정리한 뒤&lt;br&gt;다음에 들어가고 싶은 회사의 환경을 아래와 같이 추려봤었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;1. 같은 분야(iOS)에 대해 조언을 구할 동료들이 있는지&lt;br&gt;2. 개발 문화가 존재하는지&lt;br&gt;3. 다양한 경험을 해볼 수 있는지&lt;br&gt;4. 출, 퇴근 왕복 3시간 이내&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 공통적인 기술스택 등을 정리하면서 앞으로 취업준비를 어떻게 해나가야할지 리스트업을 했었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;UI&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;대다수의 기업은 &lt;b&gt;UIKit&lt;/b&gt;이 많았고&lt;br&gt;UIKit → SwiftUI로 넘어가는 회사&lt;br&gt;SwiftUI로 프로젝트를 진행하는 회사&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;비동기처리&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;async-await&lt;br&gt;RxSwift&lt;br&gt;Combine&lt;br&gt;GCD&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;아키텍처&lt;/b&gt;&lt;br&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;br&gt;MVVM이 압도적으로많았고&amp;nbsp;&lt;br&gt;MVVM + 클린아키텍처 등 또 여러가지가 있었던걸로 기억한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그 밖에,&amp;nbsp;&lt;br&gt;CI/CD, 모듈화(tuist), TDD 등등 할게 너무 많았고 이걸 다 하기엔 시간이 너무 부족하다는걸 알았다.&lt;br&gt;그냥 지금 환경에서 할 수 있는것들만 먼저 추려내 사이드프로젝트로 진행해봤다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;A 사이드프로젝트&amp;nbsp;&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;총 3명의 스터디원들과 시작했던 프로젝트는 각자 회사에서 배울만한 코드들을 해당 프로젝트에 녹여내, 성장하자는 취지로 시작했다.&lt;br&gt;&lt;b&gt;UIKit&lt;/b&gt;, &lt;b&gt;RxSwift&lt;/b&gt;, &lt;b&gt;MVC&lt;/b&gt;로 작업을 시작했고,&lt;br&gt;추후 MVVM으로 리팩토링하며 두개의 차이를 비교해보고 회고를 하는 방식으로 마무리를 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;B 사이드프로젝트&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;개인 앱으로 만들었던 BestEats라는 앱을 리뉴얼하자는 취지였다.&amp;nbsp;&lt;br&gt;iOS 개발을 아무것도 모르고 시작했던 프로젝트라&lt;br&gt;코드를 보는 내내 '와.. 내가 ..? 내가 이렇게 썼다고..?' 라는 말을 되뇌었던게 기억난다 .. ㅋㅋㅋㅋ&lt;br&gt;1년 넘게 방치해뒀던 앱을 다시 분석하고 기능을 붙이기보단&amp;nbsp;&lt;br&gt;싹 뜯어고치는게 더 빠르겠다는 생각이 들어 아래와 같이 바꿨다.&lt;br&gt;&amp;nbsp;&lt;br&gt;UIKit → &lt;b&gt;SwiftUI&lt;/b&gt;&lt;br&gt;GCD → &lt;b&gt;async-await&lt;/b&gt;&lt;br&gt;UserDefaults → &lt;b&gt;CoreData&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇게 시작했던 A 사이드프로젝트는 &lt;a href=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot; target=&quot;_blank&quot; title=&quot;이쯤인가&quot;&gt;&lt;span&gt;&lt;b&gt;이쯤인가&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot; target=&quot;_blank&quot; title=&quot;이쯤인가&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;라는 앱으로 출시되었고,&amp;nbsp;&lt;br&gt;서울시 공공API를 활용해 버스 현재위치를 추적하고 목적지 도착전에 알림을 받는 시나리오로,&lt;br&gt;3명의 iOS 개발 스터디원들과 기획, 디자인, 배포까지 경험할 수 있었던 뜻깊은 프로젝트가 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이때 가장 좋았던 경험은 Dynamic Island 기능 구현을 하기위해 SwiftUI를 다룰줄 알았어야 했는데,&lt;br&gt;B 사이드프로젝트를 동시에 진행하면서 공부했던 SwiftUI를 여기서도 써볼 수 있었던게 좋았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;‎이쯤인가&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;‎서울시 공공API를 활용하여 실시간으로 탑승 중인 버스의 현재 위치를 추적하고, 목적지 도착 전에 미리 알림을 받아보세요. 이제는 도착지 걱정 없이 버스에서 편안하게 시간을 보내세요! 주&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EbRTH/hyXWABSBHg/EP0NV2EXXu2ADKNb0OKonK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/lS8yO/hyXWnWP5gE/FntXerbQaMkKOJgv3WdqKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot; target=&quot;_blank&quot; data-source-url=&quot;https://apps.apple.com/kr/app/%EC%9D%B4%EC%AF%A4%EC%9D%B8%EA%B0%80/id6736941116&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EbRTH/hyXWABSBHg/EP0NV2EXXu2ADKNb0OKonK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/lS8yO/hyXWnWP5gE/FntXerbQaMkKOJgv3WdqKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;‎이쯤인가&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;‎서울시 공공API를 활용하여 실시간으로 탑승 중인 버스의 현재 위치를 추적하고, 목적지 도착 전에 미리 알림을 받아보세요. 이제는 도착지 걱정 없이 버스에서 편안하게 시간을 보내세요! 주&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;apps.apple.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;B 사이드프로젝트 &lt;a href=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot; target=&quot;_blank&quot; title=&quot;BestEats&quot;&gt;&lt;span&gt;&lt;b&gt;BestEats&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;는 취준 기간동안 주력으로 시간을 썼던 프로젝트였다.&lt;br&gt;그만큼 여기에 공부시간도 투자를 많이 했고, 이것저것 적용해보면서 동시에 이쯤인가 프로젝트에도 녹여내볼 수 있는 기회가 생겨&lt;br&gt;iOS 공부를 시작할때 부터 지금까지 개발에 재미를 느끼게 해줬던 프로젝트가 아니었나 싶다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://apps.apple.com/kr/app/besteats/id1618130833&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;‎BestEats&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;‎배만 채우면 되던 시대는 이제 옛날 얘기!! 한끼를 먹어도 맛있게 먹기를 고민하는 시대 맛있게 먹었던 방법들을 기록하고 나만의 맛집 기록으로 쉽게 찾고 맛있게 먹자! 이런 분들에게 추천&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c6SlXS/hyXWnWQh62/NqvWk0gYDiIOqASCfXTPLk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bXn1CZ/hyXWsRowGj/il42DvFH4HqcvBKGvrMXKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot; target=&quot;_blank&quot; data-source-url=&quot;https://apps.apple.com/kr/app/besteats/id1618130833&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c6SlXS/hyXWnWQh62/NqvWk0gYDiIOqASCfXTPLk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bXn1CZ/hyXWsRowGj/il42DvFH4HqcvBKGvrMXKK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;‎BestEats&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;‎배만 채우면 되던 시대는 이제 옛날 얘기!! 한끼를 먹어도 맛있게 먹기를 고민하는 시대 맛있게 먹었던 방법들을 기록하고 나만의 맛집 기록으로 쉽게 찾고 맛있게 먹자! 이런 분들에게 추천&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;apps.apple.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;취업시장으로&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;7월부터 10월까지 진행했던 두 가지 프로젝트와 회사의 경험들을 토대로 이력서와 포트폴리오를 작성했고,&lt;br&gt;하루 루틴은 아래와 같았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;1. 2~3군데씩 회사 지원&amp;nbsp;&lt;br&gt;2. 코딩테스트 2문제씩 풀이&lt;br&gt;3. 사이드프로젝트 진행 및 리팩토링&lt;br&gt;4. 무조건 카페가기&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;가급적 회사 지원을 할때 채용관련내용은 채용플랫폼을 통해서 보되, &lt;b&gt;지원은 회사 공식홈페이지에서 지원&lt;/b&gt;했었다.&amp;nbsp;&lt;br&gt;주 6일동안 3개월 가량 이 생활을 유지했었고, 여름철이라 폭우가 와도 카페는 무조건 갔다.&lt;br&gt;불편한 환경이 나를 일하게 만들었던것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;첫 한달은 서류광탈 소식만 와다다 들려왔고 불안하기 시작했었다.&amp;nbsp;&lt;br&gt;(기간이 길어지면 알바라도 하면서 취준을 해야되나? 개발 말고 다른길로 가야하나? 등등)&lt;br&gt;잡생각이 들때마다 기업 하나 더 지원하고, 리팩토링을 하나 더 했다. (그럼에도 불안함은 사라지지 않았지만..)&lt;br&gt;한달정도 지났을까 서류광탈하던 소식들은 조금씩 서류합격 소식으로 전해지기 시작했고,&lt;br&gt;과제도 받아보고, 면접도 볼 수 있는 기회가 생기기 시작했다.&amp;nbsp;&lt;br&gt;9월 말 부터 정말 바빴던것 같은데 과제 기한이 겹쳐 일주일안에 해야하는 촉박함도 있었고&lt;br&gt;여러 회사를 지원하다보니 서류합격 연락이 전화로 왔을때 '어떤회사요..?' 라는 말을 했던 기억도 있었다. (지원 리스트들을 보고나서 생각이 났던.. ㅠ)&lt;br&gt;&amp;nbsp;&lt;br&gt;이후 운이좋게 A 회사의 과제에 합격 후 면접을 보러갔고, 면접관은 총 네 분. 1:4로 진행이 됐었다.&amp;nbsp;&lt;br&gt;그동안의 프로젝트들, 회사 프로젝트들의 경험들에 관해 얘기했고, 기술면접에 이어 인성면접을 진행하며 그동안 봤던 면접중 가장 분위기를 편하게 만들어주셨던게 인상 깊었던것 같다.&amp;nbsp;&lt;br&gt;프로젝트 설명을 할 때는 아래와 같은 방식으로 설명했을 때 긍정적인 반응을 보였던것 같다.&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;'어떤 문제가 있었고 어떻게 해결하려고 했으며 A 방법으로 해봤지만 다시 문제가 생겨 B 방법으로 빠르게 다시 해결했던 경험이 있었다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;&lt;br&gt;&lt;/span&gt;그리고 B 방법을 하다가 이런것도 배웠다.'&amp;nbsp;&lt;br&gt;&lt;br&gt;&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;한시간 좀 넘게 봤던것 같은데 혼은 나가있었지만 시간이 금방 갔다는 생각이 들 정도로 면접경험이 좋았었던것 같다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그 외에도 다른 과제들을 진행하면서 배워가는점이 꽤 많았던것 같다.&lt;br&gt;과제 불합격한곳에 피드백 요청을 보내는것도 잊지 않았지만, 지원자가 많은지 일일히 혹은 공정성을 위해 피드백은 줄 수 없다는 답변이 대다수여서 아쉬웠다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;현재&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;A 회사의 면접을 끝마치고 숨돌릴 틈도 없이 다른 회사의 과제, 다른회사의 면접준비에 정신없었다.&lt;br&gt;그렇게 일주일 정도 지난 후 A 회사의 합격 및 처우협의 소식들 듣게 되었고 처음으로 '오퍼레터' 라는걸 정식으로 받아봤다.&lt;br&gt;&amp;nbsp;&lt;br&gt;현재는 오퍼 받은 회사에서 입사해 수습기간중에 있고, 이제 두 달째 근무해보면서 느끼는 점은 기술적 소통이 자유롭다는 점에 가장 만족하고 있는것 같다.&lt;br&gt;여러 팀이 존재하고 그중 우리 팀원은 20명 정도 되는데 iOS 개발자만 4~5 분이 계셔서 기술적으로 궁금한점이 있으면 왠만하면 친절하게 다들 알려주시려고 했던게 너무 좋았던것 같다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;입사초에 온보딩을 끝마치고 입사지원시 진행했던 과제를 SwiftUI → UIKit으로 컨버팅하는 작업을하면서 사수분의 코드리뷰를 받으며,&lt;br&gt;몰랐던 노하우들과 여러 피드백 받으면서 성장해나가고 있는것 같다.&lt;br&gt;아직도 모르는게 많고 페어프로그래밍을 하고있으면 사수분 앞에서 버벅거리기도 하지만 많이 물어보고 남은 한달의 수습기간 동안 잘 해나가보고 싶다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;고로 올해 2025 목표는 아래와 같다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2025 목표&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;아래는 2024년 목표였다.&lt;/b&gt;&lt;/p&gt;&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;&lt;div class=&quot;moreless-content&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;1486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSmVFx/btsLEGgNHBc/3PkGKK6sjjlkyzbFKsngKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSmVFx/btsLEGgNHBc/3PkGKK6sjjlkyzbFKsngKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSmVFx/btsLEGgNHBc/3PkGKK6sjjlkyzbFKsngKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSmVFx%2FbtsLEGgNHBc%2F3PkGKK6sjjlkyzbFKsngKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1296&quot; height=&quot;1486&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;1486&quot;/&gt;&lt;/span&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;일단 상반기 동안 2024년의 키워드, &lt;b&gt;선택과 집중&lt;/b&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;그래도 이후에 취준을하게 되면서 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;&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;/div&gt;&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;올해 내 목표 키워드는 &lt;b&gt;적극성과 적응&lt;/b&gt; 이다.&lt;br&gt;새로운 회사 환경에 적응과 커뮤니티 등에 적극적으로 참여하는걸 목표로 하고싶다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 스터디 그룹 만들기 or 커뮤니티 적극 활동하기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다른 iOS 개발자 코드들을 보면 정말 아직도 배워야할게 많겠다는 생각이 든다.&lt;br&gt;iOS 공부를 처음시작했을때 들었던 Swift 공식문서 스터디 모임이 있었는데 그 때 봤던 공식문서와&lt;br&gt;지금 다시보는 공식문서의 느낌은 어떨지 궁금하다.&amp;nbsp;&lt;br&gt;또 스터디때 좋았던건 현업 개발자분들이 현업에서 사용하는 팁들을 알아갈 수 있었던게 좋았고,&amp;nbsp;&lt;br&gt;이번에 기회를 만들어서 스터디 그룹을 만들어 진행을 해볼까 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 시스템 만들기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 다시본 영화 '포레스트 검프'를 보고 문득 이런 생각이 들었다.&amp;nbsp;&lt;br&gt;주인공은 지적 장애가 있지만 목표를 향해 한 가지의 일만 꾸준하게 했던부분이 단순하지만 최고의 방법이라 생각한다.&lt;br&gt;(ex. 달리기, 탁구, 군인)&lt;br&gt;또 작년에 읽었던 One thing 이라는 책에서도 이런말이 있다.&lt;br&gt;'단 하나의 일을 정하고 행했던 날을 달력에 표시해라, 표시했던 &lt;b&gt;그 사슬이 끊기지 않게 해라&lt;/b&gt;'&lt;br&gt;운동을하려면 헬스장가기가 아닌 운동화 신기, 개발 공부를하려면 카페가기 등 사소한것부터 시스템을 만들어보자.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 운동 배우기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 취준기간동안 그동안 배우고 싶었던 수영도 배웠고 (자유형, 배영, 접영까지는 잘 배운것 같다.)&lt;br&gt;기회가 된다면 다른 운동들도 더 배워보고 싶다.&lt;br&gt;수영을 더 배우던지 복싱도 배워보고 싶다. 건강에 투자하는거라면 돈 아끼지 말기!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 책 4권 이상 읽기&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2024년에는 아래의 책들을 읽었다.&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 style=&quot;width: 25%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CpBuO/btsLE2qpiFM/z0jseUiMPprPfoZm0WTmlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CpBuO/btsLE2qpiFM/z0jseUiMPprPfoZm0WTmlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CpBuO/btsLE2qpiFM/z0jseUiMPprPfoZm0WTmlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCpBuO%2FbtsLE2qpiFM%2Fz0jseUiMPprPfoZm0WTmlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;209&quot; height=&quot;251&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;1170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tNdV5/btsLDlYX8ZF/avkQRrUSzqrNjw67ko4vi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tNdV5/btsLDlYX8ZF/avkQRrUSzqrNjw67ko4vi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tNdV5/btsLDlYX8ZF/avkQRrUSzqrNjw67ko4vi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtNdV5%2FbtsLDlYX8ZF%2FavkQRrUSzqrNjw67ko4vi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;830&quot; height=&quot;1170&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;1170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/srZhD/btsLFgWf78k/Hru2IRg0RTIq8SrxJemTBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/srZhD/btsLFgWf78k/Hru2IRg0RTIq8SrxJemTBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/srZhD/btsLFgWf78k/Hru2IRg0RTIq8SrxJemTBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsrZhD%2FbtsLFgWf78k%2FHru2IRg0RTIq8SrxJemTBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1114&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br&gt;&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WWYNr/btsLC5oAK5H/yaobVtL2x5kPZMndH8E600/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WWYNr/btsLC5oAK5H/yaobVtL2x5kPZMndH8E600/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WWYNr/btsLC5oAK5H/yaobVtL2x5kPZMndH8E600/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWWYNr%2FbtsLC5oAK5H%2FyaobVtL2x5kPZMndH8E600%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;792&quot; height=&quot;1058&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 25%;&quot;&gt;부자아빠 가난한 아빠&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;사는 동안 한 번은 팔아봐라&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;아주 작은 습관의 힘&lt;/td&gt;&lt;td style=&quot;width: 25%;&quot;&gt;클린코드 (읽는중)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;개발 관련 책 외에 다른 분야의 책을 읽는것도 꽤 중요하다고 생각이 들게 해줬던것 같다.&lt;br&gt;특히 부자 아빠 가난한 아빠는 '근로소득'에 대한 내 생각을 완전 바꿔줬던 책이었다.&amp;nbsp;&lt;br&gt;모든 경제 관련책을 읽으면 항상 언급되는 책이니 두고두고 다시 읽어봐야겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;책을 보면서 항상 얻어가는게 많은것 같다.&lt;br&gt;올해 읽을 책으론&amp;nbsp;&lt;br&gt;세이노의 가르침&lt;br&gt;도파민네이션&lt;br&gt;일단 이렇게 후보로 있는데 다른 책들도 추천 받아보고싶다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;마지막으로 작년에 나에게 포기하지 않아줘서 고맙다라고 말해주고 싶다.&lt;br&gt;더 잘하라는말보다 지금처럼만 포기하지말고 꾸준하게 하자.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;2024년에는 많은 일들이 있었지만 그 시간에서도 배웠던것만, 좋은것만 기억하도록하자 다 경험이었다.&lt;/p&gt;</description>
      <category>Story</category>
      <category>2024회고</category>
      <category>ios 재취업</category>
      <category>ios 주니어 개발자 2024회고</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/232</guid>
      <comments>https://peppo.tistory.com/232#entry232comment</comments>
      <pubDate>Sun, 5 Jan 2025 19:09:19 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] UITableView cell LifeCycle</title>
      <link>https://peppo.tistory.com/231</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;최근 UIKit을 통해 작업할 일이 생겼는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;생각보다 TableView의 활용도가 꽤 많다는걸 느꼈습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 TableView를 사용한다면 알아야할 것중 하나인 lifeCycle에 대해 블로깅을 해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;아래 예시의 UI는 code base로 구현합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;테스트 해볼 UI는 아래와 같아요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-0711.07.26-ezgif.com-resize.gif&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chzug4/btsLaoWrBAA/yAOavUG515XW9j3Q3Qv2lK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chzug4/btsLaoWrBAA/yAOavUG515XW9j3Q3Qv2lK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chzug4/btsLaoWrBAA/yAOavUG515XW9j3Q3Qv2lK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/chzug4/btsLaoWrBAA/yAOavUG515XW9j3Q3Qv2lK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;511&quot; data-filename=&quot;2024-12-0711.07.26-ezgif.com-resize.gif&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TableView&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;가장 기본적인 TableView 형태로 만들고나서,&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;tableView의 delegate, dataSource 프로토콜에서 lifeCycle 역할을 하는 메서드들을 나열해보자면 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UITableViewDelegate&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;willDisplay&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-153.54.44-ezgif.com-resize.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnrPPM/btsLjGO6AKy/XY5jW38lkiQU9y4lYk6bbK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnrPPM/btsLjGO6AKy/XY5jW38lkiQU9y4lYk6bbK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnrPPM/btsLjGO6AKy/XY5jW38lkiQU9y4lYk6bbK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bnrPPM/btsLjGO6AKy/XY5jW38lkiQU9y4lYk6bbK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;373&quot; data-filename=&quot;2024-12-153.54.44-ezgif.com-resize.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cell이 나타날때 쯤 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733581867212&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)&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;: 테이블 뷰가 특정 행의 셀을 그리려고  할때 delegate에 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활용 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 무한 스크롤, 로딩 인디케이터 추가 시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;didEndDisplaying&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-153.59.33-ezgif.com-resize.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf4dJW/btsLiI8lWHQ/k8W8OMHDICUzq7T8giIJt1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf4dJW/btsLiI8lWHQ/k8W8OMHDICUzq7T8giIJt1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf4dJW/btsLiI8lWHQ/k8W8OMHDICUzq7T8giIJt1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cf4dJW/btsLiI8lWHQ/k8W8OMHDICUzq7T8giIJt1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;373&quot; data-filename=&quot;2024-12-153.59.33-ezgif.com-resize.gif&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;373&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cell이 사라진 후 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733582561333&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)&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;: delegate에게 지정된 셀이 테이블에서 제거됐을 경우 알립니다.&lt;/p&gt;
&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; - cell에 이미지가 있는 경우, 화면에 보여질 cell이 이미지 다운로드를 시작하게 되는데 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;스크롤을 빠르게하게 되면&lt;span&gt; 사라질 cell은 이미지 다운로드를 취소하도록 구현&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UITableViewDataSource&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;cellForRowAt&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734263053494&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell&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;: tableView의 특정 위치에 삽입할 cell의 dataSource를 &lt;b&gt;요청&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;dataSource를 요청&lt;/b&gt; 한다는 말이 아래 영상을 보면 이해가 되실겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-159.27.39-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cajZGD/btsLiTu2heB/nKjclT9lU6eCadMTbob9L1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cajZGD/btsLiTu2heB/nKjclT9lU6eCadMTbob9L1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cajZGD/btsLiTu2heB/nKjclT9lU6eCadMTbob9L1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cajZGD/btsLiTu2heB/nKjclT9lU6eCadMTbob9L1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;398&quot; data-filename=&quot;2024-12-159.27.39-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&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;row값이 15가 보일때 호출되는 indexPath의 값은 [0, 16] 인걸 보면 알 수 있듯&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 dataSource를 요청한다는걸 알 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;br /&gt;indexPath의 0번째 index는 section&lt;br /&gt;indexPath의 1번빼 index는 row 입니다.&lt;/blockquote&gt;
&lt;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #242b34; text-align: start;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Cell&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;init&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734794844427&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override init(style: UITableViewCell.CellStyle, reuseIdentifier: 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;cell을 초기화 하는 부분 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tableView, collectionView의 경우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정 부분 cell을 초기화를 한 후 나머지 스크롤로 생성되는 cell들은 이전에 없어진 cell들을 queue에 담아두고 &lt;b&gt;재사용&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 아래 영상을 보면 알 수 있듯&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정부분까지만 cell 초기화를 하고 추가로 생성되는 cell은 재사용이 되기때문에 cell init이 호출되지 않는걸 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-159.36.45-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTYrRv/btsLiXYnpZx/6uhTKGVyp2heddeGpKI1xk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTYrRv/btsLiXYnpZx/6uhTKGVyp2heddeGpKI1xk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTYrRv/btsLiXYnpZx/6uhTKGVyp2heddeGpKI1xk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bTYrRv/btsLiXYnpZx/6uhTKGVyp2heddeGpKI1xk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;398&quot; data-filename=&quot;2024-12-159.36.45-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&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;&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;i&gt;&lt;b&gt;prepareForReuse&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734794882197&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func prepareForReuse()&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;cell init에서 언급했던 재사용 부분 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 cell init이 됐을때는 호출되지 않다가 &lt;b&gt;init 이후&lt;/b&gt; 재사용 되는 부분에서는 prepareForReuse 메서드가 호출됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-12-159.55.30-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zZsMu/btsLiFcP5bQ/rHLXxoYEVCikbeYJmopAkK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zZsMu/btsLiFcP5bQ/rHLXxoYEVCikbeYJmopAkK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zZsMu/btsLiFcP5bQ/rHLXxoYEVCikbeYJmopAkK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zZsMu/btsLiFcP5bQ/rHLXxoYEVCikbeYJmopAkK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;398&quot; data-filename=&quot;2024-12-159.55.30-ezgif.com-speed.gif&quot; data-origin-width=&quot;480&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;활용 예시&lt;/p&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; - 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 data-ke-size=&quot;size16&quot;&gt;정리를 해보다가 궁금한점이 생겼는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;willDisplay, prepareReuse가 호출되는 시점은 비슷한데 어떤경우에 따라 사용해야 하는지 찾아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;willDisplay와 prepareReuse의 차이점?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 해보면서 느꼈던 점을 정리해보자면 아래 표와 같았습니다&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 style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;willDisplay&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;prepareForReuse&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;호출 시점&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;cell이 &lt;b&gt;화면에 표시되기&lt;/b&gt; &lt;b&gt;직전&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;cell이 &lt;b&gt;재사용되기 직전&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;사용 목적&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;cell의 최종 상태 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;cell의 이전 상태 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;대상 cell&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;화면에 보이는 모든 cell&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;재사용되는 cell&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주요 작업&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;데이터 바인딩, UI 업데이트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;데이터 초기화&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마무리해보자면 TableView LifeCycle은 아래와 같습니다.&lt;/span&gt;&lt;/blockquote&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cell init&lt;/li&gt;
&lt;li&gt;cellForRowAt&lt;/li&gt;
&lt;li&gt;(cell이 없어질때) didEndDisplaying cell&lt;/li&gt;
&lt;li&gt;willDisplay cell&lt;/li&gt;
&lt;li&gt;============ cell init 이후 ==========&lt;/li&gt;
&lt;li&gt;prepareForReuese&lt;/li&gt;
&lt;li&gt;cellForRowAt&lt;/li&gt;
&lt;li&gt;(cell이 없어질때) didEndDisplaying cell&lt;/li&gt;
&lt;li&gt;willDisplay cell&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ezgif.com-resize.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGM3ov/btsLtumRZNh/kq62Qkc8uUv2AA9zKToAW1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGM3ov/btsLtumRZNh/kq62Qkc8uUv2AA9zKToAW1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGM3ov/btsLtumRZNh/kq62Qkc8uUv2AA9zKToAW1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dGM3ov/btsLtumRZNh/kq62Qkc8uUv2AA9zKToAW1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;425&quot; data-filename=&quot;ezgif.com-resize.gif&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;UIKit에 대해 많이 접해보지 못해 기본적인 부분을 놓치고 있었는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번기회에 메서드별로 테스트도 해보니 어느정도 정리가 되는것 같았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;CollectionView도 비슷한 lifeCycle로 알고 있는데 추후 사용해보고 또 느낀점을 포스팅해봐야겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 테스트했던 전체 코드 입니다.&lt;/p&gt;
&lt;p 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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734796137395&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController {
    
    private lazy var tableView = {
        let t = UITableView()
        t.translatesAutoresizingMaskIntoConstraints = false
        t.rowHeight = 56
        t.delegate = self
        t.dataSource = self
        t.register(SomeCell.self, forCellReuseIdentifier: SomeCell.Constants.identifier)
        return t
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setupUI()
        self.setupLayout()
    }

    private func setupUI() {

    }
    
    private func setupLayout() {
        self.view.addSubview(self.tableView)
        
        NSLayoutConstraint.activate([
            self.tableView.topAnchor.constraint(equalTo: self.view.topAnchor),
            self.tableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            self.tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            self.tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
        ])
    }

}

extension ViewController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        print(&quot;========= willDisplay index: \(indexPath.row)&quot;)
    }
    
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        print(&quot;========= didEndDisplaying index: \(indexPath.row)&quot;)
    }
    
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return 30
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell: UITableViewCell
        
        guard let someCell: SomeCell = tableView.dequeueReusableCell(
            withIdentifier: SomeCell.Constants.identifier,
            for: indexPath
        ) as? SomeCell
        else {
            return UITableViewCell()
        }
        
        print(&quot;========= cellForRowAt index: \(indexPath)&quot;)
        someCell.setupUI(indexPathRow: indexPath.row)
        cell = someCell
        return cell
    }
    
    
}


final class SomeCell: UITableViewCell {
    
    private lazy var label: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.font = .boldSystemFont(ofSize: 24)
        v.textColor = .black
        return v
    }()
    
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        print(&quot;========= Cell init&quot;)
        setupLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError(&quot;init(coder:) has not been implemented&quot;)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        print(&quot;========= \(#function)&quot;)
    }
    
    func setupUI(indexPathRow: Int) {
        self.label.text = &quot;label row index: \(indexPathRow)&quot;
    }
    
    private func setupLayout() {
        self.contentView.addSubview(self.label)
        
        NSLayoutConstraint.activate([
            self.label.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor),
            self.label.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor)
        ])
    }
    
}

extension SomeCell {
    
    enum Constants {
        static let identifier: String = &quot;SomeCell&quot;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>cellforrowat</category>
      <category>didenddisplaying</category>
      <category>ios tableview lifecycle</category>
      <category>preparereuse cell</category>
      <category>tableview lifecycle</category>
      <category>tableview 생명주기</category>
      <category>willdisplay</category>
      <category>테이블뷰 생명주기</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/231</guid>
      <comments>https://peppo.tistory.com/231#entry231comment</comments>
      <pubDate>Sun, 22 Dec 2024 00:56:00 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] URLSession image upload</title>
      <link>https://peppo.tistory.com/230</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;평소 네트워크 통신을 할때 Alamofire를 사용하면서&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;직접 URLSession을 사용해 이미지 업로드를 구현해보며 내부 동작을 파악하고 싶어&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;해당 블로깅을 작성해봅니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;기본적으로 URLSession의 request 헤더 부분에 'Content-Type: application/json'을 사용하게 되는데,&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이미지나 대용량 파일 등을 전송하기 위해선 &lt;b&gt;multipart/form-data&lt;/b&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;로 전송하는게 적합 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;multipart/form-data&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;application/json&lt;/b&gt; | &lt;b&gt;multipart/form-data&lt;/b&gt; 각각 request body를 보내는 방법 부터가 다른데 아래를 먼저 보고 가봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1732451379139&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// application/json

let body: [String: Any] = [
    &quot;name&quot;: name,
    &quot;content&quot;: content
]
        
let requestBody: Data? = try? JSONSerialization.data(withJSONObject: body)
request.httpBody = requestBody&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732451427287&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// multipart/form

private func createUploadBody(params: [String: String], boundary: String, imageData: Data) -&amp;gt; Data {
    var body: Data = Data()
    let lineBreak: String = &quot;\r\n&quot;
        
    for (key, value) in params {
        body.append(&quot;--\(boundary)\(lineBreak)&quot;.data(using: .utf8)!)
        body.append(&quot;Content-Disposition: form-data; name=\&quot;\(key)\&quot;\(lineBreak)\(lineBreak)&quot;.data(using: .utf8)!)
        body.append(&quot;\(value)\(lineBreak)&quot;.data(using: .utf8)!)
    }
        
    // image 파일
    let fileName: String = &quot;test.jpeg&quot;
    let mimeType: String = &quot;image/jpeg&quot;
    body.append(&quot;--\(boundary)\(lineBreak)&quot;.data(using: .utf8)!)
    body.append(&quot;Content-Disposition: form-data; name=\&quot;image\&quot;; filename=\&quot;\(fileName)\&quot;\(lineBreak)&quot;.data(using: .utf8)!)
    body.append(&quot;Content-Type: \(mimeType)\(lineBreak)\(lineBreak)&quot;.data(using: .utf8)!)
    body.append(imageData)
    body.append(&quot;\(lineBreak)--\(boundary)--\(lineBreak)&quot;.data(using: .utf8)!)
        
    return body
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;body를 만드는 방법이 다르고 뭔가 복잡해 보이지만&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;multipart/form 방식 통신 규약&lt;/b&gt;에 대해 알고가면 조금 정리가 될겁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;위 &lt;i&gt;createUploadBody&lt;/i&gt; 메서드로 body를 만들어 서버에 전송하면 아래와 같은 형태가 됩니다&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&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;1052&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EQWQY/btsKTaEwksm/xn3sgsMdKSH79Nc1p3gIc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EQWQY/btsKTaEwksm/xn3sgsMdKSH79Nc1p3gIc1/img.png&quot; data-alt=&quot;https://m.boostcourse.org/web326/lecture/59008&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EQWQY/btsKTaEwksm/xn3sgsMdKSH79Nc1p3gIc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEQWQY%2FbtsKTaEwksm%2Fxn3sgsMdKSH79Nc1p3gIc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;454&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://m.boostcourse.org/web326/lecture/59008&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732451701059&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /test.html HTTP/1.1
Host: example.org
Content-Type: multipart/form-data;boundary=&quot;boundary&quot; // 1

// name
--boundary // 2
Content-Disposition: form-data; name=&quot;name&quot;

// content
--boundary
Content-Disposition: form-data; name=&quot;content&quot; 

// imageFile
// 3
Content-Disposition: form-data; name=&quot;image&quot;; filename=&quot;test.jpeg&quot; 
Content-Type: image/jpeg&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;1. Content-Type 헤더부분에 &lt;b&gt;boundary&amp;nbsp;&lt;/b&gt;는 &lt;b&gt;구분자&lt;/b&gt; 라고 생각하시면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;해당 boundary 값으로 어디가 시작점이고 어디가 다음데이터 혹은 마지막 지점인지 구분할 수 있게 해주는 역할을 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;대부분 boundary 값은 UUID() 값을 통해 고유한 값을 지정합니다 (request는 여러번 보내기 때문에 고유한 값을 지정해 구분이 필요하기 때문)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;2. 1번에서 설명한것 처럼 boundary 값으로 시작점을 나타냅니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;3.  'name', 'content'와 다르게 이미지 파일 업로드의 경우엔  mimeType, filename을 기입해줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;image파일을 업로드할거니 mimeType은 image/jpeg가 되겠죠&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;filename은 말 그대로 파일 이름을 넣어주시면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;body를 만들때 값이 하나라도 맞지 않거나, 띄어쓰기, 줄바꿈이 다를 경우 통신이 되지 않습니다.&lt;br /&gt;꼼꼼히 아래 규약을 잘 지키고 있는지 확인하셔야 합니다.&lt;br /&gt;&lt;br /&gt;1. boundary로 구분을 잘해주고 있는지&lt;br /&gt;2. 'lineBreak' (&quot;\r\n&quot;)이 잘 되어있는지&lt;br /&gt;3. data를 append 해줬는지&lt;br /&gt;&amp;nbsp; ex) Content-Disposition: form-data; name=\&quot;\(key)\&quot;\(lineBreak)\(lineBreak)&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 데이터 추가&lt;br /&gt;4. 파일의 경우) 파일이름이 있는지, mimeType을 지정했는지&lt;br /&gt;5. 데이터의 끝을 나타내는 경계값을 추가하여 데이터의 마지막 부분을 잘 구분 하고 있는지&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;아래는 사용 예시 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732455268066&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// UIImage를 Data로 전환
// self.selectedImage: UIImage
// guard let imageData = self.selectedImage?.jpegData(compressionQuality: 0.8) else { return }

func addTodo(
        name: String,
        content: String,
        imageData: Data,
        completion: @escaping (Result&amp;lt;SomeModel, Error&amp;gt;) -&amp;gt; Void
    ) {
        var urlComponents = URLComponents()
        urlComponents.scheme = &quot;http&quot;
        urlComponents.host = &quot;localhost&quot;
        urlComponents.port = 8080
        urlComponents.path = &quot;/testPath&quot;
        
        guard let url = urlComponents.url
        else {
            completion(.failure(NetworkError.invalidURL))
            return
        }
        
        // request
        var request: URLRequest = .init(url: url)
        request.httpMethod = &quot;POST&quot;
        // header
        let identifier: String = UUID().uuidString
        let boundary: String = &quot;Boundary-\(identifier)&quot;
        request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
        request.setValue(&quot;multipart/form-data; boundary=\(boundary)&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
        
        let body: Data = createUploadBody(
            params: [
                &quot;name&quot;: name,
                &quot;content&quot;: content
            ],
            boundary: boundary,
            imageData: imageData
        )
        request.httpBody = body
        
        let task = URLSession.shared.uploadTask(
            with: request,
            from: body,
            completionHandler: { data, response, error in
                if let error = error {
                    completion(.failure(error))
                    return
                }
                if let data = data {
                    do {
                        let decodedData: SomeModel = try JSONDecoder().decode(SomeModel.self, from: data)
                        print(#function, &quot;success: \(decodedData)&quot;)
                        completion(.success(decodedData))
                    }
                    catch {
                        completion(.failure(error))
                    }
                }
            })
        
        task.resume()
    }
    
    
    private func createUploadBody(params: [String: String], boundary: String, imageData: Data) -&amp;gt; Data {
        var body: Data = Data()
        let lineBreak: String = &quot;\r\n&quot;
        
        for (key, value) in params {
            body.append(&quot;--\(boundary)\(lineBreak)&quot;.data(using: .utf8)!)
            body.append(&quot;Content-Disposition: form-data; name=\&quot;\(key)\&quot;\(lineBreak)\(lineBreak)&quot;.data(using: .utf8)!)
            body.append(&quot;\(value)\(lineBreak)&quot;.data(using: .utf8)!)
        }
        
        // image 파일
        let fileName: String = &quot;test.jpeg&quot;
        let mimeType: String = &quot;jpeg&quot;
        body.append(&quot;--\(boundary)\(lineBreak)&quot;.data(using: .utf8)!)
        body.append(&quot;Content-Disposition: form-data; name=\&quot;imageFile\&quot;; filename=\&quot;\(fileName)\&quot;\(lineBreak)&quot;.data(using: .utf8)!)
        body.append(&quot;Content-Type: image/\(mimeType)\(lineBreak)\(lineBreak)&quot;.data(using: .utf8)!)
        body.append(imageData)
        body.append(&quot;\(lineBreak)--\(boundary)--\(lineBreak)&quot;.data(using: .utf8)!)
        
        return body
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;Alamofire의 경우도 uploadTask를 사용하는걸 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1732454611570&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UploadRequest: DataRequest {

    }

    // ...

    override func task(for request: URLRequest, using session: URLSession) -&amp;gt; URLSessionTask {
        // ...
        switch uploadable {
        case let .data(data): return session.uploadTask(with: request, from: data)
        case let .file(url, _): return session.uploadTask(with: request, fromFile: url)
        case .stream: return session.uploadTask(withStreamedRequest: request)
        }

    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;지난 블로깅에서 Kingfisher를 분해해보며&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그동안 당연하게 사용하고있던 라이브러리들의 내부구조가 어떻게 되어있는지 파악하면서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;동작원리를 알아가는데 배워가는게 많은것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이번 Alamofire를 통해 업로드 하는기능을 URLSession으로 직접 구현해보면서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;다시한번 Alamofire 내부구조도 확인할 수 있었고,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;막 가져다 쓰기보단 짬내서라도 하나씩 파악하면서 공부하는 습관을 길러야겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/POST&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/POST&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2.&lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/uploading_data_to_a_website#2923850&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/foundation/url_loading_system/uploading_data_to_a_website#2923850&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://sandclock-itblog.tistory.com/244&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sandclock-itblog.tistory.com/244&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>ios 이미지 업로드</category>
      <category>multipart/form-data</category>
      <category>urlsession upload image</category>
      <category>urlsession 이미지 업로드</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/230</guid>
      <comments>https://peppo.tistory.com/230#entry230comment</comments>
      <pubDate>Sun, 24 Nov 2024 22:42:27 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Tuist App Extension (feat. WidgetExtension)</title>
      <link>https://peppo.tistory.com/229</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;최근 LiveActivity를 적용해보면서 App Extension 경험과&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;Tuist에 녹여내는 과정중에 삽질을 많이 하게 돼 복습도 할겸 블로깅을 해보려고 합니다!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;겪었던 문제&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. Tuist를 통해 Widget Extension을 추가해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. Widget Extension에서도 Asset을 따로 만들어야 함&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;일반적으로는 App Extension의 경우 target membership을 아래와 같이 체크하면 해결 되는데,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXJcZl/btsKk8Zm1Oz/Pg4TKJnJdERXufSY6pWzKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXJcZl/btsKk8Zm1Oz/Pg4TKJnJdERXufSY6pWzKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXJcZl/btsKk8Zm1Oz/Pg4TKJnJdERXufSY6pWzKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXJcZl%2FbtsKk8Zm1Oz%2FPg4TKJnJdERXufSY6pWzKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;252&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;tuist를 적용하게 되면 아래와 같은 방법으로 추가해줘야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 20.0704px;&quot;&gt;먼저 Project.swift 파일을 열기위해 아래 명령어를 터미널에 입력해주시고&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729839462599&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist edit&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;size18&quot;&gt;Project 객체에 target을 추가해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729838795889&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Project.swift

let project = Project(
    name: appName,
    targets: [
        // App target
        .target(
            name: appName,
            destinations: .iOS,
            product: .app,
            
            // etc..
            
            dependencies: [
              // ... 
              .target(name: &quot;WidgetExtension&quot;) // 1. 의존성 추가
            ]
        ),
        // 2
        // Extension target
        .target(
            name: &quot;WidgetExtension&quot;,
            destinations: .iOS,
            product: .appExtension, // extension
            bundleId: bundleId + &quot;.WidgetExtension&quot;,
            deploymentTargets: deploymentTarget,
            infoPlist: .extendingDefault(with: [
                &quot;CFBundleDisplayName&quot;: &quot;$(PRODUCT_NAME)&quot;,
                &quot;UIUserInterfaceStyle&quot;: &quot;Light&quot;,
                &quot;NSExtension&quot;: [
                    &quot;NSExtensionPointIdentifier&quot;: &quot;com.apple.widgetkit-extension&quot;
                ],
            ]),
            sources: &quot;WidgetExtension/Sources/**&quot;,
            resources: &quot;WidgetExtension/Resources/**&quot;,
            settings: configureSettings(with: .appExtension)
        ),
    ],
    additionalFiles: [&quot;README.md&quot;],
    resourceSynthesizers: [
        .assets(),
        .fonts()
    ]
)&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;size18&quot;&gt;1. app target의 dependencies에 WidgetExtension의 의존성을 추가해줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. target을 하나 추가하여 appExtension을 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;새롭게 만들어진 target의 경우 infoPlist, sources, resources 등 다시 만들어줘야 하는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;App target에서 했던것 처럼 똑같이 추가를 해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;+ 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;공통 모듈 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;추가된 Widget Extension의 경우도 Asset을 따로 만들어줘야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;저는 공통으로 사용되는 Asset이나 코드가 있는 경우 공통으로 관리되는 모듈을 하나 더 만드는 방법으로 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 Project.swift를 열어주시고&lt;/p&gt;
&lt;pre id=&quot;code_1725539132015&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let project = Project(
    name: appName,
    targets: [
        .target(
            name: appName,
            destinations: .iOS,
            product: .app,
            bundleId: bundleId,
            deploymentTargets: deploymentTarget,
            infoPlist: .extendingDefault(with: infoPlist),
            sources: [&quot;App/Sources/**&quot;],
            resources: [&quot;App/Resources/**&quot;],
            dependencies: [
                .external(name: &quot;RxSwift&quot;),
                .external(name: &quot;RxCocoa&quot;),
                .external(name: &quot;Alamofire&quot;),
                .external(name: &quot;CoreXLSX&quot;),
                .external(name: &quot;SwiftyJSON&quot;),
                .external(name: &quot;SkeletonView&quot;),
                .target(name: &quot;Common&quot;),             // 의존성 추가
                .target(name: &quot;WidgetExtension&quot;),    // 의존성 추가
            ],
            settings: configureSettings(with: .app)
        ),
        .target(
            name: &quot;WidgetExtension&quot;,
            destinations: .iOS,
            product: .appExtension,
            bundleId: bundleId + &quot;.WidgetExtension&quot;,
            deploymentTargets: deploymentTarget,
            infoPlist: .extendingDefault(with: [
                &quot;CFBundleDisplayName&quot;: &quot;$(PRODUCT_NAME)&quot;,
                &quot;NSExtension&quot;: [
                    &quot;NSExtensionPointIdentifier&quot;: &quot;com.apple.widgetkit-extension&quot;
                ],
            ]),
            sources: &quot;WidgetExtension/Sources/**&quot;,
            dependencies: [
                .target(name: &quot;Common&quot;)           // 의존성 추가
            ],
            settings: configureSettings(with: .appExtension)
        ),
        .target(
            name: &quot;Common&quot;, // 공통으로 사용해야할 코드가 있을 경우 여기에 코드 분리
            destinations: .iOS,
            product: .framework,
            productName: &quot;Common&quot;,
            bundleId: bundleId + &quot;.Common&quot;,
            deploymentTargets: deploymentTarget,
            sources: &quot;Common/Sources/**&quot;
        ),
    ]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;위와 같이 'Common' 이라는 모듈을 만들었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;폴더 구조는 아래와 같아요.&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폴더구조&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t444O/btsJtrFOa1O/mWO2BNg5EOWisTKwo2uLvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t444O/btsJtrFOa1O/mWO2BNg5EOWisTKwo2uLvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t444O/btsJtrFOa1O/mWO2BNg5EOWisTKwo2uLvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft444O%2FbtsJtrFOa1O%2FmWO2BNg5EOWisTKwo2uLvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;478&quot; data-origin-width=&quot;627&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Usage&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공통으로 사용하는 framework (Common)&lt;/p&gt;
&lt;pre id=&quot;code_1725539316424&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// Common.swift

import ActivityKit

public struct WidgetExtensionAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        // Dynamic stateful properties about your activity go here!
        public var busNumber: Int
        public var currentBusStop: String
        public var stopLeft: Int
        public var totalStop: Int
        
        public init(busNumber: Int, currentBusStop: String, stopLeft: Int, totalStop: Int) {
            self.busNumber = busNumber
            self.currentBusStop = currentBusStop
            self.stopLeft = stopLeft
            self.totalStop = totalStop
        }
    }

    // Fixed non-changing properties about your activity go here!
    public var currentBusStopInfo: String
    
    public init(currentBusStopInfo: String) {
        self.currentBusStopInfo = currentBusStopInfo
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;b&gt;NOTE&lt;br /&gt;&lt;/b&gt;&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;struct, 변수들을 &lt;b&gt;public 및 초기화(init)시켜줘야 다른 파일에서도 import해서 사용할 수 있습니다.&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public을 붙이는 이유는 &lt;b&gt;&lt;a title=&quot;접근제어자&quot; href=&quot;https://peppo.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;접근제어자&lt;/a&gt;&lt;/b&gt; 를 참고해주세요~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용방법은 아래와 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;저는 WidgetExtensionAttributes를 App, WidgetExtension 두 군데에서 다 사용하기 위해 Common 모듈에 따로 추가했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7MxXJ/btsKkmxhUVy/LdEg4D15mv24EjuWBvPSaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7MxXJ/btsKkmxhUVy/LdEg4D15mv24EjuWBvPSaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7MxXJ/btsKkmxhUVy/LdEg4D15mv24EjuWBvPSaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7MxXJ%2FbtsKkmxhUVy%2FLdEg4D15mv24EjuWBvPSaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;507&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[App] LiveActivitiyManager&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QYuJK/btsJrJ9cKyV/uwdNR1TvCVs8k15nS88bHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QYuJK/btsJrJ9cKyV/uwdNR1TvCVs8k15nS88bHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QYuJK/btsJrJ9cKyV/uwdNR1TvCVs8k15nS88bHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQYuJK%2FbtsJrJ9cKyV%2FuwdNR1TvCVs8k15nS88bHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;415&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[WidgetExtension] WidgetExtensionLiveActivity&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NpY84/btsKjtcZHLK/AtNZTGk5hm8kvmRpkgvpWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NpY84/btsKjtcZHLK/AtNZTGk5hm8kvmRpkgvpWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NpY84/btsKjtcZHLK/AtNZTGk5hm8kvmRpkgvpWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNpY84%2FbtsKjtcZHLK%2FAtNZTGk5hm8kvmRpkgvpWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;392&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이외에도 Color, Font 등 각 모듈에서 공통으로 사용하는게 있다면 한곳에서 관리하고 가져오는 방법으로 구현하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;물론 이 방법이 답은 아니라고 생각합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;만약 다른 모듈에서 Color가 하나만 필요할 경우에도 Common 모듈을 import 해야할까? 라는 의문이 생기게 되네요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이런 경우는 따로 import 하지않고 해당 모듈에서만 필요한 Color를 사용한다던지 할것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이 부분은 좀더 익숙해져보고 다른분들에게 조언을 한번 구해보고 수정해보도록 하겠습니다. :)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;오늘은 LiveActivity를 적용해보면서 아래의 방법들에 대해 배워갈 수 있었던것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;1. Widget Extension을 적용하는 방법&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;2. tuist와 같이 사용하는 방법,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;3. 공통으로 사용하는 경우 하나의 framework로 관리하는 방법&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;하나씩 tuist 기능을 알아가면서  일단은 정리하고 있지만 추후 새롭게 알게 되는게 있으면 수정하도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.dev/Saik0s/BackgroundExperiments/blob/8ef3291d77f31a0aa95c3b8bda59a1bbcd86d6d7/Project.swift#L65&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.dev/Saik0s/BackgroundExperiments/blob/8ef3291d77f31a0aa95c3b8bda59a1bbcd86d6d7/Project.swift#L65&lt;/a&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>tuist app extension</category>
      <category>tuist common</category>
      <category>tuist extesnion</category>
      <category>tuist liveactivity</category>
      <category>tuist widget extension</category>
      <category>tuist 공통모듈</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/229</guid>
      <comments>https://peppo.tistory.com/229#entry229comment</comments>
      <pubDate>Fri, 25 Oct 2024 17:01:53 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] 고해상도 이미지 다운시 성능 개선 (Image DownSampling)</title>
      <link>https://peppo.tistory.com/228</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;URL을 통해 여러 이미지를 받아오는 작업 많이 해보셨을 겁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 해당 이미지 하나당 크기가 엄청 크다면?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 위와 같이 큰 여러개의 이미지를 다운받아야 하는경우 사용자 경험 개선과 과부하되는 메모리 문제에 대해 해결하는 방법&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다운 샘플링&lt;/b&gt;에 대해 블로깅 해보려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&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;706&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uFeyc/btsJ09LHe6C/ULc7ai4Kl7kQlAv0pD63rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uFeyc/btsJ09LHe6C/ULc7ai4Kl7kQlAv0pD63rk/img.png&quot; data-alt=&quot;이미지 사이즈가 큰 경우 여러개의 이미지를 받아올 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uFeyc/btsJ09LHe6C/ULc7ai4Kl7kQlAv0pD63rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuFeyc%2FbtsJ09LHe6C%2FULc7ai4Kl7kQlAv0pD63rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;155&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 사이즈가 큰 경우 여러개의 이미지를 받아올 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이미지를 불러오는데도 당연히 오래걸리고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;스크롤 했을때 버벅였던 현상이 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그 이유는 WWDC18 - Momory Deep Dive에서 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xthAv/btsJ0N30aW1/rntVcmF96wcH1Cukh5Fq01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xthAv/btsJ0N30aW1/rntVcmF96wcH1Cukh5Fq01/img.png&quot; data-alt=&quot;메모리 사용량은 파일 크기가 아니라 이미지의 사이즈와 관련이 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xthAv/btsJ0N30aW1/rntVcmF96wcH1Cukh5Fq01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxthAv%2FbtsJ0N30aW1%2FrntVcmF96wcH1Cukh5Fq01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;186&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메모리 사용량은 파일 크기가 아니라 이미지의 사이즈와 관련이 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이미지의 경우 메모리 사용량은 &lt;b&gt;width(너비), height(높이)&lt;/b&gt;에 의해 결정이 되는데요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;위와 같은 상황에서도 실제로 이미지 하나당 4000 * 3000 이런식으로 이미지 크기가 어마어마 했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 접근 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;해당 문제에 대해 고민은 했던 내용은&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'&lt;b&gt;모바일에 보여질거고, 이미지인데 굳이 사이즈가 저렇게 크게 필요할까?&lt;/b&gt;'&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'&lt;b&gt;최적화된 이미지 크기로 바꿀 순 없을까?&lt;/b&gt;'&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러 검색결과 &lt;b&gt;다운샘플링(downsampling)&lt;/b&gt; 이라는 키워드를 접할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사전지식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;들어가기전에 사용자가 사진을 보게되는 과정을 보고 갑시다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이미지가 render 되기까지 과정)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;1452&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TeC8p/btsJ0gSlUUs/y0quM0MorbfgxQ85FCyBN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TeC8p/btsJ0gSlUUs/y0quM0MorbfgxQ85FCyBN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TeC8p/btsJ0gSlUUs/y0quM0MorbfgxQ85FCyBN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTeC8p%2FbtsJ0gSlUUs%2Fy0quM0MorbfgxQ85FCyBN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;414&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;(UIImage) Image Load&lt;/li&gt;
&lt;li&gt;Data Buffer &amp;gt; Image Buffer로 &lt;b&gt;Decode&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lBtOj/btsJ0u35mnX/l7m9ZEowltNq6PdPnsAms1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lBtOj/btsJ0u35mnX/l7m9ZEowltNq6PdPnsAms1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lBtOj/btsJ0u35mnX/l7m9ZEowltNq6PdPnsAms1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlBtOj%2FbtsJ0u35mnX%2Fl7m9ZEowltNq6PdPnsAms1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;307&quot; data-origin-width=&quot;1496&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;size18&quot;&gt;즉, UIImageView는 말 그대로 View일 뿐이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;UIImage가 Load하는 과정에서 Data Buffer로된 이미지를 Image Buffer로 디코딩하게 되며&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이에따라 사용자가 사진을 볼 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메모리 사용량이 늘어나는 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 &lt;b&gt;메모리 사용량이 늘어나는 이유&lt;/b&gt;는 &lt;b&gt;UIImage가 디코딩된 이미지를 갖고 있기 때문&lt;/b&gt;인데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 해결하기 위해 &lt;b&gt;디코딩되기 전에 이미지 데이터를 축소시키는 방법&lt;/b&gt;을 &lt;b&gt;downsampling&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;downsampling의 과정은 아래 그림과 같아요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyNT2Y/btsJ13jSA5Q/QiYAPOoRz6cqAKNUKvkAu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyNT2Y/btsJ13jSA5Q/QiYAPOoRz6cqAKNUKvkAu1/img.png&quot; data-alt=&quot;다운샘플링 과정 (downsampling)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyNT2Y/btsJ13jSA5Q/QiYAPOoRz6cqAKNUKvkAu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyNT2Y%2FbtsJ13jSA5Q%2FQiYAPOoRz6cqAKNUKvkAu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;223&quot; data-origin-width=&quot;1528&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다운샘플링 과정 (downsampling)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;기존 이미지 로그 과정에서 decode 이전에 한 단계가 더 생겼죠&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;이 부분에서 이미지 데이터를 축소시킨 후 디코딩 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;아래는 WWDC에서 제공되는 다운샘플링 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1728566795221&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func downsample(data: Data, to size: CGSize, scale: CGFloat) -&amp;gt; UIImage? {        
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
	guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
            return nil
        }
        
    let maxDimension = max(size.width, size.height) * scale
    let downsampleOptions = [
      kCGImageSourceCreateThumbnailFromImageAlways: true,
      kCGImageSourceShouldCacheImmediately: true,
      kCGImageSourceCreateThumbnailWithTransform: true,
      kCGImageSourceThumbnailMaxPixelSize: maxDimension
    ] as CFDictionary
    
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
        return nil
    }

    return UIImage(cgImage: downsampledImage)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 하나씩 뜯어봅시다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ImageSourceOptions&lt;/b&gt;&lt;/h4&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ImageSource&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 데이터와 위 ImageSourceOptions에서 설정한대로 CGImageSource 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;CGImageSource&lt;br /&gt;&lt;/b&gt;이미지 데이터를 읽기 위해 사용되는 opaque(불투명) 타입&lt;br /&gt;이미지 데이터를 load하는데 필요한 데이터버퍼 관리 및 사용가능 한 이미지로 변환하기 위해 해당 데이터에 대한 작업 수행&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;maxDimension&lt;/b&gt;&lt;/h4&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;kCGImageSourceCreateThumbnailFromImageAlways&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 imageSource가 원본 이미지를 썸네일로 만들지 결정&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;kCGImageSourceShouldCacheImmediately&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 생성 시 이미지 디코딩 및 캐싱을 수행할지 여부&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;b&gt;kCGImageSourceCreateThumbnailWithTransform&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;kCGImageSourceThumbnailMaxPixelSize&lt;/b&gt;&lt;/h4&gt;
&lt;p 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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CGImageSourceCreateThumbnailAtIndex&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;imageSource의 해당 index에 대해 썸네일 생성 (다운샘플링 이미지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;List (tableView)에서 여러개의 이미지를 스크롤하면서 받아오는 상황&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;table style=&quot;border-collapse: collapse; width: 100%; height: 187px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 18px;&quot;&gt;다운샘플링 적용 전&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 18px;&quot;&gt;다운샘플링 적용 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 169px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 169px; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-10-1111.37.48-ezgif.com-resize.gif&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsMtFn/btsJ14KiPOJ/Zubac0vKShXkJLf0BF9kP1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsMtFn/btsJ14KiPOJ/Zubac0vKShXkJLf0BF9kP1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsMtFn/btsJ14KiPOJ/Zubac0vKShXkJLf0BF9kP1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bsMtFn/btsJ14KiPOJ/Zubac0vKShXkJLf0BF9kP1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;78&quot; height=&quot;424&quot; data-filename=&quot;2024-10-1111.37.48-ezgif.com-resize.gif&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;일반.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbFHfC/btsJ2bigmAQ/Zmk5nE8TEXV3lKKweUz5I1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbFHfC/btsJ2bigmAQ/Zmk5nE8TEXV3lKKweUz5I1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbFHfC/btsJ2bigmAQ/Zmk5nE8TEXV3lKKweUz5I1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bbFHfC/btsJ2bigmAQ/Zmk5nE8TEXV3lKKweUz5I1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;241&quot; data-filename=&quot;일반.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 169px; text-align: center;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-10-1111.38.42-ezgif.com-resize.gif&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p9ikF/btsJ2awTr21/fwlImKqFRmy4gRWcbBcbSk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p9ikF/btsJ2awTr21/fwlImKqFRmy4gRWcbBcbSk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p9ikF/btsJ2awTr21/fwlImKqFRmy4gRWcbBcbSk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/p9ikF/btsJ2awTr21/fwlImKqFRmy4gRWcbBcbSk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;78&quot; height=&quot;424&quot; data-filename=&quot;2024-10-1111.38.42-ezgif.com-resize.gif&quot; data-origin-width=&quot;78&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운샘플링.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chkY8q/btsJ10adYPt/4VBJChsqIQV6s8lodbZiB0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chkY8q/btsJ10adYPt/4VBJChsqIQV6s8lodbZiB0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chkY8q/btsJ10adYPt/4VBJChsqIQV6s8lodbZiB0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/chkY8q/btsJ10adYPt/4VBJChsqIQV6s8lodbZiB0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;241&quot; data-filename=&quot;다운샘플링.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;+ 추가&lt;/b&gt;&lt;/h3&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;실제로 Kingfisher에서도 Downsapling 기능을 지원합니다&lt;/p&gt;
&lt;pre id=&quot;code_1728539627048&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;KFImage(URL(string: urlString))
            .resizable()
            .placeholder { 
                ProgressView()
            }
            .cancelOnDisappear(true)
            .cacheMemoryOnly()
        // MARK: - 성능문제 개선 (고해상도 이미지의 경우 썸네일만 먼저 받아오도록)
            .setProcessor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 100)))
            .onFailureImage(KFCrossPlatformImage(systemName: NameSpace.ImageName.photo))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;processor에서 DownsamplingImageProcessor를 이용해 다운샘플링을 하는 과정인데요.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9jTdk/btsJ2G9ZX7c/wnh6EWGgA8O7XYOCOa1h60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9jTdk/btsJ2G9ZX7c/wnh6EWGgA8O7XYOCOa1h60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9jTdk/btsJ2G9ZX7c/wnh6EWGgA8O7XYOCOa1h60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9jTdk%2FbtsJ2G9ZX7c%2Fwnh6EWGgA8O7XYOCOa1h60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1964&quot; height=&quot;852&quot; data-origin-width=&quot;1964&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2298&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9BNWk/btsJ19Y1n7V/HkdHY37IMroK3qk9hOsVZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9BNWk/btsJ19Y1n7V/HkdHY37IMroK3qk9hOsVZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9BNWk/btsJ19Y1n7V/HkdHY37IMroK3qk9hOsVZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9BNWk%2FbtsJ19Y1n7V%2FHkdHY37IMroK3qk9hOsVZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2298&quot; height=&quot;982&quot; data-origin-width=&quot;2298&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC에서 사용하던 코드 그대로 사용중인걸 볼 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;오늘은 다운샘플링에 대해 정리해봤는데&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;여러모로 꼭 필요한 기능인것 같다는 생각이 들었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;WWDC를 주기적으로 봐야겠다는 생각도 들었고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;이런 기능들 하나하나 배워나가면서 사용자경험에 좋은 영향을 줄 수 있다면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;선택이 아닌 필수라는 생각이 들었습니다 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1&lt;b&gt;. &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2018/416/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2018/416/ (17:58~)&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;&lt;a href=&quot;https://felix-mr.tistory.com/11&quot;&gt;https://felix-mr.tistory.com/11&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3&lt;b&gt;. &lt;a href=&quot;https://velog.io/@dev_jane/UICollectionView-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-downsampling&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@dev_jane/UICollectionView-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-downsampling&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>image downsampling</category>
      <category>memory deep dive</category>
      <category>wwdc18</category>
      <category>스크롤시 버벅임 문제</category>
      <category>이미지 고해상도 다운</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/228</guid>
      <comments>https://peppo.tistory.com/228#entry228comment</comments>
      <pubDate>Fri, 11 Oct 2024 11:48:46 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Tuist (feat. xcproj 충돌)</title>
      <link>https://peppo.tistory.com/227</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt; 협업을 하다보면&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;0&quot;&gt;xcodeproj, xcworkspace&lt;/span&gt;&amp;nbsp;&lt;/b&gt;에서 충돌이 나는 문제를 자주 겪어볼텐데요.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;저도 아래 사진처럼 일일히 충돌나는부분을 수정하느라 몇시간동안 삽질을 했었습니다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;대환장파티2.png&quot; data-origin-width=&quot;2558&quot; data-origin-height=&quot;1724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkv9Qi/btsJnJAN7aO/9r92KdKtshaAMWbxK7YGR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkv9Qi/btsJnJAN7aO/9r92KdKtshaAMWbxK7YGR0/img.png&quot; data-alt=&quot;대환장파티&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkv9Qi/btsJnJAN7aO/9r92KdKtshaAMWbxK7YGR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkv9Qi%2FbtsJnJAN7aO%2F9r92KdKtshaAMWbxK7YGR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;404&quot; data-filename=&quot;대환장파티2.png&quot; data-origin-width=&quot;2558&quot; data-origin-height=&quot;1724&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;충돌을 해결할 수 있는 툴은 몇가지가 있지만&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그 중에서 요즘 모듈화로 잘 알려진 Tuist라는 툴을 사용해보면서 정리를 해보려고 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;모듈화는 추후 차근차근 스터디해보고 업데이트 할 예정입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 세팅하는것까지만!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 Tuist에 대해 한번 정리해보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;블로깅 시점 (24.09.02)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;Tuist 버전은 4.25.0&lt;/b&gt;&lt;/span&gt; 입니다&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;알고가기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;1. Tuist를 사용하게 되면 기존에 사용하던 &lt;b&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;0&quot;&gt;xcodeproj, xcworkspace &lt;/span&gt;&lt;/b&gt;파일이 필요없습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;프로젝트 설정하고 적용을하면 새로 만들어주기 때문이에요.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;2. &lt;b&gt;git 과 비슷한 느낌&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;스터디를 해보면서 느낀점은 CLI 명령어를 통해&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;수정&lt;/b&gt;하고, &lt;b&gt;업데이트&lt;/b&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하고 &lt;/span&gt;&lt;b&gt;적용&lt;/b&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하는 방식이었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 1.12em; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;앞으로 아래 명령어를 자주 사용하게 될거에요&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725257152046&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 수정
tuist edit&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725257158557&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 업데이트
tuist install&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725257162494&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 적용 및 프로젝트 파일 생성
tuist generate&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;mise라는 툴을 사용해서 tuist를 설치해줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;mise가 설치 되어있지 않다면 &lt;b&gt;&lt;a title=&quot;여기&quot; href=&quot;https://mise.jdx.dev/getting-started.html#_1-install-mise-cli&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;&lt;/b&gt;서 설치&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;mise가 설치 되어있다면 terminal에 아래 명령어 입력&lt;/p&gt;
&lt;pre id=&quot;code_1725257938711&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mise install tuist            // 현재버전 설치
mise install tuist@x.y.z      // 특정버전 설치
mise install tuist@3          // 3.x버전 설치&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tuist를 적용하기 위해서 먼저 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;빈 폴더&lt;/b&gt;&lt;/span&gt;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 아래와 같이 입력&lt;/p&gt;
&lt;pre id=&quot;code_1725258232401&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir TuistStudy // 프로젝트이름 
cd TuistStudy    // 이동해주시고&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;또는 Finder에서 그냥 빈 폴더 만드셔도 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9evfW/btsJm9UhDy2/kl1c1OKMs4PCFOM9z67rmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9evfW/btsJm9UhDy2/kl1c1OKMs4PCFOM9z67rmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9evfW/btsJm9UhDy2/kl1c1OKMs4PCFOM9z67rmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9evfW%2FbtsJm9UhDy2%2Fkl1c1OKMs4PCFOM9z67rmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;256&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 입력해주시면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725258449132&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist init&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vBPJe/btsJophyIHy/oSxRjoGe3BKiKSWIaYFUKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vBPJe/btsJophyIHy/oSxRjoGe3BKiKSWIaYFUKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vBPJe/btsJophyIHy/oSxRjoGe3BKiKSWIaYFUKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvBPJe%2FbtsJophyIHy%2FoSxRjoGe3BKiKSWIaYFUKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1216&quot; height=&quot;250&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;250&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Tests&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q6NGh/btsJnLrSgw3/JFASJGBFaQstBnvKKfIaPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q6NGh/btsJnLrSgw3/JFASJGBFaQstBnvKKfIaPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q6NGh/btsJnLrSgw3/JFASJGBFaQstBnvKKfIaPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq6NGh%2FbtsJnLrSgw3%2FJFASJGBFaQstBnvKKfIaPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;37&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;52&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;Test코드 관련된 파일들을 여기에 넣으면 될것 같아요&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; Resources&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fh0WR/btsJmK8iVc7/Q9qXUAK8s4VOGckDrzS2pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fh0WR/btsJmK8iVc7/Q9qXUAK8s4VOGckDrzS2pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fh0WR/btsJmK8iVc7/Q9qXUAK8s4VOGckDrzS2pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFh0WR%2FbtsJmK8iVc7%2FQ9qXUAK8s4VOGckDrzS2pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;182&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;182&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;Assets, Fonts 와 같은 종류들이 모여있는 곳입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 블로깅하겠지만 이곳에 Color, Image, Font 등 Namespace로 알아서 정리되어서 쏙쏙 뽑아 쓰실 수 있습니다.&amp;nbsp;&lt;br /&gt;(하드코딩으로 값을 가져올 필요없이요)&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;b&gt; Sources&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xHEbQ/btsJoXrlQGE/1cEPp7jQtxZWmMMt0GEbK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xHEbQ/btsJoXrlQGE/1cEPp7jQtxZWmMMt0GEbK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xHEbQ/btsJoXrlQGE/1cEPp7jQtxZWmMMt0GEbK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxHEbQ%2FbtsJoXrlQGE%2F1cEPp7jQtxZWmMMt0GEbK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;164&quot; data-origin-width=&quot;830&quot; data-origin-height=&quot;164&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;나머지 소스코드들이 이곳에 들어갑니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;View, ViewModel, Model 등 나누기 나름일것 같아요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Project | Package&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y7tRF/btsJnaMsppB/NzfLlpkTY8NMn4Zf51aQ00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y7tRF/btsJnaMsppB/NzfLlpkTY8NMn4Zf51aQ00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y7tRF/btsJnaMsppB/NzfLlpkTY8NMn4Zf51aQ00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy7tRF%2FbtsJnaMsppB%2FNzfLlpkTY8NMn4Zf51aQ00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;133&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;194&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;&lt;b&gt;Project.swift &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 파일에서 프로젝트 세팅을 합니다. Tuist를 사용하면서 가장 많이 손 볼곳이 될거에요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Package.swift&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이름 그대로 패키지를 관리하는 파일입니다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;s&gt;config는 추후 스터디후 블로깅하겠습니다.&lt;/s&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;pre id=&quot;code_1725259633155&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist generate&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;아래 Xcode 화면이 잘 뜨신다면 잘 따라오고 계신겁니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ef9Tm1/btsJnW7Oi7A/PcZvvOdRsbNrV9KQdnRAp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ef9Tm1/btsJnW7Oi7A/PcZvvOdRsbNrV9KQdnRAp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ef9Tm1/btsJnW7Oi7A/PcZvvOdRsbNrV9KQdnRAp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fef9Tm1%2FbtsJnW7Oi7A%2FPcZvvOdRsbNrV9KQdnRAp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;1888&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Project  세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Project 세팅을 해서 비어있는 값들을 채워주고, 원하는 세팅으로 바꿔볼겁니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;1610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q917V/btsJntrw2gz/1NM6xWFBWNPyTy8Can9Huk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q917V/btsJntrw2gz/1NM6xWFBWNPyTy8Can9Huk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q917V/btsJntrw2gz/1NM6xWFBWNPyTy8Can9Huk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq917V%2FbtsJntrw2gz%2F1NM6xWFBWNPyTy8Can9Huk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;564&quot; data-origin-width=&quot;1712&quot; data-origin-height=&quot;1610&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;pre id=&quot;code_1725259882348&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist edit&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;그럼 Manifests가 뜨는데 &lt;b&gt;Project.swift&lt;/b&gt;를 열어줍시다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBCSvf/btsJoCufxDU/iS3zYtnggKugWeJkyvzSY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBCSvf/btsJoCufxDU/iS3zYtnggKugWeJkyvzSY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBCSvf/btsJoCufxDU/iS3zYtnggKugWeJkyvzSY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBCSvf%2FbtsJoCufxDU%2FiS3zYtnggKugWeJkyvzSY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;603&quot; data-origin-width=&quot;1880&quot; data-origin-height=&quot;1888&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;Project 부분도 하나씩 알아봅시다&lt;/p&gt;
&lt;pre id=&quot;code_1725260363470&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ProjectDescription

let project = Project(
    name: &quot;프로젝트이름&quot;,                    // 프로젝트 이름
    organizationName: &quot;OrganizationName&quot;, // 조직 이름
    settings: nil,                        // 다른 프로젝트 설정을 지정할 수 있음.
    targets: [                            // 프로젝트와 관련된 대상 목록
      .target(                             
         name: projectName,               // 앱 이름
         platform: .iOS,                  // 제공하는 플랫폼 (iOS, iPad 등)
         product: .app,                   // product 용도 (app, bundle, frmaework 등)
         bundleId: &quot;&quot;,                    // 번들 ID
         infoPlist: .default,             // infoPlist 경로
         sources: [&quot;TuistStudy/Source/**&quot;], // source 디렉터리에 있는 모든 source 파일을 지정 (** = 와일드 카드 패턴 &amp;rarr; 하위 모든 경로의 파일들 포함)
         resources: &quot;TuistStudy/Resources/**&quot;,
         dependencies: [],                // 외부 프레임워크의 종속성을 여기에 추가
         settings: nil                    // 다른 설정을 지정하는데 사용
      )
    ]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 아래와 같이 작성했어요&lt;/p&gt;
&lt;pre id=&quot;code_1725260320002&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ProjectDescription

let appName: String = &quot;TuistStudy&quot;
let bundleId: String = &quot;com.peppo.tuistStudy&quot;
let infoPlist: [String: Plist.Value] = [
    &quot;UILaunchScreen&quot;: [
        &quot;UIColorName&quot;: &quot;&quot;,
        &quot;UIImageName&quot;: &quot;&quot;,
    ],
]

let project = Project(
    name: appName,
    targets: [
        .target(
            name: appName,
            destinations: .iOS,
            product: .app,
            bundleId: bundleId,
            infoPlist: .extendingDefault(with: infoPlist),
            sources: [&quot;TuistStudy/Sources/**&quot;],
            resources: [&quot;TuistStudy/Resources/**&quot;],
            dependencies: [
                // 추후 라이브러리 아래와 같이 추가 (Package.swift 에도 수정필요)
//                .external(name: &quot;Alamofire&quot;)
            ]
        ),
        .target(
            name: &quot;TuistStudyTests&quot;,
            destinations: .iOS,
            product: .unitTests,
            bundleId: &quot;io.tuist.TuistStudyTests&quot;,
            infoPlist: .default,
            sources: [&quot;TuistStudy/Tests/**&quot;],
            resources: [],
            dependencies: [.target(name: &quot;TuistStudy&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;Tuist는 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;Project.swift는 이정도로 작성해두고 이제 적용을 해봅시다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 명령어는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1725261234607&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist generate&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;잘 적용이 됐다면 Tuist 관리하에 프로젝트 파일들이 다시 생성됩니다.&lt;/p&gt;
&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;tuist generate&lt;/b&gt; 해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래 원인은 organizationName, settings의 값이 비어있었고, product에 불필요한 ' , ' 가 있었습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEPFAQ/btsJm6wM39C/xBT0pOotncZBMqhym30Iak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEPFAQ/btsJm6wM39C/xBT0pOotncZBMqhym30Iak/img.png&quot; data-alt=&quot;실패시 뜨는 로그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEPFAQ/btsJm6wM39C/xBT0pOotncZBMqhym30Iak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEPFAQ%2FbtsJm6wM39C%2FxBT0pOotncZBMqhym30Iak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;143&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;488&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 진행도중 Tuist 적용할 때 폴더세팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프로젝트 진행중 Tuist 적용하고 있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View, ViewModel, Model 등 여러가지 폴더들을 Sources 경로에 다 넣어줬습니다. (드래그앤드롭)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더구조들을 아래와 같이 세팅해줬고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTRjEN/btsJnslP0R1/5B5kxvzBKmNqHkxhiBgZK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTRjEN/btsJnslP0R1/5B5kxvzBKmNqHkxhiBgZK1/img.png&quot; data-alt=&quot;Sources 폴더구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTRjEN/btsJnslP0R1/5B5kxvzBKmNqHkxhiBgZK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTRjEN%2FbtsJnslP0R1%2F5B5kxvzBKmNqHkxhiBgZK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;156&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sources 폴더구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용&lt;/b&gt;을 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725262470264&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tuist generate&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;그럼 아래와 같이 Sources 폴더 안에 싹 정리가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 역할별로 더 모듈화도 해볼 예정이에요. (그건 추후 블로깅!)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;1052&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S9u4C/btsJoCgKfkk/e9l1kaKzKSIez3TYI4BDyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S9u4C/btsJoCgKfkk/e9l1kaKzKSIez3TYI4BDyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S9u4C/btsJoCgKfkk/e9l1kaKzKSIez3TYI4BDyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS9u4C%2FbtsJoCgKfkk%2Fe9l1kaKzKSIez3TYI4BDyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;1038&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;1052&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;Project 파일 세팅했을때 Sources/** 로 했기때문에 Sources 폴더내부에 알아서 정리가 되는 모습입니다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;Tuist는 계속 버전이 업데이트 되는만큼&amp;nbsp;변화도 많습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;처음엔 레퍼자료들을 찾는데 버전이 달라서 없는 프로퍼티들도 꽤많아 고생을 했던 기억이있네요.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;다시 한번 느낍니다 &lt;b&gt;&lt;a title=&quot;공식문서&quot; href=&quot;https://docs.tuist.io/references/project-description/structs/project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;&lt;/b&gt;의 중요성을..&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;그만큼 공식문서에 참고할만한 &lt;b&gt;&lt;a title=&quot;Example&quot; href=&quot;https://docs.tuist.io/references/examples/app_with_spm_dependencies&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;example&lt;/a&gt;&lt;/b&gt; 깃헙들도 제공해주고 있으니 잘 찾아보면서 스터디하면 좋을것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;협업간 충돌을 피하기위해 스터디하고 있지만&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;생각보다 제공해주는 매력적인 기능들이 많은것 같은데&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;하나씩 정리해보면서 추가적으로 블로깅 해보도록 하겠습니다 :)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://docs.tuist.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.tuist.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725263184047&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;What is Tuist? | Tuist&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.tuist.io&quot; data-og-source-url=&quot;https://docs.tuist.io/&quot; data-og-url=&quot;https://docs.tuist.io/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.tuist.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.tuist.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;What is Tuist? | Tuist&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.tuist.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode (요건 참고만 하세요 버전이 다릅니다.)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725263243156&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Tuist Tutorial for Xcode&quot; data-og-description=&quot;Learn how to use Tuist to create and manage complex Xcode projects and workspaces on-the-fly.&quot; data-og-host=&quot;www.kodeco.com&quot; data-og-source-url=&quot;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode&quot; data-og-url=&quot;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/maah3/hyWVXeIgQ5/TcYe9N1jKfwPFW9PPDIST1/img.png?width=1800&amp;amp;height=900&amp;amp;face=0_0_1800_900,https://scrap.kakaocdn.net/dn/88jXi/hyWVWfOC9D/ywAmsbiqLhfKQm7KTGicI0/img.png?width=1800&amp;amp;height=900&amp;amp;face=0_0_1800_900,https://scrap.kakaocdn.net/dn/iXy2e/hyWZaXMGaS/4ZBnTHpd5FVbvYsDRjmgyK/img.png?width=650&amp;amp;height=268&amp;amp;face=0_0_650_268&quot;&gt;&lt;a href=&quot;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.kodeco.com/24508362-tuist-tutorial-for-xcode&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/maah3/hyWVXeIgQ5/TcYe9N1jKfwPFW9PPDIST1/img.png?width=1800&amp;amp;height=900&amp;amp;face=0_0_1800_900,https://scrap.kakaocdn.net/dn/88jXi/hyWVWfOC9D/ywAmsbiqLhfKQm7KTGicI0/img.png?width=1800&amp;amp;height=900&amp;amp;face=0_0_1800_900,https://scrap.kakaocdn.net/dn/iXy2e/hyWZaXMGaS/4ZBnTHpd5FVbvYsDRjmgyK/img.png?width=650&amp;amp;height=268&amp;amp;face=0_0_650_268');&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;Tuist Tutorial for Xcode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to use Tuist to create and manage complex Xcode projects and workspaces on-the-fly.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.kodeco.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>iOS</category>
      <category>project.swift</category>
      <category>tuist</category>
      <category>tuist ios</category>
      <category>tuist swift</category>
      <category>tuist 튜토리얼</category>
      <category>xcodeproj confilct</category>
      <category>xcodeproj 충돌</category>
      <author>Peppo</author>
      <guid isPermaLink="true">https://peppo.tistory.com/227</guid>
      <comments>https://peppo.tistory.com/227#entry227comment</comments>
      <pubDate>Mon, 2 Sep 2024 16:50:06 +0900</pubDate>
    </item>
  </channel>
</rss>